Perlで綺麗に形態素解析をやる

[Perl 5 Advent Calendar 2016 - Qiita](http://qiita.com/advent-calendar/2016/perl5)の22日目の記事です.

僕はバイト先や大学で Perl を使って自然言語処理を行う時があるのですが, 対象のテキストに記号や URL などが含まれている場合があってまー面倒です. そういった問題を解消しながら形態素解析を行うために [Text::Shirasu](https://metacpan.org/pod/Text::Shirasu) というモジュールを使ってみましょう.

# 形態素解析をやってみる
もともと [Text::MeCab](https://metacpan.org/pod/Text::MeCab) のラッパーとして開発していたものなので, Text::MeCab と似たように扱うことができます.
手始めにこのツイートを形態素解析してみましょう.

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">綿密に打ち合わせをしたのに突然クライアントの都合で色々変わって大変な目に合うデザイナーの図 <a href="https://t.co/9DnOlaf6mT">pic.twitter.com/9DnOlaf6mT</a></p>&mdash; えかきどう (@E_K_D) <a href="https://twitter.com/E_K_D/status/810283165038379008">2016年12月18日</a></blockquote>

```perl
use strict;
use warnings;
use utf8;
use Data::Dumper;
use Text::Shirasu;

my $text = "綿密に打ち合わせをしたのに突然クライアントの都合で色々変わって大変な目に合うデザイナーの図 https://t.co/9DnOlaf6mT";
my $ts = Text::Shirasu->new(dicdir => "/usr/local/lib/mecab/dic/mecab-ipadic-neologd");

$ts->parse($text);

for my $node ( @{$ts->nodes} ) {
	printf "%s\n", $node->surface;
	print Dumper $node->feature;
}
```

実行結果は

```
綿密
$VAR1 = [
          '名詞',
          '形容動詞語幹',
          '*',
          '*',
          '*',
          '*',
          '綿密',
          'メンミツ',
          'メンミツ'
        ];
に
$VAR1 = [
          '助詞',
          '副詞化',
          '*',
          '*',
          '*',
          '*',
          'に',
          'ニ',
          'ニ'
        ];
打ち合わせ
$VAR1 = [
          '名詞',
          '一般',
          '*',
          '*',
          '*',
          '*',
          '打ち合わせ',
          'ウチアワセ',
          'ウチアワセ'
        ];
.
.
.
```
とこんな感じで出力されます.
Text::Shirasu の `new()` を呼び出す時 Text::MeCab と同じ引数を渡すことができます. ここでは [mecab-ipadic-neologd](https://github.com/neologd/mecab-ipadic-neologd) を使用するため引数でそのパスを指定しています.

また, Text::MeCab と違って, feature が配列リファレンスの状態で格納されています. これは Text::Shirasu の parse メソッドを実行した時, Text::MeCab でパースした結果を加工して保存しているからです.

# ある品詞だけを抜き出す
例えば名詞だけ抜きだしたい場合があるとします. その場合先ほどのコードに 1 行コードを加えるだけで品詞のフィルタリングを行うことができます.

```perl
use strict;
use warnings;
use utf8;
use Data::Dumper;
use Text::Shirasu;

my $text = "綿密に打ち合わせをしたのに突然クライアントの都合で色々変わって大変な目に合うデザイナーの図 https://t.co/9DnOlaf6mT";
my $ts = Text::Shirasu->new(dicdir => "/usr/local/lib/mecab/dic/mecab-ipadic-neologd");

$ts->parse($text);
# この 1 行を追加
$ts->filter(type => [qw/名詞/]);

for my $node ( @{$ts->nodes} ) {
	printf "%s\n", $node->surface;
	print Dumper $node->feature;
}
```
結果は

```
綿密
$VAR1 = [
          '名詞',
          '形容動詞語幹',
          '*',
          '*',
          '*',
          '*',
          '綿密',
          'メンミツ',
          'メンミツ'
        ];
打ち合わせ
$VAR1 = [
          '名詞',
          '一般',
          '*',
          '*',
          '*',
          '*',
          '打ち合わせ',
          'ウチアワセ',
          'ウチアワセ'
        ];
.
.
.
```

ちゃんと名詞のみ抜き出すことができているかと思います. さらに名詞の中でも一般, 固有名詞など様々な種類があります. それらも合わせて抜きだしたい場合, 先ほど加えた 1 行を次のように変更してみましょう.

    $ts->filter(type => [qw/名詞/], 名詞 => [qw/固有名詞/]);

実行してみると

```
クライアント
$VAR1 = [
          '名詞',
          '固有名詞',
          '人名',
          '一般',
          '*',
          '*',
          'CLIENT',
          'クライアント',
          'クライアント'
        ];
https
$VAR1 = [
          '名詞',
          '固有名詞',
          '一般',
          '*',
          '*',
          '*',
          'HTTPS',
          'エイチティーティーピーエス',
          'エイチティーティーピーエス'
        ];
.
.
.
```
という感じに名詞の固有名詞のみ抜き出されます。

# テキストの正規化を行う
形態素解析を行う前に, 始めの方で例に挙げた要らない文字列を削除するためにテキストの正規化を行います. 例えば次のような文章があったとしましょう.

> 明日は～祝日だからーーピースフル★【天皇誕生日】

ここで星とカッコ内の文字列も削除したいですね!!
そこで `normalize` メソッドを使いましょう!!

```perl
use strict;
use warnings;
use utf8;
use Data::Dumper;
use Text::Shirasu;

my $text = "明日は～祝日だからピーーースフル★【天皇誕生日】";
my $ts = Text::Shirasu->new(dicdir => "/usr/local/lib/mecab/dic/mecab-ipadic-neologd");

$ts->parse($text);

for my $node ( @{$ts->nodes} ) {
	printf "%s\n", $node->surface;
}
```
今のままだと

```
明日
は
～
祝日
だ
から
ピーーースフル
★
【
天皇誕生日
】
```
ですので、正規化に挑戦しましょう。
Text::Shirasu には「ー」を正規化するサブルーチンが含まれていますのでそれも利用しましょう.

```perl
use strict;
use warnings;
use utf8;
use Data::Dumper;

use Text::Shirasu qw/normalize_hyphen/;

my $text = "明日は～祝日だからピーーースフル★【天皇誕生日】";
my $ts = Text::Shirasu->new(dicdir => "/usr/local/lib/mecab/dic/mecab-ipadic-neologd");

my $n = $ts->normalize($text, \&normalize_hyphen, \&normalize_star_brackets);
$ts->parse($n);

for my $node ( @{$ts->nodes} ) {
	printf "%s\n", $node->surface;
}

sub normalize_star_brackets {
	local $_ = shift;
	s/(★|【.*】)//g;
	$_;
}
```

ここで `normalize_star_brackets` という ★ と 【天皇誕生日】 を取り除くためのサブルーチンを定義しました. そしてこれと `normalize_hyphen` を `normalize` メソッドの引数で与えます. 詳しい使い方は[ここ](https://metacpan.org/pod/Text::Shirasu#normalize)で確認できます.

このコードを実行すると, 

```
明日
は
祝日
だ
から
ピースフル
```
という結果を得ることができます!!

# 最後に
このモジュールのリポジトリは [github.com/Code-Hex/Text-Shirasu](https://github.com/Code-Hex/Text-Shirasu) です.
改善点がありましたら気軽に Pull Request を送ってください！