ELSには日本語解析プラグインが用意されています。
Japanese (kuromoji) Analysis Plugin | Elasticsearch Plugins and Integrations [6.1] | Elastic
Java等で品詞分解する事ももちろんできますが、ELSのテンプレートを利用する事で、Kibana上で品詞分解表示などが出来ます。
この記事では、まずELSのテンプレートで登録するところまでをやります。
プラグインインストール
Elasticsearchの日本語の形態素解析をする際に利用されるkuromojiは非常に便利ですが、その辞書であるIPADICは更新が止まっているためやや古い状態です。
その辞書を更新してくださった方がいらっしゃり、neologdとして公開されているためそれを導入して新語でもきちんと解析できるようにします。
neologdでkuromojiを新語に対応させる - Carpe Diem
codelibs/elasticsearch-analysis-kuromoji-neologd: Elasticsearch's Analyzer for Kuromoji with Neologd
という訳で、elasticsearch-analysis-kuromoji-neologd
をインストールします。
手順は上記サイト等を参考にしてください。
基本的な動作確認も上記サイトに記載されています。
#本当はSudachi
も試してみたかったのですが、うちのPCスペック貧弱で、辞書読み込むとOut of Memory
になっちゃうんです……
文字列解析の定義方法
ELSの文字列解析は階層構造(?)になっています。
文字列解析方法をカスタマイズする場合、この構造に沿って定義を行っていきます。
ElasticsearchのAnalyzer, Tokenizer, Token Filters, Char Filtersの一覧 - Qiita
Elasticsearch 日本語で全文検索 その2 - Hello! Elasticsearch. - Medium
1.Tokenizer で、どのような形態素解析を行うかを定義する
"tokenizer": {
"ja_neologd_tokenizer": {
"mode": "search",
"type": "kuromoji_neologd_tokenizer"
}
}
上記の場合、ja_neologd_tokenizer
という名前で、search
モード(分解の粒度)
でkuromoji_neologd_tokenizer
という形態素解析手法(プラグインに依る)を行う、と定義しています。
2.Filterで、どのようなフィルタ(文字列の変換・除外)を行うかを定義する
"greek_lowercase_filter": {
"type": "lowercase",
"language": "greek"
}
上記の場合、greek_lowercase_filter
という名前で、アルファベットを小文字にする、という定義をしています。
kuromoji_neologd_part_of_speech
というフィルターが品詞分解の時には重要になってくるのですが、ここでは、とりあえずフィルターは自前で定義することが出来る、という事を覚えていてください。
3.Analyzer で、どのTokenizerとFilterを用いるかを組み合わせて定義する
"ja_neologd_analyzer": {
"filter": [
"greek_lowercase_filter",
"cjk_width"
],
"char_filter": [
"icu_normalizer"
],
"type": "custom",
"tokenizer": "ja_neologd_tokenizer"
}
上記の場合、ja_neologd_analyzer
という名前で、analyzer
を定義しています。
analyzer
の内部で、どのようなtokenizer
(解析手法)を使用し、どのようなfilter
(変換・除外)をかけるか、を組み合わせる事ができます。
これによって、一概に文字列解析、といっても、用途に応じて様々なパターンの解析を行う事ができるようになります。
filter
の中には、cjk_width
(全半角の統一)など、フィルタ定義を自前で行っていないものもあります。
これらは、ELSにバンドルされているものや、インストールしたプラグインにデフォルトで用意されているものなどです。
上記例を、日本語訳すると以下のような感じです。
新しい感じの日本語(kuromoji_neologd_tokenizer)を、
検索にちょうどいい感じで分解(search)してちょうだい。
アルファベットは小文字(greek_lowercase_filter)で、
全半角は統一(cjk_width)してほしいな。
後、いい具合に正規化(icu_normalizer)してね!
テスト
上記analyzerを使って、文章の解析を行ってみましょう。
例文1)
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_analyzer",
"text": "3センチのNOTEBOOKって、アリ?"
}
{
"tokens": [
{
"token": "3センチ", <-- 半角数字+全角カタカナ
"start_offset": 0,
"end_offset": 4,
"type": "word",
"position": 0
},
{
"token": "の",
"start_offset": 4,
"end_offset": 5,
"type": "word",
"position": 1
},
{
"token": "notebook", <-- 小文字
"start_offset": 5,
"end_offset": 13,
"type": "word",
"position": 2
},
{
"token": "って",
"start_offset": 13,
"end_offset": 15,
"type": "word",
"position": 3
},
{
"token": "アリ", <-- 全角統一
"start_offset": 16,
"end_offset": 18,
"type": "word",
"position": 4
}
]
}
例文2)
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_analyzer",
"text": "3㌢のNotebookって、アリ?!"
}
{
"tokens": [
{
"token": "3センチ", <-- 半角数字+全角カタカナ
"start_offset": 0,
"end_offset": 2, <-- 文字数は元の文章でカウント
"type": "word",
"position": 0
},
{
"token": "の",
"start_offset": 2,
"end_offset": 3,
"type": "word",
"position": 1
},
{
"token": "notebook", <-- 小文字
"start_offset": 3,
"end_offset": 11,
"type": "word",
"position": 2
},
{
"token": "って",
"start_offset": 11,
"end_offset": 13,
"type": "word",
"position": 3
},
{
"token": "アリ", <-- 全角統一
"start_offset": 14,
"end_offset": 16,
"type": "word",
"position": 4
}
]
}
…と、このように、文章の表記ゆれも吸収して、形態素解析ができました。
#句読点はkuromoji_neologd_tokenizer
内のdiscard_punctuation
オプションのデフォルト値として「句読点を除外する」という設定の為、除外されています。
品詞分解
上記のanalyzerでいい感じに形態素解析ができるようになりましたが、「の」や「って」など、それ単体では意味をなさない言葉も出力されています。
「名詞だけ抜き出したい」「動詞のホットワードが見たい」などといった要件にも対応すべく、ELS上のみで品詞分解を定義していきます。
kuromoji_neologd_part_of_speech (stoptags)
kuromoji_neologd_part_of_speech
フィルターは、文字列を分解した後の各要素に対して処理を行うフィルターです。
オプションとして、stoptags
というものがあり、ここに定義された品詞のみ除外する事ができます。
stoptags
のデフォルト定義は以下のように、助詞とか記号が除外されています。
stoptags一覧
stoptags
の設定で重要なのが、
- 「階層構造指定っぽいけど、すべて並列で指定する」
- 「カスタムで定義した場合、デフォルト設定は破棄される(継承されない)」
という事です。
「名詞」をstoptags
に指定しても「名詞-一般」はそれに含まれません。
「助動詞」のみをstoptags
に指定した場合、「助詞」等デフォルトで除外されていた品詞は解析結果に含まれるようになります。
品詞分解テンプレート
上記をふまえて、必要な品詞を抽出し、それを省いた除外リストを作成します。
で、品詞分解アナライザを加えたテンプレート作成用JSONがこちらになります。長いのでgithubに投げました。
アナライザ
ここで作成した品詞分解アナライザは以下の4つです。
- ja_neologd_meishi_analyzer … 名詞のみ抽出対象としたアナライザ
- ja_neologd_pos_meishi_filter
- ja_neologd_doushi_analyzer … 動詞のみ抽出対象としたアナライザ
- ja_neologd_pos_doushi_filter
- ja_neologd_keiyoushi_analyzer … 形容詞のみ抽出対象としたアナライザ
- ja_neologd_pos_keiyoushi_filter
- ja_neologd_fukushi_analyzer … 副詞のみ抽出対象としたアナライザ
- ja_neologd_pos_fukushi_filter
それぞれのアナライザには、kuromoji_neologd_baseform
フィルタを加えています。
これは、「動きます」→「動き」「ます」→「動く」「ます」と、分解後に要素の原型に変換するフィルタです。
マッピング
マッピングでは、_txt
が末尾にある項目に対して、各品詞分解アナライザをかけるように設定しています。
"analyzed-meishi": {
"type": "text",
"fielddata": true,
"store": true,
"analyzer": "ja_neologd_meishi_analyzer",
"search_analyzer": "ja_neologd_meishi_analyzer"
}
- type … 項目の種別
- fielddata … 解析結果を、ソートやAggsに用いる場合、true。→ 公式
- store … 解析結果を格納する場合、true。→ 公式
- analyzer … 登録時のアナライザ。→ 公式
- search_analyzer … 検索時のアナライザ。→ 公式
テスト
名詞バージョン
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_meishi_analyzer",
"text": "春の小川はさらさら流る。 岸のすみれやれんげの花に、: 匂いめでたく、色うつくしく: 咲けよ咲けよと、ささやく如く"
}
{
"tokens": [
{
"token": "春の小川",
"start_offset": 0,
"end_offset": 4,
"type": "word",
"position": 0
},
{
"token": "岸",
"start_offset": 13,
"end_offset": 14,
"type": "word",
"position": 4
},
{
"token": "すみれ",
"start_offset": 15,
"end_offset": 18,
"type": "word",
"position": 6
},
{
"token": "れんげ",
"start_offset": 19,
"end_offset": 22,
"type": "word",
"position": 8
},
{
"token": "花",
"start_offset": 23,
"end_offset": 24,
"type": "word",
"position": 10
},
{
"token": "匂い",
"start_offset": 28,
"end_offset": 30,
"type": "word",
"position": 12
},
{
"token": "色",
"start_offset": 35,
"end_offset": 36,
"type": "word",
"position": 14
}
]
}
#春の小川、で一単語になってますね。なんでだろ…
動詞バージョン
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_doushi_analyzer",
"text": "春の小川はさらさら流る。 岸のすみれやれんげの花に、: 匂いめでたく、色うつくしく: 咲けよ咲けよと、ささやく如く"
}
{
"tokens": [
{
"token": "流る",
"start_offset": 9,
"end_offset": 11,
"type": "word",
"position": 3
},
{
"token": "咲く",
"start_offset": 43,
"end_offset": 45,
"type": "word",
"position": 16
},
{
"token": "咲く",
"start_offset": 46,
"end_offset": 48,
"type": "word",
"position": 18
},
{
"token": "ささやく",
"start_offset": 51,
"end_offset": 55,
"type": "word",
"position": 21
}
]
}
形容詞バージョン
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_keiyoushi_analyzer",
"text": "春の小川はさらさら流る。 岸のすみれやれんげの花に、: 匂いめでたく、色うつくしく: 咲けよ咲けよと、ささやく如く"
}
{
"tokens": [
{
"token": "めでたい",
"start_offset": 30,
"end_offset": 34,
"type": "word",
"position": 13
},
{
"token": "うつくしい",
"start_offset": 36,
"end_offset": 41,
"type": "word",
"position": 15
}
]
}
副詞バージョン
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_fukushi_analyzer",
"text": "春の小川はさらさら流る。 岸のすみれやれんげの花に、: 匂いめでたく、色うつくしく: 咲けよ咲けよと、ささやく如く"
}
{
"tokens": [
{
"token": "さらさら",
"start_offset": 5,
"end_offset": 9,
"type": "word",
"position": 2
}
]
}
いい感じに分解できました!stoptags
は色々試してみると面白いです。
Kibanaでの表示も一緒に書こうと思ってましたが、長くなってしまったので、また次回。
1/17 追記
GET sample-analyze-qiita/_analyze
{
"analyzer": "ja_neologd_doushi_analyzer",
"explain" : true,
"text": "春の小川はさらさら流る。 岸のすみれやれんげの花に、: 匂いめでたく、色うつくしく: 咲けよ咲けよと、ささやく如く"
}
_analyze
の際に、explain
オプションをtrue
にすると、analyzer
の内部で、tokenizer
やfilter
がどのようにかかっていっているかを、ステップごとに確認することができます。
"tokenizer": {
"name": "ja_neologd_tokenizer",
"tokens": [
{
"token": "春の小川",
"start_offset": 0,
"end_offset": 4,
"type": "word",
"position": 0,
"baseForm": null,
"bytes": "[e6 98 a5 e3 81 ae e5 b0 8f e5 b7 9d]",
"inflectionForm": null,
"inflectionForm (en)": null,
"inflectionType": null,
"inflectionType (en)": null,
"partOfSpeech": "名詞-固有名詞-一般",
"partOfSpeech (en)": "noun-proper-misc",
"positionLength": 1,
"pronunciation": "ハルノオガワ",
"pronunciation (en)": "harunoogawa",
"reading": "ハルノオガワ",
"reading (en)": "harunoogawa"
},
「春の小川」は、固有名詞として解析されたので、一区切りになっているようですね。
謎が解けてスッキリしました!
johtani様、コメントありがとうございました。