こんにちは、Perl 6アドベントカレンダーの14日目の投稿になります。
NLP 5本ノックということで、東北大学の言語処理100本ノックの『第4章: 形態素解析』の 30~34までの5本をPerl 6で解きつつ解説をしていきたいと思います。
問題文は掲載しませんので、ブラウザの別窓で下記ページを参照しながらお楽しみください:
http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch4
読み進める前に
注意!
- 読者レベルとしては、Perl 6アドベントカレンダー1日目で紹介したPerl 6 introductionなどのチュートリアルを一通り終えたレベルを想定しています。
- わかりやすく紹介するために、正確ではない表現がところどころ出てくるかもしれません。
- より正確な情報を知りたい場合は、Perl 6アドベントカレンダー1日目で紹介した公式ドキュメント や公式テストケースのroastなどを参照ください。
- 日本語訳が定着していないようなPerl 6独特の英単語を目にするかもしれません。基本的に解説中ではそのまま英単語で書き、その後補足しますのでご容赦ください。
- Perl 6のモットーはTMTOWTDI (やり方は一つじゃない) です。特に正解はありません。でも、「これのほうがもっとかっこよく簡潔に書けるよ!」といった指摘は大歓迎です!
- 元の30~34の問題文だと、30で生成されたデータをそれ以降で使う流れになっています。しかし、ここではバインダの使い方を紹介したかった関係で無視しているので注意してください。
準備
- 拙作のMeCabのPerl 6バインダを入れてください。下記コマンドで、READMEに書いてあるようにIPAdicも勝手に入ります。
$ zef install MeCab
30. 形態素解析結果の読み込み
コード
30.p6
use MeCab;
use MeCab::Tagger;
my $fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;
my @list;
for $fh.lines {
loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
my @tmp = $node.feature.split(",");
@list.push({:surface($node.surface), :base(@tmp[6]),
:pos(@tmp[0]), :pos1(@tmp[1])});
}
.say for @list;
}
$fh.close;
-
open "neko.txt", :r
で読み取り専用モードでファイルを開き、ファイルハンドルを変数に代入します。 - 一行ずつ読み取っていきます
-
$tagger.parse-tonode
で与えられた行を形態素解析します -
$node.surface
はsurfaceに、$node.feature
の7,0,1列目はこの順でbase,pos,pos1の値に代入し、ハッシュを作ります - 2.のハッシュを
@list
にpushします -
.say for @list;
で出力します
31. 動詞
31.p6
use MeCab;
use MeCab::Tagger;
my $fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;
for $fh.lines {
loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
$node.surface.say if $node.posid == 31|32|33;
}
}
$fh.close;
解説
-
open "neko.txt", :r
で読み取り専用モードでファイルを開き、ファイルハンドルを変数に代入します。 - 一行ずつ読み取っていきます
-
$tagger.parse-tonode
で与えられた行を形態素解析します - 形態素の
posid
が31|32|33だった場合に$node.surface.say
で表記を出力します
補足
-
posid
とは品詞IDのことです。下記コマンドでチェックしてみましょう:
$ nkf -w ~/.p6mecab/lib/mecab/dic/ipadic/pos-id.def | less
下記のような出力が得られたはずです。見ての通り31~33は動詞です。:
...
接頭詞,動詞接続,*,* 29
接頭詞,名詞接続,*,* 30
動詞,自立,*,* 31
動詞,接尾,*,* 32
動詞,非自立,*,* 33
副詞,一般,*,* 34
副詞,助詞類接続,*,* 35
名詞,サ変接続,*,* 36
...
32. 動詞の原形
コード
use MeCab;
use MeCab::Tagger;
my $fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;
for $fh.lines {
loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
$node.feature.split(",",:skip-empty).[6].say if $node.posid == 31|32|33;
}
}
$fh.close;
解説
-
open "neko.txt", :r
で読み取り専用モードでファイルを開き、ファイルハンドルを変数に代入します。 - 一行ずつ読み取っていきます
-
$tagger.parse-tonode
で与えられた行を形態素解析します - 形態素の
posid
が31|32|33だった場合に$node.feature.split(",",:skip-empty).[6].say
でコンマ区切りで、featureの7列目を出力します
33. サ変名詞
コード
use MeCab;
use MeCab::Tagger;
my $fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;
for $fh.lines {
loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
$node.surface.say if $node.posid == 36;
}
}
$fh.close;
解説
- "31. 動詞" の品詞IDがサ変接続の36に変わっただけです。
34. 「AのB」
コード
use MeCab;
use MeCab::Tagger;
my $fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;
for $fh.lines {
my @surfaces = gather loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
if $node.next.defined and $node.next.next.defined {
take $node.surface ~ 'の' ~ $node.next.next.surface if $node.posid R(cont) 36..67 and $node.next.surface eq 'の' and $node.next.next.posid R(cont) 36..67;
}
}
.say for @surfaces;
}
$fh.close;
解説
-
open "neko.txt", :r
で読み取り専用モードでファイルを開き、ファイルハンドルを変数に代入します。 - 一行ずつ読み取っていきます
-
$tagger.parse-tonode
で与えられた行を形態素解析します - 形態素のならびを3つごとに見ていきます。この3つの並びの形態素の先頭をA、真ん中をB、末尾をCと置くと、「Aのposid`が36~67のどれか」かつ「Bの表記が"の"」かつ「Cのposidが36~67のどれか」のときに、その3つの形態素の並びを取り出します。
補足
if $node.next.defined and $node.next.next.defined
-
if
じゃなくてwith
かなあと若干思ったんですが、roastにそういうケースがなかったので回避しています。
R(cont)
-
$a (cont) $b
の(cont)
は∋
と等価です。
$a
は$b
を元として含むを意味します。 - Rはreversed operator(逆転演算子)と呼ばれるものです。被演算子を逆転させます。つまり
$a R(cont) $b
は、$b
は$a
を元として含むを意味します。論文とかだとこっち向きが多い[要出典]と思うのでRをつけています。
以上、Perl 6でNLP 5本ノック『第4章: 形態素解析』 30~34 でした。