言語処理100本ノック 2015「第5章: 係り受け解析」の48本目「名詞から根へのパスの抽出」記録です。
前回ノックと比べて少しシンプルになりました。あまり条件がなく係り先を続けて出力しているだけだからです。
参考リンク
リンク | 備考 |
---|---|
048.名詞から根へのパスの抽出.ipynb | 回答プログラムのGitHubリンク |
素人の言語処理100本ノック:48 | 多くのソース部分のコピペ元 |
CaboCha公式 | 最初に見ておくCaboChaのページ |
環境
CRF++とCaboChaはインストールしたのが昔すぎてインストール方法忘れました。全然更新されていないパッケージなので、環境再構築もしていません。CaboChaをWindowsで使おうと思い、挫折した記憶だけはあります。確か64bitのWindowsで使えなかった気がします(記憶が曖昧だし私の技術力の問題も多分にあるかも)。
種類 | バージョン | 内容 |
---|---|---|
OS | Ubuntu18.04.01 LTS | 仮想で動かしています |
pyenv | 1.2.16 | 複数Python環境を使うことがあるのでpyenv使っています |
Python | 3.8.1 | pyenv上でpython3.8.1を使っています パッケージはvenvを使って管理しています |
Mecab | 0.996-5 | apt-getでインストール |
CRF++ | 0.58 | 昔すぎてインストール方法忘れました(多分make install ) |
CaboCha | 0.69 | 昔すぎてインストール方法忘れました(多分make install ) |
第5章: 係り受け解析
学習内容
『吾輩は猫である』に係り受け解析器CaboChaを適用し,係り受け木の操作と統語的な分析を体験します.
ノック内容
夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をCaboChaを使って係り受け解析し,その結果をneko.txt.cabochaというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.
48. 名詞から根へのパスの抽出
文中のすべての名詞を含む文節に対し,その文節から構文木の根に至るパスを抽出せよ. ただし,構文木上のパスは以下の仕様を満たすものとする.
- 各文節は(表層形の)形態素列で表現する
- パスの開始文節から終了文節に至るまで,各文節の表現を"
->
"で連結する「吾輩はここで始めて人間というものを見た」という文(neko.txt.cabochaの8文目)から,次のような出力が得られるはずである.
吾輩は -> 見た ここで -> 始めて -> 人間という -> ものを -> 見た 人間という -> ものを -> 見た ものを -> 見た
回答
回答プログラム 048.名詞から根へのパスの抽出.ipynb
import re
# 区切り文字
separator = re.compile('\t|,')
# 係り受け
dependancy = re.compile(r'''(?:\*\s\d+\s) # キャプチャ対象外
(-?\d+) # 数字(係り先)
''', re.VERBOSE)
class Morph:
def __init__(self, line):
#タブとカンマで分割
cols = separator.split(line)
self.surface = cols[0] # 表層形(surface)
self.base = cols[7] # 基本形(base)
self.pos = cols[1] # 品詞(pos)
self.pos1 = cols[2] # 品詞細分類1(pos1)
class Chunk:
def __init__(self, morphs, dst):
self.morphs = morphs
self.dst = dst # 係り先文節インデックス番号
self.phrase = ''
self.noun = False
for morph in morphs:
if morph.pos != '記号':
self.phrase += morph.surface # 記号以外の場合文節作成
if morph.pos == '名詞':
self.noun = True
morphs = []
chunks = []
sentences = []
with open('./neko.txt.cabocha') as f:
for line in f:
dependancies = dependancy.match(line)
# EOSまたは係り受け解析結果でない場合
if not (line == 'EOS\n' or dependancies):
morphs.append(Morph(line))
# EOSまたは係り受け解析結果で、形態素解析結果がある場合
elif len(morphs) > 0:
chunks.append(Chunk(morphs, dst))
morphs = []
# 係り受け結果の場合
if dependancies:
dst = int(dependancies.group(1))
# EOSで係り受け結果がある場合
if line == 'EOS\n' and len(chunks) > 0:
sentences.append(chunks)
chunks = []
for i, sentence in enumerate(sentences):
for chunk in sentence:
if chunk.noun and chunk.dst != -1:
line = chunk.phrase
current_chunk = chunk
while current_chunk.dst != -1:
line = line + ' -> ' + sentence[current_chunk.dst].phrase
current_chunk = sentence[current_chunk.dst]
print(i, '\t',line)
# 多いので制限
if i > 10:
break
回答解説
Chunkクラス
Chunkクラスは前回と比べてスッキリしました。名詞を含んでいるかをフラグとして持たせています。
class Chunk:
def __init__(self, morphs, dst):
self.morphs = morphs
self.dst = dst # 係り先文節インデックス番号
self.phrase = ''
self.noun = False
for morph in morphs:
if morph.pos != '記号':
self.phrase += morph.surface # 記号以外の場合文節作成
if morph.pos == '名詞':
self.noun = True
出力部
前回から色々とシンプルになりましたが、代わりに少し複雑にしているのが以下の出力部分です。
名詞句を含み係り先があった場合に、while
句で係り先の終端まで分節をつなげています。
for i, sentence in enumerate(sentences):
for chunk in sentence:
if chunk.noun and chunk.dst != -1:
line = chunk.phrase
current_chunk = chunk
while current_chunk.dst != -1:
line = line + ' -> ' + sentence[current_chunk.dst].phrase
current_chunk = sentence[current_chunk.dst]
print(i, '\t',line)
# 多いので制限
if i > 10:
break
出力結果(実行結果)
プログラム実行すると以下の結果が出力されます。
2 名前は -> 無い
3 どこで -> 生れた -> かとんと -> つかぬ
3 かとんと -> つかぬ
3 見当が -> つかぬ
4 何でも -> 薄暗い -> 泣いて -> 記憶している
4 した所で -> 泣いて -> 記憶している
4 いた事だけは -> 記憶している
5 吾輩は -> 見た
5 ここで -> 始めて -> 人間という -> ものを -> 見た
5 人間という -> ものを -> 見た
5 ものを -> 見た
6 あとで -> 聞くと -> そうだ
6 それは -> そうだ
6 書生という -> 人間中で -> 種族であった -> そうだ
6 人間中で -> 種族であった -> そうだ
6 一番 -> 獰悪な -> 種族であった -> そうだ
6 獰悪な -> 種族であった -> そうだ
6 種族であった -> そうだ
7 書生というのは -> 話である
7 我々を -> 捕えて -> 煮て -> 食うという -> 話である
8 当時は -> なかったから -> 思わなかった
8 何という -> 考も -> なかったから -> 思わなかった
8 考も -> なかったから -> 思わなかった
9 彼の -> 掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである
9 掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである
9 スーと -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである
9 時 -> フワフワした -> 感じが -> あったばかりである
9 感じが -> あったばかりである
10 掌の -> 上で -> 落ちついて -> 見たのが -> 人間という -> ものの -> 見始であろう
10 上で -> 落ちついて -> 見たのが -> 人間という -> ものの -> 見始であろう
10 書生の -> 顔を -> 見たのが -> 人間という -> ものの -> 見始であろう
10 顔を -> 見たのが -> 人間という -> ものの -> 見始であろう
10 見たのが -> 人間という -> ものの -> 見始であろう
10 人間という -> ものの -> 見始であろう
10 ものの -> 見始であろう
11 時 -> ものだと -> 思った -> 感じが -> 残っている
11 妙な -> ものだと -> 思った -> 感じが -> 残っている
11 ものだと -> 思った -> 感じが -> 残っている
11 感じが -> 残っている
11 今でも -> 残っている