子育て情報アプリを作れるように vol11 ~タグを自動生成する~

今回は、
クローラーで収集したデータにタグ情報を追加していきます。

で、前々から使ってみたいと思っていた
Mecabという形態素解析器を使いたいと思います。
形態素解析というのは、
簡単に言うと文章を品詞単位に分解してくれるものです。
詳しく言うと・・・
知りません!グーグルで検索して下さい笑

 
今回の目標

  1. Mecabの使い方を学ぶ
  2. Mecabを使ってタグを生成する
  3. TF-IDFを使う
  4. 自力でタグを生成する
  5. 今回の修正点

1. Mecabの使い方を学ぶ

mecabに限らず、使い方を学ぶ際は
ruby ○○○」で検索すると、大抵ヒットします。
今回も同様に「ruby mecab」で検索して、調べました。

で、まずは以下を実行

(1) brew install mecab
(2) brew install mecab-ipadic
※ 下記はmecab-ipadic-neologdをインストールするために必要
(3) brew install curl
(4) brew install xz

次にgemfileを修正
gem 'mecab', '>= 0.966'
そしてbundle installを実行します。

次に最新の辞書情報を取得してきます。
古い辞書で良いならこの処理はスキップできます。

(1) git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
(2) cd mecab-ipadic-neologd
(3) ./bin/install-mecab-ipadic-neologd -n

以後、最新の辞書に更新したい時は
./bin/install-mecab-ipadic-neologd -n
を実行すれば良さそうです。

設定は以上です。

2. Mecabを使ってタグを生成する

では、早速Rubyから呼び出してみます。

require 'mecab'

mecab = MeCab::Tagger.new("-Ochasen -d /usr/local/Cellar/mecab/0.996/lib/mecab/dic/mecab-ipadic-neologd")
parseNode = mecab.parseToNode("解析したい文字列")

while parseNode
  # parseNode.surfaceには品詞単位に分解した文字列がセットされている
  #  parseNode.featureには分解した文字列の品詞情報がセットされている
  puts "#{parseNode.surface} => #{parseNode.feature}"
  parseNode = parseNode.next # 次の品詞へ
end

と書くだけです。
正直言うと"-Ochasen"の意味を分かって無いですが
まぁ良しとします笑

試しに解析したい文字列に
"1歳の赤ちゃん"
という文字列を渡すと

1 => 名詞,数,*,*,*,*,1,イチ,イチ
歳 => 名詞,接尾,助数詞,*,*,*,歳,サイ,サイ
の => 助詞,連体化,*,*,*,*,の,ノ,ノ
赤ちゃん => 名詞,一般,*,*,*,*,赤ちゃん,アカチャン,アカチャン

おぉ〜。

これを使って、よく出てくる単語を抽出してみます。

例えば、

  • 名詞
  • 動詞
  • 形容詞

だけ取り出す場合はこんな感じで書きます。
f:id:tumiki_jp:20151005093251p:plain

続いて、
同じ単語が何回出てくるか集計します。
f:id:tumiki_jp:20151005093643p:plain

続いて続いて、
出現回数が多い単語順に並べ替えます。
f:id:tumiki_jp:20151005094130p:plain

最後に
ソートした上位の単語を登録すれば
よく出てくる単語がタグとして登録できます。
※最終的に私の作成したプログラムでは名詞だけ取り出すようにしています。
 

3. TF-IDFを使う

しかし、これだとまだ問題があります。
どの単語も一回しか出てこない場合は、 どの単語が重要か分かりません。 特にヤフー知恵袋のデータは
タグの基になるデータが「タイトル」しかなく、
そのタイトルも40文字しかありません。 これだと同じ単語なんて出てこない事の方が多いでしょう。

また、
同じ単語が何ども出てくるという事は
その単語がそのデータのタグとして ふさわしいと考えるのは安易すぎます。

 
どうしよ〜
と思ってネット検索していると
tf-idf
というキーワードが引っかかりました。

 
あれ・・・ 
どこかで聞いたことがある・・・
 

 
クロ本!
早速読み返したらやっぱり書いてありました。
ただしヤフーAPIを使った形態素解析でした。
最初からこっち使えばよかった・・・
っていう話ですが、
もう後戻りできないので
Mecabを使った方法で突き進みます。

(1) TF-IDFとは

Wikipediaには、このように書かれています。

tf-idf は、文書中の単語に関する重みの一種であり、主に情報検索や文章要約などの分野で利用される。

わかりやすく言うと ある文章を特徴付ける単語が何か判別してくれるということです。

(2) TF-IDFの考え方

公式は以下の通りです。

{ \displaystyle 
tfidf = tf \times idf
}
{ \displaystyle 
tf_{i,j} = \frac{n_{i,j}}{\sum_kn_{k,j}} 
}
{ \displaystyle 
idf_i = log \frac{|D|}{|\{d:d \ni t_i\}|} 
}

うーん、眠たくなってきた・・・笑
 
