peiprog’s blog

情報科学を学ぶ大学4年生。主にプログラミング、情報系の話題を書いていきます。

Pythonでsklearn+janomeを使って特徴ベクトル抽出

 お久しぶりです。peiprogです。
 機械学習において入力データの形式はとても大事ですよね。分類手法と同じかそれ以上に大事かもしれないですね。今回はその重要な入力データの作り方(加工の仕方)についてです。テキストから機械学習に適した形の入力データに加工します。ライブラリを使うことで短く簡潔に書けます。

目次



仕組み


仕組みはこんな感じです↓
  1. 単語ごとにわける(形態素解析
  2. TF-IDFを用いてベクトル化
用語の説明は省略します。



使用するライブラリ

sklearn 機械学習ライブラリ
janome 日本語の形態素解析ライブラリ
インストール方法は以下を参照してください。pip installなどで必要なライブラリは用意してください。
pipのインストール
sklearnのインストール
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を生成します。先ほど定義したextract_wordsを引数にします。これはvectorizer側の、ドキュメントから単語に分割する際に呼び出されるメソッドを指定することを表します。
最後にvectorizer.fit_transformでテキストからベクトルに変換します。toarrayをするとnumpyの配列になります。ベクトルが見やすくなるし、データを扱いやすくなります。

from sklearn.feature_extraction.text import TfidfVectorizer

text = "お好きなドキュメントを代入してください。"
vectorizer = TfidfVectorizer(analyzer=extract_words)
vec = vectorizer.fit_transform([text]).toarray()
#vec = vectorizer.transform([text])

 また、fit_transformはベクトル化する一定のルールを設定してベクトル化を行い、transformはすでに設定されているルールに従ってベクトル化を行います。場面によって使い分けが必要ですが、詳しくはこちらを参照してください。



まとめ

 いかがでしたか?sklearnを使うと非常に簡潔に書けますね。ただ、ドキュメント数が多くなるとベクトルの次元数が大きくなり学習時間やメモリ消費量が大きくなるため、より対象のデータを絞り込む必要があります。それについても今後書きたいと考えています。最後にまとめたコードを載せておきます。

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に代入する関数の中でそういう処理を記述する必要があります。ただ、analyzerとして呼び出される関数は引数が1つしかないのでストップワードを引数として渡すことができないです(^^;。ただTF-IDFであれば性質上ストップワードの重みは小さくなるので大丈夫かな~と思います。
 ちなみに英語を扱うのであれば、janomeを使った関数の代わりにanalyzerにステミング処理関数を渡す必要があります。英語のステミングはNLTKというライブラリがあるのでそれを活用するといいと思います。