はてなキーワードからMecCab辞書を生成する(Ruby版)

概要
このコンテンツでは、はてなキーワードからMeCabの辞書ファイルを生成するRubyプログラムを紹介しています。

作成方法は以下のサイトを参考にしています。


【参考サイト】MeCabの辞書にはてなキーワードを追加しよう
http://d.hatena.ne.jp/code46/20090531/p1


また、単語追加についてはここを参考にしています。


【参考サイト】単語の追加方法
http://mecab.sourceforge.net/dic.html


辞書ファイルはNAIST辞書を想定しています。IPAなど他の辞書を使用している場合は、適宜読み替えてご利用ください。
はてなキーワードの情報
はてなキーワードは、ユーザ編集型のキーワードサービスで、一般的な語句から、インターネットで良く使われるような定型句、ドラマ、映画、アニメのタイトルなどが数多く登録されています。2009/10/12に確認した段階では、25万語以上登録されていました。

はてなキーワードの情報は、ここから取得できます。

【参考サイト】はてなダイアリーキーワードふりがなリストを公開しました
http://d.hatena.ne.jp/hatenadiary/20060922/1158908401


ダイアリーの日付は2006/09/22になっていますが、CSVファイル内には最近のゲームやアニメのタイトルも入っているので、おそらくちゃんとメンテされているようです。

今回は、このファイルをMeCabの辞書に合ったCSV形式に整形しユーザ辞書を生成します。
MeCab辞書を作る為のCSVファイルの前説
NAIST辞書を入れていれば、CSVファイルの実物が見られます。

$ view /usr/local/lib/mecab/dic/naist-jdic/naist-jdic.csv

上記CSVファイルの中で、例えば「桜」は以下の4つが定義されています。
表層形左文脈ID右文脈IDコスト品詞品詞細分類1品詞細分類2品詞細分類3活用形活用型原形読み発音
134513451969名詞一般****サクラサクラ
135013506393名詞固有名詞人名**サクラサクラ
135113517186名詞固有名詞人名**サクラサクラ
135313537008名詞固有名詞地域一般**サクラサクラ
桜という言葉は、「樹木の桜」「姓の桜」「名の桜」「地名の桜」の4つが登録されているようです。

コストは数字が小さいものが優先的に使用されます。

左文脈IDと右文脈IDは、辞書ディレクトリ配下のファイル、「left-id.def」と「right-id.def」に繋がります。

両ファイルの中身を見ると、こうなっています。

left-id.def
1345 名詞,一般,*,*,*,*,*
1350 名詞,固有名詞,人名,姓,*,*,*
1351 名詞,固有名詞,人名,名,*,*,*
1353 名詞,固有名詞,地域,一般,*,*,*

right-id.def
1345 名詞,一般,*,*,*,*,*
1350 名詞,固有名詞,人名,姓,*,*,*
1351 名詞,固有名詞,人名,名,*,*,*
1353 名詞,固有名詞,地域,一般,*,*,*

左文脈IDと右文脈IDはお互いに繋がり易さを持っています。辞書ディレクトリのmatrix.defを見ると、例えば1350(姓)と1351(名)の連結コストは-6659と非常に低い値になっています。逆に左が「名」で右が「姓」だった場合は41というコストが設定されています。

matrix.def
1350 1351 -6659
1351 1350 41

「桜」の中では「一般」が最も低コストになっていますが、左文脈IDが姓の場合は「名」のコストが-6659され、最も低コストとして扱われるようになります。
はてなキーワードをどう変換するか
はてなキーワードのCSVファイル(IDなしの方)は、以下のような、「ふりがな」「用語」が並ぶタブ区切り形式です。

あまくさしまばらのらん 天草・島原の乱
あまくさしょとう 天草諸島
あまくさしろう 天草四郎
あまくさしろうときさだ 天草四郎時貞
あまくさしんようきんこ 天草信用金庫


これらを一語ずつ品詞やら名詞やら姓名あたりを自分で判断していたら、時間がいくらあっても足りないので、最初に挙げた参考ページと同じように、全て一般名詞として扱うことにします。

なので、例えば天草四郎は、こんな風な内容でCSV形式に変えてしまいましょう。
表層形左文脈ID右文脈IDコスト品詞品詞細分類1品詞細分類2品詞細分類3活用形活用型原形読み発音
134513451969名詞一般****サクラサクラ
天草四郎13451345?名詞一般****天草四郎アマクサシロウアマクサシロウ
文脈IDは-1を指定すれば自動採番してくれるという噂だったのですが、実行したら「自動で探したけど、対応するのが見つからなかったよ」と言われた為(文字コードの問題かな)、自前でシステム辞書ディレクトリ内の「left-id.def」と「right-id.def」を探して、それっぽいIDを拾ってきました(本当にこの方法で良いのか未確認)。

辞書によってIDは変わってきて、IPA辞書の「名詞,一般」は、我が家の環境では1285になっていました。バージョンによっても違う可能性もあるので注意が必要です。

読み・発音については、はてなのファイルの読みは全て平仮名で記述されているので、NAISTの辞書に合わせる為に片仮名に変換して登録します。発音は本来「アマクサシロー」になるべきですが、その辺は変換してると面倒なのでこのままで。

参考URLでは、CSVの末尾に「はてなキーワード」と入れて、はてなの辞書が使われたことを見やすくしてました。簡易に使用辞書を見分けたい方はその方法を取ると良いと思います。

コストの計算については、計算式で求めます。詳細は後述。

本当は品詞を「名詞,一般,*」ではなく、「名詞,一般,はてな」にしたかったのですが、文脈IDを追加しようとすると、defファイルをいろいろ変更する必要があって面倒そうなので、とりあえず今回はこのままで。
CSV生成ソース(Ruby)
上記の話を踏まえた上で、辞書を生成する為のCSVファイルを作ってみます。

ソースは以下のサイトを参考にしました。

【参考サイト】MeCabの辞書にはてなキーワードを追加しよう
http://d.hatena.ne.jp/code46/20090531/p1

【参考サイト】MeCabの辞書にはてなキーワードを追加する際に地域名(千葉県など)を省くようにする方法
http://d.hatena.ne.jp/rin1024/20090830/1251608698


※Ruby1.8.7、Fedora11にて動作確認
※MeCab、mecab-rubyがインストールされている必要があります
※インストールされてない場合は、こちらを参考にしていただければ

#!/usr/local/bin/ruby -Ku

require 'kconv'
require 'MeCab'

# http://d.hatena.ne.jp/hatenadiary/20060922/1158908401から落としたファイル
in_file = File::open( "keywordlist_furigana.csv" )

# 出力ファイル(hatena.csvというファイルを出力します)
out_file = File::open( "hatena.csv", 'w' )

i = 0

# 1行ずつ読み込み、out_fileに出力していく
while line = in_file.gets

  # EUC-JPのファイルなので、使用しているUTF-8に変換(ついでにtrim)
  # システム辞書でEUCを使用している場合は、toutf8は削ってください
  line = line.toutf8.strip

  # タブ区切り(仮名\t単語)になっているので、split
  splitted_word = line.split("\t")
  next if splitted_word.size < 2
  kana = splitted_word[0].strip
  kana = "*" if kana.to_s == ""
  word = splitted_word[1].strip

  # 日付が入ったワードは、不要なものが多いので外す
  next if /[0-9]{4}(\/|\-)[0-9]{2}(\/|\-)[0-9]{2}/ =~ word
  next if /[0-9]{4}年/ =~ word
  next if /[0-9]{1,2}月[0-9]{1,2}日/ =~ word

  # 制御文字、HTML特殊文字が入ったものは外す
  next if /[[:cntrl:]]/ =~ word
  next if /\&\#/ =~ word

  # はてなという言葉が入ってるものは、運用の為のワードが多いので削除
  # 一部、正しい用語も消してしまっているので、用途によっては下行をコメントアウト
  next if /はてな/ =~ word

  # MeCabでパース
  node = MeCab::Tagger.new( "-Ochasen" ).parseToNode( splitted_word[1] )

  #ノード数
  node_count = 0
  # 未知語数
  unk_count = 0
  #品詞細分類2が地域の数
  area_count = 0
  #品詞細分類2が人名の数
  name_count = 0

  #ノードと種類をカウント
  while node
    begin
      feature = node.feature.split( "," )
      node_word = feature[0].strip
      # BOS(stat:2)/EOS(stat:3)はスキップ
      next if node.stat == 2 or node.stat == 3
      area_count += 1 if feature[2].strip == "地域"
      name_count += 1 if feature[2].strip == "人名"
      # 未知語(stat:1)をカウント
      unk_count += 1 if node.stat == 1
      node_count += 1
    ensure
      node = node.next
    end
  end

  # node数が1つ(システム辞書で1語として解析可能)の場合は登録しない
  next if node_count <= 1 and unk_count == 0

  # 全nodeが地域名だった場合は、登録しない(東京都北区は、東京都 | 北区で分けたい為)
  next if node_count == area_count

  # 全nodeが人名だった場合は、登録しない(相田翔子は、相田 | 翔子で分けたい為)
  next if node_count == name_count

  # コストの計算
  cost = -400 * word.split(//u).size ** 1.5
  cost = -36000 if cost < -36000

  # 平仮名を片仮名に変換(jcode使わないと文字化けします)
  require 'jcode'
  kana.tr!('ぁ-ん', 'ァ-ン')  

  # 行出力 
  out_file.puts "#{word},1345,1345,#{cost},名詞,一般,*,*,*,*,*,#{word},#{kana},#{kana}"

  # 英字の場合は、小文字統一、大文字統一も出力しておく
  if word != word.downcase
    out_file.puts "#{word.downcase},1345,1345,#{cost},名詞,一般,*,*,*,*,*,#{word},#{kana},#{kana}"
  end
  if word != word.upcase
    out_file.puts "#{word.upcase},1345,1345,#{cost},名詞,一般,*,*,*,*,*,#{word},#{kana},#{kana}"
  end

  puts "#{i.to_s}件目を処理" if ( i += 1 ) % 1000 == 0
  # break if i > 3000
end

in_file.close
out_file.close

はてなキーワードのcsvファイルとこのソースを同じ場所に置いて叩けば、hatena.csvというファイルが出来ると思います。

コストについて

コストについては、参照サイトのロジックをそのまま利用しています。

MeCab の辞書構造と汎用テキスト変換ツールとしての利用
http://mecab.sourceforge.net/dic-detail.html


コストは16bitの符号付整数と書いてあったので、-32768~32767まで設定できることになります。

この時、長い言葉ほどコストは低く設定しておく必要があるそうです。例えばはてなキーワードの中には、「相続」という言葉と「相続人」という言葉がありますが、「相続」の方がコストが低ければ、「相続 | 人」のように分割される可能性が高くなります(前後の単語によって結果は変わるので、必ず数字が低い方が優先されるというわけではありませんが)。

その為、参考サイトでは長さに応じてコストを調整する、という設定をしているようです。

NAISTの辞書を見ると、ほとんどの一般名詞は正の数で設定されています。今回使った辞書はマイナスで設定してあるので、多くの場合でシステム辞書よりも今回設定したはてなキーワードの方が優先して使われることになります。
CSVから辞書を作成する
では、作成したCSVファイル(hatena.csv)を使って、辞書ファイルを作成してみます。

今回はシステム辞書に組み込むわけではなく、ユーザ辞書を作成します。

精査されていない、内容に偏りがありそうな辞書は、ユーザ辞書にしておいて、解析するコンテンツの種類に応じて簡単に使い分けられるようにしておいた方が良いのではないかという考えからです。

ユーザ辞書作成コマンド(システム辞書がNAISTの場合)
$ /usr/local/libexec/mecab/mecab-dict-index -d /usr/local/lib/mecab/dic/naist-jdic -u hatena.dic -t utf-8 -f utf-8 hatena.csv

-d : システム辞書のディレクトリ
-u : 作成するユーザ辞書名
-t : 辞書の文字コード
-f : CSVの文字コード

上記コマンドを実行すると、hatena.dicという辞書が生成されます。あとは/usr/local/etc/mecabrcのユーザ辞書の設定を書き換えるか、MeCab実行時に引数-u 辞書名を指定すれば、ユーザ辞書が使用できるようになります。
試してみる
実際にコマンド打って挙動を確かめてみます。

システム辞書(NAIST)のみ使用
$ echo アーク森ビル | mecab
アーク	名詞,一般,*,*,*,*,アーク,アーク,アーク,,
森	名詞,固有名詞,人名,姓,*,*,森,モリ,モリ,,
ビル	名詞,固有名詞,人名,名,*,*,ビル,ビル,ビル,,
EOS

はてな辞書使用
$ echo アーク森ビル | mecab -u hatena.dic 
アーク森ビル	名詞,一般,*,*,*,*,*,アーク森ビル,アークモリビル,アークモリビル
EOS

効いてるようですね。

システム辞書(NAIST)のみ使用
$ echo IPアドレス枯渇 | mecab
IP	名詞,固有名詞,組織,*,*,*,*
アドレス	名詞,一般,*,*,*,*,アドレス,アドレス,アドレス,,
枯渇	名詞,サ変接続,*,*,*,*,枯渇,コカツ,コカツ,,
EOS

はてな辞書使用
$ echo IPアドレス枯渇 | mecab -u hatena.dic 
IPアドレス枯渇	名詞,一般,*,*,*,*,*,IPアドレス枯渇,アイピーアドレスコカツ,アイピーアドレスコカツ
EOS

「IPアドレス枯渇」は、「IPアドレス | 枯渇」になって欲しい気もしますが、まぁ、こういう事例を気にしていたら時間がいくらあっても足りないので、良しとします。
あとがき
ほとんど参考サイトからの借り物のソースですが、「Ruby」で書いてあったり「文脈ID」が少し変わっていたり、正規表現が増やしてあったりするので、多少は追加情報的なものも織り交ぜられたかなぁと思います。

はてなキーワードを使うと、映画、ドラマ、アニメのタイトルなどの辞書ファイルが苦手にしている文字列に対してかなり精度が上がります。日々更新されるブログの解析などには役に立つと思います。

はてなの本サイトを見ると、「一般」とか「ゲーム」といった用語のジャンル分けがされているので、その辺りの情報もtsvファイルに入ってくれたりしたら、より使い道も上がりそうな気はします。

戻る    ご意見、ご要望