yasuokaの日記: 日本語Universal Dependenciesで二文節間の「係り受け」をエミュレートするには
一昨昨日・一昨日・昨日の日記に続いて、『言語処理100本ノック 2020』の「48. 名詞から根へのパスの抽出」を、Stanzaの日本語モデルで解いてみよう。ただしStanzaは、単語間の「係り受け」解析をおこなうツールであって、二文節間の「係り受け」を解析する機能はない。どうすればいいのか。
以前、日本語の構文解析における3つの「係り受け」にも書いたが、以下の3つの「係り受け」は、それぞれに関係はあるものの中身が異なる。
- 句の「係り受け」
- 二文節間の「係り受け」
- 単語間の「係り受け」
問題となっている「48. 名詞から根へのパスの抽出」は、二文節間の「係り受け」を必要とする。一方、Stanzaは、Universal Dependenciesにより、単語間の「係り受け」解析をおこなう。つまり、Stanzaで「48. 名詞から根へのパスの抽出」を解くには、単語間の「係り受け」を、二文節間の「係り受け」に変換する必要があるわけだ。図示すると、こういう単語間の「係り受け」グラフを、こういう二文節間の「係り受け」グラフに、うまく変換できればいい。
そう思って、これらのグラフを見比べると、日本語Universal Dependenciesにおける右向きの矢印を文節内のリンク、左向きの矢印を文節間のリンク(の逆向き)とみなせば、なんとなく二文節間の「係り受け」をエミュレートできる気がする。Google Colaboratoryで試してみよう。
!pip install stanza spacy-stanza
from spacy.tokens import Token,Span,Doc
Token.set_extension("surface",getter=lambda token:token.orth_,force=True)
Token.set_extension("base",getter=lambda token:token.lemma_,force=True)
Token.set_extension("pos",getter=lambda token:token.pos_,force=True)
Token.set_extension("pos1",getter=lambda token:token.tag_,force=True)
Span.set_extension("morphs",getter=lambda span:span,force=True)
Span.set_extension("dst",default=None,force=True)
Span.set_extension("srcs",getter=lambda span:{i for i,c in enumerate(span.doc._.chunks) if c._.dst==span.doc._.chunks.index(span)},force=True)
Doc.set_extension("_chunks",default=None,force=True)
def _make_chunks(doc):
chunks=doc._._chunks
if not chunks:
b=[]
i=0
while i<len(doc):
b.append(i)
i=max([t.i for t in doc[i].rights]+[i])+1
chunks=[Span(doc,s,e) for s,e in zip(b,b[1:]+[len(doc)])]
for c in chunks:
i=max(t.head.i for t in c)
c._.dst=len([j for j in b if j<=i])-1
doc._._chunks=chunks
return chunks
Doc.set_extension("chunks",getter=_make_chunks,force=True)
import stanza
stanza.download("ja")
from spacy_stanza import StanzaLanguage
nlp=StanzaLanguage(stanza.Pipeline("ja"))
doc=nlp("吾輩はここで始めて人間というものを見た")
chunks=doc._.chunks
for c in chunks:
if [t for t in c._.morphs if t._.pos in {"NOUN","PRON","PROPN"}]!=[]:
s=str(c)
d=chunks[c._.dst]
while d!=c:
s+=" -> "+str(d)
c=d
d=chunks[c._.dst]
print(s)
私(安岡孝一)の手元では、以下の結果になった。
吾輩は -> 見た
ここで -> 始めて -> 見た
人間という -> ものを -> 見た
ものを -> 見た
これで「48. 名詞から根へのパスの抽出」を、Stanzaで解くことができた。ただし、日本語UDの右向き矢印を文節内リンクとみなす、というアイデアは、conjやcompoundについては、例外処理が必要になるだろう。そういう例外処理にパワーを費やすくらいなら、むしろ文節など捨てて、日本語における単語間の「係り受け」解析を直接使う方向に、もっとシフトすべきかな。
日本語Universal Dependenciesで二文節間の「係り受け」をエミュレートするには More ログイン