[FrontPage ← 先頭のページに戻る]
== デカルト言語による日本語の形態素解析 : 自然言語の読解
デカルト言語で日本語の形態素解析を実行するプログラムを作ります。
日本語の文章の解析を行うことによって、デカルト言語のプログラムで自然言語の処理を行うプリプロセッサとして役立てることを目指しましょう。
(本当の最終的な目標(野望)は、日本語の読み書き・会話を可能にすることですが、それはまだまだ今はずっと未来の話です。)
形態素解析とは、入力された文章を構成する要素の語句に分解して、要素の種類を判別する処理です。
本来の一般的な形態素解析は、入力された文章を分解し、品詞に分け、その品詞の文法的な種類を決定します。しかし、ここではそこまで文法的に厳密な解析処理は行わず、文章を要素に分解して、その要素の役割を決めるものとします。
例えば、次のような文章が入力されたとしましょう。
[[BR]]
{{{
ある商店はポイントを作成するごとに利用額の数%を自動的に寄付するとのことで、直接的な行動だけでなく普段の生活でも貢献できるようになっているようだ。
}}}
この文章を、以下のように解析します。
[[BR]]
{{{
動作主 ある商店 は
対象 ポイント を
目標 作成するごと に
所属 利用額 の
対象 数% を
目標 自動的 に
説明 寄付するとのこと で
状態 直接的 な
追加 行動 だけでなく
所属 普段 の
追加 生活 でも
条件 貢献 できるように
述語 なっている ようだ
}}}
=== 1. シンプルな実装
日本語の文章のパターンは大変複雑で、コンピュータが処理するのは苦手です。
そのため、最初から複雑なパターンに対応するのは大変なので、まずはシンプルな構成で実装します。
最初は解釈できる文章に制限があるのですが、拡張していくことにより、複雑なパターンの文章も処理できることが可能になるようなヒナ型を作成します。
このヒナ型を基に、より複雑な文章に対応できるようにしたプログラムは後の項で説明します。
==== 1.1 方針
以下のような方針で作成します。
1) 「を、は、の、に、が」のような助詞による構文解析(格文法解析)を行う。
2) できるだけ語句の辞書を使わない。例えば名詞のような多数の言葉を登録しなければならないような辞書は、無くても処理できるようにする。
3) 厳密な品詞ではなく、意味的な語句に分解する。
==== 1.2 ソース
さて、シンプルな実装の簡略化したソースを示します。まだ、このレベルのソースでは、汎用的な形態素解析はできないのですが、
定められた構文にしたがった文書は十分に解析できます。
[[BR]]
{{{
<格 #k #x>
<* #x>
(
"を" <is #k "対象">
| "は" <is #k "動作主">
| "の" <is #k "所属">
| "に" <is #k "目標">
| "が" <is #k "動作主">
)
<GETTOKEN #g>
(
","
| "、"
| ","
| <SPACE>
| (
::sys <rightstr #rc #x 1>
<NEXTCHAR #lc>
(
( ::sys <syntax #rc <NONRANGE _ "あ" "ん">>
| ::sys <syntax #lc <NONRANGE _ "あ" "ん">>
)
)
)
)
<print #k #x #g>
;
<述語 #y #t>
<* #y>
(
"ます"
| "です"
| "だ"
| "した"
| <SPACE>
| <CR>
)
<GETTOKEN #t>
(
"。"
| <SPACE>
| <CR>
| <EOF>
) <print "述語" #y #t><print>
;
<文>
{
<格 #k #x>
}
<述語 #y #t>
;
<読み込み>
{
::sys <getline #x
::sys <strdelcntl #y #x>
::sys <split #l #y "。">
<foreach (#i #l)
<print ">" #i "。"><print>
::sys <syntax #i {<文>}>
>>
};
? <読み込み>;
}}}
これを、お使いのシステムに応じたコードで保存してください。WindowsならばSJIS、LinuxならばUTF-8がよいでしょう。
このソースによって、助詞は、「を、は、の、に、が」が使えます。
語尾の述語には、「ます」、「です」、「だ」、「した」が使えます。
例えば上記のソースをjp1.carとして保存して、以下のように実行します。
[[BR]]
{{{
$ descartes jp1.car
}}}
文章の入力が可能になるので、以下のように入力すると日本語の文章として形態素解析されます。
最初は簡単な文章から始めましょう。
[[BR]]
{{{
これはリンゴです。
> これはリンゴです 。
動作主 これ は
述語 リンゴ です
これは、トムのペンです。
> これは、トムのペンです 。
動作主 これ は
所属 トム の
述語 ペン です
あれは私のリンゴです。
> あれは私のリンゴです 。
動作主 あれ は
所属 私 の
述語 リンゴ です
それはミカンです。
> それはミカンです 。
動作主 それ は
述語 ミカン です
私はリンゴを投げます。
> 私はリンゴを投げます 。
動作主 私 は
対象 リンゴ を
述語 投げ ます
あなたにリンゴをあげます。
> あなたにリンゴをあげます 。
目標 あなた に
対象 リンゴ を
述語 あげ ます
これは、りんごです。
> これは、りんごです 。
動作主 これ は
述語 りんご です
地平線のかなたの夕日は景色を赤く染めます。
> 地平線のかなたの夕日は景色を赤く染めます 。
所属 地平線 の
所属 かなた の
動作主 夕日 は
対象 景色 を
述語 赤く染め ます
大きなガチョウがガァガァ鳴きます。
> 大きなガチョウがガァガァ鳴きます 。
動作主 大きなガチョウ が
述語 ガァガァ鳴き ます
}}}
実にシンプルな文章ですが、より多くの助詞や述語に対応していくことにより、後に説明するプログラムでは大抵の文章が解析できるような
プログラムになるように発展させます。
プログラムの詳細については、次の項で説明しましょう。
==== 1.3 文の読み込み
最初に文を読み込む部分について説明します。
[[BR]]
{{{
<読み込み>
{
::sys <getline #x
::sys <strdelcntl #y #x>
::sys <split #l #y "。">
<foreach (#i #l)
<print ">" #i "。"><print>
::sys <syntax #i {<文>}>
>>
};
? <読み込み>;
}}}
最初に「? <読み込み>;」が実行されます。
プログラム<読み込み>では、まず{}で括られた処理が実行されます。
{...}で括られた処理は、0回以上の繰り返しを行うことを意味します。
文が入力される度に括られた内部の処理が実行されます。
内部の処理では、まずgetline述語が呼ばれます。これは、入力から1行の入力を行うsysモジュールの組み込み関数です。
次にsplit述語が呼ばれます。これは、入力された行を、"。"で区切ったリストを作ります。
splitで作られたリストは、foreach述語によりリストの要素の文ひとつずつが、引数の述語で処理されていきます。
foreach述語の内部では、print述語でまず1文が表示され、最後にsyntax述語を使って、"文"述語により入力された文を構文解析します。
==== 1.4 文
文を、格文法の格と述語で表すこととします。
[[BR]]
{{{
<文>
{
<格 #k #x>
}
<述語 #y #t>
;
}}}
ここの文章では、格は、繰り返し複数回現れてもかまいません。
述語は、最後に一回だけ現れます。
=== 1.5 述語
述語は、「~ ます。」、「~ です。」、「~ だ。」、「~ した。」の形式を認識します。
それ以外の形式で最後に文の区切り「。」があった場合には、全体を述語とします。
[[BR]]
{{{
<述語 #y #t>
<* #y>
(
"ます"
| "です"
| "だ"
| "した"
| <SPACE>
| <CR>
)
<GETTOKEN #t>
(
"。"
| <SPACE>
| <CR>
| <EOF>
) <print "述語" #y #t><print>
;
}}}
=== 1.6 格
格は、「~を」、「~は」、「~の」、「~に」、「~が」のような助詞で区切られた文の要素を認識します。
[[BR]]
{{{
<格 #k #x>
<* #x>
(
"を" <is #k "対象">
| "は" <is #k "動作主">
| "の" <is #k "所属">
| "に" <is #k "目標">
| "が" <is #k "動作主">
)
<GETTOKEN #g>
(
","
| "、"
| ","
| <SPACE>
| (
::sys <rightstr #rc #x 1>
<NEXTCHAR #lc>
(
( ::sys <syntax #rc <NONRANGE _ "あ" "ん">>
| ::sys <syntax #lc <NONRANGE _ "あ" "ん">>
)
)
)
)
<print #k #x #g>
;
}}}
助詞の後が「、」、「,」、「,」や空白で区切られていれば、それを格として確定します。
助詞のそれぞれには、対応する格の種類、対象、動作主、所属、目標が決定されます。
一つの文章の中には複数の格が、繰り返し出てきてもかまいません。格の種類、単語、助詞の並びを繰り返し表示します。
し
しかし、区切りがなく、前後にひらがな文字が続く場合には、格とは認識しません。
[[BR]]
{{{
| (
::sys <rightstr #rc #x 1>
<NEXTCHAR #lc>
(
( ::sys <syntax #rc <NONRANGE _ "あ" "ん">>
| ::sys <syntax #lc <NONRANGE _ "あ" "ん">>
)
)
}}}
上記の部分がその処理をしています。助詞の前の文字を#rcに設定し、後の文字を#lcに設定し、
#rc, #lcが「あ」から「ん」の範囲に入っていないことを判定しています。
「まつざかにくが美味しいです。」
これは、以下のように解釈されます。
[[BR]]
{{{
動作主 まつざかにく が
述語 美味しい です
}}}
助詞と同じ「に」が含まれていますが、前後がひらがななので、助詞として認識されません。
これは、ひらがなの続く単語に対応するための処理です。
単語を辞書として持たせなくても、助詞と名詞を区別する確率を高めるための処理です。
しかし、完全とはいえまぜん。ときどき認識を間違えます。
[[BR]]
{{{
松坂にくが美味しいです。
目標 松坂 に
動作主 く が
述語 美味しい です
}}}
「に」の前が漢字なので間違えてしまっています。
しかし、普通の文では、このような場合は実際には滅多にないので大丈夫です。上記の例も通常は以下のようになりますね。
[[BR]]
{{{
松坂肉が美味しいです。
動作主 松坂肉 が
述語 美味しい です
}}}
正しく認識されています。
つまり、このプログラムは、長いひらがなの単語や、小学生の文のような、ひらがなばかりの文には弱いです。
しかし、適切に漢字とかなの混じった文ならば、正しく認識されるのです。
つづく