第5章の後半の問題を解いた記録。
対象とするファイルはwebページにもある通り、neko.txtとする。
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をCaboChaを使って係り受け解析し,その結果をneko.txt.cabochaというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.
45. 動詞の格パターンの抽出
今回用いている文章をコーパスと見なし,日本語の述語が取りうる格を調査したい. 動詞を述語,動詞に係っている文節の助詞を格と考え,述語と格をタブ区切り形式で出力せよ. ただし,出力は以下の仕様を満たすようにせよ.
動詞を含む文節において,最左の動詞の基本形を述語とする
- 述語に係る助詞を格とする
- 述語に係る助詞(文節)が複数あるときは,すべての助詞をスペース区切りで辞書順に並べる
このプログラムの出力をファイルに保存し,以下の事項をUNIXコマンドを用いて確認せよ.
コーパス中で頻出する述語と格パターンの組み合わせ
「する」「見る」「与える」という動詞の格パターン(コーパス中で出現頻度の高い順に並べよ)
# -*- coding: utf-8 -*-
__author__ = 'todoroki'
import problem41
def extractVerbPatern(sentence):
lst = []
for chunk in sentence:
if chunk.include_pos('動詞'):
src_chunks = [sentence[src] for src in chunk.srcs]
src_chunks_case = list(filter(lambda src_chunks: src_chunks.morphs_of_pos1('格助詞'), src_chunks))
if src_chunks_case:
lst.append((chunk, src_chunks_case))
return lst
if __name__ == "__main__":
f = open("neko.txt.cabocha", "r")
sentences = problem41.read_chunk(f)
verbPatterns = []
for sentence in sentences:
verbPatterns.append(extractVerbPatern(sentence))
for verbPattern in verbPatterns:
for verb, src_chunks in verbPattern:
v = verb.morphs_of_pos('動詞')[-1].base
ps = [src_chunk.morphs_of_pos1('格助詞')[-1].base for src_chunk in src_chunks]
p = " ".join(sorted(ps))
print "%s\t%s" % (v, p)
f.close()
上記プログラムの結果を出現頻度順にソートして出力するのが以下のコマンド。
上から、全動詞、「する」、「見る」、「与える」に対する処理。
python problem45.py | sort | uniq -c | sort -nr
python problem45.py | sort | awk '$1=="する"{print $0}' | uniq -c | sort -nr
python problem45.py | sort | awk '$1=="見る"{print $0}' | uniq -c | sort -nr
python problem45.py | sort | awk '$1=="与える"{print $0}' | uniq -c | sort -nr
46. 動詞の格フレーム情報の抽出
45のプログラムを改変し,述語と格パターンに続けて項(述語に係っている文節そのもの)をタブ区切り形式で出力せよ.45の仕様に加えて,以下の仕様を満たすようにせよ.
- 項は述語に係っている文節の単語列とする(末尾の助詞を取り除く必要はない)
- 述語に係る文節が複数あるときは,助詞と同一の基準・順序でスペース区切りで並べる
# -*- coding: utf-8 -*-
__author__ = 'todoroki'
import problem41
import problem45
if __name__ == "__main__":
f = open("neko.txt.cabocha", "r")
sentences = problem41.read_chunk(f)
f.close()
verbPatterns = []
for sentence in sentences:
verbPatterns.append(problem45.extractVerbPatern(sentence))
for verbPattern in verbPatterns:
for verb, src_chunks in verbPattern:
col1 = verb.morphs_of_pos('動詞')[-1].base
tmp = [(src_chunk.morphs_of_pos1('格助詞')[-1].base, str(src_chunk)) for src_chunk in src_chunks]
tmp = sorted(tmp, key=lambda x:x[0])
col2 = " ".join([col[0] for col in tmp])
col3 = " ".join([col[1] for col in tmp])
print "%s\t%s\t%s" % (col1, col2, col3)
47. 機能動詞構文のマイニング
動詞のヲ格にサ変接続名詞が入っている場合のみに着目したい.46のプログラムを以下の仕様を満たすように改変せよ.
- 「サ変接続名詞+を(助詞)」で構成される文節が動詞に係る場合のみを対象とする
- 述語は「サ変接続名詞+を+動詞の基本形」とし,文節中に複数の動詞があるときは,最左の動詞を用いる
- 述語に係る助詞(文節)が複数あるときは,すべての助詞をスペース区切りで辞書順に並べる
- 述語に係る文節が複数ある場合は,すべての項をスペース区切りで並べる(助詞の並び順と揃えよ)
このプログラムの出力をファイルに保存し,以下の事項をUNIXコマンドを用いて確認せよ.
- コーパス中で頻出する述語(サ変接続名詞+を+動詞)
- コーパス中で頻出する述語と助詞パターン
# -*- coding: utf-8 -*-
__author__ = 'todoroki'
import problem41
import problem45
def extractSahen(src_chunks):
for i, src_chunk in enumerate(src_chunks):
morphs = src_chunk.morphs
if len(morphs) > 1:
if morphs[-2].pos1 == "サ変接続" and morphs[-1].pos == "助詞" and morphs[-1].base == "を":
src_chunks.pop(i)
return src_chunk, src_chunks
return None
if __name__ == "__main__":
f = open("neko.txt.cabocha", "r")
sentences = problem41.read_chunk(f)
f.close()
verbPatterns = []
for sentence in sentences:
verbPatterns.append(problem45.extractVerbPatern(sentence))
for verbPattern in verbPatterns:
for verb, src_chunks in verbPattern:
sahen_chunks_set = extractSahen(src_chunks)
if sahen_chunks_set:
sahen_chunk, other_chunks = sahen_chunks_set
col1 = str(sahen_chunk) + verb.morphs_of_pos('動詞')[-1].base
tmp = [(other_chunk.morphs_of_pos1('格助詞')[-1].base, str(other_chunk)) for other_chunk in other_chunks]
tmp = sorted(tmp, key=lambda x: x[0])
col2 = " ".join([col[0] for col in tmp])
col3 = " ".join([col[1] for col in tmp])
print "%s\t%s\t%s" % (col1, col2, col3)
コーパス中で頻出する述語(サ変接続名詞+を+動詞)を出力するコマンド。
python problem47.py | cut -f 1 | sort | uniq -c | sort -nr
コーパス中で頻出する述語と助詞パターンを出力するコマンド。
python problem47.py | cut -f 1,2 | sort | uniq -c | sort -nr
48. 名詞から根へのパスの抽出
文中のすべての名詞を含む文節に対し,その文節から構文木の根に至るパスを抽出せよ. ただし,構文木上のパスは以下の仕様を満たすものとする.
- 各文節は(表層形の)形態素列で表現する
- パスの開始文節から終了文節に至るまで,各文節の表現を"->"で連結する
# -*- coding: utf-8 -*-
__author__ = 'todoroki'
import problem41
def extractPath(chunk, sentence):
path = [chunk]
dst = chunk.dst
while dst != -1:
path.append(sentence[dst])
dst = sentence[dst].dst
return path
if __name__ == "__main__":
f = open("neko.txt.cabocha", "r")
sentences = problem41.read_chunk(f)
f.close()
paths = []
for sentence in sentences:
for chunk in sentence:
if chunk.include_pos('名詞') and chunk.dst != -1:
paths.append(extractPath(chunk, sentence))
for path in paths:
print " -> ".join([str(chunk) for chunk in path])
49. 名詞間の係り受けパスの抽出
文中のすべての名詞句のペアを結ぶ最短係り受けパスを抽出せよ.ただし,名詞句ペアの文節番号がiとj(i<j)のとき,係り受けパスは以下の仕様を満たすものとする.
- 問題48と同様に,パスは開始文節から終了文節に至るまでの各文節の表現(表層形の形態素列)を"->"で連結して表現する
- 文節iとjに含まれる名詞句はそれぞれ,XとYに置換する
また,係り受けパスの形状は,以下の2通りが考えられる.
- 文節iから構文木の根に至る経路上に文節jが存在する場合: 文節iから文節jのパスを表示
- 上記以外で,文節iと文節jから構文木の根に至る経路上で共通の文節kで交わる場合: 文節iから文節kに至る直前のパスと文節jから文節kに至る直前までのパス,文節kの内容を"|"で連結して表示
# -*- coding: utf-8 -*-
__author__ = 'todoroki'
from collections import namedtuple
from itertools import combinations
import problem41
def extractPathIndex(i_chunk, sentence):
i, chunk = i_chunk
path_index = [i]
dst = chunk.dst
while dst != -1:
path_index.append(dst)
dst = sentence[dst].dst
return path_index
def posReplace(chunks, pos, repl, k=1):
replaced_str = ""
for morph in chunks[0].morphs:
if morph.pos == pos and k > 0:
replaced_str += repl
k -= 1
else:
if morph.pos != '記号':
replaced_str += morph.surface
return [replaced_str] + [str(chunk) for chunk in chunks[1:]]
if __name__ == "__main__":
f = open("neko.txt.cabocha", "r")
sentences = problem41.read_chunk(f)
f.close()
paths = []
N2Npath = namedtuple('N2Npath', ['X', 'Y', 'is_linear'])
for sentence in sentences:
noun_chunks = [(i, chunk) for i, chunk in enumerate(sentence) if chunk.include_pos('名詞')]
if len(noun_chunks) > 1:
for former, latter in combinations(noun_chunks, 2):
f_index = extractPathIndex(former, sentence)
l_index = extractPathIndex(latter, sentence)
f_i, l_i = list(zip(reversed(f_index), reversed(l_index)))[-1]
linear_flag = (f_i == l_i)
if linear_flag:
f_index2 = f_index[:f_index.index(f_i)+1]
l_index2 = l_index[:l_index.index(l_i)+1]
else:
f_index2 = f_index[:f_index.index(f_i)+2]
l_index2 = l_index[:l_index.index(l_i)+2]
X = [sentence[k] for k in f_index2]
Y = [sentence[k] for k in l_index2]
paths.append(N2Npath(X=X, Y=Y, is_linear=linear_flag))
for path in paths:
x = posReplace(path.X, "名詞", "X")
y = posReplace(path.Y, "名詞", "Y")
if path.is_linear:
x[-1] = "Y"
print " -> ".join(x)
else:
print "%s | %s | %s" % (" -> ".join(x[:-1]), " -> ".join(y[:-1]), path.X[-1])