Python, Janomeで日本語の形態素解析、分かち書き(単語分割)
JanomeはPythonの形態素解析エンジン。日本語のテキストを形態素ごとに分割して品詞を判定したり分かち書き(単語に分割)したりすることができる。pip
でインストール可能。
- mocobeta/janome: Japanese morphological analysis engine written in pure Python
- Welcome to janome's documentation! (Japanese) — Janome v0.4 documentation (ja)
- janome package — Janome API reference v0.4
ここでは以下の内容について説明する。
- Janomeのインストール
- JanomeとMeCab
- 解析結果の精度
- 形態素解析の速度
- Janomeで形態素解析
- 基本的な使い方
- Tokenオブジェクトの属性
- Janomeで分かち書き(単語ごとに分割)
- 引数wakati
- リスト内包表記
- 単語の出現回数をカウント
- Analyzerフレームワークの使い方
- Analyzerによる単語の出現回数のカウント
- 品詞指定の注意点
サンプルコードで使っているJanomeのバージョンは0.4.1
。バージョンが異なると仕様が異なる場合もあるので注意。
Janomeのインストール
pip
(環境によってはpip3
)でインストールできる。
$ pip install janome
JanomeとMeCab
日本語の形態素解析エンジンとして有名なものにMeCabがある。
Janomeの公式ドキュメントのFAQ(下の方)にMeCabとの比較についての回答がある。
解析結果の精度
解析結果の精度は同等。
Q. 解析結果の精度は。
A. 辞書,言語モデルともに MeCab のデフォルトシステム辞書をそのまま使わせていただいているため,バグがなければ,MeCab と同等の解析結果になると思います。
Janome公式ドキュメントFAQ
形態素解析の速度
解析速度はJanomeのほうが10倍程度遅い。
Q. 形態素解析の速度は。
A. 文章の長さによりますが,手元の PC では 1 センテンスあたり数ミリ〜数十ミリ秒でした。mecab-python の10倍程度(長い文章だとそれ以上)遅い,というくらいでしょうか。
Janome公式ドキュメントFAQ
Janomeで形態素解析
基本的な使い方
Tokenizer
をインポートしてTokenizer
オブジェクトのインスタンスを生成、tokenize()
メソッドに対象の文字列を渡す。tokenize()
メソッドはjanome.tokenizer.Token
オブジェクトのジェネレータを返す。
from janome.tokenizer import Tokenizer
t = Tokenizer()
s = 'すもももももももものうち'
print(type(t.tokenize(s)))
# <class 'generator'>
print(type(t.tokenize(s).__next__()))
# <class 'janome.tokenizer.Token'>
Token
オブジェクトをprint()
で出力すると以下のように解析結果が表示される。
for token in t.tokenize(s):
print(token)
# すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
# も 助詞,係助詞,*,*,*,*,も,モ,モ
# もも 名詞,一般,*,*,*,*,もも,モモ,モモ
# も 助詞,係助詞,*,*,*,*,も,モ,モ
# もも 名詞,一般,*,*,*,*,もも,モモ,モモ
# の 助詞,連体化,*,*,*,*,の,ノ,ノ
# うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
なお、バージョン3.10まではtokenize()
はjanome.tokenizer.Token
オブジェクトを要素とするリストを返し、tokenize()
メソッドの引数stream
をTrue
とするとジェネレータを返すようになっていた。
バージョン4.0以降でjanome.tokenizer.Token
オブジェクトのリストを取得したい場合はlist()
を使えばよい。
print(type(list(t.tokenize(s))))
# <class 'list'>
print(type(list(t.tokenize(s))[0]))
# <class 'janome.tokenizer.Token'>
Tokenオブジェクトの属性
print()
で一括出力するだけでなく、Token
オブジェクトの属性からそれぞれの情報を取得できる。
以下のToken
オブジェクトを例とする。
token = t.tokenize('走れ').__next__()
print(type(token))
# <class 'janome.tokenizer.Token'>
print(token)
# 走れ 動詞,自立,*,*,五段・ラ行,命令e,走る,ハシレ,ハシレ
surface(表層形)
print(token.surface)
# 走れ
文字列の中で使われているそのままの形。
part_of_speech(品詞)
print(token.part_of_speech)
# 動詞,自立,*,*
品詞,品詞細分類1,品詞細分類2,品詞細分類3
という文字列。細分類が定義されていないと*
となる。
split()
メソッドでカンマで分割したリストを取得可能。品詞だけ取得したい場合はそのリストの最初の要素を取り出せばいい。
print(token.part_of_speech.split(','))
# ['動詞', '自立', '*', '*']
print(token.part_of_speech.split(',')[0])
# 動詞
infl_type(活用型)
print(token.infl_type)
# 五段・ラ行
活用がない名詞などでは*
。
infl_form(活用形)
print(token.infl_form)
# 命令e
活用がない名詞などでは*
。
base_form(基本形、見出し語)
print(token.base_form)
# 走る
活用されていない基本形(原形)。辞書の見出し語に相当する。
reading(読み)
print(token.reading)
# ハシレ
phonetic(発音)
print(token.phonetic)
# ハシレ
Janomeで分かち書き(単語ごとに分割)
英語は単語ごとにスペースで区切られているので分割するのが簡単だが、日本語は難しい。
Janomeを使うと日本語のテキストを分かち書き(単語ごとに分割)することが可能。なお、厳密には形態素と単語は異なるが、ここでは深追いしない。
以下の文字列を例とする。
s = '走れと言われたので走ると言った'
for token in t.tokenize(s):
print(token)
# 走れ 動詞,自立,*,*,五段・ラ行,命令e,走る,ハシレ,ハシレ
# と 助詞,格助詞,引用,*,*,*,と,ト,ト
# 言わ 動詞,自立,*,*,五段・ワ行促音便,未然形,言う,イワ,イワ
# れ 動詞,接尾,*,*,一段,連用形,れる,レ,レ
# た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
# ので 助詞,接続助詞,*,*,*,*,ので,ノデ,ノデ
# 走る 動詞,自立,*,*,五段・ラ行,基本形,走る,ハシル,ハシル
# と 助詞,格助詞,引用,*,*,*,と,ト,ト
# 言っ 動詞,自立,*,*,五段・ワ行促音便,連用タ接続,言う,イッ,イッ
# た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
引数wakati
tokenize()
メソッドの引数wakati
をTrue
とするとToken
オブジェクトではなく表層形の文字列str
のジェネレータを返す。
print(type(t.tokenize(s, wakati=True)))
# <class 'generator'>
print(type(t.tokenize(s, wakati=True).__next__()))
# <class 'str'>
print(list(t.tokenize(s, wakati=True)))
# ['走れ', 'と', '言わ', 'れ', 'た', 'ので', '走る', 'と', '言っ', 'た']
Tokenizer
オブジェクトのコンストラクタで引数wakati=True
とすることも可能。このTokenizer
オブジェクトを使うと常に分かち書きモードで処理するようになる。
t_wakati = Tokenizer(wakati=True)
print(list(t_wakati.tokenize(s)))
# ['走れ', 'と', '言わ', 'れ', 'た', 'ので', '走る', 'と', '言っ', 'た']
リスト内包表記
リスト内包表記を使うとToken
オブジェクトから所望の属性を取り出してリスト化できる。
リスト内包表記については以下の記事を参照。
- 関連記事: Pythonリスト内包表記の使い方
元の文字列をそのまま分かち書きしたい場合はsurface
属性を使う。引数wakati
をTrue
とした場合の結果と同じ、
print([token.surface for token in t.tokenize(s)])
# ['走れ', 'と', '言わ', 'れ', 'た', 'ので', '走る', 'と', '言っ', 'た']
base_form
やpart_of_speech
で基本形や品詞のリストを取得することも可能。
print([token.base_form for token in t.tokenize(s)])
# ['走る', 'と', '言う', 'れる', 'た', 'ので', '走る', 'と', '言う', 'た']
print([token.part_of_speech.split(',')[0] for token in t.tokenize(s)])
# ['動詞', '助詞', '動詞', '動詞', '助動詞', '助詞', '動詞', '助詞', '動詞', '助動詞']
リスト内包表記でif
を使うと特定の品詞のみをリストアップしたりできる。
print([token.surface for token in t.tokenize(s)
if token.part_of_speech.startswith('動詞')])
# ['走れ', '言わ', 'れ', '走る', '言っ']
print([token.surface for token in t.tokenize(s)
if not token.part_of_speech.startswith('動詞')])
# ['と', 'た', 'ので', 'と', 'た']
print([token.surface for token in t.tokenize(s)
if token.part_of_speech.startswith('動詞,自立')])
# ['走れ', '言わ', '走る', '言っ']
print([token.surface for token in t.tokenize(s)
if token.part_of_speech.split(',')[0] in ['動詞', '助動詞']])
# ['走れ', '言わ', 'れ', 'た', '走る', '言っ', 'た']
単語の出現回数をカウント
分かち書きしたリストがあれば、それぞれの単語の出現回数をカウントするのも簡単。
Python標準ライブラリcollectionsのCounterクラスを使う。Counterの詳しい使い方は以下の記事を参照。
なお、JanomeのAnalyzerフレームワークを使ってカウントすることも可能。後述。
単語のリストをコンストラクタCounter()
に渡すとCounter
オブジェクトが得られる。
from janome.tokenizer import Tokenizer
import collections
t = Tokenizer()
s = '人民の人民による人民のための政治'
for token in t.tokenize(s):
print(token)
# 人民 名詞,一般,*,*,*,*,人民,ジンミン,ジンミン
# の 助詞,連体化,*,*,*,*,の,ノ,ノ
# 人民 名詞,一般,*,*,*,*,人民,ジンミン,ジンミン
# による 助詞,格助詞,連語,*,*,*,による,ニヨル,ニヨル
# 人民 名詞,一般,*,*,*,*,人民,ジンミン,ジンミン
# の 助詞,連体化,*,*,*,*,の,ノ,ノ
# ため 名詞,非自立,副詞可能,*,*,*,ため,タメ,タメ
# の 助詞,連体化,*,*,*,*,の,ノ,ノ
# 政治 名詞,一般,*,*,*,*,政治,セイジ,セイジ
c = collections.Counter(t.tokenize(s, wakati=True))
print(type(c))
# <class 'collections.Counter'>
print(c)
# Counter({'人民': 3, 'の': 3, 'による': 1, 'ため': 1, '政治': 1})
単語を指定するとその出現回数を取得できる。存在しない単語は0。
print(c['人民'])
# 3
print(c['国民'])
# 0
Counter
オブジェクトのmost_common()
メソッドは、(単語, 出現回数)
のタプルが出現回数の多いほうから順に並んだリストを返す。
mc = c.most_common()
print(mc)
# [('人民', 3), ('の', 3), ('による', 1), ('ため', 1), ('政治', 1)]
先頭の要素から最頻出単語とその出現回数を取得したり、出現回数順の単語のタプルを取得したりできる。
print(mc[0][0])
# 人民
print(mc[0][1])
# 3
words, counts = zip(*c.most_common())
print(words)
# ('人民', 'の', 'による', 'ため', '政治')
print(counts)
# (3, 3, 1, 1, 1)
引数wakatiを使った分かち書きだと表層形のカウントになる。動詞などを基本形でカウントしたい場合は上述のリスト内包表記を使う。
s = '走れと言われたので走ると言った'
print(collections.Counter(t.tokenize(s, wakati=True)))
# Counter({'と': 2, 'た': 2, '走れ': 1, '言わ': 1, 'れ': 1, 'ので': 1, '走る': 1, '言っ': 1})
print(collections.Counter(token.base_form for token in t.tokenize(s)))
# Counter({'走る': 2, 'と': 2, '言う': 2, 'た': 2, 'れる': 1, 'ので': 1})
なお、ここではリスト内包表記のジェネレーター版([]
ではなく()
で囲む)を使っている。()
内ではジェネレーター内包表記の()
を省略可能。
print(type(token.base_form for token in t.tokenize(s)))
# <class 'generator'>
特定の品詞のみをカウントしたり、品詞ごとの個数をカウントしたりすることもできる。
print(collections.Counter(token.base_form for token in t.tokenize(s)
if token.part_of_speech.startswith('動詞,自立')))
# Counter({'走る': 2, '言う': 2})
print(collections.Counter(token.part_of_speech.split(',')[0] for token in t.tokenize(s)))
# Counter({'動詞': 5, '助詞': 3, '助動詞': 2})
Analyzerフレームワークの使い方
バージョン0.3.4
からAnalyzerフレームワークが追加された。形態素解析の前処理・後処理が可能。
バージョン0.4.1
時点で以下のフィルターが用意されている。
CharFilter
(対象文字列の正規化などの前処理)UnicodeNormalizeCharFilter()
- Unicodeを
unicodedata.normalize()
で正規化 - 引数に
'NFC'
,'NFKC'
,'NFD'
,'NFKD'
を指定可能- デフォルトは
'NFKC'
で、全角→半角などの変換が行われる
- デフォルトは
- Unicodeを
RegexReplaceCharFilter()
- 正規表現で置換
TokenFilter
(トークンのフィルタリングなどの後処理)CompoundNounFilter()
- 連続する名詞の複合名詞化
ExtractAttributeFilter()
- 抽出する属性(
‘surface’
や‘part_of_speech’
など)を指定
- 抽出する属性(
LowerCaseFilter()
/UpperCaseFilter()
- アルファベットを小文字 / 大文字に変換
POSKeepFilter()
/POSStopFilter()
- 結果に含む / 結果から除外する品詞をリストで指定
TokenCountFilter()
- 出現回数をカウント
- バージョン
0.3.5
で追加
詳細は公式のAPIリファレンスを参照。
以下の文字列を例として基本的な使い方を説明する。analyzer
とcharfilter
, tokenfilter
をそれぞれインポートする。
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import *
from janome.tokenfilter import *
t = Tokenizer()
s = '<div>PythonとPYTHONとパイソンとパイソン</div>'
for token in t.tokenize(s):
print(token)
# < 名詞,サ変接続,*,*,*,*,<,*,*
# div 名詞,一般,*,*,*,*,div,*,*
# > 名詞,サ変接続,*,*,*,*,>,*,*
# Python 名詞,一般,*,*,*,*,Python,*,*
# と 助詞,並立助詞,*,*,*,*,と,ト,ト
# PYTHON 名詞,固有名詞,組織,*,*,*,PYTHON,*,*
# と 助詞,並立助詞,*,*,*,*,と,ト,ト
# パイソン 名詞,一般,*,*,*,*,パイソン,*,*
# と 助詞,並立助詞,*,*,*,*,と,ト,ト
# パイソン 名詞,一般,*,*,*,*,パイソン,*,*
# </ 名詞,サ変接続,*,*,*,*,</,*,*
# div 名詞,一般,*,*,*,*,div,*,*
# > 名詞,サ変接続,*,*,*,*,>,*,*
CharFilter
およびTokenFilter
をリストで指定してAnalyzer
オブジェクトを生成し、analyze()
メソッドに対象の文字列を渡す。リストの順番でフィルターが適用されるので注意。
全角を半角に変換し、正規表現によりHTMLタグを消去(空文字列で置換)、さらに名詞のみを抽出してアルファベットを小文字化、'surface'
属性のみを抽出している。
char_filters = [UnicodeNormalizeCharFilter(),
RegexReplaceCharFilter('<.*?>', '')]
token_filters = [POSKeepFilter(['名詞']),
LowerCaseFilter(),
ExtractAttributeFilter('surface')]
a = Analyzer(char_filters=char_filters, token_filters=token_filters)
for token in a.analyze(s):
print(token)
# python
# python
# パイソン
# パイソン
CompoundNounFilter()
による複合名詞化の例。
s = '自然言語処理による日本国憲法の形態素解析'
for token in t.tokenize(s):
print(token)
# 自然 名詞,形容動詞語幹,*,*,*,*,自然,シゼン,シゼン
# 言語 名詞,一般,*,*,*,*,言語,ゲンゴ,ゲンゴ
# 処理 名詞,サ変接続,*,*,*,*,処理,ショリ,ショリ
# による 助詞,格助詞,連語,*,*,*,による,ニヨル,ニヨル
# 日本国 名詞,固有名詞,地域,国,*,*,日本国,ニッポンコク,ニッポンコク
# 憲法 名詞,一般,*,*,*,*,憲法,ケンポウ,ケンポー
# の 助詞,連体化,*,*,*,*,の,ノ,ノ
# 形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
# 解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
a = Analyzer(token_filters=[CompoundNounFilter()])
for token in a.analyze(s):
print(token)
# 自然言語処理 名詞,複合,*,*,*,*,自然言語処理,シゼンゲンゴショリ,シゼンゲンゴショリ
# による 助詞,格助詞,連語,*,*,*,による,ニヨル,ニヨル
# 日本国憲法 名詞,複合,*,*,*,*,日本国憲法,ニッポンコクケンポウ,ニッポンコクケンポー
# の 助詞,連体化,*,*,*,*,の,ノ,ノ
# 形態素解析 名詞,複合,*,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
Analyzerによる単語の出現回数のカウント
バージョン0.3.5
で追加されたTokenCountFilter()
を使うと単語の出現回数をカウントできる。
POSKeepFilter()
など他のフィルターと組み合わせることが可能(ただしTokenCountFilter()
は末尾に置く)。
(単語, 出現回数)
のタプルをジェネレーターで返す。
s = '人民の人民による人民のための政治'
a = Analyzer(token_filters=[POSKeepFilter(['名詞']), TokenCountFilter()])
g_count = a.analyze(s)
print(type(g_count))
# <class 'generator'>
for i in g_count:
print(i)
# ('人民', 3)
# ('ため', 1)
# ('政治', 1)
リスト化したい場合はlist()
を使う。上述のcollections.Counter
のmost_common()
メソッドの返り値と同じ。
l_count = list(a.analyze(s))
print(type(l_count))
# <class 'list'>
print(l_count)
# [('人民', 3), ('ため', 1), ('政治', 1)]
dict()
で辞書(dict
型オブジェクト)に変換することも可能。
d_count = dict(a.analyze(s))
print(type(d_count))
# <class 'dict'>
print(d_count)
# {'人民': 3, 'ため': 1, '政治': 1}
辞書のキーを指定して単語の出現回数を取得できる。get()
メソッドを使うと存在しない単語に対してもエラーにならない。
print(d_count['人民'])
# 3
# print(d_count['国民'])
# KeyError: '国民'
print(d_count.get('国民', 0))
# 0
TokenCountFilter()
の引数att
でカウントする属性を指定できる。動詞の基本形をカウントしたい場合に便利。
s = '走れと言われたので走ると言った'
a = Analyzer(token_filters=[TokenCountFilter()])
print(list(a.analyze(s)))
# [('走れ', 1), ('と', 2), ('言わ', 1), ('れ', 1), ('た', 2), ('ので', 1), ('走る', 1), ('言っ', 1)]
a = Analyzer(token_filters=[TokenCountFilter(att='base_form')])
print(list(a.analyze(s)))
# [('走る', 2), ('と', 2), ('言う', 2), ('れる', 1), ('た', 2), ('ので', 1)]
'part_of_speech'
は細分類を含んだ文字列となる。品詞だけでカウントしたい場合は上述の内包表記を用いた方法を使う。
a = Analyzer(token_filters=[TokenCountFilter(att='part_of_speech')])
print(list(a.analyze(s)))
# [('動詞,自立,*,*', 4), ('助詞,格助詞,引用,*', 2), ('動詞,接尾,*,*', 1), ('助動詞,*,*,*', 2), ('助詞,接続助詞,*,*', 1)]
品詞指定の注意点
POSKeepFilter()
, POSStopFilter()
で品詞を限定したり除外する場合、ひとつだけ指定する場合もリストを使う。文字列をそのまま指定すると正しく処理されないので注意。
以下はPOSKeepFilter()
で助動詞のみを抽出する例。前者(文字列で指定)は助詞も抽出されてしまっている。後者(文字列のリストで指定)は正しく助動詞のみが抽出されている。
s = '吾輩は猫である'
a = Analyzer(token_filters=[POSKeepFilter('助動詞')])
for token in a.analyze(s):
print(token)
# は 助詞,係助詞,*,*,*,*,は,ハ,ワ
# で 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
# ある 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
a = Analyzer(token_filters=[POSKeepFilter(['助動詞'])])
for token in a.analyze(s):
print(token)
# で 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
# ある 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル