Hatena::Grouprekken

murawaki の雑記

2009-07-24

Segmentation of Arabic Text

アラビア語形態素解析する話は以前からあったけど、真面目に読んだことはなかった。最近の研究に、言語非依存な unsupervised word segmentation の手法の実験にアラビア語を使ったものがあって興味がわいてきた。*1でも、言語非依存な手法の論文は、言語非依存な話しかしないので、実際のところ何が起きているのか分からない。そこでちょっとアラビア語について少し調べてみた。以下は付け焼刃の知識。

そもそもアラビア語の segmentation と言われて引っ掛かるのは、アラビア語が二つの特徴を持っているから。

  • 他のセム系言語と同様、貫通接辞 (transfix) を使って屈折変化や派生変化を起こす。
  • 通常のアラビア文字は単母音を表記しない。

unsupervised word segmentation では、通常、文字列的に一致するものを同一の形態素と考え、異形態をモデル化しない。*2アラビア語の場合、貫通接辞のおかげで異形態だらけではないか。そうすると、形態素が順次結合して語を形成するという segmentation の前提が成り立たない疑いが出てくる。

母音を表記しないという特徴は異形態の問題を緩和しそうだと予想される。しかし、どの程度になるのか分からない。少し言語学の文献をあさってみたが情報がない。言語学の人は文字をあまり取り上げないし、人間ならできる曖昧性の解消を記述的にやるのはそもそも難しいだろう。

とりあえず論文で refer されていたルールベースの解析器を試す。Tim Buckwalter's Arabic Morphological Analyzer。Jon Dehdari という人のサイトから取ってくる。Perl の AraMorph.pl というのが本体で、いくつかの辞書を読み込んで動く。しかし、入力ファイルの encoding が cp1256 でないといけないという謎仕様。代わりに、これを Java に移植した AraMorph を使うことにする。こちらはインターフェースがまともで、UTF-8 を受け付ける。

実際に動かしてみるために、Wikipedia の例を使う。語根 K-T-B は、「書く」に関連する意味を表す。kataba が動詞の三人称男性単数完了で「彼は書いた」という意味になる。ローマ字表記では勝手に母音を補っているけど、アラビア文字では KTB としか書いていない。

反対に、 KTB という表記に対する解釈はどうか。AraMorph によるとさらに2つの候補があるらしい。一つは kutiba 「それは書かれた」で、やはり三人称男性単数完了。もう一つは kutub で、kitAb 「本」の複数形。これは大変だ。segmentation は、AraMorph の解釈では、それぞれ katab + a, kutib + a, kutub。しかし、KTB としか書いていないので、表層的には分割しようがない。unsupervised segmentation では a は存在しないという扱いになるのだろう。

同じ語根 K-T-B でも、YKTB に対しては、AraMorph は 4 個の候補を返す。ya-kotub, yu-kotab, yu-kotib, yu-kotab。ya-/yu- は三人称男性単数未完了の接頭辞。kotub が「書く」、kotab が「書かれる」、kotib が「書きとる」、「kotab」が「書きとられる」の未完了形。表層的な分割は Y-KTB で良いのだろうが、同じ語根でも別の語幹を一緒くたに扱うことになる。

同じ動詞でも表層形が変化する場合はないのか。ネットに転がっている動詞の活用表を見たところ、少なくとも KTB に関しては間に長母音が挟まることはなく、(接頭辞) + 不定の語幹 + (接尾辞) という枠組みで解釈できるようだ。

派生変化では kitAb (KTAB) や kAtib (KATB) のように名詞化すると語根内部に長母音が挿入される場合がある。これらは KTB とは別の形態素扱いされることになる。こうした語幹が、十分な数の token (または type) で出現するなら、この扱いはまあ妥当だろう。未知の語幹が出てきたら、語根を認識したくなるかもしれないが。

文字に関しては、他にもハムザとかシャッダとかいろいろ面倒な話があるけど、ちゃんと分かってはいない。

ここまで調べてきた感じでは、アラビア語の unsupervised segmentation は、見当違いな処理をやっているわけではなさそうだ。もちろん、分割だけができたところで何が嬉しいかと言われれば疑問ではある。

Morpho Challenge 2009アラビア語のデータセットには、フルに記号を付けたコーランが追加されている。このデータに普通の segmentation の手法を適用したら崩壊しそうだ。

追記: 後で見つけた A Comprehensive NLP System for Modern Standard Arabic and Modern Hebrew という論文NLP 的なアラビア語処理の問題を概観していて参考になる。この論文のおかげで SEMTIC という workshop を知った。

