Python
自然言語処理
nltk

KeyakiツリーバンクからPCFGを生成して構文解析

PCFGとは、文脈自由文法の各ルールに対して確率を付与したものです。ここでは、Keyaki treebankというコーパスからPCFGを生成します。


Keyaki treebankとは

Keyaki Treebankは、日本語の一貫した記述文法を具体化し、さまざまな文法現象を検索できるようにするための、解析されたコーパスです。

以下から詳細を見れます。

http://www.compling.jp/keyaki/

以下はダウンロードページです。

https://github.com/ajb129/KeyakiTreebank/releases


Keyaki Extractorで訓練データ生成

以下に、keyaki_extractor.pyというスクリプトを置きました。

https://github.com/sugiyamath/keyaki_extractor/blob/master/keyaki_extractor.py

これをインポートして以下のように訓練データを生成します。

import os

import keyaki_extractor as ke
from collections import defaultdict
from tqdm import tqdm
import re

IDREG = re.compile(r"\(ID .+?\)")
STARTREG = re.compile(r"^\(")

def main(path, fpart=".psd", outfile="out3.txt"):
print("File listing")
files = [f for f in os.listdir(path) if fpart in f]
print(files)
out = []

print("Data extracting")
for f in tqdm([os.path.join(path, g) for g in files]):
data = ke.load_data(f)
for d in data:
if d == "":
continue
tmp = ''.join(
list(ke.readiter(d, include_content=True, exclude=["ID"])))
tmp = re.sub(IDREG, "", tmp)
tmp = re.sub(STARTREG, "(S", tmp)
out.append(tmp)

print("Data writing")
with open(outfile, "w") as f:
f.write('\n'.join(out))

if __name__ == "__main__":
main("../../../keyaki/KeyakiTreebank/treebank/")

なお、main関数にKeyaki treebankのパスを指定しています。treebank内にはclosedファイルを消すか、あるいはclosedに対応したコーパスを購入して復元してください。


PCFGの訓練

以下に、cfg_toolsというスクリプト集を置いています。

https://github.com/sugiyamath/cfg_experiments/tree/master/cfg_tools

こちらの一部をインポートして、以下の訓練スクリプトを回します:

import sys

sys.path.append("./cfg_tools/3/")

import learn_pcfg as lp
import pickle as pkl
import nltk

def load_data(infile="./out3.txt"):
out = []
with open(infile) as f:
for line in f:
line = line.strip()
out.append(nltk.tree.Tree.fromstring(line))
return out

if __name__ == "__main__":
trees = load_data()
model = lp.learn_trees(trees, collapse=True, markov_order=True)
with open("model.pkl", "wb") as f:
pkl.dump(model, f)


訓練済みPCFGの公開

以下は、7z圧縮された訓練済みPCFGです(ただし、nltkを使っています)。

https://github.com/sugiyamath/cfg_experiments/blob/master/model/model.7z

この文法ルールをソートして文字列として出力したものが以下です。

https://github.com/sugiyamath/cfg_experiments/blob/master/data/keyaki_pcfg.txt


任意の文の構文解析

PCFGを使えば、構文解析ができます。nltkに用意されているいくつかのパーサを使うことができます。

訓練済みモデルを用いてパースするには以下のスクリプトを実行します:

import sys

sys.path.append("./cfg_tools/3/")

import learn_pcfg as lp
import pickle
import nltk
from nltk.parse.viterbi import ViterbiParser

def load_model(model_file="./model.pkl"):
with open(model_file, "rb") as f:
model = pickle.load(f)
return model

def eval_model(sent, grammar, tokenize, outfile="example_parse.txt"):
target = tokenize(sent)
#parser = ViterbiParser(grammar)
#parser = InsideChartParser(grammar, beam_size=50)
for x in lp.prob_parse(grammar, target, n=1):
return x

if __name__ == "__main__":
import time
import MeCab
sent = "子供が泳いでいる写真がかかっていた。"
tagger = MeCab.Tagger("-Owakati")
start = time.time()
grammar = load_model()
print(time.time() - start)
start = time.time()
print(eval_model(sent, grammar, tagger.parse))
print(time.time() - start)

[出力]

(S

(IP-IMP
(PP
(NP
(IP-EMB
(NP;*SBJ* (N 子供) (P が))
(VB 泳い)
(P で)
(VB2 いる))
(N 写真))
(P が))
(VB かかっ)
(P て)
(VB2 い)
(AX た))
(PU 。)) (p=4.51538e-41)

nltkにはViterbiParserというパーサが用意されていますが、遅いのでPCFGの訓練に利用したモジュール内に定義したprob_parse関数を用いてパースしています。


補足

このスクリプトでMeCabをちゃっかり使っていますが、Keyakiの単位に合わせるために、以下のスクリプトをまず回しています。

# coding: utf-8

import re
from tqdm import tqdm

regex = re.compile(r"'(.+?)'")
with open("./keyaki_pcfg.txt") as f:
data = f.read()

out =[x.group(1) for x in tqdm(regex.finditer(data))]
with open("foo.csv", "w") as f:
for o in out:
if "," in o:
print("HO")
continue
t = "{},,,1,名詞,一般,*,*,*,*,None,None,None,None\n".format(o)
f.write(t)

これは、ユーザ定義辞書にとりあえず語彙ルールを突っ込むというものです。そのユーザ辞書は以下に置きました:

https://github.com/sugiyamath/cfg_experiments/tree/master/data/mecab_userdic


参考

[0] http://www.compling.jp/keyaki/

[1] https://www.nltk.org/_modules/nltk/parse/viterbi.html

[2] https://github.com/salmanahmad/6.863/

[3] http://www.nltk.org/book/ch08-extras.html