ストップワード


AIって結局何なのかよく分からないので、とりあえず100日間勉強してみた Day43


経緯についてはこちらをご参照ください。



■本日の進捗

●ストップワードを理解

■はじめに

引き続き「Pythonではじめる機械学習(オライリー・ジャパン)」で学んでいきます。

BoWを用いたセンチメント分析モデルの前処理として特徴量削減に該当する手法を試していきたいと思います。

■低頻度なノイズ除去

まずはCountVectorizerのmin_dfパラメータを用いたノイズ除去を行ってみます。

min_df(minimum document frequency)とは、訓練データとして用いるドキュメント内で、トークンが出現する回数(または割合)が閾値よりも低ければそのトークンをノイズとして無視してくれる機能です。

基本的な考え方としては、誤字やセンチメント分析的な意味を持たない数字、単語などは特定のドキュメントにしか現れないはずで、これを除外することでノイズ除去ができるだろうということです。

映画記録:1983回目
7977年に見た映画の中でも最高でした。何度も見ているし、また見たいと思もいました。

例えば上記のようなレビューがあったとしたら、恐らく「1983」という数字は他のレビューには現れなくて、7977という数字は複数回現れる(?)でしょう。(「7977年アカデミー賞ノミネートだけあって面白い!」など)
そして「思もい」という誤字も他のレビューには現れないことが期待されますが、それ以外の単語は良くある単語なのでかなり多くのレビューで現れるでしょう。

なお、引数に整数を指定すれば、その数より少ないドキュメントにしか出現しないトークンを無視します。引数に浮動小数を指定すれば、全ドキュメント内で出現する割合が指定数以下であるトークンを無視します。

前回までのモデルの予測精度は0.88でした。

import numpy as np
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression

reviews_train = load_files("C:/Users/****/Documents/Python/aclImdb/train")
text_train, y_train = reviews_train.data, reviews_train.target

reviews_test = load_files("C:/Users/****/Documents/Python/aclImdb/test")
text_test, y_test = reviews_test.data, reviews_test.target

vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(text_train)
X_test = vectorizer.transform(text_test)

model = LogisticRegression(C=0.1, max_iter=10000, n_jobs=-1)
model.fit(X_train, y_train)

score = model.score(X_test, y_test)
print("score :{:.2f}".format(score))

それでは、上記と同じパラメータでmin_dfを追加してみます。

import numpy as np
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression

reviews_train = load_files("C:/Users/****/Documents/Python/aclImdb/train")
text_train, y_train = reviews_train.data, reviews_train.target

reviews_test = load_files("C:/Users/****/Documents/Python/aclImdb/test")
text_test, y_test = reviews_test.data, reviews_test.target

min_df = [2, 4, 6, 8, 10]
for i in min_df:
    vectorizer = CountVectorizer(min_df=i)
    X_train = vectorizer.fit_transform(text_train)
    X_test = vectorizer.transform(text_test)

    model = LogisticRegression(C=0.1, max_iter=10000, n_jobs=-1)
    model.fit(X_train, y_train)

    score = model.score(X_test, y_test)
    print(f"score (min_df = {i}) :{score:.2f}")

min_dfを整数で比較してみましたが、予測精度はほとんど変わりません。これはコーパスのドキュメント数がなかなかに多く、低頻度の単語がそれほど多くなかったり(モデルにとって)意味を持つものがこの範囲にないのかもしれません。ノイズとなるような単語の頻度が高い可能性も考えられますが、これ以上min_dfを上げるのは勇気が要ります。(極端な話、goodやfunが抜け落ちて、Itやisだけ残るようなことになりかねません。)

浮動小数を指定した場合も見てみます。こちらの方が直観的でしょうか。

import numpy as np
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression

reviews_train = load_files("C:/Users/****/Documents/Python/aclImdb/train")
text_train, y_train = reviews_train.data, reviews_train.target

reviews_test = load_files("C:/Users/****/Documents/Python/aclImdb/test")
text_test, y_test = reviews_test.data, reviews_test.target

min_df = [0.001, 0.005, 0.01, 0.02, 0.05]
for i in min_df:
    vectorizer = CountVectorizer(min_df=i)
    X_train = vectorizer.fit_transform(text_train)
    X_test = vectorizer.transform(text_test)

    model = LogisticRegression(C=0.1, max_iter=10000, n_jobs=-1)
    model.fit(X_train, y_train)

    score = model.score(X_test, y_test)
    print(f"score (min_df = {i}) :{score:.2f}")

全体の1%くらいから精度が落ち始めています。予想通り重要な単語が除去され始めていることも考えられます。

■高頻度なノイズ除去

