はじめに
以前の記事でspaCyで手探りで遊んでみました。
記事の中で、spaCyのPhraseMatcherの名前にふれていましたが、今回、実際にPhraseMatcherを試してみたというメモです。
spaCyのPhraseMatcher
さて、そのspaCyのPhraseMatcherですが、うまい例えかは自信がありませんが「当社版のポ○モンを作りたいので是非企画〜開発をお願いしたい」的なノリで、ある文書からあるフレーズをgrep(検索)できる! というものです。
しかも、ポ○モンへの似せ具合をいくつか加減できるというスグレモノです。
あとで、実際の例をお見せしますが、「桜が咲く」を検索条件に指定することで、「桜が咲いた」「桜が咲かない」といった活用も含めてマッチさせることも、さらには「向日葵が咲いた」までマッチさせることもできます。
一方、本来のピュアなgrepでは正規表現の範囲で本当に指示通りのものが検索結果として得られますが、PhraseMatcherでは、得られた検索結果を見て確かにそれも対象になるべきだねという驚きやもう少し割り引いてほしかったなーという感想が得られるところがあります。
これは、「当社版のポ○モン」のようなオーダーをした人もされた人も、実際に得られた仕上がりを見た時に感じる感想と似たところがあるかもしれません。
ですが、あくまでコンピューターへの手続き的な命令で実現されるので、大半は、「こんなお願いの仕方でよく気づいてくれたね」という成功事例のような気がします。
姉妹APIとして、Matcherがありますが、これはまた改めて。
公式ドキュメント
PhraseMatcherのAPI仕様書です。 spacy.io
ただ、私見ですが、実際どうやって使ったら良いんだろうというところが、API仕様書からは読み取りにくく感じました。
もちろん、公式には(ちょっと離れたセクションですが)チュートリアルもあって、言語ガチ勢ではない私でも、次のページも併用することで、こうやれば良いのねというところが分かるようになりましたので、両方のページをいったりきたりすることをおすすめします。
試してみる
では実際にやってみます。
'桜が咲く', '桜が美しい', '美しい桜' を検索条件として与えた実験です。
先述の「ポ○モンの似せ加減」に相当するものを、PhraseMatcherのコンストラクターのattrパラメータで指定できます。
ここでは記事の主旨に従って、語弊を恐れずに説明するとおおよそ次のように言えると考えています。
- LEMMA: 見出し語に標準化した上で上記の「桜が...」のいずれかにマッチするものを検索します。
- POS: 例で指定されているフレーズを、おおまかな品詞分類し、その品詞の並びで表現されているフレーズを文書全体から検索。
- TAG: より細分した品詞での、POSと同様のもの。
- DEP: 例で指定されているフレーズのトークンの間の依存関係と似た依存関係で表現されているフレーズを文書全体から検索。
1は表記揺れをカバーした検索に近い挙動となりますし、他は「例えばこんな感じと似た言い回しのフレーズ」というものに対して、何を軸に似たと考えるかの加減で多少違いが出るというところでしょうか。
4は簡単な例だと2、3と大きな違いが出にくそうですが、少し複雑なもの(というか長めの検索例のフレーズ)の場合は、主語に修飾語をいっぱいくっつけて主部を大きくしがちな人の文書から、実際にそのような言い回しになっている箇所を見つけるといったちょっと変わった例の応用が考えられます*1
他にもあります。また上記のようなおおよその説明ではなく、厳密な定義をお求めの方は下記をご覧ください。 Rule-based matching · spaCy Usage Documentation
サンプルプログラム
import sys import spacy from spacy.matcher import PhraseMatcher nlp = spacy.load('ja_core_news_md') #検索対象の文書 _text = """ 桜が咲く。 きれいな桜が咲かない。 桜が早く咲く。 桜よ咲け。 桜が咲いた。 大きな向日葵。 夏にはひまわりが咲く。 緑が映える。 緑が綺麗だ。 草木が生える。 草木が煌めく。 """ doc = nlp(_text.replace(" ", "。").replace(" ", "、")) #検索条件(に相当するフレーズ例) terms = ['桜が咲く', '桜が美しい', '美しい桜'] patterns = [nlp(term) for term in terms] # https://spacy.io/usage/rule-based-matching # adding-patterns-attributes ATTRS = ['LEMMA', 'POS', 'TAG', 'DEP'] # 'ENT_TYPE','SHAPE' for ATTR in ATTRS: pm = PhraseMatcher(nlp.vocab, attr=ATTR) pm.add('RULE__' + ATTR, None, *patterns) for i, j, k in pm(doc): print(nlp.vocab.strings[i], doc[j:k])
実行結果
RULE__LEMMA 桜が咲く RULE__LEMMA 桜が咲か RULE__LEMMA 桜が咲い RULE__POS 桜が咲く RULE__POS 桜が咲か RULE__POS 桜が早く RULE__POS 桜が咲い RULE__POS ひまわりが咲く RULE__POS 緑が映える RULE__POS 緑が綺麗 RULE__POS 草木が生える RULE__POS 草木が煌めく RULE__TAG 桜が咲く RULE__TAG 桜が咲か RULE__TAG 桜が早く RULE__TAG 桜が咲い RULE__TAG ひまわりが咲く RULE__TAG 緑が映える RULE__TAG 草木が生える RULE__TAG 草木が煌めく RULE__DEP 桜が咲く RULE__DEP 桜が咲か RULE__DEP 早く咲く RULE__DEP 桜が咲い RULE__DEP ひまわりが咲く RULE__DEP 緑が映える RULE__DEP 緑が綺麗 RULE__DEP 草木が生える RULE__DEP 草木が煌めく
*1:もっとspaCyを活かした例はあると思いますが、私の身近な例の範囲で例えたため、そんなに響かない例になっていたらご容赦ください。