scikit-learnを使ってナイーブベイズでスパムメッセージを分類してみます。
データセットのロード
今回はUCIで提供されているSMS Spam Collection Data Setを使います。 データセット全体で5572サンプル(内スパムは747)からなり、各サンプルはSMSのメッセージ本文とそれがスパムかどうかの2項目のみを持ちます。
UCI Machine Learning Repository: SMS Spam Collection Data Set SMS Spam Collection Dataset | Kaggle
CSVファイル(spam.csv)でダウンロードし、本文をX、スパムかどうか(目的変数)をyにそれぞれ抽出します。
import pandas as pd original_df = pd.read_csv('spam.csv', encoding='latin-1') # ゴミカラムの除去 original_df.drop(['Unnamed: 2', 'Unnamed: 3', 'Unnamed: 4'], axis=1, inplace=True) original_df['v1'].value_counts() # ham 4825 # spam 747 X = pd.DataFrame(original_df['v2']) y = original_df['v1'].apply(lambda s: 1 if s == 'spam' else 0) # 目的変数(スパムなら1)
Bag-of-Words
Xにはまだ生の文章が入っているので、ここから特徴量を抽出する必要があります。
Bag-of-Wordsは学習サンプルに現れた語の出現頻度(どの語がいくつ現れたか)をベクトル化した特徴量です。 学習サンプルに現れた全ての語の種類数が、特徴量の数となるため、各サンプルは極めて疎なベクトルとなります。
Bag-of-Wordsを抽出する方法として、scikit-learnではCountVectorizerが提供されています。
学習データでfitした後、 vocabulary_
にアクセスして語の種類数をカウントすると2284となっており、例えば'buy'や'space'が出現していることがわかります(後ろの数字は辞書順のソート番号になっており、'buy'であれば414番目になります)。
このCountVectorizerを使って、transformで文章をベクトル化すると、2284の特徴量を持ったnumpy行列に変換されます。
from sklearn.model_selection import train_test_split from sklearn.feature_extraction.text import CountVectorizer # 学習データ(557サンプル)とテストデータ(5015サンプル)の分離 X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.1) # CoutVectorizer vectorizer = CountVectorizer() vectorizer.fit(X_train['v2']) print('Vocabulary size: {}'.format(len(vectorizer.vocabulary_))) print('Vocabulary content: {}'.format(vectorizer.vocabulary_)) # Vocabulary size: 2284 # Vocabulary content: {'buy': 414, 'space': 1832, 'invaders': 1048, 'chance': 463, 'win': 2199, 'orig': 1445, 'arcade': 276, ... # 文章を特徴ベクトル化 X_train_bow = vectorizer.transform(X_train['v2']) X_test_bow = vectorizer.transform(X_test['v2']) print('X_train_bow:\n{}'.format(repr(X_train_bow))) print('X_test_bow:\n{}'.format(repr(X_test_bow))) # X_train_bow: # <557x2284 sparse matrix of type '<class 'numpy.int64'>' # with 7471 stored elements in Compressed Sparse Row format> # X_test_bow: # <5015x2284 sparse matrix of type '<class 'numpy.int64'>' # with 53413 stored elements in Compressed Sparse Row format>
ナイーブベイズによる分類
得られた特徴ベクトルからナイーブベイズで学習・テストさせてみます。
scikit-learnではいくつかナイーブベイズの実装が有りますが、今回は分類タスクに使われるBernoulliNBで学習・テストを行います。
結果としては、学習データでは精度97.8%と高い値をマークしましたが、テストデータでは92.0%となっているため、過学習の傾向が見られます。
from sklearn.naive_bayes import BernoulliNB model = BernoulliNB() model.fit(X_train_bow, y_train) print('Train accuracy: {:.3f}'.format(model.score(X_train_bow, y_train))) print('Test accuracy: {:.3f}'.format(model.score(X_test_bow, y_test))) # Train accuracy: 0.978 # Test accuracy: 0.920
CountVectorizerのチューニング
min_df
CountVectorizerの vocabulary_
を見ていると意味のない語が多いことがわかります。
例えば、 08000839402
や 1250
といった数字や、mayb
や undrstnd
といったおそらくスペルミスなど、特定の文章にしか出てこない固有の語が含まれており、こうした語は未知のデータでも現れない可能性が高いため、分類には役に立たず、過学習を進めてしまいます。
CountVectorizerには min_df
というオプションが用意されています。
min_dfは整数で設定し、学習データ全体で現れたサンプル数がmin_df未満の語については特徴ベクトルからは除外する、というものです。
ここでは3を設定しており、3サンプル以上現れた語のみが特徴量となります。 結果として、語数は2284から504まで減り、テストデータの精度も4.7%改善しました(92.0%→96.7%)。
vectorizer = CountVectorizer(min_df=3) vectorizer.fit(X_train['v2']) print('Vocabulary size: {}'.format(len(vectorizer.vocabulary_))) # Vocabulary size: 504 X_train_bow = vectorizer.transform(X_train['v2']) X_test_bow = vectorizer.transform(X_test['v2']) model.fit(X_train_bow, y_train) print('Train accuracy: {:.3f}'.format(model.score(X_train_bow, y_train))) print('Test accuracy: {:.3f}'.format(model.score(X_test_bow, y_test))) # Train accuracy: 0.982 # Test accuracy: 0.967
ストップワード
また stop_words
というオプションもあり、ストップワードを指定することができます。
ストップワードは、文法的に特別な意味を持っている語など、分類にかかわらず多くの文章で出現する語(例えば"and"、"you"、"has"など)のことで、これらも分類の役に立たないため、特徴量から除去するのが一般的です。
stop_wordsには除去したい文字列リストの他、'engilish'を渡すと付属のストップワードを除去できます。
学習データに適用してみますと、語数は2284から2110まで減らせました。
get_stop_words()
でストップワードの一覧が取得できます(“becomes"や"go"などが含まれています)。
ただし、ストップワードを除去した特徴量では、学習データ・テストデータともに精度は落ちてしまいました。 おそらく非スパムメッセージに現れやすい語(例えば"I"や"my")がストップワードとして除去されてしまったためと思われます。
vectorizer = CountVectorizer(stop_words='english') vectorizer.fit(X_train['v2']) print('Vocabulary size: {}'.format(len(vectorizer.vocabulary_))) print('Stop words: {}'.format(vectorizer.get_stop_words())) # Vocabulary size: 2110 # Stop words: frozenset({'becomes', 'go', 'former', 'describe', 'eight', 'himself', ... X_train_bow = vectorizer.transform(X_train['v2']) X_test_bow = vectorizer.transform(X_test['v2']) model.fit(X_train_bow, y_train) print('Train accuracy: {:.3f}'.format(model.score(X_train_bow, y_train))) print('Test accuracy: {:.3f}'.format(model.score(X_test_bow, y_test))) # Train accuracy: 0.955 # Test accuracy: 0.892

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎
- 作者: Andreas C. Muller,Sarah Guido,中田秀基
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/05/25
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る