min_dfとは逆の発想で、あまりにも頻度が高い単語(映画レビューにおけるmovie, cinema や、一般的すぎる he, is など)は逆にノイズなのではないかという考え方です。

同様にmax_dfも試してみましょう。

import numpy as np
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression

reviews_train = load_files("C:/Users/****/Documents/Python/aclImdb/train")
text_train, y_train = reviews_train.data, reviews_train.target

reviews_test = load_files("C:/Users/****/Documents/Python/aclImdb/test")
text_test, y_test = reviews_test.data, reviews_test.target

max_df = [0.95, 0.80, 0.75, 0.50, 0.25]
for i in max_df:
    vectorizer = CountVectorizer(max_df=i)
    X_train = vectorizer.fit_transform(text_train)
    X_test = vectorizer.transform(text_test)

    model = LogisticRegression(C=0.1, max_iter=10000, n_jobs=-1)
    model.fit(X_train, y_train)

    score = model.score(X_test, y_test)
    print(f"score (max_df = {i}) :{score:.2f}")

極端な割合で振ってみましたが、興味深い結果になりました。全体の25%以上に出現する単語を除去してようやく精度が落ち始めました。全体の50%程度では、そこまで高頻度な単語が極端に少ないのか、あまり意味を持たないのでしょうか。とにかくコーパス全体が多様な語彙で形成されていることが分かります。

■ストップワード

ノイズを除去する方法は他にもあります。

ストップワード(stop words)とは、テキストデータから取り除かれる頻出語のことで、ドキュメント全体の意味や文脈に対して重要でない単語が対象になることが多いです。主に接続詞や冠詞などで、英語であれば “a, the, is, are, at, on, that, what, which…” 、日本語であれば “て、に、を、は…” といった感じです。

これは自分でリスト化することもできますが、予め用意されているライブラリ固有のリストを使用することで適用できます。

つまり、訓練データ全体に対しての頻度の割合でノイズ除去をしていたmin_dfとは違い、その言語内で共通の”学習に”不要であろう単語を除いてしまえば自然と学習しやすい特徴量になるだろうという考え方です。

scikit-learnでは引数にenglishを渡すことで英語固有のストップワードを読み込めます。

import numpy as np
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression

reviews_train = load_files("C:/Users/****/Documents/Python/aclImdb/train")
text_train, y_train = reviews_train.data, reviews_train.target

reviews_test = load_files("C:/Users/****/Documents/Python/aclImdb/test")
text_test, y_test = reviews_test.data, reviews_test.target

vectorizer = CountVectorizer(stop_words='english')
X_train = vectorizer.fit_transform(text_train)
X_test = vectorizer.transform(text_test)

model = LogisticRegression(C=0.1, max_iter=10000, n_jobs=-1)
model.fit(X_train, y_train)

score = model.score(X_test, y_test)
print("score without stopwords :{:.2f}".format(score))

同様に精度向上は見られませんでした。

なお、引数 ‘english’ に用いたストップワードリストの中身は明示的にアクセスすることはできません。リストをカスタマイズしたい場合は、下記のようにENGLISH_STOP_WORDSを読み込んで利用することをすすめます。

from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS



■おわりに

今回精度向上への寄与は確認できませんでしたが、実はこれらノイズ除去のメリットは単に精度を求めるためのものだけではありません(精度向上できなかった言い訳ではないですよ?笑)。特徴量削減をすることで、モデルが学習するための計算コストも削減することができます。これはより大規模なコーパスや高度なモデルに対して大きな意味を持ちます。

しかし、同時にコーパス全体への理解なしにむやみに特徴量を削減してしまうのも危険なので、ドキュメントの特徴を見極めたり削減した単語を確認(本当は今回もこれをするともっと理解が深まったはず)しながら進めることが求められるでしょう。

なお、今回(に限らずだが)単語の重要度についての必要性を議論していますが、言語的に意思疎通に重要かどうかという意味ではなく、あくまで機械学習モデルの学習データとして重要かどうかについてだけの話なのでご承知おきください。

■参考文献

  1. Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
  2. ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
  3. API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html
  4. Maas, Andrew L. and Daly, Raymond E. and Pham, Peter T. and Huang, Dan and Ng, Andrew Y. and Potts, Christopher, Learning Word Vectors for Sentiment Analysis, Proceedings of the 49th Annual Meeting of the Association for Computational Linguistics: Human Language Technologies, June, 2011, Portland, Oregon, USA, Association for Computational Linguistics, 142–150, http://www.aclweb.org/anthology/P11-1015
  5. Potts, Christopher. 2011. On the negativity of negation. In Nan Li and David Lutz, eds., Proceedings of Semantics and Linguistic Theory 20, 636-659.


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です