*1ヘブライ語も扱われていたりするが、今回は扱わない。アラビア文字は少し読めるようになったが、ヘブライ文字はまったく読めない。未だに全部同じ文字に見える。

*2:allomorph を扱う研究がないわけではない。

2009-07-12

XBL と event model ではまる

indicime が動かないならと、日曜 JavaScripter として自分で書いてみる。するとイベントがらみではまったのでメモ。一応は解決。

やりたいことは indicime と同じ。add-on が window.addEventListener で keypress イベントを登録。捕捉した keypress イベントをもとに、文字を変換した keypress イベントを発生させ、元のイベントはキャンセルする。

コードにすると以下の通り。

// mapped は変換後の文字
var event2 = document.createEvent("KeyboardEvent");
event2.created = true;
event2.initKeyEvent("keypress", true, true, event.view, 
		     false, false, false, false, 
		     0, mapped.charCodeAt(0));
event.target.dispatchEvent(event2);
event.preventDefault();
event.stopPropagation();

結論を先に言うと、event.target を event.originalTarget に変更すると解決した。

症状。ブラウザ上の browser 要素内 (つまり、普通のページの中) でのキー入力は正しく変換できるが、browser 要素外の UI (URL 欄など) では何も起きない。

仕方がないので、両者の振る舞いの違いを JavaScript Debugger (Venkman) で調べてみた。しかし、肝心な部分が native code だから JavaScript から見えない。そもそもイベント処理はあちこちに飛んで分かりにくいのに。それでも 2 点、違いを見つけた。

一つは dispatchEvent の戻り値が違う。browser 要素内のイベントでは false が返る。つまり誰かが preventDefault を呼んでいる。一方 UI 上のイベントでは true が返る。多分、false が返った時は、keypress イベントを受けて、実際の動作に変換している部分があって、そこがイベントをキャンセルしているのだろう。内部実装の都合っぽい気がするが、DOM の Event model 的にこれで良いのだろうか。

もう一つは call stack の変化が違う。browser 内でイベントを発生させると、XPCNativeWrapper function wrapper が積まれて、その上に dispatchEvent が積まれる。さらにその上で listener の handleEvent が次々と呼ばれる。一方、UI 上でイベントを発生させた場合、dispatchEvent を実行した関数の上に、autocomplete.xml の onxblkeypress や tabbrowser.xml の handleEvent が直接積まれる。なぜそうなるのか分からない。ちなみにこれらの XBL の event handler は、control などの特殊なキー入力のみに反応する仕様になっている。

先に書いたように、event.target を event.originalTarget に代えたら両者ともうまくいくようになった。この二つの何が違うのか。例えば URL 欄でイベントを発生させた時、event.originalTarget は html:input だが、event.target は textbox。その親子関係は textbox - xul:hbox - xul:hbox - html:input となっている。event.target.dispatchEvent だと html:input にイベントが届かない。

しかし target と originalTarget の違いをちゃんと理解しているわけではない。MDCComparison of Event Targets というページがあるけど説明がない。

nsIDOMNSEvent.idl にも The original target of the event, before any retargetings. とあるだけ。Bug 51263 のおけげで originalTarget が相当昔 (2000年) からあったことが分かったが、作られた経緯は分からないまま。

Mozilla で properties の key に特殊な文字を使うとはまる

他にも Mozilla でまったのでメモ。今度は stringbundle の properties で key に特殊な文字を使ったら駄目だった。

やりたいことは、キーボードの入力を適当な言語の文字に変換すること。この変換テーブルをどうやって記述するか。indicime と同じ様に properties ファイルに記述してみることにした。properties ファイルは、XBL の stringbundle 要素に記述することによってロードする。

properties ファイルには、key=value という形式の property が並んでいる。ここで、

!=1
#=3

と記述すると認識されない。! と # はコメント開始という仕様だから、これらの行はまるごと無視される。

しかし、エスケープして

\!=1
\#=3

と記述した上で

stringbundle.getString("!");

としても値が得られない。dump を見ると Failed to get string という stringbundle.xml のエラーが出力されている。

結論から言うと、key は unescape されないので、

stringbundle.getString("\\!");

として \! という key を見れば 1 が入っている。

properties ファイルを parse するのは nsPersistentProperties.cpp だが、ここでは value しか unescape されていない。この挙動は Java の仕様 と異なっている。バグと言っても良いのではないか。調べた限りではバグ報告されていないが。

そもそも properties に変換表をつっこむのが間違いなのだろう。

