MeCab

MeCabというものを知った。あの、食べる「めかぶ」だ。日本語の形態素解析をするソフトである。Yahooのapiとか、「ちゃせん」とかいろいろあるようだが、これが新しくて速いようだ。

形態素解析という言葉は、何度か耳にしている。初めて聞いたのは、コールセンターなどに寄せられた問い合わせ内容を分析するソフトの説明を聞いたときだ。その後、いわゆる「人口無能」というもの、「意味不明だがなんとなく文章として成立している」というものを作るのに使われているということだった。

「言葉をコンピュータで解析する」とか、自動翻訳とかいうものについては、私はとても否定的に考えている。つまり、「そんなのはムリだ」という考えである。実際、形態素解析を使って一番おもしろいのは「無能」であり、自動翻訳はせいぜい、その文章に含まれる単語を辞書で引いて並べるくらいのことしかできていない。

私は語学は好きだが、外国語を読むというのはつまり辞書を引くことである。そして、辞書を引くというのはある単語をリストのなかから探すことでは、ない。それだけなら、コンピューターでできるだろう。

しかし、まったく未知の文章を辞書をひいて理解しようとした人にはわかるだろうが、辞書があっても引けないことがある。まずは動詞の活用。辞書に載っている動詞は原型だけである。そして動詞の活用の仕方は多様で、単純に規則化することもできない。それから同音意義語や、特別な言い回し、とにかく例外だらけなのである。

文法という、なまじ法則化されたようなものがあるから、人は言葉を科学的に扱おうとするが、結局言葉は科学の対象にはならない。極端なことを言うと、言葉というのは科学を拒否するものである。自動翻訳の精度もずいぶん向上したようだが、聞いた話ではとにかく大量の例文を持つそうである。そしてその中から同じ表現をさがしてくる、という方法がとられているようだ。

話がそれたが、perlでmecabを使ってみた。職場のWindows XPで、active perlで使ってみた。おもしろそうなので続きは家で、と思ったら、xpで使えたモジュールがvistaでインストールできなかった。vistaはよく俺を困らせる。

しかし、私はvistaを使うことをやめない。もうvistaもリリースされてずいぶんたつ。これだけたってvistaに移植されていないのには、理由があるのだ。だが、私にはさくらインターネットのレンタルサーバがある。さくらに最近mecabがインストールされたという情報を見て、sshで入って使えることを確認している。cygwinも、virtual pcもいらない。私はすでにfreebsd(だっけ)が動くサーバを持っているのだ。

さて、ではさくらでめかぶを。と、その前にcgiを動かすだけでひと騒動。まず、cgiを動かすディレクトリは決めてある。そしてパスワードを設定してある。パスワードが何か忘れている。なんとか思い出す。mecabを呼ぶperlが動くことをsshで確認する。しかしcgiにするとinternal server errorになる。いろいろ悩んだ末、permissionが777だと動かないことがわかる。755にしたら、動く。

#!/usr/bin/perl -w

use strict;
use Encode;
use MeCab 0.97;

open IN,"<jpsjis.txt";

print "Content-type: text/html\n";
print "\n";
print "<h3>mecab test</h3>\n";

while(<IN>){
    my $str = $_;

    my $t = new MeCab::Tagger("-Owakati");

    Encode::from_to($str, "Shift_JIS", "EUC-JP");
    $str = $t->parse($str);

    Encode::from_to($str, "EUC-JP", "Shift_JIS");
    print "<p>$str</p>\n";
}
close IN;


このソースは、shift_jisで保存してあり、読み込むファイル 「jpsjis.txt」もshift_jisである。そのため、エンコード処理が入っている。

私のcgiは全部UTF8にしており、これもUTF8にしてエンコードなどしたくなかったのだが、utf8だとどうしてもうまくいかない。mecabがeuc-jpなのだろうか?cgiをやるときはいつも日本語の文字コードで悩む。日本人のハンデである。

このスクリプトは日本語でかかれた文章を形態素で「分かち書き」するだけのものである。とりあえずmecabをcgiで動かすテスト用スクリプトである。

本当にやりたいことのひとつが、アクセスログの検索後から名詞を抜き出すことだ。webalizerの検索語一覧は、複数の検索文字列をそのまま表示するので、同じ名詞が含まれていても別の検索語としてカウントされる。

"delphi  ics ftp"
"delphi コンポーネント テキスト ルビ"
"delphi ハードコピー bitblt"


というようになる。この場合、"delphi"というキーワードを数えたいのである。私のサイトだと、"delphi", "tokai", "トーカイ", "whea-logger" などがこれで数えられて上位にランクされるはずだ。

次にやりたいのが、人口無能、あるいはbot、のようなもの。すでにtwitterにたくさんあるbotである。

これらは形態素解析するだけでなく、それを再構成する必要がある。このときに使われるのが「マルコフ連鎖」とかいうリクツである。マルコフなんたらがどういうことなのかはなんとなくわかるのだが、はっきりとはわからない。だが、少なくとも形態素で分解できてその品詞まで判定してもらえば、あとはなんとかなりそうだ。

#!/usr/bin/perl -w

use strict;
use Encode;
use MeCab 0.97;

open IN,"<jpsjis.txt" or die;

print "Content-type: text/html\n";
print "\n";
print "<h3>mecab test</h3>\n";


while(<IN>){
    my $m = new MeCab::Tagger ("");
    my $str2 = $_;

    Encode::from_to($str2, "Shift_JIS", "EUC-JP");

    for (my $n = $m->parseToNode ($str2); $n ; $n = $n->{next}) {
        if($n->{posid}==38){
            my $surf=$n->{surface};
            Encode::from_to($surf, "EUC-JP", "Shift_JIS");
            print $surf."<br>";
        }
    }
}

close IN;


これで名詞だけを抜き出せる。$n->{posid} というのが品詞IDで、これは自由に設定できるようだが、さくらでは38になっていた。区別さえできればよいので、そのまま、「38だったらと」いう条件で$n-{surface}を表示した。

そして文章のなかで名詞の出現回数をかぞえるには、名詞を保存していき、すでにあるかを検索して、

なかったら追加する、ということをすればよい。私がやろうとしている規模であればたいしたことはないが、大量に処理するならDBが必要かな。

今、「英単語はどうなるのかな」と思ってアクセスログを読ませてみたら、逆に英単語しか表示されない。そう、日本語はエンコードされているからデコードしないといけないのだ。メンドクサイ・・・。

ログから検索文字列を抜き出し、それをデコードする処理が必要だ。これは以前作ってある。

さっきeuc_jpでデコードするのがどうこう、と言っていた件は、めかぶの辞書がeuc_jpだからだそうだ。そして辞書をUTF8に変更できるようだ。あとでやろう。

さくらのログを表示させようとして、残っているのが前日の日付であることに気づいた。1日引けば・・・と思ったところで月初だったら、年初だったら・・・とそう単純ではないことに気づいたが、

調べると簡単にできた。

my $yesterday = DateTime->now->subtract( days => 1 );


とりあえずmecabがどんなものか試すスクリプト。htmlからpostしたテキストを形態素に分割して品詞等を表示する。

#!/usr/bin/perl -w

use strict;
use CGI;
use utf8;
use Encode;
use MeCab 0.97;


my $q= CGI->new;

my $value=$q->param('text');

print $q->header(-charset=>'utf-8'),
$q->start_html(-title=>'mecab'),
$q->p($value),
$q->br;


print "\n";
print "<hr>\n";

my $m = new MeCab::Tagger ("");
my $str2 = $value;

Encode::from_to($str2, "UTF8", "EUC-JP");

for (my $n = $m->parseToNode ($str2); $n ; $n = $n->{next}) {

    my $surf=$n->{surface};
    Encode::from_to($surf, "EUC-JP", "UTF8");
    print '<font size="5">'.$surf.' </font>';

    my $feature=$n->{feature};
    Encode::from_to($feature, "EUC-JP", "UTF8");

    print '<font size="2">'.$feature.' </font>';

    print '<font size="1">'.$n->{posid}.' </font>';

    if($n->{posid}==4){
        print "<br><br>";
    }
    if($n->{posid}==9){
        print "<br>";
    }
}

print $q->end_html;

exit;





cgiを呼び出すhtml。cgiもこのhtmlファイルも、文字コードはutf-8で保存する。

<html lang="ja" xml:lang="ja" xmlns="hxtp://www.w3.org/1999/xhtml">
<head>
<meta hxtp-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>just post</title>
</head>
<body>
<form action="meca6.cgi" target=_self method=post>
<textarea name="text" rows="20" class=input cols="100"></textarea><br>
<input type=submit value="post">
</form>
</body></html>


とりあえず名詞の抽出をする。頻出単語を数えるなどをしてみるが、一般名詞と固有名詞の区別も簡単ではない。たとえば「中国国家発展改革委員会」という言葉は

中国[47]国家[38]発展[36]改革[36]委員[38]会[51]

という風に分離される。カッコ内の数字は品詞の種類を表す数値である。こういうものはくっつけたいが、なんでもくっつけるわけにもいかない。とりあえず、いろんな文章を形態素分離させてみて、どういうパターンがあるかを見て、38->36はくっつける、などとひとつひとつ決めていくしかないか。

「人口無能」であれば、その辺はテキトーにやって、頻度や乱数をつかって文章を切り貼りしていけばいい。だが、まったくの「無能」ではやっぱり面白くない。「圧縮新聞」はそれを新聞でやることによって、ある程度のもっともらしさを保っている。ツイッターのツイートやブログなどでは、文体が多様すぎて混沌としすぎる。