Fire Engineering

化学の研究者→消防士→ITエンジニア(2016年11月〜)

Rubyで文章を特徴ベクトルに変換するモジュールを作った。

 最近、自然言語処理関係に興味を持ち、いろいろやっています。今回作ったものは、例えば、人工知能に関する文章をプログラムに渡すと、

{ "人工知能": 3.4, "自動運転": 2.8, "研究": 1.5, ・・・・ }

といったように、文章の特徴を表す単語(以下、特徴語という)を抽出し、その特徴語がどれだけ文章の特徴を表しているかを数値化します。結果は、Rubyにおけるハッシュで返し、特徴語をkey、値をvalueとします。このハッシュが文章の特徴ベクトルになります。

 実はこれ、以前も同じようなことをやっています。

hirotsuru.hatenablog.com

 以前も2つの文章の特徴ベクトルを生成し、ベクトル同士の角度から類似度を推定するというようなことをやっています。しかし、特徴ベクトルを作るのって、まだまだ検討の余地があるなーって思っていたので、またいろいろやってみました。

 また、特徴ベクトルを作るというプロセスは、自然言語処理に関わらず、非常に重要なプロセスです。「情報推薦システム入門:講義スライド」という非常に勉強になるスライドから拝借した画像を以下に示します。

f:id:hirotsuru314:20160724212134p:plain

 この画像からわかるように、文章に限らず、画像や音声など様々なデータをプログラムで処理する際、その特徴を抽出することが必要となります。この際に、データを特徴ベクトルで表すということがよくやられています。特徴ベクトルを作ることができると、それらの類似度の推定やカテゴライズなどもできるようになります。

つくったもの

 コードは下のリンクから見れます。

NLP/Feature_vector at master · hirotsuru314/NLP · GitHub

 私のポートフォリオでも私の経歴や制作物の情報などを載せていますので、よければ見てください。

www.hirotsuru.sakura.ne.jp

なにができるか

 実際に以下の文章をベクトル化したものを見てみましょう。

 機械学習とは、人工知能における研究課題の一つであり、様々な分野への応用が期待される。その一つがビッグデータを用いたデータマイニングである。 』

 この文章を、テキストファイルに格納し、コマンドライン引数でプログラムに渡します。

$ ruby Sample.rb text.txt
{"機械学習"=>0.436, "人工知能"=>0.39, "研究"=>0.236, "課題"=>0.363, 
"一つ"=>0.431, "様々"=>0.279, "分野"=>0.259, "応用"=>0.331, 
"期待"=>0.361, "ビッグデータ"=>0.529, "データマイニング"=>0.465}

 以上のように、結果としてハッシュが返ってきました。コードは以下のような感じです。

$:.unshift File.dirname(__FILE__)
require 'natto'
require "idf_dic"

module TfIdf

  #Mecabによる形態素解析(単語分割)
  def split_words(text)
    @arr = Array.new
    nm = Natto::MeCab.new('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
    nm.parse(text) do |n|
      surface = n.surface
      feature = n.feature.split(',')
      #名詞のみを抽出する
      if feature.first == '名詞' && feature.last != '*'
        @arr.push(surface) #テキストを単語分割して配列で返す
      end
    end
  end

  #TF値を計算
  #配列をkeyが形態素、valueがTF値(Float)のハッシュに変換
  def calculate_tf
    @tf = Hash.new
    @arr.each do |word|
      if(@tf.key?(word))
        @tf[word] += 1
      else
        @tf[word] = 1
      end
    end
    @tf.each do |key, value|
      @tf[key] = value.to_f/@tf.size
    end
  end

  def calculate_tfidf
    @tfidf = Hash.new
    @tf.each do |key, value|
      if $idf.has_key?(key)
        @tfidf[key] = ($idf[key] * value).round(3)
      end
    end
    puts @tfidf
  end
end

 流れとしては、Mecabで単語分割して名詞だけを抽出する、単語の出現回数を数える(TF値)、事前に算出したIDF値と掛け合わせる、結果をハッシュで返すといったことをやっています。

 結果を見てみると、「機械学習」とか「ビッグデータ」とか専門的な言葉は、その文章の特徴となるため、比較的大きな値となり、一方で「研究」とか「分野」といった言葉は、様々文章に出てくる頻出ワードであり、文書の特徴を表さないので、値が小さくなっています。以上の結果から、一定の信頼性をもった文書の『特徴語抽出』と『特徴ベクトル生成』ができたと言えると思います。

技術メモ

形態素解析に用いる辞書を更新する。

 以前の記事にも書きましたが、形態素解析に用いる辞書を更新することはかなり重要です。

hirotsuru.hatenablog.com

 通常は、記事にも書いている『mecab-ipadic-NEologd』を使えば、だいたい対応できると思います。ただし、アニメのタイトルなどのいわゆるオタク用語?のようなものにまで対応しようとすると、独自に辞書を定義する必要があるかもしれません。

・DF-IDFによる単語の重み付け

 DF-IDFについての説明は以前の記事に書いています。

 上の例では、短文を分析したので、それぞれの単語は1回ずつしか登場しないのですが、きちんと特徴を表していそうな単語に大きな値が示されているのは、IDFにより、単語の重み付けができている証拠です。

 IDFの算出には、分析対象となる文章以外に多くの文章群を必要とします。つまり、その単語の希少性(レアさ)を調べるために、その単語が多くの文章に出現するのか、または特定の分野でしか登場しないレアな単語なのかを調べることが単語の重み付けに繋がります。

 私の場合、この文章群にWikipediaのAbstractを用いました。Abstractの中でも100語以上あるものだけを抽出し、IDF辞書として定義しました。私のコードの中の"Making_idf_dic.rb"に文章群を渡すと、IDF辞書をハッシュで返すようになっています。文章群はファイル名"documents.txt"でカレントディレクトリにおき、テキストファイル内は文章間を改行で区切ったものを渡すとIDF辞書が作成できるようにしています。

・IDFを外部ファイルに格納した

 私の場合、IDFはWikipediaの文章から事前に定義しているので、その値は変わることはありません。そのため、このIDF辞書を外部ファイル(idf_dic.dat)に格納しました。これで、自分専用のIDF辞書が完成です。IDF辞書の値は、特徴ベクトルにおける次元に相当するものなので、結果に大きく影響することは間違いありません。またいろいろ検討してみたいと思っています。

終わりに

 最近、やっと少しずつ日本語文章の取り扱い方がわかってきたので、他に手を出さず、最近作ったモジュールを使ってWebアプリを作っています。また、何かできたらどんどんアウトプットしていきたいと思うので、またよろしくお願いします!