もう少し分かりやすく書くと

{ \displaystyle 
tfidf = \frac{ドキュメント内の単語出現回数}{ドキュメント内の単語の総数} \times log \frac{ドキュメントの総数}{単語が出現する文書の回数} 
}

こうなるようです。
 
実際に例を出してみます。

ドキュメントA内の単語(子育て, 大変, 子育て, つらい, 育児, つらい)
ドキュメントB内の単語(子育て、楽しい、離乳食、おいしい, 育児)
[ドキュメントAのTF]
  TF(単語)       = ドキュメント内の単語出現回数 / ドキュメント内の単語の総数
  TF(子育て)     = 2 / 6 = 0.33
  TF(大変)       = 1 / 6 = 0.16
  TF(つらい)     = 2 / 6 = 0.33
  TF(育児)       = 1 / 6 = 0.16
[ドキュメントAのIDF]
  IDF(単語)      = log(ドキュメントの総数 / 単語が出現する文書の回数)
  IDF(子育て)    = log(2 / 2) = 0.00
  IDF(大変)      = log(2 / 1) = 0.30
  IDF(つらい)    = log(2 / 1) = 0.30
  IDF(育児)      = log(2 / 2) = 0.00
[ドキュメントAのTF-IDF]
  TF-IDF(子育て) = 0.33 * 0.00 = 0.000
  TF-IDF(大変)   = 0.16 * 0.30 = 0.0480
  TF-IDF(つらい) = 0.33 * 0.30 = 0.099
  TF-IDF(育児)   = 0.16 * 0.00 = 0.000

この例では、
ドキュメントAを特徴付ける単語は
「つらい」
になります。
つまり、「つらい」をタグとして登録すれば良いことになります。

なるほどなぁ〜って感じです。

(3) TF-IDFをRubyで実装する

TF-IDFについて少し理解したので、
プログラムを作成しようと思ったのですが、
ここでまたちょっとした壁が・・・。
TFの方は対象のドキュメントがあれば簡単に計算することができますが、
IDFの方は

  • ドキュメントの総数
  • 対象の単語が出現したドキュメントの総数

が必要です。
この2つの値は分析する度に蓄積されていくものなので、
どこかに保存しておかないといけません。
とりあえず、jsonファイルに保存することにしました。
f:id:tumiki_jp:20151019022419p:plain こんな感じで、
あとでタグが更新できるように
URLと各単語の出現数を記録しておきます。

4. 自力でタグを生成する

あと、もう一つ問題があります。
Mecabを使って形態素解析すると
例えば、「1歳」という文字列は
「1」と「歳」に分解されてしまいます。
これだと、「1歳」というタグを作れないので
こういう時はMecabを使わずに自力で解析することにします。
解析と言うとすごくカッコいい感じがしますが、
大したことはしません。
単純に正規表現で抽出するだけです。
とりあえず今回は年齢を抽出するメソッドを作成しますが、
他にもタグにできそうな情報があればで後でメソッドを追加していけばいいと思います。
f:id:tumiki_jp:20151018231448p:plain

5. 今回の修正点

以上の事を踏まえてプログラムを書いてみました。
ファイルを追加したり修正したりと、
結構変更点があるので、
今回はソースコードを貼らずに
githubのリンクだけ貼っておきます。
興味のある方はご確認ください。
github.com

で、実行した結果がこちら

YouTube
f:id:tumiki_jp:20151019011329p:plain

cookpad
f:id:tumiki_jp:20151019011413p:plain

Yahoo知恵袋
f:id:tumiki_jp:20151019011432p:plain

GENERATED TAGS => の右側に表示されているのが、自動生成したタグになります。

こ、これは・・・
タグが微妙〜笑
使い物にならなそうですね・・・。
必要のない名詞はもっと削っていかないとダメですね。
(でもこんなの一個一個見てられない・・・。)
あと、もう一つ思った事があります。

例えば、「・・・赤いほっぺ・・・」という文章があったとして
これを形態素解析すると「赤い」と「ほっぺ」になります。
さらに「赤い」は形容詞なので、省かれて
「ほっぺ」がタグとして登録されてしまいます。
でもこれって、「赤いほっぺ」というタグの方が望ましいと思うわけです。

何かフリダシに戻された気分・・・。
ちーん。

でも、ここはあきらめずにネット検索
すると 「日本語係り受け解析器」
というキーワードが引っかかってきました。
これが使えるかどうかまだわかりませんが、
少しだけ可能性を感じています。
一筋の光がさしたような気持ちです。
なので、次の機会に取り組みます。

最後に

今回は、調査や開発に大変時間がかかってしまいましたので
ここらへんで一旦完成(ストップ)とします。

理想を言うと、  
もっと人工知能的な事をしたかったですが、
私がそこに足を踏み入れてしまうと、
何年かかるかわからないのでやりません。
もし簡単に実現できる方法があれば教えてください。
即採用します笑
 
では。

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例