Skip to content

Instantly share code, notes, and snippets.

@dakeshi19
Created May 24, 2020 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dakeshi19/1c3cf576e06b9e17e591d605a42f99e3 to your computer and use it in GitHub Desktop.
Save dakeshi19/1c3cf576e06b9e17e591d605a42f99e3 to your computer and use it in GitHub Desktop.
Elasticsearchの日本語関係のanalyzer設定のJsonテキストを出力するツール(ただし汎用ではなく、筆者のフェイバリット設定です)
def set_analyzer(an, tk, fl):
# ======================== ここからtokenizerとfilterの定義 ===============================
def _set_filter_and_tokenizer(tk, fl):
デフォルトとするtoken_chars = [ # N-Gramで分かち書きするフィールドでターゲットとする文字種(最近、symbolは入れる方が良いと思い始めている)
"letter",
"digit",
"symbol"
]
tk.update({
# kuromoji
"kuro_tk": {
"type": "kuromoji_tokenizer",
"mode": "search"
},
# N-Gram
"ng_tk": {
"type": "ngram",
"min_gram": 2,
"max_gram": 3,
"token_chars": デフォルトとするtoken_chars
},
# Edge N-Gram
"eNg_tk": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 10
}
})
fl.update({
# ひらがなをカタカナに変換するfilter
"hiragana_2_katakana": {
"type": "icu_transform",
"id": "Hiragana-Katakana"
},
# edge_ngramをfilterで使うと、kuromojiとedge_ngramを条件付きだが同時対応したようなアナライザーが作れる。→ mp_filter参照
"eNgram_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 10
},
# filterにngramが使えることの確認のため。ただし、今回の例ではこれを使ったanalyzer定義はない。
"ngram_filter": {
"type": "ngram",
"min_gram": 2,
"max_gram": 3,
"token_chars": デフォルトとするtoken_chars
},
# 形態素解析時(kuromojiを想定)の最初の1トークンを取得する...に近い動作をさせるfilter
"getInitial_filter": {
"type": "predicate_token_filter",
"script": {
"source": "token.getStartOffset() === 0"
}
},
# 形態素解析をを行うとともに、読み仮名をあてがったりしつつ、さらに実質N-Gramにも同時対応させるためのfilter
"mp_filter": {
"type": "multiplexer",
"filters": [
"eNgram_filter",
"kuromoji_readingform, eNgram_filter",
"kuromoji_readingform, hiragana_2_katakana, eNgram_filter"
]
}
})
_set_filter_and_tokenizer(tk, fl)
# ======================== ここからanalyzerの定義 ===============================
def _def_anlz(_tk, _cf, _fl):
return {
"type": "custom",
"tokenizer": _tk,
"char_filter":_cf,
"filter": _fl
}
## charfilter (筆者は「charfilter」のカスタム作成はしない派。(よく分かっていないのもあるが...)
cf1 = [ #筆者が kuromojiで形態素解析をかけるためのcharfilterとして 適していると考えているものの一覧
"icu_normalizer",
"kuromoji_iteration_mark", #
"html_strip"
]
cf2 = [ #筆者が 英語系やN-Gramの場合のcharfilterとして 適していると考えているものの一覧
"icu_normalizer",
"html_strip"
]
## 拡張子とアナライザーの対応づけを格納
# (このツール固有のワザであり、mappings/settingsとして必須の仕組みというわけではない)
ext_x_anlz = {}
# ----------ここから実際の定義開始--------------------
# whitespace(スペース区切りで単語分割する以外、実質何もしない。検索時analyzeで入力語を尊重したい時に使うかも)
_name = "whitespace"
an[_name] = _def_anlz("whitespace", cf2, [])
ext_x_anlz["ws"] = _name
# 筆者が、日本語フリーテキストの表記揺れ考慮のバランスが良いと考えているアナライザー設定。ただし、ひらがなやカタカナが多い固有名詞は苦手
_name = "ja-default_anlz"
an[_name] = _def_anlz( "kuro_tk",cf1,[
"kuromoji_baseform",
"kuromoji_part_of_speech",
"ja_stop",
"lowercase",
"kuromoji_number",
"kuromoji_stemmer"
])
ext_x_anlz["ja-default"] = _name
# 固有名詞やひらがなに強い...というわけではないが、形態素解析しつつも極力何もしない(元の単語やストップワードらしきものも残置する)ことにより、
# 辞書に無い単語もほどほどにヒットさせる。
_name = "ja1_anlz"
an["ja1_anlz"] = _def_anlz("kuro_tk", cf1,[
"lowercase",
"kuromoji_stemmer"
])
ext_x_anlz["ja1"] = _name
# ja-defaultよりは厳格だがja1よりは表記揺れを許容
_name = "ja2_anlz"
an["ja2_anlz"] = _def_anlz("kuro_tk", cf1,[
"kuromoji_baseform",
"lowercase",
"kuromoji_stemmer"
])
ext_x_anlz["ja2"] = _name
# 読み仮名をあてがう
_name = "jaReadingform_anlz"
an[_name] = _def_anlz("kuro_tk",cf1,[
"kuromoji_readingform",
"lowercase",
"hiragana_2_katakana", ## filter定義参照
"kuromoji_stemmer"
])
ext_x_anlz["jaRf"] = _name
# N-Gram(日本語に向くと言われている 2,3グラム)
_name = "ng_anlz"
an[_name] = _def_anlz("ng_tk",cf2,[
"hiragana_2_katakana" ## filter定義参照
])
ext_x_anlz["ng"] = _name
# Edge N-Gram
_name = "eNg_anlz"
an[_name] = _def_anlz("eNg_tk",cf2,[
"lowercase",
"hiragana_2_katakana" ## filter定義参照
]
)
ext_x_anlz["eNg"] = _name
# ひらがなやカタカナでの.... ちょっとしたトリック入りのアナライザー0(サジェストに使えるかも)
_name = "jaR_x_eNg_anlz"
an[_name] = _def_anlz(
"kuro_tk",cf2,[
"kuromoji_readingform",
"lowercase",
"hiragana_2_katakana", ## filter定義参照
"eNgram_filter" ## filter定義参照
])
ext_x_anlz["jaR_x_eNg"] = _name
# ひらがなやカタカナでの.... ちょっとしたトリック入りのアナライザー1(サジェストに使えるかも)
_name = "yomiInitial"
an[_name] = _def_anlz(
"kuro_tk",cf2,[
"kuromoji_readingform",
"lowercase",
"hiragana_2_katakana", ## filter定義参照
"getInitial_filter" ## filter定義参照
])
ext_x_anlz["yomiInitial"] = _name
# ひらがなやカタカナでの.... ちょっとしたトリック入りのアナライザー2(サジェストに使えるかも)
_name = "mp_anlz"
an[_name] = _def_anlz(
"kuro_tk",cf2,[
"mp_filter" ## filter定義参照
])
ext_x_anlz["mp"] = _name
# ひらがなをカタカナに読み換える以外に何もしない(検索時analyzeでの利用を想定)
_name = "almostNoop_anlz"
an[_name] = { # tokenizerが「keyword」なので、他のものと違いそれに合わせた定義
"type": "custom",
"tokenizer": "keyword",
"filter": [
"hiragana_2_katakana" ## filter定義参照
]
}
#ext_x_anlz["xxxx"] = _name #拡張子対応表は見送り
# 拡張子 x 推奨アナライザー名を戻す
return ext_x_anlz
"""
Elasticsearchの日本語関係のanalyzer設定のJsonテキストを出力するツール
・Python 3.x
・想定Elasticsearch 6.8
  (※ ただし、どちらかと言えば、ver 7.xでワーニングがでない方に寄せているつもり。)
・出力されたJsonをkibanaに貼り付けして実行する前提です。
・なお、ツールといっても、対話的というわけでもシンタックスチェックを行うわけでもないです。
ジェネレータのような面もありますが、それほど柔軟ではなく、PythonのdictからJsonに変換しているだけです。
というのも、ごく稀に、myフェイバリット設定をアップデートやマイナーチェンジしたい時があるのですが、
Jsonファイルのプロパティ間の関係を思い出すのに苦ししているので、腹がかかえの秘伝のJsonにコメントを付けたり
というのも違うような気がしたので、自分ルールをPythonプログラムに刻みつけておくことにしたというところです。
・mappingの方は、プロトタイプ時の利用を想定しているので、Dynamic Templateに重点をおいた定義です。
見ての通り、この設定を全部かませると、空間効率はあまりよろしく無いのでご注意ください。
"""
import sys
from myAnalyzerConfGeneratorMod import *
import json
true = True # ちょいとズルい方法
tk,fl,an,prps,dt = {},{},{},{},[]
# 基本構造の定義(settingsとmappingsのおおよそ標準のシンタックスの骨組みの俯瞰もかねる) ========
## 以降、tk,flなどを設定した上で、プログラムの最後に標準出力にJsonを出力する。
SETTINGS_AND_MAPPINGS = {
"settings": {
"analysis": {
"tokenizer": tk,
"filter": fl,
"analyzer": an
}
},
"mappings": {
# "_doc": { ## この囲みはEsの7系だと廃止されているようなので、筆者の6系でも「?include_type_name=false」を付けてPUTすることにして、この囲みフィールドは用いていない
"dynamic_templates": dt,
"properties": prps
# }
}
}
# settings ----------------------
ext_x_anlz = set_analyzer(an, tk, fl)
## ここまでのコンフィグのダンプ(デバッグ)
print("生成プロパティなど", file=sys.stderr)
print(tk.keys(), fl.keys(), file=sys.stderr)
print(an.keys(), file=sys.stderr)
print(ext_x_anlz, file=sys.stderr)
# mappings ----------------------
##フィールド名.拡張子でその他のアナライズをかけたフィールドにアクセスできるように定義(する元ネタ)
FIELDS = {
"raw": {
"type": "keyword"
},
"ini": {
"type": "text",
"analyzer": "yomiInitial",
"fielddata": true
}
}
FIELDS.update({
ext: {
"type": "text",
"analyzer": anlz
}
for ext,anlz in ext_x_anlz.items() # リスト内包表記方式でマルッと定義する
})
## dynamic template 
DTの定義名 = "my_hybrid_style_for_string" # ここでは一つしかないが、位置付けをはっきりさせるために定義
自分流デフォルトのアナライザー名 = "ja-default_anlz" # Esで名前が決められているわけではない。変数anで定義されているものをピックアップしている。
dt.append(
{
DTの定義名: {
"match_mapping_type": "string",
"mapping": {
"analyzer": 自分流デフォルトのアナライザー名,
"fielddata": true,
"store": true,
# デフォルト以外は事前定義のものを生成
"fields": FIELDS #上記参照
}
}
}
)
## 個別のtype定義
### 開発期間中だとしても基本的にはワイルドカード的設定をする類のものではないので、個別に型定義するフィールドの設定(GEO系フィールドや時間系フィールド)
def def_props(prps):
GEO検索_GEO_POINTフィールド名 = 'location'
GEO検索_GEO_SHAPEフィールド名 = 'shape'
prps.update( {
GEO検索_GEO_POINTフィールド名 : {
"type": "geo_point"
},
GEO検索_GEO_SHAPEフィールド名: {
"type": "geo_shape",
"strategy": "quadtree" # Es 6系ならおまじないで付けておく
}
})
def_props(prps)
print(json.dumps(SETTINGS_AND_MAPPINGS, indent=4))
print('\n\nkibana貼り付け用サンプル\n')
sampleText = "関西国際空港。 東京都の山寺. すもももももももものうち。 すもも も もも も もも の うち, 渡辺さんちの太郎くんと渡邊さんちの花子さん"
idxName = '/ldgourmet/_analyze'
for i in an.keys():
print('POST /ldgourmet/_analyze\n', json.dumps({
"text": sampleText,
"analyzer": i
},ensure_ascii=False),'\n',file=sys.stderr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment