y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

『言語処理 100 本ノック』に PHP で挑む (問題 30)

『言語処理 100 本ノック』に PHP で挑戦しています。前回までで第一章を終えましたが、少し飛ばして今回は第四章を進めます。
www.cl.ecei.tohoku.ac.jp

準備

夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.

第四章では形態素解析について学びます。利用するデータ (neko.txt) は以下からダウンロードできます。まず、ここからデータをダウンロードしてファイルの内容を確認します。
100本ノックで用いるコーパス・データ

$ wget http://www.cl.ecei.tohoku.ac.jp/nlp100/data/neko.txt
$ head neko.txt
一

 吾輩は猫である。
名前はまだ無い。

 どこで生れたかとんと見当がつかぬ。
何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
吾輩はここで始めて人間というものを見た。
しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。
この書生というのは時々我々を捕えて煮て食うという話である。

この文章を MeCab を使って形態素解析します。『自然言語処理の基礎』によると、形態素とは意味を持つ最小の言語単位のことであり、形態素を対象とした解析プロセス、主に「単語分割」「品詞付与」「原形の復元」のことを形態素解析というようです。Web では Wikipedia にも説明があります。ためしに「我輩は猫である。」という文を MeCab で処理してみると次のようになり、単なる文字列として与えられた入力文に対して、上記の三つの処理が行われていることが分かります*1
形態素解析 - Wikipedia

$ echo '我輩は猫である。' | mecab
我輩    名詞,一般,*,*,*,*,我輩,ワガハイ,ワガハイ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
猫      名詞,一般,*,*,*,*,猫,ネコ,ネコ
で      助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ある    助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
。      記号,句点,*,*,*,*,。,。,。
EOS

MeCab は広く利用されている形態素解析器の一つで、以下のウェブページからインストールできます。ウェブページにも記載があるように、MeCab 本体と辞書の両方をインストールする必要があります。
MeCab: Yet Another Part-of-Speech and Morphological Analyzer

以下は、現時点で最新版の MeCab 0.996 を CentOS 7 にインストールする例です。./configure の際には文字コードUTF-8 とすることを指定します。

$ tar xf mecab-0.996.tar.gz
$ cd mecab-0.996
$ ./configure --with-charset=utf8
$ make
$ make check
$ sudo make install

続いて IPA 辞書をインストールします。私が試したところでは、MeCab 本体の文字コードUTF-8 と指定しても、辞書をインストールする際に改めて文字コードの指定が必要になるようです。

$ tar xf mecab-ipadic-2.7.0-20070801.tar.gz
$ cd mecab-ipadic-2.7.0-20070801
$ ./configure --with-charset=utf8
$ make
$ sudo make install

用途に応じて他の辞書をインストールすることもできますが、辞書ごとに品詞体系も異なりますので、この後のプログラムは辞書に合わせて実装していく必要があります。たとえば、先ほどの「我輩は猫である。」という文の解析結果は IPA 辞書を使ったものですが、同じ文を Unidic 辞書を使って解析すると、次のように IPA 辞書とは異なる結果が得られます。なお、実行例でわかるように、mecab の実行時に辞書を指定できるので、複数の辞書をインストールしておくこと自体には特に問題はありません*2

$ echo '我輩は猫である。' | mecab -d /usr/local/lib/mecab/dic/unidic
我輩    ワガハイ        ワガハイ        我が輩  代名詞
は      ワ      ハ      は      助詞-係助詞
猫      ネコ    ネコ    猫      名詞-普通名詞-一般
で      デ      ダ      だ      助動詞  助動詞-ダ       連用形-一般
ある    アル    アル    有る    動詞-非自立可能 五段-ラ行       終止形-一般
。                      。      補助記号-句点
EOS

固有表現などは日々新しい言葉が生まれていますが、それらの言葉が辞書に採録されていないと、適切に単語分割することができません。NEologd という辞書には、Web のリソースを解析して得られた新語が採録されています。今回は利用していませんが*3、新聞記事や Web 上のテキストなどを扱うときには、このような辞書を利用するとよい結果が得られるかもしれません。
mecab-ipadic-neologd/README.ja.md at master · neologd/mecab-ipadic-neologd · GitHub

さて、MeCab をインストールできたら、今回利用するデータ (neko.txt) を MeCab で解析します。これは次のようにコマンドを実行するだけです。

$ mecab neko.txt >neko.txt.mecab

出力されるファイルの内容は以下のとおりです。

$ head -n 20 neko.txt.mecab
一      名詞,数,*,*,*,*,一,イチ,イチ
EOS
EOS
       記号,空白,*,*,*,*, , , 
