Perl 6でNLP 5本ノック 『第4章: 形態素解析』 35~39 後編

こんにちは、Perl 6アドベントカレンダーの16日目の投稿になります。

NLP 5本ノックということで、東北大学の言語処理100本ノックの『第4章: 形態素解析』の 35~39までの5本のうちの後半2本をPerl 6で解きつつ解説をしていきたいと思います。

問題文は掲載しませんので、ブラウザの別窓で下記ページを参照しながらお楽しみください：
http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch4

# 読み進める前に

## 注意!
+ 読者レベルとしては、[Perl 6アドベントカレンダー1日目](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1)で紹介した[Perl 6 introduction](https://github.com/hankache/perl6intro)などのチュートリアルを一通り終えたレベルを想定しています。
+ わかりやすく紹介するために、正確ではない表現がところどころ出てくるかもしれません。
 + より正確な情報を知りたい場合は、[Perl 6アドベントカレンダー1日目](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1)で紹介した[公式ドキュメント](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1#%E5%85%AC%E5%BC%8F%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88) や公式テストケースの[roast](http://qiita.com/titsuki/items/549ac95b8c37feb1dee1#roast)などを参照ください。
+ 日本語訳が定着していないようなPerl 6独特の英単語を目にするかもしれません。基本的に解説中ではそのまま英単語で書き、その後補足しますのでご容赦ください。
+ Perl 6のモットーはTMTOWTDI (やり方は一つじゃない) です。特に正解はありません。でも、「これのほうがもっとかっこよく簡潔に書けるよ！」といった指摘は大歓迎です！
+ 元の38~39の問題文だと、30で生成されたデータをそれ以降で使う流れになっています。しかし、ここではバインダの使い方を紹介したかった関係で無視しているので注意してください。

## 準備

+ 拙作の[MeCabのPerl 6バインダ](https://github.com/titsuki/p6-MeCab)を入れてください。下記コマンドで、READMEに書いてあるようにIPAdicも勝手に入ります。

```
$ zef install MeCab
```

# 38. ヒストグラム

## コード

```
use MeCab;
use MeCab::Tagger;
use SVG;
use SVG::Plot;

my $text-fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;

my @bag-of-words = gather for $text-fh.lines {
    loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
        next if $node.stat == MECAB_BOS_NODE|MECAB_EOS_NODE;
        take $node.surface;
    }     
}.Bag.categorize(:as{ 1 }, { $_.value }).map({ .key => [+] .values }).sort: { $^a.key <=> $^b.key };

my $plot-fh = open 'histogram.svg', :w ;
$plot-fh.say(
    SVG.serialize: SVG::Plot.new(
        :title( 'ヒストグラム' ),
        :width( 400 ),
        :height( 300 ),
        :background( '#fffff0' ),
        :colors( '#4169e1' ),
        :label-font-size(10),
        :labels( @bag-of-words>>.key ),
        :values( item(@bag-of-words>>.value) ),
    ).plot(:stacked-bars)
);

$text-fh.close;
$plot-fh.close;
```

## 解説

### 前半

1. `gather for take` で単語の遅延リストを生成します
 + `$tagger.parse-tonode`で形態素解析して、`take`で表記を取得します。
2. `.Bag` で表記、頻度のペアのリストを生成します
3. `.categorize(:as{ 1 }, { $_.value })` で `$_.value`をキーとするハッシュに対して、`:as{ 1 }`で値`1`を追加していきます。
4. `.map({ .key => [+] .values })`で、頻度がキーで、その頻度を持つ要素の数が値となるように変換します。このとき`[+]`は被演算子のリスト(i.e. `1`のリスト)に対して`+`演算を行います。
5. `.sort: { $^a.key <=> $^b.key }` で頻度の昇順にソートします
6. 5.で生成された単語の出現頻度、その出現頻度をとる単語の種類数のペアのリストを変数に代入します

### 後半

+ `:stacked-bars`を使います
+ `:labels( @bag-of-words>>.key )` で、単語の出現頻度を横軸に指定します
+ `:values( item(@bag-of-words>>.value) )` で、その出現頻度をとる単語の種類数を縦軸に指定します


![histogram.png](https://qiita-image-store.s3.amazonaws.com/0/149227/f9671426-1067-7e11-b022-8589fb14f1a9.png)

よさそうです

# 39. Zipfの法則

## コード

```
use MeCab;
use MeCab::Tagger;
use SVG;
use SVG::Plot;

my $text-fh = open "neko.txt", :r;
my MeCab::Tagger $tagger .= new;

my @bag-of-words = gather for $text-fh.lines {
    loop (my MeCab::Node $node = $tagger.parse-tonode($_); $node; $node = $node.next) {
        next if $node.stat == MECAB_BOS_NODE|MECAB_EOS_NODE;
        take $node.surface;
    }     
}.Bag.sort: { $^b.value <=> $^a.value };

my $plot-fh = open 'Zipf.svg', :w ;
$plot-fh.say(
    SVG.serialize: SVG::Plot.new(
        :title( 'Zipf' ),
        :width( 800 ),
        :height( 600 ),
        :background( '#fffff0' ),
        :colors( '#4169e1' ),
        :fill-width( 1.1 ),
        :label-font-size(10),
        :x( (1 .. +@bag-of-words).list>>.log10 ),
        :values( item(@bag-of-words>>.value>>.log10) ),
        :min-y-axis( -0.0001 ) # trick
    ).plot(:xy-points)
);

$text-fh.close;
$plot-fh.close;
```

## 解説

### 前半

1. `gather for take` で単語の遅延リストを生成します
 + `$tagger.parse-tonode`で形態素解析して、`take`で表記を取得します。
2. `.Bag` で表記、頻度のペアのリストを生成します
3. `.sort: { $^b.value <=> $^a.value }` で値の降順にソートします。
4. 3.で生成された単語、出現頻度のペアのリストを変数に代入します

### 後半

+ `:xy-points`を使います
+ `:x( (1 .. +@bag-of-words).list>>.log10 )` で、順位の対数をとったものを横軸に指定します
+ `:values( item(@bag-of-words>>.value>>.log10) )` で、頻度の対数を取ったものを縦軸に指定します

![Zipf.png](https://qiita-image-store.s3.amazonaws.com/0/149227/a45b0597-032e-23fa-6fa6-58be72545815.png)
よさそうです


以上、Perl 6でNLP 5本ノック 『第4章: 形態素解析』 35~39 後編　でした。