peiprog’s blog

情報科学を学ぶ大学4年生。主にプログラミング(機械学習寄り)の話題を書いていきます。

Pythonでテキストの機械学習(相互情報量を使った特徴ベクトル選定)

今回は、ドキュメント群から生成したベクトルから、機械学習で重要な特徴ベクトルを抽出する内容です。テキストをベクトル化したものは何万次元にもなりますが、中には10000個あるドキュメントの中で1回しか出てこない単語など学習に必要のないデータが大量にあります。また、次元数が多いと学習時間が長くなり、メモリの消費量も増えます。
 そのため、今回は相互情報量に基づいて、学習に本当に必要な特徴ベクトルの抽出の仕方を考えます。

目次


相互情報量とは


 相互情報量とは、ざっくりいうと2つの事象(の集合)がどれだけ互いに関連性があるかを表す数値です。定義は以下のとおりです。
こちらにより詳しいことが載ってます。相互情報量とは

\displaystyle
I(X;Y) = \sum_{y\in{Y}}\sum_{x\in{X}}p(x,y)\log_2\frac{p(x,y)}{p(x)p(y)}


実装するアルゴリズム

 ドキュメントから生成した単語の頻度を表すベクトルとラベルにおいての相互情報量は以下のようになります。Classesはドキュメントに紐付いたラベルの集合で、Wordsはベクトル化されている単語の出現頻度を表す集合です。

\displaystyle
I(Words;Classes) = \sum_{word\in{Words}}\sum_{class\in{Classes}}p(word, class)\log_2\frac{p(word,class)}{p(word)p(class)}



 今回は単語ごとのすべてのラベルの相互情報量の中で最大の数値を比較して、機械学習に使うデータを選択します。
   相互情報量を用いた以下の式を単語ごとの関連度の指標として、すべての単語でこの数値を計算します。相互情報量の特性上、値が大きいとラベルと単語の出現頻度の関連性が高いことがわかるため、値が大きければ大きいほど学習に重要な単語であると判断できます。


\displaystyle
Output(word) = MAX\Bigl(p(word, Classes)\log_2\frac{p(word,Classes)}{p(word)p(Classes)}\Bigl)


詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~


実装

 先ほど説明したアルゴリズムを実装していきます。式を展開してまとめると以下のようになります。c(〜)は該当する事象の出現数を意味します。
 単純に値を比較するだけのため、単語の出現頻度、ラベルの数に依存しない数値は定数とみなして省略してあります。

\displaystyle
Output(word) = MAX\Bigl(c(word, Classes)\log_2\frac{c(doc)c(word,Classes)}{c(word)c(Classes)}\Bigl)

必要なパラメータは以下のとおりです。

c(word, class) ラベルがclassの全ドキュメント中のwordの出現数
c(class) ラベルがclassのドキュメントの数
c(word) 全ドキュメント上のwordの出現数
c(doc) ドキュメントの総数


 以下が単語ごとの相互情報量を保管したリストを返す関数です。extract_wordsメソッドは前回の記事に乗っています。
   vectorsはドキュメントをベクトル化したもののリスト、doc_listはドキュメントのリスト、label_listはドキュメントに対応するラベルのリスト、label_numはラベルの種類の数です。

def get_mi_list(vectors, doc_list, label_list, label_num):
    mi = []  # 単語ごとの相互情報量を保管するリスト
    word_sum = np.sum(vectors, axis=0)  # 全ドキュメント上の単語の出現数

    # すべてのドキュメントを単語リストに変換してラベルごとに保存
    labels_doc_words = [[] for i in range(label_num)]
    for doc_index in range(len(doc_ls)):
        labels_doc_words[labels[doc_index]].extend(extract_words(doc_ls[doc_index]))  # ドキュメントを単語のリストに変換
    # 各ラベルに属するデータ数を保管
    label_count = []
    for label in range(label_num):
        label_count.append(labels.count(label))

    # 全ワードの相互情報量を計算
    for word_index in range(len(words)):
        max_num = -100.0
        # 全ラベルの相互情報量の最大値をリストに追加
        for label in range(label_num):
            counter = labels_doc_words[label].count(words[word_index])  # ラベルがlabelの全文章中で出現するwordの数
            mi_num = counter * \
                         (np.log2(len(label_list) * counter / (label_count[label] * word_sum[word_index])))
            if not math.isnan(mi_num):
                max_num = max(mi_num, max_num)
            mi.append(max_num)

    return np.array(mi)

 実装は難しいことは無いですが、注意点はlogの中身が0以下だとNaNが返るため if not math.isnan(~)をする必要があるという点です。
 返ってきた相互情報量のリストに基づいて学習対象のデータを選定することで学習に重要な特徴ベクトルを抽出できます。