吾輩    名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
猫      名詞,一般,*,*,*,*,猫,ネコ,ネコ
で      助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ある    助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
。      記号,句点,*,*,*,*,。,。,。
EOS
名前    名詞,一般,*,*,*,*,名前,ナマエ,ナマエ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
まだ    副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ
無い    形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ
。      記号,句点,*,*,*,*,。,。,。
EOS
EOS
       記号,空白,*,*,*,*, , , 
どこ    名詞,代名詞,一般,*,*,*,どこ,ドコ,ドコ

MeCab は入力ファイルを行ごとに解析し、各行の末尾に EOS (End Of Sentence の略) というマーカーが入ります。したがって、入力ファイルで空行が続く箇所は出力でも EOS が連続します。また、句点の後に必ず EOS が出力されるわけでもありません。一行に複数の文をまとめて記述すると、次のように EOS が含まれない結果となります。今回はテキストファイルをそのまま MeCab で処理しましたが、入力の文章が文単位で改行されていない場合などは、MeCab で解析する前に適切な前処理が必要になることもあります。

$ echo '我輩は猫である。名前はまだ無い。' | mecab
我輩    名詞,一般,*,*,*,*,我輩,ワガハイ,ワガハイ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
猫      名詞,一般,*,*,*,*,猫,ネコ,ネコ
で      助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ある    助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
。      記号,句点,*,*,*,*,。,。,。
名前    名詞,一般,*,*,*,*,名前,ナマエ,ナマエ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
まだ    副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ
無い    形容詞,自立,*,*,形容詞・アウオ段,基本形,無い,ナイ,ナイ
。      記号,句点,*,*,*,*,。,。,。
EOS
30. 形態素解析結果の読み込み

形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,1文を形態素マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.

単純なファイル読み込みを実装するという内容で、特に難しい点はないのですが、指定されている各要素がテキストファイルのどのフィールドに対応するのかが問題文には示されていません。これについては、下記のページに記載されています*4
MeCab: オリジナル辞書/コーパスからのパラメータ推定

このページはオリジナルの辞書からパラメータ推定を行う手順について説明しているのですが、ページ内の「Seed 辞書の準備」の箇所に辞書のフォーマットについての説明があります。このうち 1 カラム目の「表層形」が neko.txt.mecab の行頭の文字列に、5 カラム目以降の「素性列」がタブ区切りの右側に対応します*5。この情報にしたがって、必要なフィールドを格納していきます。

[2016-08-14 追記]
MeCab のトップページの「とりあえず解析してみる」の項目に出力フォーマットが簡潔に記載されていました。

表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音

[追記ここまで]

プログラムの実装は簡単です。問題文には「1 文を形態素のリストとして」とあるので、文ごとにまとめることを意図しているのかもしれませんが、第四章の各問題を読んだ限りでは、全文章を一階層で持っても問題なさそうなので、そのように実装しました*6。なお、compact 関数は指定された「変数」の変数名と値から配列を作成するものです。

<?php
function read_mecab_data($filename)
{
    $morphs = [];
    $file = fopen($filename, 'rb');
    while (($line = fgets($file)) !== false) {
        $morphs[] = parse_line(trim($line));
    }
    fclose($file);
    return $morphs;
}

function parse_line($line)
{
    if ($line === 'EOS') {
        return build_morph('EOS', null, null, null);
    }
    list ($surface, $features) = explode("\t", $line);
    $features = explode(',', $features);
    return build_morph($surface, $features[6], $features[0], $features[1]);
}

function build_morph($surface, $base, $pos, $pos1)
{
    return compact('surface', 'base', 'pos', 'pos1');
}

次のようなコードで動作を確認できます。

<?php
require __DIR__ . '/read_mecab_data.php';

$morphs = read_mecab_data('neko.txt.mecab');
var_dump($morphs);

*1:原形の復元については、助動詞「で」について原形の「だ」が復元されているところに注目してください。

*2:デフォルトの辞書は /usr/local/etc/mecabrc で指定されます。MeCab を既定の設定でインストールすると /usr/local/lib/mecab/dic/ipadic となり、IPA 辞書を既定の設定でインストールすると、このディレクトリ以下に配置されます。

*3:NEologd では「我輩は猫である」が固有名詞として一つの形態素になってしまいました。

*4:https://taku910.github.io/mecab/ の目次から辿れます。

*5:未知語の場合は unk.def の 5 カラム目以降が出力されます。IPA 辞書では csv ファイルと unk.def でカラム数が異なっているのですが、今回の問題を解く範囲では特に問題はなさそうなので、区別せずに実装します。

*6:このような持ち方にすると入力の行番号に関する情報が失われますが、それが必要になる問題はありません。