構文解析メモ
はじめに
「構文解析」まわりについてちょっと調べたのでメモ。
ただ、資料が少なくて内容が怪しい部分が多い。
構文解析とは
- 入力された文に対して、文を構成しているそれらの構文構造を同定すること
- 文法規則が定められたプログラミング言語、正規表現、HTML/XMLなどを解析するために使われている
- 自然言語では、複数の単語で1つの句を形成したりある単語が別の単語を修飾するなどの現象があるので、文法を考え構文解析することで自然文を解析する
文の構造
- 構造とは、要素同士がなんらかの相互作用によって結びついている集合と見れる
- 「要素」と「それらがどのように関係しあっているか」が重要
直接構成要素分析(immediate constituent analysis)
- 自然文に対して、直接的に、どのような構成要素からできているかを分析する
- 「文は、後置詞句と動詞から成る」
- 「後置詞句は、名詞と助詞から成る」
- などの知見を得る
文法
- 文法は、要素とその要素同士の関係性を記述したもの
- 「句構造文法」は4つのクラスに分けられる
- チョムスキー階層
- Typeの数字が大きいほど記述に関する制限が強い
- Typeの数字が小さい文法とはより数字が大きい文法を包含している
- 「生成規則」は、形式文法の構成要素では、「ある規則の左側にある記号を右側の記号列で置き換える」というのを表す
【Type0】制限のない文法・句構造文法
【Type1】文脈依存文法
【Type2】文脈自由文法
【Type3】正規文法
自然文での生成規則の例
- 「文は、後置詞句と動詞句から成る」「後置詞句は、名詞と助詞から成る」「動詞句は、副詞と動詞から成る」という規則が合った場合を考える
- 「太郎がぺろりと食べた」という文の場合
- 文 -> 後置詞句 動詞句
- 後置詞句 -> 名詞 助詞
- 名詞 -> 太郎
- 助詞 -> が
- 動詞句 -> 副詞 動詞
- 副詞 -> ぺろりと
- 動詞 -> 食べた
- 非終端記号
- 「文」「後置詞句」「動詞句」「名詞」「助詞」「副詞」「動詞」
- 終端記号
- 「太郎」「が」「ぺろりと」「たべた」
- 開始記号
- 「文」
導出
- 生成規則の右側で、複数の非終端記号が現れた場合、以下の処理の仕方がある
- 一番左のものから置き換えを実行するのが「最左導出」
- 一番右のものから置き換えを実行するのが「最右導出」
- 例えば、「文 -> 後置詞句 動詞句」で、
- 「後置詞句」の方から置き換えるのが「最左導出」
- 「動詞句」の方から置き換えるのが「最右導出」
文法の記法
バッカス・ナウア記法(Backus-Naur Form)
- 文脈自由文法を記述するための記法の一つ
- いくつか拡張されたものが存在する
- 「<記号> ::= <記号列>」「<記号> ::= <記号列> | <記号列>」のような形式の導出規則の集合
- 左の<記号>に出てくる記号=非終端記号
- 左の<記号>に一度も出てこない記号=終端記号
- 縦棒「|」は、どちらかの"選択"を表す記号
- BNFによるBNFの文法表現(from wikipedia)
<syntax> ::= <rule> | <rule> <syntax> <rule> ::= <opt-whitespace> "<" <rule-name> ">" <opt-whitespace> "::=" <opt-whitespace> <expression> <line-end> <opt-whitespace> ::= " " <opt-whitespace> | "" ……… "" は空文字列を表す <expression> ::= <list> | <list> "|" <expression> <line-end> ::= <opt-whitespace> <eol> | <line-end> <line-end> <list> ::= <term> | <term> <opt-whitespace> <list> <term> ::= <literal> | "<" <rule-name> ">" <literal> ::= '"' <text> '"' | "'" <text> "'"
解析アルゴリズム
- 文法(生成規則)が与えられるとして、その文法がどのように構成されているかを求めるアルゴリズム
- (字句解析や形態素解析された)文に対して構文木を出力する
- (正規文法を包含する)文脈自由文法の解析アルゴリズムは古くからよく研究されている
- 以下、文脈自由文法に関する解析アルゴリズム
チャート法
- 完成した部分木と解析途中の部分木をチャートと呼ばれるデータ構造で扱う
- 頂点が入力文中の位置、弧が解析途中の状態を表す有効グラフ
- 弧には、ラベル(文法規則の左辺の記号)と空所リスト(未解析の記号リスト)を持つ
- 空所を持つ弧(空所リストが空でない)は、解析途中の部分木を表す(活性弧)
- 空所を持たない弧(空所リストが空)は、完成した部分木を表す(不活性弧)
CYK(Cocke-Younger-Kasami)アルゴリズム
- 以下の形(チョムスキー標準形)で表される生成規則を持つ文脈自由文法の構文解析アルゴリズム
- A -> BC (A,B,Cは非終端記号)
- A -> a (aは終端記号)
- CYK法ではチョムスキー標準形のものしか扱えない
- チャート法の一種
- アルゴリズム
- 0.テーブルTを初期化
- 1.「A->a」からTの対角要素を求める
- for i in N
- T(i,i) = {A | A->w_i}
- 2.「A->BC」から対角要素以外の部分を求める
- for k=1 to N-1
- for i=1 to N-k
- T(i,i+k) = ∪^{k}_{j=1} {A | A->BC, B∈T(i,i+j-1), C∈T(i+j,i+k)}
- 3.T(1,N)が開始記号集合に含まれる非終端記号であれば、導出可能
アーリー法
- 横型トップダウンチャート法
- アルゴリズム
- 1.入力字句列w1..wnに対して、辞書引きを行い、wkの文法カテゴリがTの場合、[T wk]という不活性弧を位置k-1からkの間に張る
- 2.最後に、位置0から0までという(特殊な)間に[S ?]という活性弧を張る
- 3.位置jとk(j<=k)の間のすべての弧に対し、再帰的に重複がない限り繰り返す
- 3-a.活性弧で最初の空所が[B ?]という形式の場合、規則B->XY..があれば、位置kとkの間に[B [X ?][Y ?]..]という項の活性弧を張る
- 規則を適用して新しい活性弧を作る
- 3-b.不活性弧で[X ..]という形式の場合、位置iからjの間の活性弧の項Aで、最も左の空所を含む構造が[X ?]であれば、A中の[X ?]を[X ..]で置き換えた項をA'として、位置iからkの間にこうA'の弧を張る
- 活性この中の最も左の食う書に結合して新しい弧を作る
- 4.位置0からnの間に[S ..]という項の不活性弧が少なくとも1つ張られていれば、解析成功
- 曖昧性がある場合に1つ以上張られる
LL法
- LL構文器は以下で構成される
- スタック
- 構文解析表:スタック最上段の記号と入力文字列の字句から適用すべき文法規則を決定するための表
LR法
- LR構文解析器は以下で構成される
- アルゴリズム
- 1.入力ポインタを入力文の先頭にセット、スタックに初期状態を表す0をpush
- 2.スタック最上段の状態sと入力ポインタの指す字句aにに対して、action[s,a]を調べる
- 移動
- action[s,a]が「sn」形式だったら、現在の字句に対応する非終端記号aと状態nをスタックにpushし、入力ポインタを一つ先へ移動
- 還元
- action[s,a]が「rn」形式だったら、n番目の文法規則を適用する
- それが「A->α」とすると、
- αの構成要素数だけスタックからpop
- 次に、スタック最上段の状態s'と文法規則Aからgoto[s',A]を調べ、Aとgoto[s',A]をスタックにpush
- 入力ポインタは進めない
- 受理
- action[s,a]が「acc」だったら、受理(導出)されることを示す
- 誤り
- action[s,a]がそれ以外の場合、受理(導出)されないことを示す
- 3.ステップ2に戻り繰り返す
再帰下降法
- 非終端記号に対応したプロシージャ(関数)を用意し、すべての非終端記号を処理するまで再帰的に呼び出す
- 以下は、四則演算の再帰下降パーサ例
//再帰下降構文解析による四則演算(解析しながら計算も行う) //参考: //http://www.prefield.com/algorithm/string/parser.html //http://www.slideshare.net/phi16/ss-9401439 /* expr ::= term | term '+' term | term '-' term //式 term ::= fact | fact '*' fact | fact '/' fact //項 fact ::= '(' expr ')' | 数字列 //因子 */ #include <iostream> #include <string> class shisoku { std::string text; std::string::iterator itr; public: shisoku(const std::string& str){ text = str; itr = text.begin(); } int expr(){ int ret = term(); while(*itr == '+' || *itr == '-'){ if(*itr == '+'){ itr++; ret += term(); } else if(*itr == '-'){ itr++; ret -= term(); } } return ret; } int term(){ int ret = fact(); while(*itr == '*' || *itr == '/'){ if(*itr == '*'){ itr++; ret *= fact(); } else if(*itr == '/'){ itr++; ret /= fact(); } } return ret; } int fact(){ if(*itr == '('){ itr++; int ret = expr(); itr++; return ret; } else{ int ret = 0; while('0' <= *itr && *itr <= '9'){ ret *= 10; ret += *itr - '0'; itr++; } return ret; } } }; int main(){ std::string str; std::cin >> str; shisoku ssk(str); std::cout << ssk.expr() << std::endl; return 0; }
その他の解析モデル
確率文脈自由文法
- 文脈自由文法において、生成規則に書き換える条件付き確率を与えたもの
- 2つ以上の解析が可能な(構文的曖昧性がある)場合、どちらの解釈を優先するかを定量的に表せる
自然言語の文法
参考文献
- 田中穂積「自然言語処理-基礎と応用-」
- 奥村学「自然言語処理の基礎」
- http://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AB%E3%82%B9%E3%83%BB%E3%83%8A%E3%82%A6%E3%82%A2%E8%A8%98%E6%B3%95
- http://ja.wikipedia.org/wiki/%E6%A7%8B%E6%96%87%E8%A7%A3%E6%9E%90%E5%99%A8
- http://ja.wikipedia.org/wiki/%E5%BD%A2%E5%BC%8F%E8%A8%80%E8%AA%9E%E3%81%AE%E9%9A%8E%E5%B1%A4