Skip to content

Instantly share code, notes, and snippets.

@dakeshi19
Created September 18, 2020 08:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dakeshi19/8892b5fcc55262cb527c202568bfabfd to your computer and use it in GitHub Desktop.
Save dakeshi19/8892b5fcc55262cb527c202568bfabfd to your computer and use it in GitHub Desktop.
spaCyのMatchのお試し
import sys
import spacy
from spacy.matcher import Matcher
import re
nlp = spacy.load('ja_core_news_md')
_text = """
関西国際空港側の居酒屋で、酒を飲みすぎたため、電車で眠りこけてしまったため、寝過ごしてしまった。終点の駅で目が醒めたのだが、周りに何もなくてとても淋しい気持ちになった。ふと夜空を見上げると、八分咲きの美しい桜の花が慰めてくれるようだった。
本当はもう少しこなれた文章をどこかから引用したかったが、著作権的なところの問題を考えたくなかったのと、ルールのベンチマークに使うには都合の良い文章が見つからなかったため、ひとまず創作した。
冒頭のテキストは決して私の美的感覚を反映したものではないことをお断りしたい。もちろん、本来はもっと文才があるということを言いたいわけではない。また、この文章は「クワイン(Quine)」的なものになっているわけでも、なんらかの縦読みなどの暗号的なものや仕掛けが含まれているものではない。深読みしないでください。
ムーンサルトはお断りしたい。
ムーンサルト斬りしたい。
お飾りが綺麗だ。
文章をどこからか引用する。
文章を引用する。
"""
doc = nlp(_text.replace(" ", "。").replace(" ", "、"))
mc = Matcher(nlp.vocab)
def ruletag(regex, op=None):
if op:
return {"TAG": {"REGEX": regex}, "OP": op}
return {"TAG": {"REGEX": regex}}
名詞動詞形容詞 = [ruletag("^(名詞|動詞|形容詞|形状詞|限定詞|接辞|接頭辞)(?!.*(非自立可能|助数詞可能|副詞可能)).*$")]
名詞 = [ruletag("^名詞")]
動詞形容詞 = [ruletag("^(動詞|形容詞|形状詞)")]
任意 = [ruletag("^(?!補助記号).+$")]
N = 7
for i in range(N):
ptnX = 名詞動詞形容詞 + 任意 * i + 名詞
mc.add("RULE_X_"+str(i), None, ptnX)
ptnY = 名詞動詞形容詞 + 任意 * i + 動詞形容詞
mc.add("RULE_Y_"+str(i), None, ptnY)
def 繋がり担保(token1, token2):
"""
token1とtoken2の間に(間接的でも良いので)依存関係が成立しているかをチェックする。
※依存関係がない場合は、意味のあるフレーズを切り出すには不向きなであり、これを枝狩りするためのもの。
"""
t1 = token1
while t1.dep_ != 'ROOT':
if (t1h := t1.head).i == token2.i:
return True
t1 = t1h
return False
def 近傍取得(token, XXX, right=True):
"""
tokenに対して、右向き(right=True)に、今回の仕様でのフレーズ成立の為に必要な語尾のトークンを取得する。
right=Falseは左向きとなる。XXXは語尾に続けていく対象とする依存関係を指定する。
"""
INC, LIMIT = (1, len(doc)) if right else (-1, -1)
i = token.i
while (i := i + INC) != LIMIT:
x = doc[i]
if x.dep_ in ('punct'): # 句読点にあたった場合はそこで終了
break
if right:
if x.dep_ not in XXX:
break
else:
if x.dep_ not in XXX and x.head.i != token.i:
break
return i - INC
def 対になる語を見つける(token, p):
"""
token(動詞、形容詞や形状詞)と対になる目的語などを取得する。
"""
# 対とみなす語のうち、より優先する「依存語」の種別(例. 動詞の場合、主語よりも目的語を優先する)
depprior1 = ['obj', 'nmod', 'nsubj', 'iobj', 'obl']
depprior2 = ['nsubj', 'iobj', 'nmod', 'obl', 'obj']
depprior = depprior1 if token.tag_.startswith(
'動詞') else depprior2
目印 = sorted([t for t in token.children if p <= t.i < token.i],
key=lambda x: depprior.index(x.dep_) if x.dep_ in depprior else len(depprior))
if 目印:
return 目印[0]
return token
def 複合語(token):
"""
「この人は美的感覚が...」→ 「感覚」というトークンからみて複合語を成立させるために「美的」部分を取得する。
"""
return 近傍取得(token, ('compound', 'dep'), right=False)
def フレーズらしくする(r, p, l):
"""
「ボールを素早く蹴る」というフレーズが「蹴る」を起点に取得できているとして、
「サッカーボールを蹴る」というホットワード風のフレーズを取得する。
※pは「ボール」のトークン位置、lは蹴るのトークン位置。
(※原文の中では、「黒いサッカーボールを...」というセンテンスとなっているイメージ)
"""
起点token = doc[l]
# (1)動詞の後ろに続く助動詞を取得する 等
#
継ぎ足し = 近傍取得(起点token, ('aux', 'advcl'), right=True)
if l < 継ぎ足し:
活用 = doc[l: 継ぎ足し + 1].text
else:
tmpA = 起点token.text
tmpB = 起点token.lemma_
活用 = tmpA if tmpA[-1] == tmpB[-1] else tmpB
# (2)注目しているトークンが複合語であれば、複合語の形にする
複合語はじまり = 複合語(起点token)
# (3)注目している語と対になる語(動詞に対して目的語...)を取得する
相手 = 対になる語を見つける(起点token, p)
ここから = 複合語(相手)
ここまで = 近傍取得(相手, ('case'), right=True) # 「ボールを蹴る」の「ボール」に対して助詞の「を」取得
# 上記、(1)、(2)、(3)を関連するトークンの並び順を考慮した上で、結合する。
if ここまで < 複合語はじまり:
return doc[ここから: ここまで + 1].text + doc[複合語はじまり:l].text + 活用
else:
return doc[ここから: ここまで + 1].text + 活用
def 名詞フレーズらしくする(span):
# a = span[-1]
# if a.text == 'ため' and a.tag_.startswith('名詞'):
# return span[:-2].text
return span.text
def 帳尻合わせ(phrases_text):
"""
複数のマッチルールに該当したことなどにより、重複した場合は除外する...など
"""
hiragana = re.compile(r'[\u3041-\u309F]{1,5}')
# 同じものが連続で現れる場合は重複除外する。
tmp = []
i = 0
phstr = phrases_text[i]
while (i := i + 1) and i < len(phrases_text):
n = phrases_text[i]
if phstr == n:
continue
# 4文字以下で全てひらがなの場合はフレーズにふさわしくないとして除外(経験則)
if not hiragana.fullmatch(phstr):
print(phstr)
phstr = n
# マッチさせる
phrases_text = []
for match_id, s, e in mc(doc):
span = doc[s:e]
first = span[0]
last = span[-1]
if 繋がり担保(first, last) or 近傍取得(last, ('compound', 'dep'), right=False) == first.i:
rule = nlp.vocab.strings[match_id]
if last.tag_.startswith(('動詞', '形容詞', '形状詞')):
phrases_text.append(フレーズらしくする(rule, first.i, last.i))
else:
phrases_text.append(名詞フレーズらしくする(doc[複合語(first): last.i + 1]))
帳尻合わせ(phrases_text)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment