$Id: bindings.html,v 1.9 2004/08/06 18:05:13 taku-ku Exp $;
各種スクリプト言語 (perl, ruby, python, Java) から, MeCab が提供する形態素解析の機能を利用可能です. 各バインディングは SWIG というプログラ ムを用いて, 自動生成されています. SWIG がサポートする他の言語も 生成可能だと思われますが, 現在は, 作者の管理できる範囲内ということで, 上記の4つの言語のみを提供しております.
各言語バイディングのインストール方法は, perl/README, ruby/README, python/README, java/README を御覧下さい.
MeCab::Tagger というクラスのインスタンスを生成し, parse (もしくは parseToString) というメソッドを呼ぶことで, 解析結果が文字列として取得できます. MeCab::Tagger のコンストラクタの引数は, 基本的に mecab の実行形式に与え るパラメータと同一で, それらを配列として表現します. ただし, perl の場合は, 配列のリファレンスを与えます.
この配列は, C の main 関数に与える引数と 同じ構造でなければなりません. 配列の第一要素は, プログラム名等を入れてください. (実際には, 第一引数はダミーで, 使われることはありません).
use MeCab; my @arg = ($0, "-Ochasen"); $m = new MeCab::Tagger (\@arg); print $m->parse ("今日もしないとね"); # parseToString () でも可能
require 'MeCab' arg = [$0, "-Ochasen"] m = MeCab::Tagger.new (arg) print m.parse ("今日もしないとね") # parseToString () でも可能
import sys import MeCab arg = [sys.arg[0], "-Ochasen"] m = MeCab.Tagger (arg) print m.parse ("今日もしないとね") # parseToString () でも可能
import jp.ac.aist_nara.cl.mecab.Tagger; import jp.ac.aist_nara.cl.mecab.Node; public static void main(String[] argv) { String[] arg = new String [2]; arg[0] = argv[0] arg[1] = "-Ochasen" Tagger tagger = new Tagger (arg); System.out.println (tagger.parse ("太郎は二郎にこの本を渡した.")); // parseToString () でも可能 }
MeCab::Tagger クラスの, parseToNode という メソッドを呼ぶことで, 「文頭」という特別な形態素が MeCab::Node クラスのインスタンスとして 取得できます.
MeCab::Node は, 双方向リストとして表現されており, next(), prev() というメソッド があります. それぞれ, 次の形態素, 前の形態素を MeCab::Node クラスのインスタンスとして 返します. 全形態素には, next () メソッドを順次呼ぶことでアクセスできます.
MeCab::Nodeクラスには, hasNode () というメソッドがあります. 返り値が 0 の場合は, このインスタンスには, 形態素情報が保持されておりません. 文末の形態素の次の形態素は, 0 が返ります.
以下に perl の例を示します. この例では, 各形態素を順次にアクセスし, 形態素の表層文字列, 前の品詞と現在の品詞, その形態素までのコストを表示し ます.
use MeCab; my @arg = ($0, "-Ochasen"); my $m = new MeCab::Tagger (\@arg); my $n = $m->parseToNode ("今日もしないとね"); # MeCab::Node のインスタンスが返る while ($n->hasNode () != 0) { printf ("%s\t%s-%s\t%d\n", $n->getSurface(), # 表層 $n->prev()->getFeature (), # ひとつ前の品詞 $n->getFeature(), # 現在の品詞 $n->getTotalCost () # その形態素までのコスト ); $n = $n->next (); # 次に移動 }
ある Node (便宜上 rnode と表現)について, rnode.prev() は常に定義でき, 最適な 1つ前の Node を返します. しかし, 一般には, rnode に接続する 1つ前の node (便宜上 lnode と表現) は, 複数存在します.
MeCab::Path クラスは, lnode と, lnode が rnode に接続した場合, rnode.getTotalCost() が返すべきコストの2つの情報を保持するクラスです.
MeCab::Path は, 単方向リストで表現されています. まず, rnode.getPath () を呼ぶことで, Path のうち, 1つが取得できます. さらに, MeCab::Path クラスには, next() というメソッドがあり, rnode に接続する「別の」 Path インスタンスを返します. 単方向リストであるため, next() を順次に呼ぶことで, すべての Path を 取得することができます. また, MeCab::Path クラスには, hasPath () というメソッドがあります. 返り値が0 の場合は, このインスタンスには, Path が保持されておりません. hasPath () の戻り値を用いて, すべての path を巡回しかたどうか判定できます.
MeCab::Path クラスには, さらに, getNode () 及び getCost () メソッドがあります. getNode() は, rnode に接続する lnode であり, getCost()は, , lnode が rnode に仮に接続した場合の rnode.getTotalCost() が返すべきコストになります.
rnode に接続する複数の Path のそれぞれについて, getCost () を呼び, それらの最小値が,rnode が持つべきコスト (rnode.getTotalCost()) になります. また, rnode.prev() の返り値は, 最小値を出力した Path が保持する Node となります.
一般に Path をすべて保持するには 多くのメモリを必要とし, 解析に時間がかかります. MeCab は高速性を犠牲にしたくないために, デフォルトの動作では, Path を保持せず, 最適解しか求めません. そのため, Path を展開したい場合は, MeCab を -a オプション付きで 起動する必要があります.
以下の例は, 最適解とのコストの差が 8000 点以下の Node を深さ優先で探索し, 表示する例です.
#!/usr/bin/perl use MeCab; my %Node; sub search () { my $rnode = shift @_; my $rid = $rnode->getID(); # 右 Node の ID my $bcost = $rnode->getTotalCost(); # 右 Node の 最適コスト $Node{$rid} = $rnode; # 解析したという印を付ける # 右 Node に接続するすべての Path について.. for (my $p = $rnode->getPath (); $p->hasPath(); $p = $p->next()) { next if ($p->getCost() - $bcost > 8000); # 解析幅以下のものみを残す my $lnode = $p->getNode(); my $lid = $lnode->getID(); next if (defined $Node{$lid}); # 印がついてるものは無視 &search ($lnode); # lnode を新しい親にして, 再帰 } } my $m = new MeCab::Tagger ([$0, "-a"]); # -a をつける, 全探索を行なう. my $n = $m->parseToNode ("今日もしないとね"); # いったん 文末まで捜査する while ($n->next()->hasNode()) { $n = $n->next(); } # 深さ優先で, 後ろから前へ探索 &search ($n); for my $id (sort {$a <=> $b} keys %Node) { print $Node{$id}->isBest() ? "* " : " "; # Best の場合は, * を表示する print $id, "\t", $Node{$id}->toString(), "\n"; }
もし, コンストラクタや, 解析途中でエラーが起きた場合は, RuntimeError 例外が発生します. 例外のハンドリングの方法は, 各言語のリファレンスマニュアルを ごらんください. 以下は, python の例です
try: m = MeCab.Tagger (sys.argv) print m.parse ("今日もしないとね") except RuntimeError, e: print "RuntimeError:", e;
parseToNode の返り値は, 「文頭」という特別な形態素を示す MeCab::Node インタンスです. さらに, 「文末」という特別な形態素も存在いたしますので, 注意してください. もし, これらを無視したい場合は, 以下のように next() でそれぞれを読み飛ばしてください.
my $n = $m->parseToNode ("今日もしないとね"); $n = $n->next(); # 「文頭」を無視 while ($n->next()->hasNode() != 0) { # next () の hasNode を調べる printf ("%s\n", $n->getSurface()); $n = $n->next (); # 次に移動 }
MeCab::Node および MeCab::Path の実体(メモリ上にある形態素情報)は, MeCab::Tagger インスタンスが管理しています. MeCab::Node, MeCab::Pathは, Node,Path の実体を指している参照にすぎせん. そのために, parseToNode が呼ば れる度に, 実体そのものが, 上書きされていきます. 以下のような例は ソースの意図する通りには動きません.
m = MeCab.Tagger (sys.argv) n1 = m.parseToNode ("今日もしないとね") n2 = m.parseToNode ("さくさくさくら") # 以下のコードを実行した時点で, RuntimeError 例外が発生する. while (n1.hasNode () != 0): print n1.getSurface () n1 = n1.next ()
上記の例では, n1 の指す中身が, 「さくさくさくら」を解析した時点で 上書きされており, 使用できなくなっています. 上記の場合, n1.hasNode() を呼んだ瞬間に RuntimeError 例外が発生します.
複数の Node を同時にアクセスしたい場合は, 複数の MeCab::Tagger インスタ ンスを生成してください.
以下に, SWIG用のインタフェースファイル の一部を示します. バイディングの実装言語の都合上, C++ のシンタックスで 表記されていますが, 適宜読みかえてください. また, 各メソッドの動作も添え ていますので参考にしてください.
class Tagger { public: // コンストラクタ, スクリプト言語からは, 文字列の配列として与える. Tagger (int argc, char *argv[]); // 文字列を解析し, 解析結果を文字列として得る. char* parseToString (char *); // 文字列を解析し, 先頭の形態素(Node)を返す Node parseToNode (char *); // parseToString () と同じ char* parse (char *); // NBest 解の出力 char *parseNBest(int N, char*) // 解析結果を確からしいものから順番に取得する場合の初期化 int parseNBestInit (char *) // 次に確からしい解析結果を文字列として取得. // parseNBestInit () を実行しておく必要がある int next (); // 次に確からしい解析結果の先頭の形態素(Node) を返す // parseNBestInit () を実行しておく必要がある Node nextNode() // mutex を使い, インスタンスを排他的に lock する // C インタフェースの mecab_lock と同じ int lock (); // lock を解除する // C インタフェースの mecab_unlock と同じ int unlock (); }; class Path { // 指している Path 実体が使用可能の場合は, 1 それ以外は 0 を返す int hasPath (); // 次の Path を返す Path next (); // このパスを通ったときの 文頭からの最適コストを返す unsigned int getCost (); // Path が保持する Node を返す Node getNode (); } class Node { public: // 指している Node 実体が使用可能の場合は, 1 それ以外は 0 を返す int hasNode (); // 最適パス上にある形態素の場合は, 1 それ以外は 0 を返す int isBest(); // 1つ前の Node を返す Node prev (); // 1つ先の Node を返す Node next (); // この Node に接続する, 左側の Path の 1つを返す Path getPath (); // 文字列表現として取得する. (default の mecab の出力と同一) char* toString (); // 形態素の表層文字列を取得する char* getSurface (); // 形態素に付与される素性 (品詞,活用,読みなど) を CSV 形式で取得する char* getFeature (); // 形態素の表層の文字列の長さを取得する unsigned int getLength (); // 形態素の開始位置を取得する. unsigned int getBegin (); // 形態素の終了位置を取得する. unsigned int getEnd (); // 形態素に付与された一意の ID を取得する unsigned int getID (); // 形態素の前々件の状態番号を取得する unsigned int getRcAttr2 (); // 形態素の前件の状態番号を取得する unsigned int getRcAttr1 (); // 形態素の後件の状態番号を取得する unsigned int getLcAttr (); // 品詞 ID を 取得する unsigned int getPOSID (); // この形態素までのコストを取得する unsigned int getTotalCost (); // 前形態素との連接コストを取得する unsigned int getConnectionCost (); // 単語生起コストを取得する unsigned int getWordCost (); // 単語生起コスト + 連接コストの和 (形態素コスト) を取得する unsigned int getNodeCost (); };
perl/test.pl, ruby/test.rb, python/test.py, java/test.java にそれぞれの言語のサンプルがありますので, 参考にしてください.
$Id: bindings.html,v 1.9 2004/08/06 18:05:13 taku-ku Exp $;
taku-ku@is.aist-nara.ac.jp