自然言語処理において、文章をそのまま機械学習モデルに流すことはできず、ベクトルに変換する必要があります。
今回はscikit-learn, janomeを用いて文章からベクトルを生成する方法についてまとめます。
目次
仕組み
仕組みはこんな感じです
- janomeを用いて単語ごとにわける(形態素解析)
- scikit-learnのVectorizerでTF-IDFという手法でベクトル化
TF-IDFについてざっくり説明すると、多く出てくる単語を重要視するが、どの文章にも出てくるような単語は重要視しないようにする、といった感じです。
詳しくはこちらのサイトを見るといいと思います。
https://dev.classmethod.jp/articles/yoshim_2017ad_tfidf_1-2/
使用するライブラリ
sklearn, janomeを使用します。
pip install scikit-learn
pip install janome
文章を単語に分割する処理
sklearnにはベクトライザーという、文章からベクトルを生成できるクラスがあります。それを使うためには単語の分割方法を定義する必要があります。
そのため、まずはベクトルを生成するために単語ごとに分ける処理のメソッドを定義します。ここで先ほどインストールしたライブラリのjanomeを使用します。
以下がドキュメントを単語ごとに分割するプログラムです。与えられたドキュメントを単語に分割し、数値文字列と記号を取り除いた単語のリストを返すメソッドです。
from janome.tokenizer import Tokenizer
def extract_words(text):
words = []
t = Tokenizer() # トークン化してくれるクラス
tokens = t.tokenize(text) # トークン化
for token in tokens:
if not token.surface.isdigit() and token.part_of_speech.split(',')[0] != "記号": # 記号と数字を取り除く
words.append(token.surface)
return words
Tokenizerはトークン化してくれるクラスで、このクラスのtokenizeメソッドを使うことで単語の分割をしてくれます。
次に取得したトークンに着目して必要な単語だけをリストに保存していきます。isdigit()は数値の文字列かを判定するメソッドです。
また、token.part_of_speechはその単語の品詞や活用形をカンマ区切りで保持する文字列です。
これをカンマで分割して0番目の文字列を比較します。0番目の文字列は品詞を表す文字列です。
Vectorizer生成+ベクトル化
次にTF-IDF重みでベクトル化をします。まず、ドキュメントをベクトル化してくれるオブジェクトvectorizerを生成します。
引数のanalyzerはvectorizer側でドキュメントから単語に分割する際に呼び出される処理を指定できます。ここに先ほどのextract_wordsを指定します。
最後にvectorizer.fit_transformでテキストからベクトルに変換します。toarrayをするとnumpyの配列になります。
from sklearn.feature_extraction.text import TfidfVectorizer
documents = ["お好きなドキュメントを代入してください。",] # 文章文字列リスト
vectorizer = TfidfVectorizer(analyzer=extract_words)
vec = vectorizer.fit_transform(documents).toarray()
#vec = vectorizer.transform(documents)
また、fit_transformはベクトル化する一定のルールを設定してベクトル化を行い、transformはすでに設定されているルールに従ってベクトル化を行います。
fitはコンパイルすることと似た感じです。詳しくはこちらを参照してください。
まとめ
sklearnを使うと非常に簡潔に書けることが分かりました。
また、janomeは未知の単語の定義や品詞や正規表現で絞り込みが可能で、カスタマイズ性が高いです。それについては別記事でまとめる予定です。
最後にまとめたコードを載せておきます。
from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import TfidfVectorizer
def extract_words(text):
words = []
t = Tokenizer()
tokens = t.tokenize(text)
for token in tokens:
if not token.surface.isdigit() and token.part_of_speech.split(',')[0] != "記号":
words.append(token.surface)
return words
text = "お好きなドキュメントを代入してください。"
vectorizer = TfidfVectorizer(analyzer=extract_words)
vec = vectorizer.fit_transform([text]).toarray()
補足
これを実行した方でストップワードを取り除かれてないじゃん?と思う人がいるかもしれません。
TfidfVectorizerの中身を見たところ、analyzerに関数を代入、もしくはVectorizerのbuild_analyzerをオーバーライドするとストップワードを設定しても取り除いてくれないみたいです。
analyzerを定義してなおかつ、ストップワードを取り除きたいのであれば、analyzerに代入する関数の中でそういう処理を記述する必要があります。
関数オブジェクトを生成して返すメソッドにストップワードを渡すなどするとできます。ただTF-IDFであれば性質上ストップワードの重みは小さくなるので大丈夫かな~と思います。
ちなみに英語を扱うのであれば、janomeを使った関数の代わりにanalyzerにステミング処理関数を渡す必要があります。英語のステミングはNLTKというライブラリがあるのでそれを活用するといいと思います