これまでテキストマイニングの流れを説明し、文書から TF-IDF を指標として特徴となる語彙を抽出し、それらの語彙を可視化するといったことをしてきました。
少し間が空いてしまいましたが、こうして得られた文書の特徴をベクタライズする流れを今回は記述します。
ベクトル化する
機械学習などの手法で文書を扱うにあたり、まずその対象の特徴を数列 (一次元配列) にするのがよくある方法です。
線形の数列同士であれば、たとえば類似度を算出したり、似たもの同士を分類するといったことも、機械学習ライブラリを用いて簡単におこなうことができます。
今まで扱ってきた自然言語処理は結局のところ、その文書を表す数列を求めるまでの処理といっても過言ではないでしょう。
ベクタライズのイメージ
自然言語で書かれたテキスト文書を
↓↓↓↓↓
数列 (一次元配列) に変換する
ベクトルの生成
このようなテキストの処理はスクリプト言語をグルー (糊) として順次処理していくと、柔軟であり検証もしやすく便利です。
ワードカウントする
ワードカウントはデータ分析でよくある事前処理のひとつで、たとえば Hadoop などでのサンプルとしても登場するので、よく知られるポピュラーな方法のひとつです。
Ruby を使うときわめて簡潔に記述できます。
# @hits にあれば件数を加算、無ければ 1 を用意する
def wordcount(word)
@hits.has_key?(word) ? @hits[word] += 1 : @hits[word] = 1
end
こうして「単語」すなわち「特徴」と、そのカウントがハッシュとして求まります。ハッシュの値でのソートは次のようにします。
hash.sort{|a, b| b[1] <=> a[1]}
ただしハッシュの順序は現在の言語仕様ではともかく、本来的に保証されるとは限らないので気をつけましょう。たとえば Ruby の現在の安定版では保証されますが、他の Python の場合は OrderedDic を利用するなどします。
ベクタライズする
各文書の特徴をワードカウントしたなら、全体の語彙を一覧とし、各文書が全語彙のどれをどの数だけ含んでいるかを数列にします。
これも単にあれば 1 なければ 0 の数の列にするという方法と、登場回数をそのまま数列にする方法とがあります。前者はベルヌーイ分布、後者は多項分布になります。
今回は多項分布を想定します。あまり処理効率は考えず、文書ごとのベクトルと、全体の語彙すべてを詰め込んだファイルを作ってみます。
@all_words = [] # 全語彙を格納する配列
@word_vector = {} # どの文書がどういうベクトルを持つかを保持するハッシュ
dic = {}
open(filename) do |file|
file.each_line do |line|
word, count = line.force_encoding("utf-8").strip.split(',')
dic[word] = count.to_i # ワードカウントの結果をハッシュに詰め込む
end
end
@all_words.concat(dic.keys) # 全語彙表に登場した語彙を入れていく
@word_vector[filename] = dic # 文書ごとにハッシュをハッシュに格納
@all_words.sort.uniq # すべての語彙はこれで求められる
このように、登場するすべての語彙と、各文書がどの語彙をどの回数だけ含むかという数列を詰め込んだハッシュができあがるわけです。
これをベクタライズしていきます。
# まずはハッシュを走査
@word_vector.each {|filename, words|
vector = [] # 空のベクトルを用意して
@all_words.each {|word| # ソートされた語彙表ごとに
vector << words[word].to_i # 登場回数を数値として埋め込んでいく
# Ruby の場合はハッシュにキーが無ければ nil が返り
# nil を .to_i すれば既定で 0 になるので
# 無ければそのまま 0 が埋め込まれることになる
}
write_file(filename, vector) # ファイルを書き出す
}
まとめ
今回はベクタライズができました。プログラミングができる人にとってはごくごく簡単な処理であったと思います。
こうして得られた数列同士をどのように処理していくかは機械学習の話になりますが次回以降解説していきます。