はじめに
具体的にどうという話ではないのですが、一部の界隈では、貧弱な環境でgrepコマンドのみで様々なテキスト調査、下手をすれば人間をAIに見立てたNLPを実施しなければならんということが無いでしょうか。
貧弱な環境の解決にはなりませんが、もし近くに多少自由になる検索エンジンがあるなら、便利な調査アプローチをとることができる、こんな使い方もあるのではということで、書き綴ってみます。
確認はElasticsearch6.4で実施していますが、実験というほど深掘りはしてないので、タイトル負けしているかもしれません。ご容赦ください。
世のgrep調査ニーズの実態は検索エンジンでの検索行為に近いのではないかという仮説
もう少し補足します。
検索エンジンでは、次のような、grepに比べてある種のあいまいな検索に対応しています。
また、次のように、クエリのシンタックスそのものや検索条件の作り方が、冒頭のような「多少幅のある探し物ニーズ」そのものを表しているため、grepで黒魔術な正規表現等を繰り出す必要がありません。 (もちろん、grepの世界観はそれはそれで素晴らしいのですが...)
- クエリーのシンタックスが(SQLに慣れた人からすると少し分かりにくい面もあるが)全文検索寄りの用途として見ると分かりやすい*1。
- 複数インデックスの串刺し、複数フィールドを対象に同じ検索条件で検索しやすい
- 検索条件無しでも検索しやすい
実際にこれらの用途のためにElasticsearchにデータを入れ込むかはともかく、サイト内検索に対応している場合などは、それを利用しても良いのではないでしょうか。
... というところを(実際もっと高度にやっている方達もいるでしょうが)声に出して言ってみたくなったので荒削りながら以下に例としてあげてみました。
丸囲み文字などできれば使って欲しく無い表記をマルッと調査
Webサイトの記事などでは、厳密には規定していないものの、ライティングの都合やもろもろの理由で使用してほしくない文字を後から調査するというような場合もあると思います。*2
例えば、こちらの文字などです。
https://matome.naver.jp/odai/2134260789846270701/2134261371246625403
grepで対応している正規表現ではこれらの飛び地になっている文字を指定するとなると「OR」型の表現*3となりやたらめんどくさいのですが、検索エンジンでの検索クエリだと、ANALAYZERが良くある設定になっていることが前提ですが、
POST car_and_animal/_search { "query" : { "match": { "title_ja": "㋐ ㋑ ㋒ ㋓ ㋔ ㋕ ㋖ ㋗ ㋘ ㋙ ㋚ ㋛ ㋜ ㋝ ㋞ ㋟ ㋠ ㋡ ㋢ ㋣ ㋤ ㋥ ㋦ ㋧ ㋨ ㋩ ㋪ ㋫ ㋬ ㋭ ㋮ ㋯ ㋰ ㋱ ㋲ ㋳ ㋴ ㋵ ㋶ ㋷ ㋸ ㋹ ㋺ ㋻ ㋼ ㋽ ㋾" } } }
などとすれば、ここにあげた文字のいずれかを含むドキュメントを検知できます。
同じく、ここでは詳しく書きませんが、Elasticsearchの「should」の「minimum_should_match」も全文検索らしい用法だと思いますし、世のgrep調査で本当はこんな加減で調査できれば良かったのにという例に当てはまるような気がします。
誤記の可能性があるものを探す
やや変な例ですが、チームに英語ネイティブの人が多く、「ライオン」と表記してほしいところ、「ライアン」としてテキスト登録する人が後を絶ちません。
このような誤記を探します。誤記には稀にですが「ライエン」というものもぼちぼちあり、これも対象としたいです。他にも似たようなものがあるかもしれません。
↓
Elasticsearchで言うと、fuzzyを使います。
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-fuzzy-query.html
POST car_and_animal/_search { "query": { "fuzzy":{ "title_ja":{"value":"ライオン"}}}, } ↓ ”ライオン”そのものもだが、"ライエン"や"ライアン"などが存在するか確認できる。
fuzziness、prefix_length、max_expansions、transpositionsといった曖昧さをコントロールするパラメータもあります。
パラメータで加減できるところも含めて、grepなどで正規表現で同じことをしようとするとかなり難しいですね。 (その分ヒットしてほしくないものも引き連れてヒットするトレードオフはありますが...。)
Term suggesterでも(インデックスでsuggest対象になっているかの手間はありますが)似たような利用方法が可能だと思います。
同じ意味を表す用語が複数の表記にバラついているものがないか
前項のfuzzyでも炙り出せる場合がありますが、ここでは別の方法です。
「渡邊」と「渡部」は、「渡辺」に寄せたいが、これをし損ねているものがあるか...というのが一番端的な例です。
アナライザー(kuromoji)の設定がそれなりになされているとして、その上で、
例えば、
POST car_and_animal/_search { "query" : { "bool": { "must": { "match": { "content.my_analyzed": "ターゲットとしているワード(これに統一したいもの)" } }, "must_not": { "match": { "content.my_not_analyzed": "ターゲットとしているワード(これに統一したいもの)" } } } } }
というクエリで検索します。
ここで、content.my_analyzedとcontent.my_not_analyzedは、それぞれ、データソースのドキュメントに対し、前者が規定のアナライズがされているもの、後者がされていないものになります。
上記に書けた範囲の仕込みだと少し無理がある例かもしれませんが、ちょっとごまかし気味にアイディアの意図を説明すると、標準化すると同じものになるが、実際のドキュメント中は意図したどおりに統一した表記になっていない...というものを検索して探しているといるというイメージになります。
もっと単純に、次のような素直に調査する案も当然あります。
POST car_and_animal/_search { "query" : { "match": { "title_ja": "ライオン" } }, "highlight" : { "fields" : { "title_ja" : {"type" : "plain"} } } } ↓ hits(一部) "title_ja": [ "<em>ライオン</em>は百獣の王で..." ]
でドキュメントが帰ってくるか、また、具体的にマッチした部分をhighlightで識別しやすいようにするというやり方がありそうです。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html
※highlightはもともとは検索結果をユーザー向けに見やすくするものかと思いますが、この類の調査やデバッグに利用するという使い方も実は便利そうです。 独自のタグで囲んでおけば、結果をそのまま後続の別の調査で取り扱いやすいと思います。
ドキュメントがkuromoji辞書に無い言葉を使っていないか(≒できるだけ標準的・平易で無難な言い回しをしているか/ミーハーにならないように新語は控えめか。)
kuromojiの辞書のありようを逆手に取って、ライティングが一定の範囲に収まっているかを発見的に確認するという発想です。
次のような、未知語と名詞関連以外を除去するようなアナライザの設定を行います。
"my_kuromoji": .... (中略) "filter":[ "my_filter" ] (中略) "filter":{ "my_filter":{ "type":"kuromoji_part_of_speech", "stoptags" :[ 未知語と名詞関連以外は除去 ※名詞関連は多少取捨選択した方が良いかもしれないが、ここでは説明を簡単にするため名詞は全般的に残すこととした。 ] }}
作成した文書をアナライズします。
POST grep/_analyze { "analyzer": "my_kuromoji", "text": "どこべろずんちょは大人しいが怒らせると襲いかかってくる" } ↓ トークンの一覧が返ってきます。上述の設定からいうとドキュメント中の未知語と名詞の一覧が返ってきます。 { "tokens": [ { "token": "どこ", "start_offset": 0, "end_offset": 2, "type": "word", "position": 0 }, { "token": "べろ", "start_offset": 2, "end_offset": 4, "type": "word", "position": 1 }, { "token": "ちょ", "start_offset": 6, "end_offset": 8, "type": "word", "position": 3 } ] }
返ってきたトークンの件数やワードに対して仮の閾値と比較して、数が多いようであればアラームをあげるというような仕組みが考えられると思います。
他にもありそう...
以下は、ちゃんと考えていませんが、こんなものもうまくやれば生のgrepよりいろいろ気の利いたことができるかも(ただし、ANALYZERの多少トリッキーな助けも必要かも...)な例としてありそうかなというところをキーワードだけですがあげてみました。ちょっと盛ってるかもですが...
表記ゆれ日付の記述を探す
全半角混在などにもある程度強いため。
てにおはがおかしい(かもしれない)ものを探す/文体のおおよその統一
フレーズ検索系のクエリの邪道(?)な使い方や、char_filterで大胆な名寄せ相当および先述のkuromoji_part_of_speechでの特定の品詞除外を組み合わせると文章の骨格が抜き出せる、あるいは外骨格の方を残すことで、てにおはがおかしいものを探したり、文体が叙述式にある程度統一されているかの確認。
なんとなくこんな表現の範囲におさまってそうかのチェック
略。前項と発想は同じ。この記事のアウトラインを考えた時には別にしようとして分けてみたものの、事例を忘れてしまったので、無理に分けなくても良かったかも...
無駄なコピペコード、類似コードの検索
More Like Thisか、フレーズ検索をぶち込むかというところ。
腰を据えてやるなら、リファクタリングツールを使った方が良いでしょうが...
プログラムっぽい記述
いわゆるググラビリティが低いプログラムの記述の横並び調査など。
エラーログの本格分析前の下調べ
定型フォーマットでしかも絶えず流れ続けるようなログは、近いところで言うと例えばkibanaで楽しく分析やML的な手法で検知できるのだと思います。
ただ、javaのスタックトレースなど、明らかに異常が発生して今まで出なかったようなエラーログが1GB吐き出された...のような場合に、膨大なログテキストの中から、まずは注目すべき箇所をあぶり出すにあたって、マッチングのクエリを駆使するとログを読み解く手がかりが得られるのではと考えています。
時系列分析の前のPoCoC
PoCoCはこの場限りの造語です。PoC活動自体のProofです。
前項で言っていることと近い話で、例えばkibanaで時系列分析(のPoC)をする前に、雑にデータをインデックスに取り込んでおき、ここまであげたような検索エンジンの使い方等を組み合わせて、そもそもどんなログフォーマットやデータ内容が多数決的にありそうなのか、kibanaへの取り込み設計の方向性を見定める...というようなところに使えないかという考えでここにあげてみました。
four-letter wordのチェック
他と毛色がやや違いますが、作成した文書でNGワードを使用していないかのチェック...というようなケースにおいて、RDBでの対応表みたいなものの厳格な管理をしなくとも、NGワードやNGワードの表記揺れのワードを一つのドキュメントに気まぐれにつっこんでやり、このドキュメントに対して、OR検索をしたり、More Like Thisクエリーで検索をして、ヒットするかどうかで、ほどほどのNGワードチェックができると考えています。
さいごに
実のところ、この類の調査がどこまでリアルな例かはともかく、例えばとある検索サイトやサイト内検索では(仕様のシンプル化の方針などにより)あえて使用しなかったElasticsearchのリッチな検索クエリの使い方を掘り下げる良い思考実験となりました。 (あくまで私にとっては...ですが。)
また、上記の例では例のための例ということと私のスキルの都合で、アナライザーのプラグインを自作する領域までは踏み込みませんでしたが、軽微な文字変換のプラグインを自作すると、文字どおりプラグイン的に、少々気の利いたgrepパターンを積み上げていくこともできそうです。
このブログの片隅でもまとめることで、Elasticsearchの素晴らしい機能をしゃぶりつくせる小さなきっかけとなるようなものがあれば、改めて書き出して見たいとおもいます。
以上