search
LoginSignup
6
Help us understand the problem. What are the problem?

この記事のすべてのコードはこのGoogle Colabから試すことができます。

SudachiPyの品詞

形態素解析後、形態素の品詞によって異なる処理をしたいということがよくあります。SudachiPyでは品詞情報はMorphemepart_of_speech()part_of_speech_id()のメソッドでアクセスできます。

import sudachipy
dic = sudachipy.Dictionary()
tok = dic.create()
morphs = tok.tokenize("品詞によって違う処理をする必要があります")
for m in morphs:
  print(m.surface(), m.part_of_speech(), sep="\t")
品詞	('名詞', '普通名詞', '一般', '*', '*', '*')
に	('助詞', '格助詞', '*', '*', '*', '*')
よっ	('動詞', '一般', '*', '*', '五段-ラ行', '連用形-促音便')
て	('助詞', '接続助詞', '*', '*', '*', '*')
違う	('動詞', '一般', '*', '*', '五段-ワア行', '連体形-一般')
処理	('名詞', '普通名詞', 'サ変可能', '*', '*', '*')
を	('助詞', '格助詞', '*', '*', '*', '*')
する	('動詞', '非自立可能', '*', '*', 'サ行変格', '連体形-一般')
必要	('名詞', '普通名詞', '形状詞可能', '*', '*', '*')
が	('助詞', '格助詞', '*', '*', '*', '*')
あり	('動詞', '非自立可能', '*', '*', '五段-ラ行', '連用形-一般')
ます	('助動詞', '*', '*', '*', '助動詞-マス', '終止形-一般')

一番簡単なやりかたは直接品詞Tupleの要素の文字列で条件を作ることです。

for m in morphs:
  print(m.surface(), m.part_of_speech()[0] == "名詞", sep="\t")
品詞	True
に		False
よっ	False
て		False
違う	False
処理	True
を		False
する	False
必要	True
が		False
あり	False
ます	False

PosMatcher

SudachiPy 0.6.1以降では、品詞が定義された集合に含まれているかというチェックを簡単に行えるようにPosMatcherのAPIが追加されました。PosMatcherはDictionaryのpos_matcherの関数を呼び出して作ります。引数の型によって2つの使い方があります。

関数から生成

一つ目の作り方は、pos_matcherの引数としてCallable(例えば、lambda関数)を渡すことです。品詞タグに対して真偽を返すような関数を渡すと、同様の動作をするPosMatcherが作られます。

m1 = dic.pos_matcher(lambda x: x[0] == "名詞")
for m in morphs:
  print(m.surface(), m1(m), sep="\t") # PosMatcherを関数として使います
品詞	True
に		False
よっ	False
て		False
違う	False
処理	True
を		False
する	False
必要	True
が		False
あり	False
ます	False

部分品詞リストから生成

二つ目の作り方は、引数として部分品詞のリストを渡すことです。部分品詞とは、品詞Tupleの一部を省略可能な書き方です。省略したい要素をNoneにすると、ワイルドカードとしてマッチします。

("名詞",) # 品詞の一層目が"名詞"のすべての品詞。Tupleのシンタックスとして、最後のカンマは必要。
(None, None, "サ変可能") # 品詞の2層目が"サ変可能"。
() # すべての品詞にマッチする。

部分品詞のPosMatcherの使い方の例は以下です。

m2 = dic.pos_matcher([("名詞",)])
for m in morphs:
  print(m.surface(), m2(m), sep="\t")
品詞	True
に		False
よっ	False
て		False
違う	False
処理	True
を		False
する	False
必要	True
が		False
あり	False
ます	False

集合の処理

一度作ったPosMatcherのオブジェクトを集合の処理で違うPosMatcherに変えることもできます。基本的な4つの処理を実装しています。Dictionary.pos_matcherで同じPosMatcherを作ることも可能ですが、場合によって集合の処理での作り方のほうがわかりやすいケースもあります。この機能はSudachiPy 0.6.3以降です。

合併 (和集合)

nouns = dic.pos_matcher(lambda x: x[0] == "名詞")
verbs = dic.pos_matcher(lambda x: x[0] == "動詞")
verbs_or_nouns = nouns | verbs
for m in morphs:
  print(m.surface(), verbs_or_nouns(m), sep="\t")
品詞	True
に		False
よっ	True
て		False
違う	True
処理	True
を		False
する	True
必要	True
が		False
あり	True
ます	False

交叉 (積集合)

conj_form = dic.pos_matcher(lambda x: x[5] == "連用形-一般")
verbs_in_conj_form = verbs & conj_form
for m in morphs:
  print(m.surface(), verbs_in_conj_form(m), sep="\t")
品詞	False
に		False
よっ	False
て		False
違う	False
処理	False
を		False
する	False
必要	False
が		False
あり	True
ます	False

差・相対補 (差集合)

suru_possible = dic.pos_matcher(lambda x: x[2] == "サ変可能")
nouns_suru_not_possible = nouns - suru_possible
for m in morphs:
  print(m.surface(), nouns_suru_not_possible(m), sep="\t")
品詞	True
に		False
よっ	False
て		False
違う	False
処理	False
を		False
する	False
必要	True
が		False
あり	False
ます	False

補・絶対補 (補集合)

not_nouns = ~nouns
for m in morphs:
  print(m.surface(), not_nouns(m), sep="\t")
品詞	False
に		True
よっ	True
て		True
違う	True
処理	False
を		True
する	True
必要	False
が		True
あり	True
ます	True

PosMatcherの品詞

PosMatcherに入っている品詞とその数を参照することもできます。Pythonの普通のlen関数で辞書の品詞セット中のマッチした品詞の数を調べることができます。

print(len(verbs_in_conj_form))
print(list(verbs_in_conj_form))
108
[('動詞', '一般', '*', '*', '五段-ラ行', '連用形-一般'), ('動詞', '一般', '*', '*', '下一段-マ行', '連用形-一般'), ...]

PosMatcherのiterは必ず品詞をID順で返します。すべての品詞にマッチするPosMatcherは現在のすべての品詞をIDで参照可能な順番で返します。map((y, x) for (x, y) in enumerate(matcher))で品詞→品詞IDのmapを作ることができます。

速度

PosMatcherのもう一つの目的は品詞分岐の高速化です。ロジックはRustで実装されて、品詞IDで高速に品詞のチェックをします。速度の比較は以下の3つのケースで行いました。コードはColabに載っています

# Baseline: 品詞情報をつかわない
count = 0
for m in morphs:    
    count += 1

# Case 1: 品詞情報をそのまま使う
count = 0
for m in morphs:
  pos = m.part_of_speech()
  if pos[0] == "名詞" or pos[0] == "動詞":
    count += 1

# Case 2: PosMatcherを使う
count = 0
for m in morphs:  
  if matcher(m):
    count += 1

Baselineは2.5マイクロ秒程度、Case1は5.6マイクロ秒、Case2は4.1マイクロ秒でした。Baselineにくらべると、Case1は3.1マイクロ秒のオーバーヘッドがあり、Case2は1.6マイクロ秒のオーバーヘッドがあります。この簡単なテストではPosMatcherのオーバーヘッドは2分の1です。条件が複雑になればなるほど、この差が広がります。

終わりに

PosMatcherはSudachiPyでMorphemeをフィルタリングするための簡単なAPIです。直接品詞を参照するよりおすすめです。コードの簡略化と処理速度の向上のメリットがあります。

よいSudachi Lifeを。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6
Help us understand the problem. What are the problem?