自然言語処理(NLP)入門 ― 統語論・意味論・語用論から拡張遷移ネットワーク、フレームと意味ネットワークとの統合まで
統語論・意味論・語用論という言語理解の3つの層を整理し、正規表現・文脈自由文法・拡張遷移ネットワーク(ATN)による構文解析の形式理論を比較したうえで、意味文法によるデータベース自然言語インターフェースの考え方と、再帰下降パーサーを前回のフレームシステムおよび意味ネットワークに統合する実装をSwiftで確認します。
目次
- はじめに
- 1. 言語処理と理解という問題
- 2. 統語論・意味論・語用論 ― 意味を捉える3つの層
- 3. 構文解析の形式理論:正規表現・文脈自由文法・拡張遷移ネットワーク
- 4. 意味文法と自然言語インターフェース
- 5. 再帰下降パーサーをフレームシステムに統合する
- 6. 意味ネットワークへの自然言語インターフェース
- まとめ
- 注釈
- 参考資料
はじめに
前回の記事では、意味ネットワーク・フレーム・意味プリミティブといった知識表現の手法を確認しました。しかし、これらの構造に知識を入れる、あるいは知識から自然な文を作るには、人間が使う自然言語とコンピュータ内部の構造とを橋渡しする仕組みが必要です。それを扱うのが自然言語処理(Natural Language Processing, NLP)、とりわけ自然言語理解(Natural Language Understanding, NLU)です。
この記事では、記号主義AIの時代に整理された言語理解の基本概念(統語論・意味論・語用論)と、構文解析のための形式理論(正規表現・文脈自由文法・拡張遷移ネットワーク)を確認します。そのうえで、意味文法によるデータベースへの自然言語インターフェースという応用例を見て、最後に再帰下降パーサーを前回のフレームシステムおよび意味ネットワークに統合する実装をSwiftで確認します。
1. 言語処理と理解という問題
「言語処理」と「言語理解」は同じではありません。文字列としての入力を規則に従って操作するだけなら「処理」ですが、それが指す内容を内部表現として構築し、推論や応答に使えて初めて「理解」と呼べます。
この違いを象徴するのが、Joseph Weizenbaum の ELIZA(1966年)と Terry Winograd の SHRDLU(1968〜1972年)です(注1・注2)。
- ELIZAはキーワードに反応して定型文を組み立てるパターンマッチングの塊で、内部に意味の表現を持ちません。それでも人間らしく見えたのは、応答のテンプレートが巧妙だったためです。
- SHRDLUは「積み木の世界(blocks world)」という限定された領域について、構文解析・意味解釈・世界のモデル(今どの積み木がどこにあるか)を統合していました。「あなたが持っているものより高い積み木を見つけて、箱に入れて」のような指示を実行でき、「どの積み木を指しているか分かりません」のように、理解できない場合はそれを表明できました。
両者を分けるのは、入力を内部の構造化された表現(前回の記事でいうフレームや意味ネットワーク)に変換し、それをもとに推論できるかどうかです。この構造化のプロセスで必ず直面するのが曖昧性です。
- 語彙的曖昧性: 「bank」は「銀行」にも「土手」にもなる
- 構文的曖昧性: "I saw the man with the telescope" は「望遠鏡を持った男を見た」とも「望遠鏡で男を見た」とも解釈できる(前置詞句がどこに係るかが一意に決まらない)
- 照応的曖昧性: 「それ」「彼」が何を指すか(SHRDLUはこれを直前の会話の記憶で解決していました)
以降の節では、この曖昧性を減らしながら文を構造化するための概念と形式理論を順番に見ていきます。
2. 統語論・意味論・語用論 ― 意味を捉える3つの層
言語理解は、伝統的に3つの層に分けて考えられます。
| 層 | 扱う対象 | 失敗する例 |
|---|---|---|
| 統語論(Syntax) | 語順・品詞・文の構造規則。意味とは独立に「文法的に正しいか」を判定する | Noam Chomsky の "Colorless green ideas sleep furiously"(無色の緑の観念が猛烈に眠る)は文法的には完全に正しいが、意味的には支離滅裂(注3) |
| 意味論(Semantics) | 語や文が文字通り何を表すか(リテラルな意味の合成) | 「彼は銀行の土手に座った」は文法的に正しく単語の意味も存在するが、"bank"の語義選択を誤ると意味が崩れる |
| 語用論(Pragmatics) | 発話が行われた文脈・状況・話者の意図 | 「塩を取っていただけますか」は文字通りには相手の能力を尋ねる疑問文だが、実際には「取ってほしい」という依頼(間接発話行為)として機能する |
統語論は「形」を、意味論は「文字通りの意味」を、語用論は「実際に何をするための発話か」を扱う、と整理すると分かりやすくなります。SHRDLUが「積み木の世界」という限定領域でうまく機能したのは、この3層すべてを小さく閉じた領域の中で扱いきれたからです。逆に言えば、汎用の自然言語理解が難しいのは、語用論的な文脈(世界知識・会話の履歴・話者の意図)を無限に近い範囲で扱わなければならないためです。
3. 構文解析の形式理論:正規表現・文脈自由文法・拡張遷移ネットワーク
文の構造(統語論)をどこまで機械的に解析できるかは、**言語のパーシング(構文解析)**を形式言語理論の枠組みで捉えると見通しがよくなります。Chomsky は文法を生成能力によって4段階の階層(Chomskyの階層)に整理しました(注4)。
| 型 | 文法 | 言語 | 対応するオートマトン |
|---|---|---|---|
| Type 3 | 正規文法(Regular Grammar) | 正規言語(Regular Language) | 有限ステート・オートマトン(Finite State Automaton) |
| Type 2 | 文脈自由文法(Context-Free Grammar) | 文脈自由言語(Context-Free Language) | プッシュダウン・オートマトン(Pushdown Automaton) |
| Type 1 | 文脈依存文法(Context-Sensitive Grammar) | 文脈依存言語(Context-Sensitive Language) | 線形拘束オートマトン(Linear Bounded Automaton) |
| Type 0 | 一般文法/句構造文法(Unrestricted / Phrase Structure Grammar) | 一般言語(帰納的可算言語) | チューリング・マシン(Turing Machine) |
下の型ほど表現力が高く、上位の言語をすべて含みます(正規言語 ⊂ 文脈自由言語 ⊂ 文脈依存言語 ⊂ 一般言語)。自然言語の構文解析で実際によく使われるのは、正規表現・CFG・ATNの3つです。それぞれがこの階層のどこに位置するかを見ていきます。
正規表現・正規文法・有限ステート・オートマトン(Type 3)
正規表現は正規文法の実用的な記法で、有限ステート・オートマトンと等価な表現力しか持ちません。固定的なパターンの照合には向きますが、入れ子構造(再帰的な埋め込み)を扱えないという原理的な限界があります。
let pattern = #"^the (\w+) (chased|hit) the (\w+)$"#
let regex = try! NSRegularExpression(pattern: pattern)
func matches(_ text: String) -> Bool {
let range = NSRange(text.startIndex..., in: text)
return regex.firstMatch(in: text, range: range) != nil
}
matches("the cat chased the dog") // true
matches("the cat the dog chased escaped") // false ―― 埋め込み文には対応できない
"the cat the dog chased escaped"(犬が追いかけた猫は逃げた、に相当する中央埋め込み文)のように、名詞句の中に別の文が埋め込まれる構造は、有限ステート・オートマトンでは原理的に扱えません。これが正規言語の表現力の限界であり、再帰を扱うには文脈自由文法以上の形式が必要になります。
文脈自由文法・プッシュダウン・オートマトン・再帰遷移ネットワーク(Type 2)
CFGは書き換え規則(生成規則)の集合として文法を定義し、規則の右辺に自分自身の非終端記号を含められるため、再帰的な構造を宣言的に表現できます。CFGが生成する文脈自由言語は、スタックを1本持つプッシュダウン・オートマトンが受理できる言語のクラスとちょうど一致します。
S -> NP VP
NP -> Det N | Det N RelClause
VP -> V NP
RelClause -> Rel NP V ; 例: "that the dog chased"
NP が RelClause を含み、RelClause が再び NP を含められるため、先ほどの中央埋め込み文のような構造も自然に生成・解析できます。
CFGと同じ表現力(文脈自由言語)を、状態と矢印から成る遷移図として書き直したものが**再帰遷移ネットワーク(Recursive Transition Network, RTN)**です(注5)。RTNは有限ステート・オートマトンの矢印を拡張し、矢印から別のネットワークを呼び出すことを許します。この「呼び出し」がプッシュダウン・オートマトンのスタックへの積み下ろしに対応しており、CFGの再帰的な生成規則をネットワーク図として手続き的に表現し直したものだと理解できます。CFGが「何が正しい文か」を宣言的に定義するのに対し、RTNは「どうやって解析を進めるか」という手順を状態遷移として示す点が異なります。
拡張遷移ネットワーク(Augmented Transition Network, ATN)
ATN は William Woods が1970年に提案した形式で(注6)、RTNをさらに次の点で**拡張(augment)**しています。
- 矢印に任意の条件(テスト)と動作(アクション)を付与できる
- レジスタと呼ばれる作業領域に値を書き込みながら解析を進められる
これにより、主語と動詞の数の一致チェック、受動態から能動態への変換、意味構造の組み立てを解析と同時に、手続き的に行えます。文脈自由文法や文脈依存文法のように書き換え規則を並べるのではなく、矢印に任意の処理を埋め込めるため、ATNの表現力は理論上チューリング・マシンと同等(Type 0相当)になり得ます。強力な反面、文法そのものが一般的なプログラムに近づき、解析の見通し(停止性や曖昧性の把握)が悪くなるというトレードオフがあります。
この設計により、構文解析の結果として最初から意味的な構造(フレームに近いもの)を組み立てることができ、実際に自然言語データベースインターフェースの実装に使われました(4節で扱います)。
ここまでの形式をChomskyの階層に沿って整理すると、次のようになります。
| 形式 | 対応する言語クラス | 特徴 |
|---|---|---|
| 正規表現(正規文法) | 正規言語(Type 3) | 高速だが再帰的な入れ子構造を扱えない |
| CFG | 文脈自由言語(Type 2) | 宣言的で分かりやすいが、意味構築や文脈依存処理とは別立てになる |
| RTN | 文脈自由言語(Type 2) | CFGと同じ表現力を、呼び出し可能な遷移ネットワークとして手続き的に表現する |
| ATN | 理論上はType 0相当 | レジスタとアクションで文脈依存的な処理と意味構築を解析と同時に行える。文法自体が複雑になる |
4. 意味文法と自然言語インターフェース
CFGやATNは、まず統語的な構造を解析し、そのあとで意味を割り当てる、という2段階の設計を前提にしがちです。しかし、データベース管理システム(Database Management System, DBMS)への問い合わせのような特定の狭い領域に限って言えば、統語的に正しくてもその領域では意味を持たない解析結果を大量に生み出してしまい、非効率です。
この問題への解として考えられたのが**意味文法(Semantic Grammar)**です。NP(名詞句)やVP(動詞句)のような汎用的な統語カテゴリの代わりに、その領域に特化したカテゴリを文法規則に直接埋め込みます。
<SHIP-QUERY> -> "list" <SHIP-ATTRIBUTE> "of" <SHIP-CLASS>
<SHIP-ATTRIBUTE> -> "speed" | "draft" | "displacement"
<SHIP-CLASS> -> "destroyers" | "carriers" | "the Iowa class"
このように書くと、文法自体が「その領域で意味をなす言い回し」しか受理しません。統語的な曖昧性の多くはそもそも発生せず、解析の結果がそのまま意味の確定、すなわち自然文からDBMSの**照会言語(Query Language, QL)**への変換になります。
この手法を採用した代表例が、1970年代に作られたデータベース自然言語インターフェース(Natural Language Interface, NLI)です。
- LUNAR(Woods, 1972年, BBN社): アポロ計画で持ち帰った月の岩石の化学分析データベースを、地質学者が自然言語で問い合わせられるようにしたシステム。ATNベースの構文解析器と手続き的意味論(procedural semantics)を組み合わせ、1971年のデモでは訓練を受けていない利用者の質問の約90%に正しく答えられました(注7)。
- LIFER/LADDER(Hendrix, 1978年): 米海軍の艦船データベースへの自然言語インターフェース。意味文法を使い、構文解析と意味解釈を一体化した3層アーキテクチャを採用しました(注8)。
意味文法は1文単位の解析には有効ですが、実際のやり取りは複数の質問にまたがります。たとえば「アポロ12号の岩石のケイ素含有量は?」に続けて「アポロ11号は?」とだけ尋ねられた場合、後者は「アポロ11号の岩石のケイ素含有量は?」を省略した**楕円文(elliptical query)です。こうした省略や、「それ」「その岩石」のような照応表現を解決するために、当時のNLIシステムはヒストリーリスト(history list、質問履歴表)**と呼ばれる機構を備えていました。それまでの問い合わせで言及された実体(対象・属性など)を記録しておき、新しい問い合わせが不完全な場合に直前の文脈から欠けている要素を補うという仕組みです。これは2節で触れた語用論(文脈に依存する意味)を、システムとして具体的に実装した一例と言えます。
これらのシステムに共通するのは、汎用的な文法ではなく、対象領域に特化した文法を用意することで、効率的かつ頑健な自然言語インターフェースを実現したという発想です。トレードオフとして、領域が変われば文法を作り直す必要があり、汎用性・移植性には乏しいという弱点があります。
5. 再帰下降パーサーをフレームシステムに統合する
最後に、CFGの規則をそのままプログラムの構造として実装する**再帰下降パーサー(Recursive Descent Parser)**を使い、パーサーの出力を前回の記事のフレームシステムに直接流し込む実装を確認します。
再帰下降パーサーは、CFGの非終端記号ひとつひとつに対応する関数を用意し、その関数が右辺の記号に対応する他の関数を呼び出す、というシンプルな設計です。文法の再帰構造がそのまま関数呼び出しの再帰になるため、正規表現では扱えなかった入れ子構造も自然に処理できます。
ここでは、パーサーが解析木を組み立てる代わりに、フレームのスロットを直接埋めていく設計にします。これは意味文法やATNのレジスタと同じ発想で、「解析=構造の構築」を一体化させたものです。
final class ActionFrame {
var agent: String?
var action: String?
var object: String?
var instrument: String?
var summary: String {
var text = "\(agent ?? "?")が\(object ?? "?")を\(action ?? "?")"
if let instrument {
text += "(\(instrument)を使って)"
}
return text
}
}
// 文法:
// S -> NP V NP (PP)?
// PP -> "with" NP
// NP -> ("the" | "a") N
final class RecursiveDescentParser {
private let tokens: [String]
private var position = 0
init(_ text: String) {
tokens = text.split(separator: " ").map(String.init)
}
private func peek() -> String? { position < tokens.count ? tokens[position] : nil }
private func advance() -> String? { defer { position += 1 }; return peek() }
func parseSentence() -> ActionFrame {
let frame = ActionFrame()
frame.agent = parseNounPhrase()
frame.action = advance()
frame.object = parseNounPhrase()
if peek() == "with" {
_ = advance() // "with" を読み飛ばす
frame.instrument = parseNounPhrase()
}
return frame
}
// NP -> (Det) N ―― Sからも、PP内からも再帰的に呼ばれる
private func parseNounPhrase() -> String? {
if peek() == "the" || peek() == "a" { _ = advance() }
return advance()
}
}
let parser = RecursiveDescentParser("the boy hit the ball with the bat")
let frame = parser.parseSentence()
print(frame.summary) // boyがballをhit(batを使って)
parseSentence は S -> NP V NP (PP)? という規則そのものを関数呼び出しの並びとして表現しており、parseNounPhrase は S からも PP の内部からも共通して呼び出されます。これがCFGの非終端記号の再利用と再帰下降パーサーの関数呼び出しが1対1で対応するということです。
重要なのは、パーサーが返しているのが解析木ではなく、ActionFrame という前回の記事と同じ発想のフレーム構造そのものである点です。これはATNのレジスタが解析と同時に意味構造を組み立てていたのと同じ考え方であり、意味文法が構文解析と意味解釈を一体化していたのとも同じ発想です。構文解析(このパーサー)と知識表現(フレーム)という2つの技術は、こうして地続きにつながっています。
このパーサーは NP の中に PP を再帰的に埋め込む処理までは対応していません。実運用のATNでは、まさにこの再帰呼び出し(サブネットワークへのPUSH)を使って、任意の深さの入れ子構造を扱えるようにしています。
6. 意味ネットワークへの自然言語インターフェース
前節ではフレームに統合しましたが、前回の記事で扱ったもう一つの知識表現である意味ネットワークにも、同じ考え方でNLIを組み込めます。ここでは4節の意味文法と、5節の再帰下降パーサーを組み合わせ、英語の質問文をそのまま SemanticNetwork.query の呼び出しに変換します。
前回の記事の Relation と SemanticNetwork をそのまま再掲します。
enum Relation: String {
case isA = "is-a"
case canDo = "can"
case hasProperty = "has"
}
final class SemanticNetwork {
// ノードと矢印の管理、is-a の継承を辿る query(_:relation:) は前回の記事の実装のまま
func query(_ name: String, relation: Relation) -> String? { /* ... */ }
}
質問文の側は、汎用的なNP/VPではなく、この意味ネットワーク専用のカテゴリだけを受理する意味文法として設計します。
<QUERY> -> "what" "can" <CONCEPT> "do" ; canDo を尋ねる
| "what" "does" <CONCEPT> "have" ; hasProperty を尋ねる
<CONCEPT> -> 任意の識別子(ネットワーク上のノード名)
この文法を再帰下降パーサーで実装し、解析結果をそのまま SemanticQuery(ノード名と関係のペア)というフレーム的な構造に落とし込みます。
struct SemanticQuery {
let concept: String
let relation: Relation
}
final class SemanticNetworkParser {
private let tokens: [String]
private var position = 0
init(_ text: String) {
tokens = text.split(separator: " ").map(String.init)
}
private func peek() -> String? { position < tokens.count ? tokens[position] : nil }
private func advance() -> String? { defer { position += 1 }; return peek() }
private func expect(_ word: String) -> Bool {
guard peek() == word else { return false }
_ = advance()
return true
}
// <QUERY> -> "what" "can" <CONCEPT> "do" | "what" "does" <CONCEPT> "have"
func parseQuery() -> SemanticQuery? {
guard expect("what") else { return nil }
if expect("can"), let concept = advance(), expect("do") {
return SemanticQuery(concept: concept, relation: .canDo)
}
if expect("does"), let concept = advance(), expect("have") {
return SemanticQuery(concept: concept, relation: .hasProperty)
}
return nil
}
}
最後に、パース結果を意味ネットワークへの問い合わせとして実行する部分をつなげます。
extension SemanticNetwork {
func answer(_ question: String) -> String {
guard let q = SemanticNetworkParser(question).parseQuery() else {
return "質問を理解できませんでした"
}
guard let result = query(q.concept, relation: q.relation) else {
return "\(q.concept) について分かりません"
}
return "\(q.concept) \(q.relation.rawValue) \(result)"
}
}
前回の記事で組み立てた net(Canary・PenguinがBirdのis-aで、BirdがcanDo: Fly、PenguinだけがcanDo: Swimを持つネットワーク)に対して、そのまま質問文を投げてみます。
net.answer("what can Penguin do") // "Penguin can Swim" ―― Penguin自身の値が優先される
net.answer("what can Canary do") // "Canary can Fly" ―― Birdからis-aで継承した値
"what can Canary do" という質問には、Canary 自身に canDo の値がないため、query が is-a の継承チェーンを遡って Bird の Fly を返します。つまり、意味文法とパーサーが担うのは統語解析だけで、「デフォルト推論と例外」という意味ネットワーク自体の推論規則には一切手を加えていません。自然言語インターフェースは、あくまで既存の知識表現の「窓口」であり、推論の実体は前回の記事で作ったネットワーク構造の側にある、という役割分担が明確になります。
この設計は4節のLUNAR・LIFER/LADDERとまったく同じ構造です。意味文法が「その領域で意味をなす言い回し」だけを受理し、パーサーの出力がそのままバックエンド(データベースの照会言語、あるいはここでは意味ネットワークのquery呼び出し)に渡る、という一本のパイプラインになっています。
まとめ
| 概念 | 役割 | 課題 |
|---|---|---|
| 統語論 | 文の構造規則を扱う | 文法的に正しくても意味の妥当性は保証しない |
| 意味論 | 語・文の文字通りの意味を合成する | 文脈依存の意味変化(語義選択など)を単独では扱えない |
| 語用論 | 発話の文脈・話者の意図を考慮する | 形式化・自動処理が難しい |
| 正規表現 | パターン照合による解析 | 再帰的な入れ子構造を扱えない |
| 文脈自由文法(CFG) | 再帰的な構造を宣言的に定義する | 文脈依存処理や意味構築とは別立てになりがち |
| 拡張遷移ネットワーク(ATN) | レジスタとアクションで手続き的に解析しながら意味構造も構築する | 文法自体がプログラムに近く複雑になる |
| 意味文法 | 領域特化の語彙で構文解析と意味解釈を一体化する | 領域ごとに文法を作り直す必要があり移植性が低い |
| フレーム/意味ネットワークへのNLI | 再帰下降パーサーの出力を既存の知識表現にそのまま渡す | あくまで「窓口」であり、推論規則自体は知識表現の側にある |
正規表現・CFG・ATNは、突き詰めれば「入力の構造をどう表現し、どう構築するか」という知識表現の問題の一種です。実装例で見たように、再帰下降パーサーがフレームや意味ネットワークの構造を直接埋めていく設計は、構文解析と知識表現という2つの技術を橋渡しする典型例だと言えます。とりわけ6節の意味ネットワークへのNLIでは、パーサーが行っているのは統語解析だけで、「デフォルト推論と例外」という推論そのものは前回の記事で作ったSemanticNetwork.queryの側が担っている、という役割分担がはっきり見えます。
なお、ここで扱ったのはいずれも1970年代の記号主義的(ルールベース)なアプローチです。現在主流の統計的・ニューラルなNLP(単語埋め込みやTransformerなど)は、文法規則を人手で書く代わりに大量のテキストからパターンを学習するという、まったく異なる発想に立っています。この対比は別の記事で改めて扱います。
注釈
- Joseph Weizenbaum, ELIZA(1966年)。キーワードへのパターンマッチングと定型応答の組み合わせで、内部に意味表現を持たない。
- Terry Winograd, SHRDLU(1968〜1972年)。「積み木の世界」という限定領域で統語論・意味論・語用論(会話の記憶による照応解決)を統合した自然言語理解システム。
- Noam Chomsky, "Syntactic Structures"(1957年)。"Colorless green ideas sleep furiously" は文法的に正しいが意味的には支離滅裂という例で、統語論と意味論の独立性を示した。
- Noam Chomsky, "Three Models for the Description of Language"(1956年)。正規文法・文脈自由文法・文脈依存文法・一般文法(句構造文法)の4段階からなるChomskyの階層を提示した。
- 再帰遷移ネットワーク(Recursive Transition Network, RTN)。文脈自由文法と同じ表現力を、呼び出し可能な有限状態ネットワークとして表現する形式。
- William A. Woods, "Transition Network Grammars for Natural Language Analysis"(1970年)。RTNにレジスタとアクションを追加した拡張遷移ネットワーク(ATN)を提案。
- Woods et al., LUNAR システム(1972年, BBN社)。ATNベースの構文解析と手続き的意味論を用いた、月の岩石データベースへの自然言語インターフェース。1971年のデモで未訓練の利用者の質問の約90%に正答。
- Gary G. Hendrix, LIFER/LADDER(1978年)。意味文法を用いた米海軍艦船データベースへの自然言語インターフェース。
参考資料
- SHRDLU - Wikipedia
- Chomsky hierarchy - Wikipedia
- Recursive transition network - Wikipedia
- Augmented transition network - Wikipedia
- Transition Network Grammars for Natural Language Analysis - William A. Woods (PDF)
- LIFER/LADDER - Wikipedia
- LUNAR (QA) System - GM-RKB
- Recursive descent parser - Wikipedia
- ELIZA - Wikipedia
元記事(Bitz Notebook): 自然言語処理(NLP)入門 ― 統語論・意味論・語用論から拡張遷移ネットワーク、フレームと意味ネットワークとの統合まで