こんにちは、@Yoshimiです。
前回、janomeの基礎部分を紹介しました。今回は、janomeの中でも非常に強力なAnalyzerモジュールの紹介です。
Analyzerモジュール
データ分析に置いて前処理・後処理は必須です。もちろん自然言語でも前処理・後処理は必須です。特に、自然言語処理の場合、名詞や助詞、未知語、そして辞書(プログラム上)にはない単語などが存在し、その組み合わせで解析を行っていく必要があります。最近では、HTMLのスクレイピングを行ってWebページを解析する、一部の文書を抽出して解析するなど用途も様々です。その難しい処理を行ってくれるのが、janomeのAnalyzerというわけです。
Analyzerの主な前処理と後処理は以下の通りです。
前処理
文章の抽出、不要な文字列の削除(HTMLタグなど)、文字種の統一(英字は全て英小文字にするなど)、スペルミス・変換ミスなどによる表記ゆらぎの補正などをします。
後処理
分かち書き後の字句(トークン)を対象としており、数字の置換(数字の名詞は全て0に置き換えるなど)、特定の品詞のみの抽出をします。
Analyzerは、以下の3つを組み合わせて使用します。
- 文字の正規化などの前処理を行うCharFilter
- 形態素解析後の後処理を行うTokenFilter
- 分かち書きされたトークン単位で処理するTokenFilter
CharFilter
UnicodeNormalizeCharFilter | Unicodeを正規化することで表記ゆれを吸収する
|
RegexReplaceCharFilter | 正規表現パターンにマッチした文字列を置換する |
TokenFilter
POSKeepFilter | 取得するトークンの品詞を指定する(それ以外は読み捨てる) |
POSStopFilter | 読み捨てるトークンの品詞を指定する(それ以外は取得する) |
LowerCaseFilter | 英字を小文字にする |
UpperCaseFilter | 英字を大文字にする |
CompoundNounFilter | 複合名詞を作る |
ExtractAttributeFilter | トークンから抽出する属性(“surface”や”base_form”など)を指定します |
TokenCountFilter | 出現回数をカウント |
基本的な使い方
from janome.tokenizer import Tokenizer from janome.analyzer import Analyzer from janome.charfilter import * from janome.tokenfilter import * t = Tokenizer() s = '<div>ABCとabcとパイソンとパイソンと1、2、3、4と一、二、三</div>' for token in t.tokenize(s): print(token)
ABC 名詞,一般,*,*,*,*,ABC,*,*
と 助詞,並立助詞,*,*,*,*,と,ト,ト
abc 名詞,固有名詞,組織,*,*,*,abc,*,*
と 助詞,並立助詞,*,*,*,*,と,ト,ト
パイソン 名詞,一般,*,*,*,*,パイソン,*,*
と 助詞,並立助詞,*,*,*,*,と,ト,ト
パイソン 名詞,一般,*,*,*,*,パイソン,*,*
と 助詞,並立助詞,*,*,*,*,と,ト,ト
1 名詞,数,*,*,*,*,1,*,*
、 記号,読点,*,*,*,*,、,、,、
2 名詞,数,*,*,*,*,2,*,*
、 記号,読点,*,*,*,*,、,、,、
3 名詞,数,*,*,*,*,3,*,*
、 記号,読点,*,*,*,*,、,、,、
4 名詞,数,*,*,*,*,4,*,*
と 助詞,格助詞,引用,*,*,*,と,ト,ト
一 名詞,数,*,*,*,*,一,イチ,イチ
、 記号,読点,*,*,*,*,、,、,、
二 名詞,数,*,*,*,*,二,ニ,ニ
、 記号,読点,*,*,*,*,、,、,、
三 名詞,数,*,*,*,*,三,サン,サン
名詞,サ変接続,*,*,*,*,,*,* div 名詞,一般,*,*,*,*,div,*,* > 名詞,サ変接続,*,*,*,*,>,*,*
このままだと無駄な単語があり適切なデータ分析ができません。</
やdiv
といったHTML部分は正規表現で削除するなどの前処理を行います。全角を半角にして統一したりすると単語数をチェックしたりするのにも便利です。
主な前処理として以下の3つです。
- 全角を半角に変換
- 正規表現によりHTMLタグを消去(空文字列で置換)
- 名詞のみを抽出してアルファベットを小文字化、’surface’属性のみを抽出
char_filters = [UnicodeNormalizeCharFilter(), #Unicodeを正規化することで表記ゆれを吸収 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)
abc
パイソン
パイソン
1
2
3
4
一
二
三
英語のABCがabcに、パイソンがパイソンへと置換されました。全角、半角で固有名称に差があるという事は稀だと思うので、解析にも役立つと思います。
複合名詞
解析をしていて大切だなと思ったのは「意味の変わってしまう単語をどうするか」です。例えば、自然言語と自然では意味合いも変わってきます。しかし、形態素解析では、「自然言語」を「自然」と「言語」に分ける処理が行われます。その別れてしまう名詞を一つにするのが複合化です。
CompoundNounFilter()による複合名詞化が可能です。
s = '自然言語処理は大変です。日本語は四字熟語や話し言葉が多いからです。' for token in t.tokenize(s): print(token)
言語 名詞,一般,*,*,*,*,言語,ゲンゴ,ゲンゴ
処理 名詞,サ変接続,*,*,*,*,処理,ショリ,ショリ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
大変 名詞,形容動詞語幹,*,*,*,*,大変,タイヘン,タイヘン
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
四 名詞,数,*,*,*,*,四,ヨン,ヨン
字 名詞,接尾,助数詞,*,*,*,字,ジ,ジ
熟語 名詞,一般,*,*,*,*,熟語,ジュクゴ,ジュクゴ
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
話し言葉 名詞,一般,*,*,*,*,話し言葉,ハナシコトバ,ハナシコトバ
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
多い 形容詞,自立,*,*,形容詞・アウオ段,基本形,多い,オオイ,オーイ
から 助詞,接続助詞,*,*,*,*,から,カラ,カラ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
「自然言語」「四字熟語」などが想定どおり別れていますね。これを複合名詞化したいと思います。CompoundNounFilter()
を使用します。
a = Analyzer(token_filters=[CompoundNounFilter()]) for token in a.analyze(s): print(token)
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
大変 名詞,形容動詞語幹,*,*,*,*,大変,タイヘン,タイヘン
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
四字熟語 名詞,複合,*,*,*,*,四字熟語,ヨンジジュクゴ,ヨンジジュクゴ
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
話し言葉 名詞,一般,*,*,*,*,話し言葉,ハナシコトバ,ハナシコトバ
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
多い 形容詞,自立,*,*,形容詞・アウオ段,基本形,多い,オオイ,オーイ
から 助詞,接続助詞,*,*,*,*,から,カラ,カラ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
単語もなんとなくきれいに意図している固有名詞になっています。
単語の出現回数のカウント
TokenCountFilter()
で簡単に単語の出現回数をカウントできます。また、POSKeepFilter
、POSStopFilter
といった他のフィルターと組み合わせることが可能です。
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)
(‘自然’, 1)
(‘言語’, 1)
(‘処理’, 1)
(‘日本国’, 1)
(‘憲法’, 1)
(‘形態素’, 1)
(‘解析’, 1)
(‘非常’, 2)
(‘大変’, 1)
リスト化したい場合はlist()を使う。上述のcollections.Counterのmost_common()メソッドの返り値と同じ。
l_count = list(a.analyze(s)) print(type(l_count)) # <class 'list'> print(l_count)
[(‘自然’, 1), (‘言語’, 1), (‘処理’, 1), (‘日本国’, 1), (‘憲法’, 1), (‘形態素’, 1), (‘解析’, 1), (‘非常’, 2), (‘大変’, 1)]
その他
そのままの形態素解析の単語数と動詞の基本形へ変形した単語数を調べてみます。
s = '走れと言われたので思いっきり走った。とても疲れた。' a01 = Analyzer(token_filters=[TokenCountFilter()]) print(list(a01.analyze(s))) print('=' * 10) a02 = Analyzer(token_filters=[TokenCountFilter(att='base_form')]) print(list(a02.analyze(s))) for i in text: print("======================") print(i) for token in t.tokenize(i): print(" " + token.surface + " | " + token.base_form)
==========
[(‘走る’, 2), (‘と’, 1), (‘言う’, 1), (‘れる’, 1), (‘た’, 3), (‘ので’, 1), (‘思いっきり’, 1), (‘。’, 2), (‘とても’, 1), (‘疲れる’, 1)]======================
走れと言われたので思いっきり走った。とても疲れた。
走れ | 走る
と | と
言わ | 言う
れ | れる
た | た
ので | ので
思いっきり | 思いっきり
走っ | 走る
た | た
。 | 。
とても | とても
疲れ | 疲れる
た | た
。 | 。
最後に
janome.Analyzerだけでも高機能なことがわかりました。英語と比べて日本語は非常に複雑です。この面倒な前処理を簡単に実行してくれるモジュールはどんどん実戦で使っていきたいと思います。Mecabと迷うな・・・
参考:
https://www.tutorialfor.com/questions-138610.htm
https://medium.com/@mocobeta/janome-0-3-4-release-63ed21f4fda9
Janome v0.4 documentation (ja)