2009-07-10

gfarm memo

gfarm に関するメモ。たまに更新している。

replica の作成

gfarm は replica を自動的に作らない。デフォルトでは 1 ファイル 1 ホスト。replica の作成はコマンド gfrep で明示的に行う。

host_list_file に使うノードの一覧を記述して、

gfrep -H host_list_file gfarm_filepath

とすると、適当なホストに replica が作成される。gfarm_filepath がディレクトリなら再帰的にコピーしてくれる。ただし 1 並列で実行されるため、巨大なディレクトリを指定すると、完了までに時間がかかりまくる。

workaround として、サブディレクトリに分割し、

gfrep -H host_list_file gfarm_subdir1

として gfrep 自体を並列に実行。

gfrep には並列数を指定するオプション -j があるが、man によれば「OpenMP C コンパイラコンパイルされたときに限り有効」とのこと。

replica の消去は

gfrm -h hostname gfarm_path

ノード選択のアルゴリズムは把握していない。漫然と gfrep をやっていると、特定ノードに replica が集中し、ディスクがいっぱいになって困る。

2010/09/16 追記: 最近の資料 によると、gfarm2fs のオプションで、close 時に自動複製が作成できるらしい。試したことないけど。

2011/11/03 追記: -o ncopy=3 を試してみたら、すぐ近くのホストにばかり replica を作る。私が望んだ挙動ではない。

replica の操作権限

他人の (自分に書き込み権限がない) ファイルの replica を作れる。また、作った replica を消せる。いいのか?

replica と障害

replica を持つノードがすべて死んでいる場合、当然ファイルの読み出しに失敗する。gfarm2fs を使ってアクセスする場合、open は成功するが read の段階ではじめて失敗する。また、stat も成功する。

replica がなくなったことにいつ master が気付くのか分からない。gfwhere はレプリカがないファイルについてエラーを吐くが、gfwhere 自体は replica の存在をチェックしていない様子。実際に file read しようとして失敗した時点で、replica の欠如を master が知るみたい。

gfreg/gfexport

gfarm 上のファイルの操作は、gfarm2fs でファイルシステムにマウントした上で、通常のファイル操作コマンドを使えばよいが、基本的なファイル操作コマンドに対応する gfarm 用コマンドも用意されている。gfstat, gfls, gfchmod など。ここで指定される path はいずれも gfarm 上の path。つまり、gfarmファイルシステム内で閉じている。

では、そもそもローカルファイルを gfarm 上に持っていくにはどうすればいいのかと思ったら、gfreg というコマンドが用意されていた。逆に、gfarm 上のファイルを読み出す場合には gfexport。

わざわざそんなコマンドを使う意味がどこにあるのかと言われそうだが、少なくとも一つ使う場面がある。障害が発生したとき。マウントしてしまうと、read などのシステムコールを読んだ後、その先で何が起きているか strace では分からないけど、直に gfarm のライブラリを呼び出すとどこで躓くのかわかる。

gfreg は明らかに容量が足りないノードに突撃して、やっぱりディスク容量が足りませんでしたとエラーを吐くことがある。ディスクの残りを 0 にした時点で打ち切った、不完全なファイルが gfarm に登録される。

gfmkdir -p?

mkdir の gfarm 版に -p オプションがない。ディレクトリが既に存在すると怒られる。いつのまにか追加されていた。

gfcp?

cp に対応する gfarm のコマンドはない。と思ったら、ソースの中には gfcp.c があった。けど、デフォルトではインストールされない。

普通は gfarm2fs したあと cp すればいいので支障はない。

-h

普段はオプション -h (--human-readable) を愛用しているが、gfarm 用コマンドには実装されていない。 ls してファイルサイズが 759933251 と言われても桁が分からない。gfdf などには実装されていた。

読み出しエラー

注意: ちゃんと調査していない。

マウントした状態で open/read すると、結構な頻度でファイル読み出しに失敗する。最初から失敗しているのか、途中で失敗するのかは確認していない。再実行すると成功したりする。ネットワーク接続のエラーが発生した時に、retry とかをやらずにそのままファイルシステムレベルのエラーにしている感じ。ソースを確認していないけど。

authentication error

よくはまる罠。authentication error というエラーを吐いて失敗。原因はいろいろあるけど、よくあるのが鍵 .gfarm_shared_key の期限切れ。

ユーザ間のデータ移動

gfarm 上でユーザ間のデータ移動がうまくいかない。permission で group に対して +w しても permission denied と言われる。