2010-11-12 12:05:45 +08:00
|
|
|
"""
|
|
|
|
|
======================================================
|
|
|
|
|
Classification of text documents using sparse features
|
|
|
|
|
======================================================
|
|
|
|
|
|
2012-08-21 14:17:44 +08:00
|
|
|
This is an example showing how scikit-learn can be used to classify documents
|
|
|
|
|
by topics using a bag-of-words approach. This example uses a scipy.sparse
|
|
|
|
|
matrix to store the features and demonstrates various classifiers that can
|
|
|
|
|
efficiently handle sparse matrices.
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2012-08-21 14:17:44 +08:00
|
|
|
The dataset used in this example is the 20 newsgroups dataset. It will be
|
|
|
|
|
automatically downloaded, then cached.
|
2010-11-12 12:05:45 +08:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Author: Peter Prettenhofer <peter.prettenhofer@gmail.com>
|
|
|
|
|
# Olivier Grisel <olivier.grisel@ensta.org>
|
|
|
|
|
# Mathieu Blondel <mathieu@mblondel.org>
|
2016-03-04 17:41:12 +08:00
|
|
|
# Lars Buitinck
|
2013-04-30 14:23:46 +08:00
|
|
|
# License: BSD 3 clause
|
2021-10-22 21:33:22 +08:00
|
|
|
|
2011-02-27 09:05:02 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# Configuration options for the analysis
|
|
|
|
|
# --------------------------------------
|
2016-12-09 00:53:01 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# If True, we use `HashingVectorizer`, otherwise we use a `TfidfVectorizer`
|
|
|
|
|
USE_HASHING = False
|
2017-12-13 20:02:39 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# Number of features used by `HashingVectorizer`
|
|
|
|
|
N_FEATURES = 2**16
|
2011-07-22 18:46:42 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# Optional feature selection: either False, or an integer: the number of
|
|
|
|
|
# features to select
|
|
|
|
|
SELECT_CHI2 = False
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2011-07-22 18:46:42 +08:00
|
|
|
|
2020-06-09 11:23:14 +08:00
|
|
|
# %%
|
2019-08-27 04:51:34 +08:00
|
|
|
# Load data from the training set
|
|
|
|
|
# ------------------------------------
|
|
|
|
|
# Let's load data from the newsgroups dataset which comprises around 18000
|
|
|
|
|
# newsgroups posts on 20 topics split in two subsets: one for training (or
|
|
|
|
|
# development) and the other one for testing (or for performance evaluation).
|
2022-03-14 16:47:36 +08:00
|
|
|
from sklearn.datasets import fetch_20newsgroups
|
2013-06-29 06:25:54 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
categories = [
|
|
|
|
|
"alt.atheism",
|
|
|
|
|
"talk.religion.misc",
|
|
|
|
|
"comp.graphics",
|
|
|
|
|
"sci.space",
|
|
|
|
|
]
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2011-03-20 01:39:45 +08:00
|
|
|
data_train = fetch_20newsgroups(
|
2022-03-14 16:47:36 +08:00
|
|
|
subset="train", categories=categories, shuffle=True, random_state=42
|
2013-06-29 06:25:54 +08:00
|
|
|
)
|
2011-02-27 09:05:02 +08:00
|
|
|
|
2011-03-20 01:39:45 +08:00
|
|
|
data_test = fetch_20newsgroups(
|
2022-03-14 16:47:36 +08:00
|
|
|
subset="test", categories=categories, shuffle=True, random_state=42
|
2013-06-29 06:25:54 +08:00
|
|
|
)
|
2013-02-01 22:04:03 +08:00
|
|
|
print("data loaded")
|
2011-02-27 09:05:02 +08:00
|
|
|
|
2016-10-25 21:20:30 +08:00
|
|
|
# order of labels in `target_names` can be different from `categories`
|
|
|
|
|
target_names = data_train.target_names
|
2011-07-08 02:04:08 +08:00
|
|
|
|
2013-01-20 21:55:15 +08:00
|
|
|
|
2013-01-10 08:13:15 +08:00
|
|
|
def size_mb(docs):
|
|
|
|
|
return sum(len(s.encode("utf-8")) for s in docs) / 1e6
|
|
|
|
|
|
2017-12-13 20:02:39 +08:00
|
|
|
|
2013-01-10 08:13:15 +08:00
|
|
|
data_train_size_mb = size_mb(data_train.data)
|
|
|
|
|
data_test_size_mb = size_mb(data_test.data)
|
|
|
|
|
|
|
|
|
|
print(
|
|
|
|
|
"%d documents - %0.3fMB (training set)" % (len(data_train.data), data_train_size_mb)
|
2013-01-20 21:55:15 +08:00
|
|
|
)
|
2013-06-29 06:21:27 +08:00
|
|
|
print("%d documents - %0.3fMB (test set)" % (len(data_test.data), data_test_size_mb))
|
2018-12-14 23:40:49 +08:00
|
|
|
print("%d categories" % len(target_names))
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# Vectorize the training and test data
|
|
|
|
|
# -------------------------------------
|
|
|
|
|
#
|
2010-11-14 03:23:04 +08:00
|
|
|
# split a training set and a test set
|
2011-02-27 09:05:02 +08:00
|
|
|
y_train, y_test = data_train.target, data_test.target
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# Extracting features from the training data using a sparse vectorizer
|
|
|
|
|
from time import time
|
|
|
|
|
|
|
|
|
|
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
|
|
|
from sklearn.feature_extraction.text import HashingVectorizer
|
|
|
|
|
|
2010-11-12 12:05:45 +08:00
|
|
|
t0 = time()
|
2022-03-14 16:47:36 +08:00
|
|
|
|
|
|
|
|
if USE_HASHING:
|
2017-06-21 07:01:55 +08:00
|
|
|
vectorizer = HashingVectorizer(
|
2022-03-14 16:47:36 +08:00
|
|
|
stop_words="english", alternate_sign=False, n_features=N_FEATURES
|
2012-12-14 08:51:10 +08:00
|
|
|
)
|
|
|
|
|
X_train = vectorizer.transform(data_train.data)
|
|
|
|
|
else:
|
|
|
|
|
vectorizer = TfidfVectorizer(sublinear_tf=True, max_df=0.5, stop_words="english")
|
|
|
|
|
X_train = vectorizer.fit_transform(data_train.data)
|
2013-01-10 08:13:15 +08:00
|
|
|
duration = time() - t0
|
|
|
|
|
print("done in %fs at %0.3fMB/s" % (duration, data_train_size_mb / duration))
|
2013-02-01 22:04:03 +08:00
|
|
|
print("n_samples: %d, n_features: %d" % X_train.shape)
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# Extracting features from the test data using the same vectorizer
|
2010-11-12 12:05:45 +08:00
|
|
|
t0 = time()
|
2011-07-30 20:51:06 +08:00
|
|
|
X_test = vectorizer.transform(data_test.data)
|
2013-01-10 08:13:15 +08:00
|
|
|
duration = time() - t0
|
|
|
|
|
print("done in %fs at %0.3fMB/s" % (duration, data_test_size_mb / duration))
|
2013-02-01 22:04:03 +08:00
|
|
|
print("n_samples: %d, n_features: %d" % X_test.shape)
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
2014-11-30 05:53:22 +08:00
|
|
|
# mapping from integer feature name to original token string
|
2022-03-14 16:47:36 +08:00
|
|
|
if USE_HASHING:
|
2014-11-30 05:53:22 +08:00
|
|
|
feature_names = None
|
|
|
|
|
else:
|
2021-09-07 16:56:57 +08:00
|
|
|
feature_names = vectorizer.get_feature_names_out()
|
2014-11-30 05:53:22 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# Keeping only the best features
|
|
|
|
|
from sklearn.feature_selection import SelectKBest, chi2
|
|
|
|
|
|
|
|
|
|
if SELECT_CHI2:
|
|
|
|
|
print("Extracting %d best features by a chi-squared test" % SELECT_CHI2)
|
2011-07-22 18:46:42 +08:00
|
|
|
t0 = time()
|
2022-03-14 16:47:36 +08:00
|
|
|
ch2 = SelectKBest(chi2, k=SELECT_CHI2)
|
2011-07-22 18:46:42 +08:00
|
|
|
X_train = ch2.fit_transform(X_train, y_train)
|
|
|
|
|
X_test = ch2.transform(X_test)
|
2021-09-07 16:56:57 +08:00
|
|
|
if feature_names is not None:
|
2014-11-30 05:53:22 +08:00
|
|
|
# keep selected feature names
|
2021-09-07 16:56:57 +08:00
|
|
|
feature_names = feature_names[ch2.get_support()]
|
2013-02-01 22:04:03 +08:00
|
|
|
print("done in %fs" % (time() - t0))
|
|
|
|
|
print()
|
2011-07-22 18:46:42 +08:00
|
|
|
|
2011-07-08 02:04:08 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# Benchmark classifiers
|
|
|
|
|
# ------------------------------------
|
|
|
|
|
#
|
|
|
|
|
# First we define small benchmarking utilities
|
|
|
|
|
import numpy as np
|
|
|
|
|
from sklearn import metrics
|
|
|
|
|
from sklearn.utils.extmath import density
|
|
|
|
|
|
|
|
|
|
|
2011-07-08 02:04:08 +08:00
|
|
|
def trim(s):
|
|
|
|
|
"""Trim string to fit on terminal (assuming 80-column display)"""
|
|
|
|
|
return s if len(s) <= 80 else s[:77] + "..."
|
|
|
|
|
|
2010-11-25 18:03:10 +08:00
|
|
|
|
2010-11-12 12:05:45 +08:00
|
|
|
def benchmark(clf):
|
2013-02-01 22:04:03 +08:00
|
|
|
print("_" * 80)
|
|
|
|
|
print("Training: ")
|
|
|
|
|
print(clf)
|
2010-11-12 12:05:45 +08:00
|
|
|
t0 = time()
|
|
|
|
|
clf.fit(X_train, y_train)
|
|
|
|
|
train_time = time() - t0
|
2013-02-01 22:04:03 +08:00
|
|
|
print("train time: %0.3fs" % train_time)
|
2010-11-12 12:05:45 +08:00
|
|
|
|
|
|
|
|
t0 = time()
|
|
|
|
|
pred = clf.predict(X_test)
|
|
|
|
|
test_time = time() - t0
|
2013-02-01 22:04:03 +08:00
|
|
|
print("test time: %0.3fs" % test_time)
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2015-03-03 01:50:53 +08:00
|
|
|
score = metrics.accuracy_score(y_test, pred)
|
2013-12-19 13:26:38 +08:00
|
|
|
print("accuracy: %0.3f" % score)
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2011-04-12 02:40:11 +08:00
|
|
|
if hasattr(clf, "coef_"):
|
2013-02-01 22:04:03 +08:00
|
|
|
print("dimensionality: %d" % clf.coef_.shape[1])
|
|
|
|
|
print("density: %f" % density(clf.coef_))
|
2011-07-08 02:04:08 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
if feature_names is not None:
|
2013-02-01 22:04:03 +08:00
|
|
|
print("top 10 keywords per class:")
|
2016-10-25 21:20:30 +08:00
|
|
|
for i, label in enumerate(target_names):
|
2011-12-20 01:16:51 +08:00
|
|
|
top10 = np.argsort(clf.coef_[i])[-10:]
|
2016-10-25 21:20:30 +08:00
|
|
|
print(trim("%s: %s" % (label, " ".join(feature_names[top10]))))
|
2013-02-01 22:04:03 +08:00
|
|
|
print()
|
2010-11-14 00:26:01 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
print("classification report:")
|
|
|
|
|
print(metrics.classification_report(y_test, pred, target_names=target_names))
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
print("confusion matrix:")
|
|
|
|
|
print(metrics.confusion_matrix(y_test, pred))
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2013-02-01 22:04:03 +08:00
|
|
|
print()
|
2012-03-04 00:52:11 +08:00
|
|
|
clf_descr = str(clf).split("(")[0]
|
|
|
|
|
return clf_descr, score, train_time, test_time
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2012-03-04 00:52:11 +08:00
|
|
|
|
2022-03-14 16:47:36 +08:00
|
|
|
# %%
|
|
|
|
|
# We now train and test the datasets with 15 different classification
|
|
|
|
|
# models and get performance results for each model.
|
|
|
|
|
from sklearn.feature_selection import SelectFromModel
|
|
|
|
|
from sklearn.linear_model import RidgeClassifier
|
|
|
|
|
from sklearn.pipeline import Pipeline
|
|
|
|
|
from sklearn.svm import LinearSVC
|
|
|
|
|
from sklearn.linear_model import SGDClassifier
|
|
|
|
|
from sklearn.linear_model import Perceptron
|
|
|
|
|
from sklearn.linear_model import PassiveAggressiveClassifier
|
|
|
|
|
from sklearn.naive_bayes import BernoulliNB, ComplementNB, MultinomialNB
|
|
|
|
|
from sklearn.neighbors import KNeighborsClassifier
|
|
|
|
|
from sklearn.neighbors import NearestCentroid
|
|
|
|
|
from sklearn.ensemble import RandomForestClassifier
|
|
|
|
|
|
|
|
|
|
|
2012-03-04 00:52:11 +08:00
|
|
|
results = []
|
2012-12-25 20:16:05 +08:00
|
|
|
for clf, name in (
|
2018-07-17 13:08:04 +08:00
|
|
|
(RidgeClassifier(tol=1e-2, solver="sag"), "Ridge Classifier"),
|
2019-08-03 04:11:46 +08:00
|
|
|
(Perceptron(max_iter=50), "Perceptron"),
|
2019-08-25 05:51:47 +08:00
|
|
|
(PassiveAggressiveClassifier(max_iter=50), "Passive-Aggressive"),
|
2014-11-21 21:38:31 +08:00
|
|
|
(KNeighborsClassifier(n_neighbors=10), "kNN"),
|
2019-05-09 21:19:20 +08:00
|
|
|
(RandomForestClassifier(), "Random forest"),
|
|
|
|
|
):
|
2013-02-01 22:04:03 +08:00
|
|
|
print("=" * 80)
|
|
|
|
|
print(name)
|
2012-03-04 00:52:11 +08:00
|
|
|
results.append(benchmark(clf))
|
2011-01-18 23:13:54 +08:00
|
|
|
|
2010-11-14 00:26:01 +08:00
|
|
|
for penalty in ["l2", "l1"]:
|
2013-02-01 22:04:03 +08:00
|
|
|
print("=" * 80)
|
|
|
|
|
print("%s penalty" % penalty.upper())
|
2010-11-14 00:26:01 +08:00
|
|
|
# Train Liblinear model
|
2017-01-15 21:09:44 +08:00
|
|
|
results.append(benchmark(LinearSVC(penalty=penalty, dual=False, tol=1e-3)))
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2010-11-14 00:26:01 +08:00
|
|
|
# Train SGD model
|
2018-07-17 13:08:04 +08:00
|
|
|
results.append(benchmark(SGDClassifier(alpha=0.0001, max_iter=50, penalty=penalty)))
|
2010-11-12 12:05:45 +08:00
|
|
|
|
2010-11-14 00:26:01 +08:00
|
|
|
# Train SGD with Elastic Net penalty
|
2013-02-01 22:04:03 +08:00
|
|
|
print("=" * 80)
|
|
|
|
|
print("Elastic-Net penalty")
|
2018-07-17 13:08:04 +08:00
|
|
|
results.append(
|
|
|
|
|
benchmark(SGDClassifier(alpha=0.0001, max_iter=50, penalty="elasticnet"))
|
|
|
|
|
)
|
2011-04-12 02:40:11 +08:00
|
|
|
|
2012-03-27 16:40:50 +08:00
|
|
|
# Train NearestCentroid without threshold
|
2013-02-01 22:04:03 +08:00
|
|
|
print("=" * 80)
|
|
|
|
|
print("NearestCentroid (aka Rocchio classifier)")
|
2012-03-27 16:40:50 +08:00
|
|
|
results.append(benchmark(NearestCentroid()))
|
|
|
|
|
|
2011-06-06 17:07:34 +08:00
|
|
|
# Train sparse Naive Bayes classifiers
|
2013-02-01 22:04:03 +08:00
|
|
|
print("=" * 80)
|
|
|
|
|
print("Naive Bayes")
|
2011-12-31 19:52:51 +08:00
|
|
|
results.append(benchmark(MultinomialNB(alpha=0.01)))
|
|
|
|
|
results.append(benchmark(BernoulliNB(alpha=0.01)))
|
2017-08-28 21:31:45 +08:00
|
|
|
results.append(benchmark(ComplementNB(alpha=0.1)))
|
2011-09-06 18:12:00 +08:00
|
|
|
|
2013-02-01 22:04:03 +08:00
|
|
|
print("=" * 80)
|
|
|
|
|
print("LinearSVC with L1-based feature selection")
|
2015-03-05 21:49:18 +08:00
|
|
|
# The smaller C, the stronger the regularization.
|
|
|
|
|
# The more regularization, the more sparsity.
|
|
|
|
|
results.append(
|
|
|
|
|
benchmark(
|
|
|
|
|
Pipeline(
|
|
|
|
|
[
|
2017-01-15 21:09:44 +08:00
|
|
|
(
|
|
|
|
|
"feature_selection",
|
|
|
|
|
SelectFromModel(LinearSVC(penalty="l1", dual=False, tol=1e-3)),
|
|
|
|
|
),
|
|
|
|
|
("classification", LinearSVC(penalty="l2")),
|
2021-10-07 16:13:00 +08:00
|
|
|
]
|
2017-01-15 21:09:44 +08:00
|
|
|
)
|
2021-10-07 16:13:00 +08:00
|
|
|
)
|
|
|
|
|
)
|
2012-03-04 00:52:11 +08:00
|
|
|
|
|
|
|
|
|
2020-06-09 11:23:14 +08:00
|
|
|
# %%
|
2019-08-27 04:51:34 +08:00
|
|
|
# Add plots
|
|
|
|
|
# ------------------------------------
|
|
|
|
|
# The bar plot indicates the accuracy, training time (normalized) and test time
|
|
|
|
|
# (normalized) of each classifier.
|
2022-03-14 16:47:36 +08:00
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
2012-03-04 00:52:11 +08:00
|
|
|
indices = np.arange(len(results))
|
|
|
|
|
|
2013-02-14 09:05:35 +08:00
|
|
|
results = [[x[i] for x in results] for i in range(4)]
|
2012-03-04 00:52:11 +08:00
|
|
|
|
|
|
|
|
clf_names, score, training_time, test_time = results
|
2012-08-21 14:17:44 +08:00
|
|
|
training_time = np.array(training_time) / np.max(training_time)
|
|
|
|
|
test_time = np.array(test_time) / np.max(test_time)
|
2012-03-04 00:52:11 +08:00
|
|
|
|
2014-05-16 11:09:30 +08:00
|
|
|
plt.figure(figsize=(12, 8))
|
|
|
|
|
plt.title("Score")
|
2015-10-22 22:19:40 +08:00
|
|
|
plt.barh(indices, score, 0.2, label="score", color="navy")
|
|
|
|
|
plt.barh(indices + 0.3, training_time, 0.2, label="training time", color="c")
|
|
|
|
|
plt.barh(indices + 0.6, test_time, 0.2, label="test time", color="darkorange")
|
2014-05-16 11:09:30 +08:00
|
|
|
plt.yticks(())
|
|
|
|
|
plt.legend(loc="best")
|
|
|
|
|
plt.subplots_adjust(left=0.25)
|
|
|
|
|
plt.subplots_adjust(top=0.95)
|
|
|
|
|
plt.subplots_adjust(bottom=0.05)
|
2012-03-04 00:52:11 +08:00
|
|
|
|
2012-12-25 20:16:05 +08:00
|
|
|
for i, c in zip(indices, clf_names):
|
2014-05-16 11:09:30 +08:00
|
|
|
plt.text(-0.3, i, c)
|
2012-03-04 00:52:11 +08:00
|
|
|
|
2014-05-16 11:09:30 +08:00
|
|
|
plt.show()
|