Skip to content

Instantly share code, notes, and snippets.

@lowener
Last active November 24, 2023 03:09
Show Gist options
  • Save lowener/cf2358ee1d595884b1292be0ad91c0d1 to your computer and use it in GitHub Desktop.
Save lowener/cf2358ee1d595884b1292be0ad91c0d1 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "aa11c40d",
"metadata": {},
"source": [
"# Imports"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "48f4c264",
"metadata": {},
"outputs": [],
"source": [
"import cudf\n",
"import cupy as cp\n",
"import numpy as np\n",
"from cuml.preprocessing import LabelEncoder, OneHotEncoder\n",
"from cuml.naive_bayes import GaussianNB, MultinomialNB, CategoricalNB, BernoulliNB, ComplementNB\n",
"from cuml.cluster import KMeans\n",
"from sklearn.model_selection import train_test_split\n",
"from cuml.feature_extraction.text import TfidfVectorizer, HashingVectorizer, CountVectorizer\n",
"import math\n",
"import matplotlib.pyplot as plt\n",
"import itertools\n",
"from sklearn.naive_bayes import GaussianNB as GaussianNB_sk\n",
"from sklearn.naive_bayes import BernoulliNB as BernoulliNB_sk\n",
"from sklearn.naive_bayes import CategoricalNB as CategoricalNB_sk\n",
"from sklearn.naive_bayes import MultinomialNB as MultinomialNB_sk\n",
"from sklearn.naive_bayes import ComplementNB as ComplementNB_sk\n",
"\n",
"import numpy as np\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"id": "60ed554e",
"metadata": {},
"source": [
"# Loading News Aggregator dataset \n",
"\n",
"The dataset is loaded with cudf. For more information on cudf see the documentation [here](https://docs.rapids.ai/api/cudf/stable).\n",
"\n",
"Then we check the class distribution and the sparsity of the data"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3eb0dba4",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>ID</th>\n",
" <th>TITLE</th>\n",
" <th>URL</th>\n",
" <th>PUBLISHER</th>\n",
" <th>CATEGORY</th>\n",
" <th>STORY</th>\n",
" <th>HOSTNAME</th>\n",
" <th>TIMESTAMP</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>Fed official says weak data caused by weather,...</td>\n",
" <td>http://www.latimes.com/business/money/la-fi-mo...</td>\n",
" <td>Los Angeles Times</td>\n",
" <td>1</td>\n",
" <td>ddUyU0VZz0BRneMioxUPQVP6sIxvM</td>\n",
" <td>www.latimes.com</td>\n",
" <td>1394470370698</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>Fed's Charles Plosser sees high bar for change...</td>\n",
" <td>http://www.livemint.com/Politics/H2EvwJSK2VE6O...</td>\n",
" <td>Livemint</td>\n",
" <td>1</td>\n",
" <td>ddUyU0VZz0BRneMioxUPQVP6sIxvM</td>\n",
" <td>www.livemint.com</td>\n",
" <td>1394470371207</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>US open: Stocks fall after Fed official hints ...</td>\n",
" <td>http://www.ifamagazine.com/news/us-open-stocks...</td>\n",
" <td>IFA Magazine</td>\n",
" <td>1</td>\n",
" <td>ddUyU0VZz0BRneMioxUPQVP6sIxvM</td>\n",
" <td>www.ifamagazine.com</td>\n",
" <td>1394470371550</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>Fed risks falling 'behind the curve', Charles ...</td>\n",
" <td>http://www.ifamagazine.com/news/fed-risks-fall...</td>\n",
" <td>IFA Magazine</td>\n",
" <td>1</td>\n",
" <td>ddUyU0VZz0BRneMioxUPQVP6sIxvM</td>\n",
" <td>www.ifamagazine.com</td>\n",
" <td>1394470371793</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>Fed's Plosser: Nasty Weather Has Curbed Job Gr...</td>\n",
" <td>http://www.moneynews.com/Economy/federal-reser...</td>\n",
" <td>Moneynews</td>\n",
" <td>1</td>\n",
" <td>ddUyU0VZz0BRneMioxUPQVP6sIxvM</td>\n",
" <td>www.moneynews.com</td>\n",
" <td>1394470372027</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" ID TITLE \\\n",
"0 1 Fed official says weak data caused by weather,... \n",
"1 2 Fed's Charles Plosser sees high bar for change... \n",
"2 3 US open: Stocks fall after Fed official hints ... \n",
"3 4 Fed risks falling 'behind the curve', Charles ... \n",
"4 5 Fed's Plosser: Nasty Weather Has Curbed Job Gr... \n",
"\n",
" URL PUBLISHER \\\n",
"0 http://www.latimes.com/business/money/la-fi-mo... Los Angeles Times \n",
"1 http://www.livemint.com/Politics/H2EvwJSK2VE6O... Livemint \n",
"2 http://www.ifamagazine.com/news/us-open-stocks... IFA Magazine \n",
"3 http://www.ifamagazine.com/news/fed-risks-fall... IFA Magazine \n",
"4 http://www.moneynews.com/Economy/federal-reser... Moneynews \n",
"\n",
" CATEGORY STORY HOSTNAME TIMESTAMP \n",
"0 1 ddUyU0VZz0BRneMioxUPQVP6sIxvM www.latimes.com 1394470370698 \n",
"1 1 ddUyU0VZz0BRneMioxUPQVP6sIxvM www.livemint.com 1394470371207 \n",
"2 1 ddUyU0VZz0BRneMioxUPQVP6sIxvM www.ifamagazine.com 1394470371550 \n",
"3 1 ddUyU0VZz0BRneMioxUPQVP6sIxvM www.ifamagazine.com 1394470371793 \n",
"4 1 ddUyU0VZz0BRneMioxUPQVP6sIxvM www.moneynews.com 1394470372027 "
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path = '~/Downloads/uci-news-aggregator.csv'\n",
"dataset=cudf.read_csv(path,sep = \",\")\n",
"# business; technology; entertainment; health\n",
"dataset['CATEGORY'] = dataset.CATEGORY.map({ 'b': 1, 't': 2, 'e': 3, 'm': 4 })\n",
"dataset.head()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f8d63575",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3 152469\n",
"1 115967\n",
"2 108344\n",
"4 45639\n",
"Name: CATEGORY, dtype: int32"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Class distribution\n",
"dataset['CATEGORY'].value_counts()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "34d6df57",
"metadata": {},
"outputs": [],
"source": [
"Y, X = dataset['CATEGORY'], dataset['TITLE']\n",
"le = LabelEncoder()\n",
"y = le.fit_transform(Y)\n",
"\n",
"X_train_text, X_test_text, y_train, y_test = train_test_split(X, y, random_state=1)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "83d5a920",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.013490494373383653\n",
"0.013362668958684477\n"
]
}
],
"source": [
"vec = TfidfVectorizer(stop_words='english', ngram_range=(1,1))\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"\n",
"# Print sparsity of train and test\n",
"print(x_train.nnz / (x_train.shape[0] * x_train.shape[1])*100)\n",
"print(x_test.nnz / (x_test.shape[0] * x_test.shape[1])*100)"
]
},
{
"cell_type": "markdown",
"id": "92903d9d",
"metadata": {
"tags": []
},
"source": [
"# Gaussian NB\n",
"\n",
"Transform the text through a TF-IDF vectorizer and iterate through the dataset to do multiple partial fits of Gaussian naive Bayes."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1079a683",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 12.3 s, sys: 2.23 s, total: 14.5 s\n",
"Wall time: 22 s\n",
"0.8769999742507935\n",
"0.8840000033378601\n",
"0.878083348274231\n",
"0.8805833458900452\n",
"0.8756666779518127\n",
"0.8796666860580444\n",
"0.8786666393280029\n",
"0.8777499794960022\n",
"0.8823529481887817\n",
"CPU times: user 4.36 s, sys: 2.74 s, total: 7.1 s\n",
"Wall time: 22.8 s\n"
]
}
],
"source": [
"vec = TfidfVectorizer(stop_words='english', ngram_range=(1,3), min_df=5)\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"\n",
"def dataset_traversal(X, Y, partial_function):\n",
" chunk_size = 12000\n",
" classes = cp.unique(Y)\n",
" lower = 0\n",
" for upper in iter(range(chunk_size, X.shape[0], chunk_size)):\n",
" partial_function(X[lower:upper], Y[lower:upper], classes)\n",
" lower = upper\n",
" partial_function(X[upper:], Y[upper:], classes)\n",
"\n",
"mnb = GaussianNB()\n",
"%time dataset_traversal(x_train,\\\n",
" y_train,\\\n",
" lambda x,y, c: mnb.partial_fit(x, y, c))\n",
"\n",
"%time dataset_traversal(x_test,\\\n",
" y_test,\\\n",
" lambda x, y, c: print(mnb.score(x, y)))\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "a0c9ccc9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 2min 47s, sys: 1min 29s, total: 4min 17s\n",
"Wall time: 4min 17s\n",
"0.885\n",
"0.8736\n",
"0.8802\n",
"0.8828\n",
"0.8836\n",
"0.8738\n",
"0.8806\n",
"0.881\n",
"0.8832\n",
"0.8784\n",
"0.8714\n",
"0.879\n",
"0.8754\n",
"0.8782\n",
"0.8816\n",
"0.8844\n",
"0.875\n",
"0.8764\n",
"0.877\n",
"0.8864\n",
"0.8796\n",
"0.8842975206611571\n",
"CPU times: user 3min 8s, sys: 2min 7s, total: 5min 16s\n",
"Wall time: 5min 16s\n"
]
}
],
"source": [
"vec = TfidfVectorizer(stop_words='english', ngram_range=(1,3), min_df=5)\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"x_train_np, x_test_np = x_train.get(), x_test.get()\n",
"y_train_np, y_test_np = y_train.to_numpy(), y_test.to_numpy()\n",
"\n",
"def dataset_traversal(X, Y, partial_function):\n",
" chunk_size = 5000\n",
" classes = np.unique(Y)\n",
" lower = 0\n",
" for upper in iter(range(chunk_size, X.shape[0], chunk_size)):\n",
" partial_function(X[lower:upper], Y[lower:upper], classes)\n",
" lower = upper\n",
" partial_function(X[upper:], Y[upper:], classes)\n",
"\n",
"mnb = GaussianNB_sk()\n",
"%time dataset_traversal(x_train_np,\\\n",
" y_train_np,\\\n",
" lambda x, y, c: mnb.partial_fit(x.toarray(), y, c))\n",
"\n",
"%time dataset_traversal(x_test_np,\\\n",
" y_test_np,\\\n",
" lambda x, y, c: print(mnb.score(x.toarray(), y)))\n"
]
},
{
"cell_type": "markdown",
"id": "5dea07cd",
"metadata": {
"jp-MarkdownHeadingCollapsed": true,
"tags": []
},
"source": [
"# Bernoulli + CountVectorizer\n",
"\n",
"\n",
"In the Bernoulli variant, the feature vector is binarized. That's why using a CountVectorizer transformer is useful: You're more interested in the presence of the word rather than it's frequency."
]
},
{
"cell_type": "code",
"execution_count": 241,
"id": "33c0cb40",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 44.4 ms, sys: 12.1 ms, total: 56.5 ms\n",
"Wall time: 56.5 ms\n",
"CPU times: user 14.9 ms, sys: 19.6 ms, total: 34.5 ms\n",
"Wall time: 34.2 ms\n"
]
},
{
"data": {
"text/plain": [
"0.8568723201751709"
]
},
"execution_count": 241,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vec = CountVectorizer(stop_words='english', binary=True, ngram_range=(1,3))\n",
"\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"\n",
"bnb = BernoulliNB()\n",
"%time bnb.fit(x_train, y_train)\n",
"%time bnb.score(x_test, y_test)"
]
},
{
"cell_type": "code",
"execution_count": 247,
"id": "b735ce40",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 293 ms, sys: 72.1 ms, total: 365 ms\n",
"Wall time: 365 ms\n",
"CPU times: user 141 ms, sys: 90.9 ms, total: 232 ms\n",
"Wall time: 231 ms\n"
]
},
{
"data": {
"text/plain": [
"0.8568817764310402"
]
},
"execution_count": 247,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vec = CountVectorizer(stop_words='english', binary=True, ngram_range=(1,3))\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"x_train_np, x_test_np = x_train.get(), x_test.get()\n",
"y_train_np, y_test_np = y_train.to_numpy(), y_test.to_numpy()\n",
"\n",
"bnb = BernoulliNB_sk()\n",
"%time bnb.fit(x_train_np, y_train_np)\n",
"%time bnb.score(x_test_np, y_test_np)"
]
},
{
"cell_type": "markdown",
"id": "5957db9b",
"metadata": {
"jp-MarkdownHeadingCollapsed": true,
"tags": []
},
"source": [
"# TF-IDF + Multinomial\n",
"\n",
"Transform the text through a TF-IDF vectorizer, and run a multinomial naive Bayes model."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "637d0f2e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 55.4 ms, sys: 7.57 ms, total: 63 ms\n",
"Wall time: 63 ms\n",
"CPU times: user 20.3 ms, sys: 8.16 ms, total: 28.4 ms\n",
"Wall time: 28.2 ms\n"
]
},
{
"data": {
"text/plain": [
"0.9248046875"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vec = TfidfVectorizer(stop_words='english', ngram_range=(1,3))\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"\n",
"mnb = MultinomialNB()\n",
"%time mnb.fit(x_train, y_train)\n",
"%time mnb.score(x_test, y_test)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "da09815a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 264 ms, sys: 67.6 ms, total: 332 ms\n",
"Wall time: 332 ms\n",
"CPU times: user 31.8 ms, sys: 27.9 ms, total: 59.7 ms\n",
"Wall time: 59.4 ms\n"
]
},
{
"data": {
"text/plain": [
"0.9248046967473131"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vec = TfidfVectorizer(stop_words='english', ngram_range=(1,3))\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"x_train_np, x_test_np = x_train.get(), x_test.get()\n",
"y_train_np, y_test_np = y_train.to_numpy(), y_test.to_numpy()\n",
"\n",
"mnb = MultinomialNB_sk()\n",
"%time mnb.fit(x_train_np, y_train_np)\n",
"%time mnb.score(x_test_np, y_test_np)"
]
},
{
"cell_type": "markdown",
"id": "5ff606c1-a2c1-461f-9ef1-e912d5735c79",
"metadata": {},
"source": [
"# CountVectorizer + Complement\n",
"Complement naive Bayes models should be coupled with a CountVectorizer to have the best results."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "767cd8ed-bd27-4e3a-9a6d-6c516bfef80c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3 0.360943\n",
"1 0.274531\n",
"2 0.256485\n",
"4 0.108042\n",
"Name: CATEGORY, dtype: float64"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# First let's visualize the class imbalance\n",
"\n",
"dataset['CATEGORY'].value_counts().to_pandas().plot(kind='bar', title='histogram of the class distributions')\n",
"dataset['CATEGORY'].value_counts() / len(dataset)"
]
},
{
"cell_type": "code",
"execution_count": 252,
"id": "65a127f2-be0d-4ab8-b87f-45aabbe4a7fa",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 56.8 ms, sys: 12.3 ms, total: 69.1 ms\n",
"Wall time: 69.2 ms\n",
"CPU times: user 22.4 ms, sys: 7.27 ms, total: 29.7 ms\n",
"Wall time: 29.8 ms\n"
]
},
{
"data": {
"text/plain": [
"0.9502959251403809"
]
},
"execution_count": 252,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vec = CountVectorizer(stop_words='english', ngram_range=(1,3))\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"\n",
"cnb = ComplementNB()\n",
"%time cnb.fit(x_train, y_train)\n",
"%time cnb.score(x_test, y_test)"
]
},
{
"cell_type": "code",
"execution_count": 253,
"id": "73005bfc-6ffb-45af-9809-00d5345c597a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 67.5 ms, sys: 31.8 ms, total: 99.3 ms\n",
"Wall time: 99.5 ms\n",
"CPU times: user 26.6 ms, sys: 11.4 ms, total: 38 ms\n",
"Wall time: 37.7 ms\n"
]
},
{
"data": {
"text/plain": [
"0.9449836611747742"
]
},
"execution_count": 253,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vec = CountVectorizer(stop_words='english', ngram_range=(1,3))\n",
"x_train = vec.fit_transform(X_train_text)\n",
"x_test = vec.transform(X_test_text)\n",
"x_train_np, x_test_np = x_train.get(), x_test.get()\n",
"y_train_np, y_test_np = y_train.to_numpy(), y_test.to_numpy()\n",
"\n",
"cnb = ComplementNB_sk()\n",
"%time mnb.fit(x_train_np, y_train_np)\n",
"%time mnb.score(x_test_np, y_test_np)"
]
},
{
"cell_type": "markdown",
"id": "67f23185",
"metadata": {},
"source": [
"# Categorical\n",
"\n",
"To transform the text to categorical data, you can apply a clustering technique to merge the terms that are similar.\n",
"\n",
"To create these clusters, you could reuse a previously fitted naive Bayes model but just for the purpose of clustering those words."
]
},
{
"cell_type": "markdown",
"id": "aef23dff",
"metadata": {},
"source": [
"## Preprocessing"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "84ed876e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"count 1000.000000\n",
"mean 14.967000\n",
"std 19.519501\n",
"min 1.000000\n",
"25% 6.000000\n",
"50% 11.000000\n",
"75% 18.000000\n",
"max 254.000000\n",
"dtype: float64\n"
]
}
],
"source": [
"# First fit a TfIdf on the train dataset\n",
"tfidfvec = TfidfVectorizer(stop_words='english', min_df=10)\n",
"x_train = tfidfvec.fit_transform(X_train_text)\n",
"\n",
"# Fit a Multinomial on the TdIdf data\n",
"mnb = MultinomialNB().fit(x_train, y_train)\n",
"\n",
"# Use a KMeans algorithm to cluster on what the Multinomial NB learned of the TfIdf.\n",
"# This means that the words that contribute similarly to a category will be clustered together\n",
"km = KMeans(n_clusters=1000, random_state=1)\n",
"feature_to_cluster = km.fit_predict(mnb.feature_log_prob_.T)\n",
"feats2cluster = OneHotEncoder().fit_transform(feature_to_cluster)\n",
"\n",
"# Print statistics on the repartition of the words in the clusters\n",
"print(cudf.Series(feats2cluster.sum(0)[0]).describe())"
]
},
{
"cell_type": "markdown",
"id": "d04509d7",
"metadata": {},
"source": [
"Here each cluster holds in average around 15 words"
]
},
{
"cell_type": "code",
"execution_count": 225,
"id": "ada1038b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"47 117\n",
"1597 beam\n",
"2114 broadband\n",
"2435 carriers\n",
"2618 charter\n",
"3788 defective\n",
"4056 dire\n",
"4406 dual\n",
"5367 fixes\n",
"8365 materials\n",
"9072 networking\n",
"10900 recognition\n",
"11466 rollout\n",
"13666 tracker\n",
"14088 unveiling\n",
"Name: token, dtype: object\n",
"\n",
"\n",
"3293 core\n",
"3603 cyber\n",
"4751 enterprise\n",
"5719 gaming\n",
"8738 models\n",
"9801 pc\n",
"10074 platform\n",
"14338 virtual\n",
"Name: token, dtype: object\n"
]
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"# Lets plot the repartition of the words in each cluster\n",
"# And print the words in a few clusters\n",
"plt.hist(feature_to_cluster.get(), bins='auto')\n",
"print(tfidfvec.vocabulary_[cp.where(feature_to_cluster == 127)[0]])\n",
"print(\"\\n\")\n",
"print(tfidfvec.vocabulary_[cp.where(feature_to_cluster == 632)[0]])"
]
},
{
"cell_type": "code",
"execution_count": 226,
"id": "eb0d6f7d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(316814, 14967)\n",
"(14967, 1000)\n"
]
}
],
"source": [
"# For Categorical Naive Bayes, the count of words is transformed into a count of cluster\n",
"vocab = tfidfvec.vocabulary_\n",
"countvec = CountVectorizer(stop_words='english')\n",
"countvec.vocabulary_ = vocab\n",
"\n",
"x_train = countvec.transform(X_train_text)\n",
"x_test = countvec.transform(X_test_text)\n",
"print(x_train.shape)\n",
"print(feats2cluster.shape)\n",
"\n",
"x_train_cluster = (x_train @ feats2cluster)\n",
"x_test_cluster = (x_test @ feats2cluster)\n",
"\n",
"# For each cluster we will have:\n",
"# - 0: absence of those wprds.\n",
"# - 1: presence of those words\n",
"# - 2: multiple presence of those words (2+)\n",
"\n",
"x_train_cluster.data[x_train_cluster.data > 2] = 2\n",
"x_test_cluster.data[x_test_cluster.data > 2] = 2"
]
},
{
"cell_type": "markdown",
"id": "1a61a25e",
"metadata": {},
"source": [
"Little hack to make sure that if a cluster's max number is 1 in training, it is also 1 in testing"
]
},
{
"cell_type": "code",
"execution_count": 227,
"id": "23582cf5",
"metadata": {},
"outputs": [],
"source": [
"max_one = cp.where(x_train_cluster.max(0).todense() == 1)[1]\n",
"for cluster in max_one:\n",
" samples = (x_test_cluster[:, cluster] > 1)\n",
" if samples.nnz == 0:\n",
" continue\n",
" samples = cp.where(samples.todense())[0]\n",
" x_test_cluster[samples, cluster] = 1"
]
},
{
"cell_type": "markdown",
"id": "ac2198bd",
"metadata": {},
"source": [
"## Categorical model training\n",
"\n",
"Now that the preprocessing is done we can train the Categorical model and see how it performs on these clusters"
]
},
{
"cell_type": "code",
"execution_count": 239,
"id": "6e561526",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/dev/shm/rapids22.04_env/lib/python3.8/site-packages/cuml/naive_bayes/naive_bayes.py:1498: UserWarning: X dtype is not int32. X will be converted, which will increase memory consumption\n",
" warnings.warn(\"X dtype is not int32. X will be \"\n",
"/dev/shm/rapids22.04_env/lib/python3.8/site-packages/cupyx/scipy/sparse/compressed.py:545: UserWarning: Changing the sparsity structure of a csr_matrix is expensive. lil_matrix is more efficient.\n",
" warnings.warn('Changing the sparsity structure of a '\n",
"/dev/shm/rapids22.04_env/lib/python3.8/site-packages/cuml/naive_bayes/naive_bayes.py:1516: UserWarning: X dtype is not int32. X will be converted, which will increase memory consumption\n",
" warnings.warn(\"X dtype is not int32. X will be \"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 110 ms, sys: 4.87 ms, total: 115 ms\n",
"Wall time: 112 ms\n",
"CPU times: user 64.7 ms, sys: 127 ms, total: 191 ms\n",
"Wall time: 193 ms\n"
]
},
{
"data": {
"text/plain": [
"0.9256380200386047"
]
},
"execution_count": 239,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"%time cnb = CategoricalNB().fit(x_train_cluster, y_train)\n",
"%time cnb.score(x_test_cluster, y_test)"
]
},
{
"cell_type": "code",
"execution_count": 240,
"id": "56a11bc1",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/dev/shm/rapids22.04_env/lib/python3.8/site-packages/sklearn/utils/validation.py:593: FutureWarning: np.matrix usage is deprecated in 1.0 and will raise a TypeError in 1.2. Please convert to a numpy array with np.asarray. For more information see: https://numpy.org/doc/stable/reference/generated/numpy.matrix.html\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 13.7 s, sys: 434 ms, total: 14.2 s\n",
"Wall time: 14.2 s\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/dev/shm/rapids22.04_env/lib/python3.8/site-packages/sklearn/utils/validation.py:593: FutureWarning: np.matrix usage is deprecated in 1.0 and will raise a TypeError in 1.2. Please convert to a numpy array with np.asarray. For more information see: https://numpy.org/doc/stable/reference/generated/numpy.matrix.html\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 4.4 s, sys: 110 ms, total: 4.51 s\n",
"Wall time: 4.51 s\n"
]
},
{
"data": {
"text/plain": [
"0.9256379906254438"
]
},
"execution_count": 240,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x_train_cluster_np = x_train_cluster.get().todense()\n",
"x_test_cluster_np = x_test_cluster.get().todense()\n",
"y_train_np, y_test_np = y_train.to_numpy(), y_test.to_numpy()\n",
"\n",
"%time cnb = CategoricalNB_sk().fit(x_train_cluster_np, y_train_np)\n",
"%time cnb.score(x_test_cluster_np, y_test_np)"
]
},
{
"cell_type": "markdown",
"id": "14d61f69",
"metadata": {},
"source": [
"# Performance gain"
]
},
{
"cell_type": "code",
"execution_count": 254,
"id": "ae51c493",
"metadata": {},
"outputs": [],
"source": [
"variants = ['Gaussian', 'Bernoulli', 'Multinomial', 'Complement', 'Categorical']\n",
"time_training_cuml = np.array([12300, 26, 63, 69, 112]) / 1000\n",
"time_testing_cuml = np.array([4360, 34, 28, 30, 193])/ 1000\n",
"time_training_sk = np.array([257000, 365, 332, 99, 14200])/ 1000\n",
"time_testing_sk = np.array([316000, 231, 59, 38, 4510])/ 1000\n",
"training_gain = time_training_sk / time_training_cuml\n",
"testing_gain = time_testing_sk / time_testing_cuml"
]
},
{
"cell_type": "code",
"execution_count": 256,
"id": "174c66d2",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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RoMBAwawatWqUoRmZl2A26yOqfig+Yi4OiK2Bc4GzmvjvpMjojYiaocMGZJPgGbWIe+++26lQzAza7W82qw8E65lwJYF68NSWXNuBY5q575m1gqrV6/mU5/6FCNHjmSXXXbhtttua9z25ptvcthhh/GTn/zkfftddtll7LXXXuy2225MnDixsfyoo45izz33ZOedd2by5MmN5QMGDODMM89k5MiRPPjggwwYMIBzzz2XkSNHMmbMGF588cV8P6iZdQldqc3KM+GaDYyQNFxSH7JB8FMLK0gaUbD6KeDptDwVOEZSX0nDgRHAwznGatYt3HXXXWyxxRbMnz+fxx57jLFjxwKwatUqjjjiCI499li+9KUvvWef6dOn8/TTT/Pwww8zb9485syZw/333w/AlClTmDNnDnV1dVx55ZWNzzxbvXo1e++9N/Pnz+ejH/0oq1evZsyYMcyfP5/99tuvaANpZtZUV2qzcku4ImItMAG4G1gE3B4RCyVNknRkqjZB0kJJ88jGcZ2U9l0I3A48DtwFnB4Rvi5h1kG77ror99xzD2effTZ//vOf2XjjjQEYN24cp5xyCieeeOL79pk+fTrTp09n1KhR7LHHHjzxxBM8/XT22+jKK69s/AX43HPPNZb37NmTT3/6043H6NOnD4cffjgAe+65J0uXLs35k5pZV9CV2qxcH14dEdOAaU3Kzi9Y/loL+34H+E5+0Zl1P9tvvz1z585l2rRpnHfeeRx00EEA7Lvvvtx1110cd9xxSHrPPhHBt7/9bb785S+/p3zmzJn88Y9/5MEHH6R///4ccMABrFmzBoB+/frRs+e/ZnLp3bt343F79uzJ2rVr8/yYZtZFdKU2q+KD5s2sfJ5//nn69+/P8ccfz1lnncXcuXMBmDRpEgMHDuT0009/3z6HHnooU6ZMabz7Z9myZbz00ku89tprDBw4kP79+/PEE08wa9as9+1rZtYRXanNyrWHy8ya195bojtiwYIFnHXWWfTo0YPevXvz4x//mKOPPhqAK664gi984Qt861vf4tJLL23c55BDDmHRokXss88+QDa49Gc/+xljx47l2muvZccdd2SHHXZgzJgxZf88ZlY+brM6RhGx/lqdQG1tbdTV1bW6fqnnE8lbJU50K61Fixax4447VjqMqlbsbyRpTkTUViik3LSlzXJ7ZZXgNqtlbW2v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zMwsZ064zMzMzHLmebjMKqTUU5O0d+qQmpoa6urqGh/302DAgAGsWrWqFKGZmXV77uEys1y9++67lQ7BzKzinHCZdSOrV6/mU5/6FCNHjmSXXXbhtttua9z25ptvcthhh/GTn/zkfftddtll7LXXXuy2225MnDixsfyoo45izz33ZOedd2by5MmN5QMGDODMM89k5MiRPPjggwwYMIBzzz2XkSNHMmbMGF588cV8P6iZWZVxwmXWjdx1111sscUWzJ8/n8cee4yxY8cCsGrVKo444giOPfZYvvSlL71nn+nTp/P000/z8MMPM2/ePObMmcP9998PwJQpU5gzZw51dXVceeWVjc9oXL16NXvvvTfz58/nox/9KKtXr2bMmDHMnz+f/fbbr2hSZ2bWlTnhMutGdt11V+655x7OPvts/vznP7PxxhsDMG7cOE455RROPPHE9+0zffp0pk+fzqhRo9hjjz144oknePrppwG48sorG3utnnvuucbynj178ulPf7rxGH369OHwww8HYM8992Tp0qU5f1Izs+riQfNm3cj222/P3LlzmTZtGueddx4HHXQQAPvuuy933XUXxx13HJLes09E8O1vf5svf/nL7ymfOXMmf/zjH3nwwQfp378/BxxwAGvWrAGgX79+9OzZs7Fu7969G4/bs2dP1q5dm+fHNDOrOu7hMutGnn/+efr378/xxx/PWWedxdy5cwGYNGkSAwcO5PTTT3/fPoceeihTpkxpvGNx2bJlvPTSS7z22msMHDiQ/v3788QTTzBr1qyyfhYzs86k4j1ckqYAhwMvRcQuqewy4AjgbeAZ4JSIeLViQZrloL3TOHTEggULOOuss+jRowe9e/fmxz/+MUcffTQAV1xxBV/4whf41re+xaWXXtq4zyGHHMKiRYvYZ599gGxA/M9+9jPGjh3Ltddey4477sgOO+zAmDFjyv55zMw6C0VEZQOQ9gNWATcVJFyHADMiYq2kSwAi4uyWjlNbWxt1dXWtft9Sz4GUt0p8OVtpLVq0iB133LHSYVS1Yn8jSXMiorZCIZmZlUTFLylGxP3AyiZl0yOiYZDHLGBY2QMzMzMzK5GKJ1yt8AXgzkoHYWZmZtZeVZ1wSToXWAvc0sz28ZLqJNUtX768vMGZtUOlL+FXM/9tzKwrq9qES9LJZIPpPx/NtMQRMTkiaiOidsiQ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",
"text/plain": [
"<Figure size 612x720 with 5 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"x = np.arange(2) # the label locations\n",
"width = 0.33 # the width of the bars\n",
"fig = plt.figure(figsize=(8.5,10))\n",
"\n",
"for i in range(5):\n",
" ax = plt.subplot(3,2,i+1)\n",
" ax.bar(x[0] + width/2 + 0.01, time_training_cuml[i], width, color='green')\n",
" ax.bar(x[0] - width/2 - 0.01, time_training_sk[i], width, color='royalblue')\n",
" ax.bar(x[1] + width/2 + 0.01, time_testing_cuml[i], width, color='green')\n",
" ax.bar(x[1] - width/2 - 0.01, time_testing_sk[i], width, color='royalblue')\n",
"\n",
" ax.set_ylabel('Time (s)')\n",
" ax.set_title(variants[i])\n",
" ax.set_xticks(x)\n",
" ax.set_xticklabels([\"train speedup\\n{:.1f}x\".format(training_gain[i]),\n",
" \"test speedup\\n{:.1f}x\".format(testing_gain[i])],\n",
" fontdict={'fontsize': 11,})\n",
" ax.legend(['cuml', 'sklearn'])\n",
"\n",
"fig.tight_layout()\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b513fa16",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment