はてなキーワードからMeCabのユーザ辞書を作る

今まで作ったWebサービスのいくつかでMeCabを使っているんですが、最近の言葉がMeCabの辞書に反映されていないので、特に話し言葉に近いブログとかの単語判別が甘いという悩みがありました。

そんな訳で、以前からずっとやろうと思っていたまま後延ばしにしていた、はてなキーワードからMeCabのユーザ辞書を作って利用するようにする作業を行いました。

手順などは、以下のサイトを参考にさせてもらいました。
はてなキーワードからMecCab辞書を生成する(Ruby版)

以下、実際に行った手順。

はてなキーワードファイルをダウンロードする

以下のページからはてなキーワードがまとまって入っているCSVファイルをダウンロードします。
はてなダイアリーキーワードふりがなリストを公開しました - はてなダイアリー日記

$ wget "http://d.hatena.ne.jp/images/keyword/keywordlist_furigana.csv"

辞書作成用のCSVファイルを作成する

最初は上記Ruby版の変換スクリプトをそのまま使わせて頂いたのですが、スワップを使い切ってしまうぐらいメモリを食ってしまって処理が止まってしまうという事態になってしまった為、PHPスクリプトを書き直して変換処理を掛ける事にしました。

#!/usr/bin/php
<?php
$fd_in = fopen('keywordlist_furigana.csv', 'r');
$fd_out = fopen('hatena.csv', 'w');

$i = 0;
while ( !feof($fd_in) ) {
    // EUC-JPのファイルなので、使用しているUTF-8に変換(ついでにtrim)
    // システム辞書でEUCを使用している場合は、toutf8は削ってください
    $line = trim(mb_convert_encoding(fgets($fd_in), 'utf-8', 'euc-jp'));
    // タブ区切り(仮名\t単語)になっているので、split
    $words = explode("\t", $line);
    if ( count($words) < 2 ) { continue; }
    $kana = ( $words[0] == '' ) ? '*' : trim($words[0]);
    $word = trim($words[1]);

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

    // 制御文字、HTML特殊文字が入ったものは外す
    $pattern = '/[[:cntrl:]]/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }
    $pattern = '/\&\#/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }

    // はてなという言葉が入ってるものは、運用の為のワードが多いので削除
    // 一部、正しい用語も消してしまっているので、用途によっては下行をコメントアウト
    $pattern = '/はてな/';
    if ( preg_match($pattern, $word) === 1 ) { continue; }

    // MeCabでパース
    $nodes = explode("\n", `echo '{$word}' | mecab --unk-feature='未知語'`);

    $node_count = 0;
    $unk_count = 0;
    $area_count = 0;
    $name_count = 0;

    // ノードと種類をカウント
    foreach ( $nodes as $node ) {
        $result_mecab = explode("\t", $node);
        $node_word = trim($result_mecab[0]);
        $feature = explode(',', $result_mecab[1]);
        // BOS/EOSはスキップ
        if ( $node_word === 'BOS' || $node_word === 'EOS' ) { continue; }
        $area_count += ( trim($feature[2]) === '地域' ) ? 1 : 0;
        $name_count += ( trim($feature[2]) === '人名' ) ? 1 : 0;
        $unk_count += ( $node_word === '未知語' ) ? 1 : 0;
        $node_count++;
    }

    // node数が1つ(システム辞書で1語として解析可能)の場合は登録しない
    if ( $node_count <= 1 && $unk_count === 0 ) { continue; }
    // 全nodeが地域名だった場合は、登録しない(東京都北区は、東京都 | 北区で分けたい為)
    if ( $node_count === $area_count ) { continue; }
    // 全nodeが人名だった場合は、登録しない(相田翔子は、相田 | 翔子で分けたい為)
    if ( $node_count === $name_count ) { continue; }

    // コストの計算
    $cost = -400 * pow(mb_strlen($word, 'utf-8'), 1.5);
    if ( $cost < -36000 ) { $cost = -36000; }

    // 平仮名を片仮名に変換
    $kana = mb_convert_kana($kana, 'C', 'utf-8');

    // 行出力
    $output = "$word,1345,1345,$cost,名詞,一般,*,*,*,*,$word,$kana,$kana\n";
    fputs($fd_out, $output);

    // 英字の場合は、小文字統一、大文字統一も出力しておく
    if ( $word !== strtolower($word) ) {
        $output = strtolower($word).",1345,1345,$cost,名詞,一般,*,*,*,*,$word,$kana,$kana\n";
        fputs($fd_out, $output);
    }
    if ( $word !== strtoupper($word) ) {
        $output = strtoupper($word).",1345,1345,$cost,名詞,一般,*,*,*,*,$word,$kana,$kana\n";
        fputs($fd_out, $output);
    }

    $i++;
    if ( $i % 1000 === 0 ) {
        echo "{$i}件目を処理\n";
    }
}
fclose($fd_in);
fclose($fd_out);
?>

基本ベタ移植で、コメントもほぼそのまま流用させて頂きました。

(追記(2010/3/27 01:10) 平仮名を片仮名に変換するところでエンコーディングを指定し忘れていたのでコードを修正しました。あとCSVへの出力形式が間違っていたのでそちらも修正しました。)

MeCab用ユーザ辞書に変換する

できあがったCSVファイルから、以下のようにMeCab用ユーザ辞書に変換します。

$ /usr/lib/mecab/mecab-dict-index -d `awk '/^dicdir/{print $3}' /etc/mecabrc` \
-u hatena.dic -f utf-8 -t utf-8 hatena.csv

mecab-dict-indexのパスは環境によって違うと思うので適宜置き換えて下さい。また、システム辞書のパスも環境によって違うと思うので、/etc/mecabrcから取ってくるような書き方にしてみました。

ユーザ辞書が使われるように設定する

/etc/mecabrcに以下の行を加えると、MeCabを実行した時に必ずユーザ辞書が使われるようになります。

userdic = /path/to/hatena.dic

またはMeCabコマンド実行時にユーザ辞書を指定する事もできます。

$ echo "けいおん二期ktkr" |mecab
けい    名詞,一般,*,*,*,*,けい,ケイ,ケイ
おん    名詞,一般,*,*,*,*,おん,オン,オン
二      名詞,数,*,*,*,*,二,ニ,ニ
期      名詞,接尾,助数詞,*,*,*,期,キ,キ
ktkr    名詞,固有名詞,組織,*,*,*,*
EOS
$ echo "けいおん二期ktkr" |mecab -u /path/to/hatena.dic
けいおん        名詞,一般,*,*,*,*,けいおん,ケイオン,ケイオン
二      名詞,数,*,*,*,*,二,ニ,ニ
期      名詞,接尾,助数詞,*,*,*,期,キ,キ
ktkr    名詞,一般,*,*,*,*,ktkr,キタコレ,キタコレ
EOS

自宅サーバの方には反映済みなので、MeCabを使っているWebサービス(今北川柳とか、あいウェ文とか)は、若干単語の判別性能が上がってるんじゃないかなと思います。