MeCab
イベント
該当するコンテンツが見つかりませんでした
マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
1年ほど前に、日本語アナライザーを比較する記事を書きました。 前回の記事: 日本語アナライザーの比較(Kuromoji / Sudachi / MeCab / LLM の性能検証) あれから1年がたち、日本語の検索まわりは少しずつ変わりました。 新しい選択肢も出てきましたし、考え方も少し変わりました。 そこで今回は、続編としてもう一度比較します。 ただし、前回とまったく同じことを繰り返すわけではなく、 2026年の今なら、こう測るともっと良い、というやり方で見直します。 今回の検証は「去年と同じ条件での定点観測」ではありません。そのため、今回の数値を前回のものとそのまま比較できません。 検証に使用したプログラム等は GitHub リポジトリで公開しています。同リポジトリ内の GLOSSARY.md にまとめた用語集を参照してください。 目次 この記事で見ること、見ないこと この1年で変わったこと 1. 既存のアナライザーは、ちゃんと進化していた 2. 「Elasticsearch の中で使えるか」で整理すると分かりやすい 3. いちばん大きな変化:形態素解析に頼らない検索 4. LLM は「アナライザー」ではなく「参考枠」 今回の改良点(前回との違い) 再検証:実際に比べてみる 使うテキスト 比べるアナライザー 結果:トークン数 結果:専門用語の扱い 結果:アナライザー間の似ている度合い(Jaccard) 結果:検索クエリでの動作 参考:LLM は何を「キーワード」として拾ったか アナライザーの選び方ガイド まとめ Links この記事で見ること、見ないこと 先に、ゴールを明確に整理します。 読みながら「結局なにを比べているの?」と迷わないためです。 この記事で見ること: それぞれのアナライザーが、日本語をどう単語に区切るか。 検索用のトークンとして、どれが使いやすいか。 専門用語・英数字・単位(例:NSAIDs、300〜500mg)が保たれるか。 実際の検索クエリで、目的の文書がちゃんとヒットするか。 この記事で深くは扱わないこと: 大規模なデータでの検索ランキング評価。 人手による「この結果は正しい」という関連度判定。 LLM を Elasticsearch のアナライザーとして使う構成。 この1年で変わったこと 1. 既存のアナライザーは、ちゃんと進化していた まず押さえたいのは、定番のツールは止まっていない、ということです。 Kuromoji は、Elastic 公式の日本語アナライザーとして引き続き使えます。 Sudachi は、外部プラグイン(Works Applications の elasticsearch-sudachi)として進化を続け、 新しい Elasticsearch にも対応してきました。 Sudachi の辞書は、数か月おきに新語が追加されています。 MeCab や Janome も、Python 前処理用の選択肢として現在も利用されています。 ここで大事なのは、Kuromoji と Sudachi の「立場」が違うことです。 – Kuromoji は Elastic 公式 の Japanese analysis plugin です。 – Sudachi は 外部プラグイン です。 つまり、Sudachi を使うときは、 使っている Elasticsearch のバージョンに対応しているかを必ず確認します。 2. 「Elasticsearch の中で使えるか」で整理すると分かりやすい ここで混乱しやすいのが、「結局どのアナライザーを Elasticsearch で使えるの?」という点です。 実は、全部を同じようには使えません。 3つのグループに分けると分かりやすいです。 Elasticsearch の中で動く(プラグイン):Kuromoji(公式)、Sudachi(外部)。 Elasticsearch の中では動かない:MeCab、Janome、Lindera。 これらは Python などで先にトークン化し、その結果を Elasticsearch に入れて使います。 番外(参考枠):LLM。これはアナライザーとは目的が違います(あとで説明します)。 さらに、実行環境による違いもあります。 Self-Managed(自前で運用): Kuromoji などの公式プラグインは、各ノードに analysis-kuromoji をインストールし、ノードを再起動して使います。外部プラグイン(Sudachi など)も入れられます。 Elastic Cloud Serverless: Kuromoji などの core analysis plugins は最初から利用できます。 一方で、外部プラグインの追加や、独自ファイルのアップロードはできません。 そのため、Sudachi などの外部プラグインや、ファイルとして配置する独自辞書(synonyms / stop words / language analyzer 用 dictionary files など)を前提にした構成は使えません 。 ただし、同義語についてはファイルアップロードではなく、synonyms API を使って管理できます。 3. いちばん大きな変化:形態素解析に頼らない検索 一言でいうと、この1年で「検索のやり方そのもの」に選択肢が増えました。 これまでの日本語検索は、形態素解析で単語に区切り、その単語で探すのが基本でした。 これは今も有効で、なくなりません。 ただ、もう1つの道が実用的になりました。 意味で探す検索(セマンティック検索) です。 仕組みをシンプルにいうと、こうです。 文章を「意味のベクトル(数字の並び)」に変換し、意味が近いものを探します。 このとき、形態素解析で単語に区切る必要はありません。 Elasticsearch では、semantic_text という仕組みと、 EIS(Elastic Inference Service)経由の多言語の埋め込みモデルを使うことで、 日本語でもこの検索がぐっと手軽になりました(例として、EIS では Jina Embeddings v5 系や Microsoft Multilingual E5 Large などの embedding model が利用できます)。 実務で考えると、これは大きいです。 「ロキソニン」と入れなくても、「痛み止め」で関連文書を拾える、というような検索ができます。 本番環境では、既定の inference endpoint に依存せず、利用する埋め込みモデルの inference_id を明示するのが安全です。既定モデルはバージョンや環境によって変わる可能性があり、複数インデックスで異なる embedding model が混在するとランキングに影響するためです。 4. LLM は「アナライザー」ではなく「参考枠」 前回は LLM(当時は GPT-4o)も比較に入れました。 今回も LLM を見ますが、立ち位置をはっきり分けます。 なぜかというと、LLM はインデックス用のトークナイザーとは目的が違うからです。 LLM を、Kuromoji や Sudachi と横並びにして「どれが良いアナライザーか」と比べると、 かえって混乱します。 そこで今回は、LLM を別カテゴリ(参考枠)として、次の点だけ見ます。 専門語を「意味のまとまり」として拾えるか。 検索の補助(キーワード抽出や意味理解)に使えそうか。 ここで、混同しやすい点を1つ整理します。 「LLM によるトークン分割は再現性がない」という声もありますが、必ずしもそうとは限りません。 LLM の tokenizer そのものは、同じ条件なら基本的に同じ結果になります。 バラつくのは、「重要語を抜き出して」とお願いしたときの 生成結果 のほうです。 なので今回は、再現できるように、モデル名・プロンプト・temperature を記録します。 今回の改良点(前回との違い) 前回より良くした点を、正直に宣言します。 詳しくは METHODOLOGY.md を見てください。ここでは要点だけ。 正規化を「入口」でそろえる。 Python で NFKC 正規化を1回だけかけ、同じ入力を全アナライザーに渡します。 機能語の除去を「品詞ベース」に統一する。 手書きのストップワード一覧ではなく、助詞・記号などの品詞でそろえて除きます。 Kuromoji を「正解」と決めつけない。 類似度を1つの数字で出すだけでなく、全アナライザー間の一致や、専門語の扱いも見ます。 実際の検索クエリで動作を確認する。 トークンが似ているかだけでなく、「探したい文書が見つかるか」を見ます。 バージョンを記録する。 Elasticsearch・プラグイン・辞書・ライブラリ・LLM の情報を残し、来年また比べられるようにします。 再検証:実際に比べてみる 使うテキスト 医療系のテキストを2つ使います。 Text 1:ロキソニンの説明文(前回と同じ、短めの文)。なじみのある例として。 Text 2:アセトアミノフェンの説明文(今回のために書き下ろした、少し長い文)。 専門用語・カタカナの薬品名・英語の略語・数値を多く含みます。 比べるアナライザー Elasticsearch の中: Kuromoji(標準、Elasticsearch にもともとあるアナライザー)、Kuromoji_search( kuromoji_tokenizer を mode: search に設定して、この記事用に作ったアナライザーですのでElasticsearch にもともと入っている名前ではありません。)、Sudachi(A / B / C)。 Python で前処理: MeCab、Janome。 (Lindera は Rust 製の新しい選択肢ですが、今回の環境では Python 版を導入できなかったため、 本文での紹介にとどめ、計測には含めていません。) 参考枠(LLM): openai-gpt-oss-120b(EIS 経由)。 結果:トークン数 クリーニング後の、ユニークなトークン数です(実測値)。 Text 1(ロキソニン、約137文字): アナライザー ユニークなトークン数 Kuromoji(標準) 34 Kuromoji(search) 34 Sudachi A 36 Sudachi B 33 Sudachi C 33 MeCab 35 Janome 38 Text 2(アセトアミノフェン、約290文字): アナライザー ユニークなトークン数 Kuromoji(標準) 64 Kuromoji(search) 65 Sudachi A 64 Sudachi B 58 Sudachi C 56 MeCab 70 Janome 72 ここで読み取れることを少しだけ。 細かく分割する MeCab や Janome はトークン数が多めです。 Sudachi は C(大きい単位)になるほどトークン数が減り、複合語をまとめていることが分かります。 ただし「数が多い=良い」ではありません。大事なのは、次に見る専門用語の扱いと検索のヒットです。 結果:専門用語の扱い ここが検証の肝となる、興味深いポイントです。 特定の専門用語が、意図通りにひと塊のトークンとして保持されたかを確認します(○ = 単一語として検出)。 Text 1(ロキソニン): 用語 kuromoji kuromoji_search sudachi_a sudachi_b sudachi_c mecab janome ロキソニン ○ ○ ○ ○ ○ × ○ 解熱鎮痛 × × × × × × × 非ステロイド性抗炎症薬 × × × × × × × NSAIDs ○ ○ ○ ○ ○ ○ ○ 炎症 ○ ○ ○ ○ ○ ○ ○ 発熱 ○ ○ ○ ○ ○ ○ ○ Text 2(アセトアミノフェン): 用語 kuromoji kuromoji_search sudachi_a sudachi_b sudachi_c mecab janome アセトアミノフェン ○ ○ ○ ○ ○ × ○ 中枢神経系 × × × × × × × 解熱鎮痛薬 × × × × ○ × × 非ステロイド性抗炎症薬 × × × × × × × NSAIDs ○ ○ ○ ○ ○ ○ ○ インフルエンザ ○ ○ ○ ○ ○ × ○ 300mg × × × × × × × 肝機能障害 × × × × ○ × × アナフィラキシーショック × × × × × × × スティーブンス・ジョンソン症候群 × × × × × × × ここから読み取れることを、いくつか。 まず、カタカナの薬品名(ロキソニン、アセトアミノフェン)や インフルエンザ は、 ほとんどのアナライザーが1語のまま残しました。 ただし、今回の MeCab の構成だけは残しませんでした。 一点だけ補足します、 これは「MeCab はダメ」という話ではありません。 分割のされ方は、使う辞書(UniDic 系か IPAdic 系かなど)や設定の影響が大きいです。 今回の MeCab + 使用辞書(UniDic)の組み合わせでは、カタカナ語が細かく分割される傾向がありました。 次に、英字の略語 NSAIDs は、すべてのアナライザーが1語で保持しました。 英字のかたまりは、そのまま残りやすいです。 そして、長い複合語(非ステロイド性抗炎症薬、中枢神経系、アナフィラキシーショック、 スティーブンス・ジョンソン症候群)は、すべてのアナライザーが分割しました。 どれも、そのままでは1語になりません。 面白いのは、解熱鎮痛薬 と 肝機能障害 を、Sudachi の C モードだけが1語で残したことです。 C モードは大きい単位でまとめるため、こうした複合語をひとかたまりにできます。 数値+単位の 300mg は、どのアナライザーも1語にしませんでした (今回の元の文が 300〜500mg なので、300・500・mg に分かれます)。 ここで大事なのは、「1語で残る=良い」ではない、ということです。 細かく分割されると、部分一致で拾いやすくなります(再現率が上がる)。 1語でまとまると、完全一致やフレーズ検索でズレにくくなります(精度が上がる)。 つまり、どちらが良いかは「あなたの検索の目的」で決まります。 非ステロイド性抗炎症薬 のような長い語を1語で完全一致させたいなら、 ユーザー辞書への登録や、フレーズ検索の併用を検討します。 結果:アナライザー間の似ている度合い(Jaccard) 次に、アナライザーどうしがどれくらい似ているかを見ます。 前回は「Kuromoji にどれだけ似ているか」だけを見ましたが、 今回は Kuromoji を正解と決めつけず、全ペアを比べます(1.00 が完全一致)。 Text 1(ロキソニン): kuromoji kuromoji_search sudachi_a sudachi_b sudachi_c mecab janome kuromoji 1.00 1.00 0.84 0.63 0.63 0.64 0.85 kuromoji_search 1.00 1.00 0.84 0.63 0.63 0.64 0.85 sudachi_a 0.84 0.84 1.00 0.64 0.64 0.58 0.72 sudachi_b 0.63 0.63 0.64 1.00 1.00 0.42 0.54 sudachi_c 0.63 0.63 0.64 1.00 1.00 0.42 0.54 mecab 0.64 0.64 0.58 0.42 0.42 1.00 0.62 janome 0.85 0.85 0.72 0.54 0.54 0.62 1.00 Text 2(アセトアミノフェン): kuromoji kuromoji_search sudachi_a sudachi_b sudachi_c mecab janome kuromoji 1.00 0.98 0.85 0.67 0.62 0.59 0.74 kuromoji_search 0.98 1.00 0.84 0.69 0.64 0.59 0.76 sudachi_a 0.85 0.84 1.00 0.67 0.64 0.68 0.68 sudachi_b 0.67 0.69 0.67 1.00 0.87 0.44 0.58 sudachi_c 0.62 0.64 0.64 0.87 1.00 0.42 0.54 mecab 0.59 0.59 0.68 0.44 0.42 1.00 0.53 janome 0.74 0.76 0.68 0.58 0.54 0.53 1.00 数字が多いので、読み方をまとめます。 kuromoji と kuromoji_search はほぼ同じでした(1.00〜0.98)。 今回のテキストでは、search モードの差はほとんど出ませんでした。 複合語の固有名詞(例:関西国際空港)が多い文では差が出やすくなります。 (この点は、次の検索クエリの結果で確認します。) kuromoji / janome / sudachi_a は互いに近い(細かく分割するグループ)。 sudachi_b と sudachi_c は互いに近い(大きい単位でまとめるグループ)。 mecab は、他と最も離れていました。 ただしこれは MeCab 固有の特徴というより、今回使用した辞書・設定による切り方の違いです。 この「グループ分け」は、そのまま選び方の指針になります。 細かく拾いたい → kuromoji / sudachi A / janome。 まとめたい → sudachi B / sudachi C。 mecab は独特なので、目的に合うかを個別に確認する。 結果:検索クエリでの動作 最後に、実際の検索で確かめます。 ここが、検索システムとして一番大事なところです。 少数の文書を登録し、クエリごとに「期待する文書が拾えるか」を見ます(○ = ヒット)。 クエリ 期待文書 kuromoji kuromoji_search sudachi_a sudachi_b sudachi_c 空港 doc 1 ○ ○ ○ ○ ○ 関西空港 doc 1 ○ ○ ○ ○ × NSAIDs doc 2 ○ ○ ○ ○ ○ 300mg doc 2 ○ ○ ○ ○ ○ アセトアミノフェン doc 2 ○ ○ ○ ○ ○ (doc 1 は「関西国際空港は大阪府にある国際空港です。」、 doc 2 はアセトアミノフェンの説明文です。) 結果を読み解きます。 まず、ほとんどのクエリは、すべてのアナライザーでヒットしました。 唯一の取りこぼしは、関西空港(略称)を Sudachi C で検索したときだけです。 なぜでしょうか。 Sudachi C は、大きい単位でまとめるため、関西国際空港 を1つのトークンにします。 そのため、略称の「関西空港」とはうまく一致せず、ヒットしませんでした。 これは、まさに精度と再現率のトレードオフです。 大きい単位(Sudachi C)は、正式名称での完全一致に強い。 ただし、略称や部分的なクエリは取りこぼすことがあります。 細かい単位(Kuromoji や Sudachi A)は、部分一致で拾いやすい。 一方で、うれしい結果もあります。 英字の略語 NSAIDs、数値+単位の 300mg、カタカナの専門語 アセトアミノフェン は、 すべてのアナライザーで検索できました。 300mg は1つのトークンではありませんでしたが、300 と mg が別々に索引されるため、検索では拾えます。 ただし、ここは設定に依存します。 今回の query 設定ではヒットしましたが、operator(and / or)、 クエリ側のアナライザー、フィールド側のアナライザーの設定によって結果は変わります。 ここでの学びは、最初に立てた問いそのものです。 トークンが1語できれいに残るかどうかと、検索で見つかるかどうかは、必ずしも一致しません。 最終的に大事なのは「ユーザーが探したい文書が見つかるか」です。 なお、今回の小さな例では、Kuromoji の標準と mode: search で差は出ませんでした。 mode: search の効果は、複合語の固有名詞がもっと多いデータで効いてきます。 参考:LLM は何を「キーワード」として拾ったか 最後に、参考枠の LLM(EIS 経由の gpt-oss-120b)を見ます。 くり返しになりますが、これはアナライザーの比較ではありません。 「意味のまとまりとして、専門語を拾えるか」を見るための参考です。 抽出されたキーワードは次の通りです。 Text 1(ロキソニン): ロキソニン錠 / ロキソニン / 非ステロイド性抗炎症薬 / NSAIDs / 解熱鎮痛作用 / 関節リウマチ / 変形性関節症 / 腰痛症 / 肩こり / 歯痛 / 手術後 / 外傷後 / 炎症 / 痛み / 風邪 / 熱 Text 2(アセトアミノフェン): アセトアミノフェン / 中枢神経系 / 解熱鎮痛薬 / 非ステロイド性抗炎症薬 / NSAIDs / 抗炎症作用 / 一般用医薬品 / 頭痛 / 歯痛 / 月経痛 / 関節痛 / インフルエンザ / 風邪 / 発熱 / 成人 / 1回300〜500mg / 1日3回 / 経口投与 / 肝機能障害 / 高齢者 / 用量調整 / 重篤な副作用 / 肝障害 / アナフィラキシーショック / スティーブンス・ジョンソン症候群 ここが、形態素解析との大きな違いです。 形態素解析がすべて分割してしまった長い専門語を、LLM は1つの意味のまとまりとして拾いました。 たとえば、非ステロイド性抗炎症薬、中枢神経系、アナフィラキシーショック、 スティーブンス・ジョンソン症候群 などです。 さらに、1回300〜500mg や 1日3回 のような、用量を表す「意味のかたまり」も拾っています。 一言でいうと、LLM は「索引用の最小単位」ではなく「意味のまとまり」を取り出します。 このため、LLM が向いているのは次のような場面です。 クエリの意図を理解する(クエリ理解)。 文章から重要語を抜き出す(キーワード抽出)。 意味で探す検索(セマンティック検索)の補助。 逆に、インデックスのトークン化には向きません。 理由は3つあります。 生成結果は毎回まったく同じとは限らない(再現性が低い)。 大量の文書をすべて LLM に通すのはコストが高い。 そもそも目的が、転置インデックス用の最小トークンを作ることではない。 今回使用した LLM の設定(再現性のため): 使用モデル:openai-gpt-oss-120b 実行環境:EIS(Elastic Inference Service)経由 temperature:0 プロンプト:付録(GitHub のリポジトリ)に掲載 アナライザーの選び方ガイド ここまでをふまえて、用途別の選び方をまとめます。 「結局どれを使えばいいの?」への答えです。 部分一致や再現率を重視したい(広く拾いたい) → Kuromoji、または Sudachi A(細かく分割)。 完全一致・フレーズ検索を重視したい(複合語をまとめたい) → Sudachi C。 バランスを取りたい → Sudachi B。 新語・製品名・固有名詞が多い → 辞書更新の速い Sudachi、または Kuromoji に辞書を足す構成。 意味で探したい(言い換えにも強くしたい) → 形態素解析ではなく、semantic_text + 多言語埋め込み(EIS)。 Elasticsearch の中だけで完結させたい → 実質、Kuromoji か Sudachi(ほかは Python 前処理が必要)。 LLM → インデックスのトークン化には向きません。 クエリ理解やキーワード抽出など、検索の「補助」に使うのが向いています。 まとめ 最後に、覚えておきたいことを1つだけ。 「いちばん良いアナライザー」は存在しません。用途で決まります。 この1年での大きな変化は、選択肢が増えたことです。 形態素解析は今も主役の1つですが、意味で探すセマンティック検索という道も、 日本語で手軽に使えるようになりました。 次の一歩としては、自分の検索でよく使うクエリをいくつか決めて、 この記事の方法で実際に試してみるのがおすすめです。 results/ に数値が出るので、自分のデータで「どれが合うか」を確かめられます。 ※本記事の Python コードと検証環境は、Claude Codeを使って作成しました。 Links Kuromoji(analysis-kuromoji)プラグイン kuromoji analyzer kuromoji_tokenizer semantic_text フィールド semantic_text による意味検索 Elastic Inference Service(EIS) EIS の対応モデル (gpt-oss-120b など) 自前クラスタから EIS を使う (Cloud Connect) カスタムプラグイン/バンドルのアップロード (Serverless の制約の出典) Hosted と Serverless の違い Synonyms API (Serverless で同義語を使う方法) The post Kuromoji・Sudachi・MeCab・Janome・LLM・semantic search の使い分け【2026】 first appeared on Elastic Portal .
こんにちは、AIテクノロ ジー グループのエンジニアの吉田です。 本記事は Enigmo Advent Calendar 2025 の 18日目の記事です。 普段は検索システム全般、 機械学習 システムのMLOps、AI関連の機能開発を担当しております。 この記事では「AIでさがす」サービスのリニューアルについて紹介します。 「AIでさがす」サービスとは 「AIでさがす」サービスは、 BUYMA のWebサイトおよびアプリで提供している、AIを活用した商品提案サービスです。 実際の機能は以下からご利用頂けます。( BUYMA アカウントでのログインが必要となります。) 「AIでさがす」サービス ユーザーが文章で質問すると、AIが質問内容を理解し、おすすめの商品を提案します。例えば「春のデートにぴったりなワンピースを教えて」といった質問に対して、AIが回答文とともに具体的な商品を紹介します。 従来のキーワード検索では見つけにくかった商品や、ユーザー自身が気づいていなかった新しい商品との出会いを提供することで、 BUYMA でのショッピング体験をより豊かにすることを目指しています。 ※商品画像はモザイク加工しております。 リニューアルの背景 旧システムは、ChatGPT API を活用した商品提案サービスでしたが、主な課題が3点ありました。 BUYMA の知識不足 ChatGPT が一般的な知識で回答を生成するため、 BUYMA ならではのトレンドや商品特性を反映できない。 根拠の不明確さ ChatGPT の回答に 参照元 がない。 検索キーワード生成の精度 形態素解析 ツールの MeCab を併用していましたが、文脈や意味を理解した検索キーワード生成ができない。 また、リリースから2年が経過し、本格的にバージョンアップが必要なタイミングでもありました。 ※旧システムの詳細は こちらの記事 で紹介しております。 ちょうどチームメンバーが社内ドキュメントのAI検索システムを開発しており、この仕組みを BUYMA の多数の記事コンテンツに適用すれば、より BUYMA らしい商品提案が可能になると考えました。 そこで、今回のリニューアルでは、 BUYMA 内の記事コンテンツ群をベースに会話するエージェントを作成しました。これにより、 BUYMA ならではの知識を持ったAIが、より BUYMA でおすすめしたい商品を提案できるようになりました。 システム変更前後の比較 旧システムと新システムの違いは以下の通りです。 旧システムでは、ChatGPT が一般的な知識で回答を生成し、 MeCab による単純な 形態素解析 で検索キーワードを生成していました。そのため、 BUYMA ならではの文脈を理解した商品提案が難しい状況でした。 新システムでは、 BUYMA 内記事コンテンツを参照した Vertex AI Search が回答文を生成し、Gemini が文脈を理解した検索キーワードを生成します。その結果、より BUYMA らしい商品提案が可能になりました。 それぞれの処理フローは以下の通りです。 旧システム処理フロー BUYMA 基幹システムから「AIでさがす」 API にリクエスト 「AIでさがす」 API がユーザーの質問を ChatGPT API に送信 ChatGPT が回答文とおすすめアイテムリストを生成 アイテム名を MeCab ( 形態素解析 )で解析し、検索キーワードを生成 検索 API で商品情報を取得し、ユーザーに表示 新システム処理フロー BUYMA 基幹システムから「AIでさがす」 API にリクエスト 「AIでさがす」 API がユーザーの質問を Vertex AI Search に送信 Vertex AI Search (事前に BUYMA 内記事コンテンツをインポート済み)が回答文を生成 質問文と回答文を Gemini に送信し、検索キーワードを生成 検索 API で商品情報を取得し、ユーザーに表示 アーキテクチャ ー特徴 1. Vertex AI Search Vertex AI Search を利用して、インポートした BUYMA 内記事コンテンツをベースに会話を行うエージェントを構築しました。 BUYMA 内記事コンテンツのインポート 約4000件の記事をデータストアにインポート プロンプト設計 「ファッション ECサイト BUYMA のショッピングアドバイザー」として定義し、ユーザーの質問に対して最適な商品を提案する形で回答を生成 2. Gemini Gemini を活用する事により、会話内容から商品検索キーワードを生成する機能を作成しました。 プロンプト設計 「 ECサイト の検索キーワードを生成する専門家」として定義し、会話の文脈を理解して検索キーワードを生成 MeCab との違い MeCab は単語の分解のみだが、Gemini は文脈を理解してブランド名・カテゴリ名・モデル名を組み合わせた検索キーワードを生成 実装時の課題・解決策・工夫した点 Vertex AI Search の幻覚への対応 初回質問時に Vertex AI Search が過去から質問が続いているような幻覚を見る場合がありました。当初は初回と2回目以降の会話を共通のプロンプトで行っており、「ユーザーの過去の質問履歴」の項目に入っている文言の有無から初回なのか、2回目以降の会話なのかを判断する指示を出していました。ところが、「過去」という文言に引きずられてなのか、初回なのに過去の質問をAI側が捏造して、その続きとして回答する場合が稀にありました。 プロンプトテンプレートを初回用と2回目以降用の2種類に分け、初回用のプロンプトからは「ユーザーの過去の質問履歴」の文言自体を削除する事によって対応しました。 敵対的クエリへの対応 敵対的クエリ(不適切な質問)の場合、Vertex AI Search の API からのレスポンスフォーマットが通常とは異なるものになり、要約が生成できないにもかかわらず、無理やり商品紹介を行ってしまいました。 敵対的クエリーのフォーマットを検知した場合は、要約失敗として扱い、商品紹介を行わないように修正しました。 この場合以外でも稀に異なるフォーマットのレスポンスになる場合があり、サービス継続に支障が出ないように都度改善を行いました。 Gemini のライブラリ移行 もともと使用していたライブラリがサポート終了を迎えるため、社内では実績がない新しいライブラリに移行する必要がありました。移行後、従来使用していた Gemini モデルが初期設定では使用できず、次世代のモデルを試したところレスポンスタイムが大幅に遅くなってしまいました。新しいライブラリという事もあり、AIツールではなかなか解決できず、最終的には Google サポートに問い合わせして解決に至りました。 得られた学びとノウハウ AIツールの活用と限界 「AIでさがす」のバックエンド API の リポジトリ は、ほぼ全部作り直したのですが、AIツールを活用する事によって、 工数 を節約する事ができました。Terraform 関連のリソース修正、テストケース作成やMOCK用のフロントエンド実装等においてもAIツールにより大幅な 工数 削減ができました。 一方で、Gemini のライブラリ移行など、ドキュメントの記載やインターネット上での知見が少ない領域ではAIツールでは解決できず、結果的に公式サポートへの問い合わせが必要でした。 AIの不確定な挙動への対応 Vertex AI Search の幻覚や部分的な失敗など、AIサービス特有の不確実性に対して、初回用と2回目以降用でテンプレートを分けるなど、細かな調整が重要でした。また、プロンプトだけではどうする事もできない場合があり、そのような場合は後処理でルールベースのロジックを追加する必要がありました。 効果測定 リニューアル後、以下のような指標が上昇しました。 1スレッドあたりの質問数の平均 会話の継続性が向上し、ユーザーが複数回質問を続けるようになりました。 1ユーザー1日あたりの質問数 利用頻度が向上し、ユーザーがより積極的に機能を活用するようになりました。 検索URLに遷移された回数 商品検索への誘導効果が向上し、実際の商品閲覧につながるケースが増加しました。 これらの結果から、 BUYMA 内記事コンテンツを根拠とした回答の提供と、文脈を理解した検索キーワード生成により、ユーザーの満足度と利用価値が向上したと考えられます。 直近の対応/今後の展開・課題 金額絞り込み機能の追加(今月対応) ユーザーからの要望が多い金額絞り込み機能の対応をしました。価格帯に関する質問に対して適切な商品提案ができていない課題があったため、Gemini で検索 API 用の金額フィルタークエリを生成することで対応しました。 コンテンツの拡充 現在は BUYMA 内記事コンテンツのみを Vertex AI Search にインポートしていますが、今後は YouTube での発信内容も追加する予定です。記事以外のコンテンツも活用することで、より幅広い情報をユーザーに提供できるようになります。 継続的なメンテナンス AIのライブラリやモデルは随時更新されていくため、継続的なメンテナンスが課題となります。特に Gemini や Vertex AI Search などのサービスは進化が早く、新しいモデルへの対応や非推奨ライブラリ/バージョンから移行など、定期的な見直しが必要です。 まとめ 本記事では、「AIでさがす」サービスのリニューアルについて紹介しました。 旧システムでは ChatGPT と MeCab を使用していましたが、 BUYMA 特有の知識不足や根拠の不明確さなどの課題がありました。リニューアルでは Vertex AI Search と Gemini を採用し、 BUYMA 内記事コンテンツを根拠とした回答生成と文脈を理解した検索キーワード生成を実現しました。 実装時には敵対的クエリへの対応やAIサービス特有の不確実性への対処など様々な課題に直面しましたが、ロジックでの細かい制御やAIツールの活用により解決できました。リニューアル後は会話継続性や利用頻度、商品検索への誘導効果が明らかに向上しています。 明日の記事は同じAIテクノロ ジー グループの髙橋さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
はじめまして!一橋大学SDS研究科 修士1年の佐藤祥太 ( @Shota_Sato01 ) です。今回私は8月のCA Tech JOBインターンに参加させていただきました! この記事では、配属先のAI Shiftでの取り組みについてご紹介させていただきます! 配 属部署について 今回のインターンでは、AI Shiftに配属になりました。「人とAIの協働を実現し人類に生産性革命をもたらす」というMISSIONのもと、AIエージェントやVoiceBotの開発に取り組んでいます。 ビジネスサイドとエンジニアサイドが議論しながら、組織で一体となってプロダクトの改善に取り組んでいるのが印象的でした。 タスク 概要と背景 AI Shiftが提供しているVoiceBotは、電話応対業務を自動化するサービスです。 その中で、お客様の発話が「何について言及しているのか」を正しく特定するのは会話の破綻を防ぐために非常に重要なファクターになります。 今回のインターンで私が取り組んだのは主にこの部分で、お客様の発話内容が何を示しているのかを膨大なリストの中から特定する、「エンティティリンキング」というタスクに取り組みました。 このタスクの課題としては 入力が音声認識結果であるため、認識誤りがあった場合正解となるエンティティと表記が一致しない 対象のリストが膨大であるとき、発話内容と正解となるエンティティを紐付けることが困難 という点があげられます。 そこで、私は、 音声認識誤りに頑健 効率的に候補を絞り込める (Nを小さくできる) という特徴を持つようなロジックについて検討を行い、提案した手法の検証を行いました。 手法 今回は、以下に示す計7つの手法 (BaseLine×3, SoftMatcha, MeCab+部分文字列検索×3) について検証を行い、定量的、定性的に比較を行いました。 BaseLine BaseLineの基本的なアイデアは、入力として、transcription (音声入力をテキストにしたもの) とエンティティのリスト全件分をLLMに渡し、出力として、transcriptionがリストの中のどのエンティティを指しているかを返します。 今回は、以下の3種類のプロンプトを作成しました。 Zero-Shot: transcriptionとエンティティリストをプロンプトの中に埋め込み応答を生成 Few-Shot: Zero-Shotに、入出力例を追加 CoT: Zero-Shotに、段階的に候補を絞り込むという指示を追加 候補集合の絞り込み BaseLineではLLMが数万件の候補から1つの正解を抽出する必要があり、正解率やプロンプト長の増加による実行速度への悪影響が懸念されます。 そこで、「LLMに入力する際の候補集合を事前に絞り込むことで、精度と実行時間を向上させられるのではないか」と考え、絞り込みの手法を複数提案し、検証を行いました。 SoftMatcha SoftMatcha [1]は高速かつ柔らかいパターンマッチ検索ツールです(余談になりますが、 2025年の言語処理学会@長崎 で実際に聴講させていただいた中で印象的な発表の一つでした)。 エンティティリスト全件をソース、transcriptを入力として、この手法で検索をかけてヒットしたものを候補集合に選定しました。 MeCabによる形態素解析 + 部分文字列検索 MeCab [2]は形態素解析のツールで、意味を持つ最小の表現単位(形態素)に分解することができます。 分解後の各形態素をキーワードとして、エンティティリストの全件に対し部分一致となるかどうかを判定し、絞り込みを行うのが基本的なアイデアになります。 今回は、このアイデアをベースに3種類の絞り込みのロジックを検討しました。 MeCab: transcriptionの各形態素に対し、得られた検索結果の和集合を候補集合にする MeCab + Cutdown: 上記の各検索結果のうち、サイズが1000以下のもののみを選択し、それらの和集合を候補集合にする MeCab + Comb: transcriptionからn個の形態素を選択し、それらをすべて含むエンティティを検索。これを全ての組み合わせに対し実行し、得られた検索結果の和集合を候補集合にする MeCab + Combについて補足させていただきます。上記の例であれば、n=2のとき、検索対象となる形態素の組み合わせは {(株式, 会社) | (株式, A) | (株式, えー) | (株式, A) | (会社, A) | (会社, えー) | (会社, A) | (A, えー) | (A, A) | (えー, A)} となります。 これらの各組み合わせに対し、すべての形態素に対し部分一致となるエンティティを検索し、それらの和集合を取ります。このような操作をn=1〜(transcriptionの形態素数)まで再帰的に行い、各試行で候補集合のサイズが1000を下回った時を最終的な候補集合として選択します。 ※なお、本検証においてはMeCabの辞書として unidic-lite を使用しています。 実験設定 今回は、エンティティリストとして、約2万件の固有名詞を対象にしました。 これらに対し、固有名詞のテキストをTTSを用いて合成音声を作り、それらを音声認識にかけることにより擬似的な音声認識結果を100件生成し、それらに対し、各手法の正解率と実行時間を評価します。 なお、実験で用いたLLMはGemini-2.0-Flashとし、今回扱うデータは、アルファベットの小文字化や空白除去等の正規化処理を事前に施しました。 実験結果 候補集合の絞り込みについて ここでは、BaseLine以外の手法についての候補集合の絞り込みについて議論します。 各手法毎の絞り込みを行った際の候補集合のサイズ ave_size:候補集合に含まれるエンティティ件数の平均値 LLM_miss:LLMが誤答したときのave_size included: 正解が含まれている候補集合の数 左側が音声認識がうまくいったとき、すなわち、音声の入力を正しくテキストに変換できたケースでの絞り込み結果になります(実用上では、このような場合は完全一致となるエンティティの検索を行えば良いと思いますが、参考のために併記させていただきます)。 右側は音声認識誤りが含まれるときの絞り込み結果です。 SoftMatchaは、認識誤りのない場合には、ほぼ一意に決定できるまでに候補集合を絞り込めています。一方で、少しでも認識誤りが含まれてしまうと検索結果にほとんどヒットせず、候補集合自体を作ることができなくなってしまう、という結果になりました。 一方、MeCabでは、認識誤りの有無にかかわらず、ほぼ全ての入力に対して、絞り込んだ集合の中に正解を含むことができています。しかし、最終的な候補集合のサイズの削減効果は限定的で、Nを小さくするという目標はあまり達成できていません。 MeCab + αの2手法は、いずれもMeCabと比較し効果的に候補を絞れていることが確認できます。元は約2万件あったデータを数百のオーダーにまで絞り込みを行えており、かつ、認識誤りを含む場合にも、およそ65%の割合で正解を含むことができています。 正解率と実行時間の評価 ここでは、全手法について、正解率と実行時間について議論します。 各手法毎の正解率と実行時間。実行時間は100回分の処理時間を1回あたりに換算したもの BaseLineの3手法については、正解率に多少変動は見られるものの、大幅な性能の改善は見られませんでしたが、認識誤りのない場合に対し、Few-Shotで正解率が大きく低下しているのが気になりました(おそらく例の与え方が良くなかった...🤔)。 SoftMatchaについて、音声認識がうまく行っている場合には100%の正解率を誇っています。一方で、認識誤りがある場合に関しては、もっとも正解率が低くなっています。これは今回のような実験設定に対しては、意味の近さを用いた柔らかな検索手法では対応できず、絞り込みの時点で多くが失敗しているためだと考えられます。また、事前の絞り込みの段階で時間がかかってしまい、今回の全手法の中で最も実行時間が長いという結果になりました。 MeCabについて、こちらはBaseLine手法とあまり性能の変化が見られませんでした。 これは、絞り込み後の候補集合のサイズがあまり小さくならなかったということを反映していると考えられます。 MeCab + αの2手法について、こちらは効果的に絞り込みを行えたこともあり、正解率、実行時間ともに顕著な性能の向上が確認できます。ロジック的には単純ですが、有効な手法が提案できたのではないかと思います。 定性分析 ここでは、これまでの実験結果を踏まえて、BaseLine以外のそれぞれの手法のメリット・デメリットを分析します。 SoftMatcha この手法のメリットは、意味的に変化しない誤字に対して強いという点です。 例えば、「株式会社第一」というエンティティが正解のときに、「株式会社第1」というtranscriptionが得られたケースを考えます。音声認識的には誤りが存在しますが、この誤りは意味的には変化していません。このような事例に対しては正確に候補集合を抽出できました。同様に、文字の全角・半角の違いなどにも頑健です。 デメリットとしては、削除誤りや意味的に異なるフレーズに置換されるような誤りが生じるケースに弱いという点です。 MeCab この手法のメリットは、一部でも形態素が正しければ候補として拾えるという点です。 上記のような「株式会社AえーA」のような場合でも、「株式」や「会社」といった部分的に正解している形態素が含まれていれば候補集合に正解を含むことができます。 デメリットとしては、すべての形態素に対して検索を行うだけでは候補の絞り込みがほとんどできていいない点です。 MeCab + Cutdown この手法のメリットは、候補集合を小さくできることにあります。先ほどのMeCabの致命的すぎるデメリットを克服するべく、リストに頻出する形態素は機械的に除外するという制約をかけました。結果として、うまく候補を絞れました。 デメリットとしては、頻出形態素のみ、もしくは頻出形態素+音声認識誤りからなるようなエンティティを取りこぼしてしまうということです。 例として正解が「株式会社AI Shift」の時にtranscriptionが「株式会社AIシフト」となった場合を考えてみます。 このとき、「株式」「会社」「AI」のいずれも一般的なキーワードかと思います。このようなケースでは、結果として「シフト」のみが検索対象の形態素となり、正解を候補集合に含めることはできなくなります。頻出する形態素を決める基準を何件以上とするかの閾値の調整が重要になりそうです。 MeCab + Comb この手法のメリットは、MeCab + Cutdownと同様に、候補集合を小さくできることにあります。また、機械的に頻出形態素を除外するという操作をしないので、MeCab + Cutdownのデメリットを補うことができます。 デメリットとしては、制約を厳しくする、すなわち、nの数が大きくなると、正解となるエンティティを最終的に取りこぼしてしまう可能性が高くなる点です。例えば、n = transcriptionの形態素数のときは、すべての形態素に誤りが含まれない場合、もしくは削除誤りしか存在しないケースでしか正解を候補集合に含めることができなくなってしまいます。nの数をどこまで大きくするべきかは事前に調整が必要だと感じました。 まとめと展望 ここまで色々と実験についてお話ししましたが、結論としては以下になります。 MeCab + αの手法が効果的っぽい Cutdown、Combのどちらが良いかは検索対象となるエンティティの集合の特性に依存しそう Cutdown:表層が似ていないとき(特徴的な形態素があるとき)に強い Comb:表層が似ているとき(頻出する形態素が存在するとき)に強い 今回の検証は人工的に生成した認識結果をもとにしたものですが、実際の音声対話では言い淀みや、エンティティに直接関係しない発話が含まれるケースが存在します。今後の展望としては、そのような多様な発話に対する頑健性の評価や対処方法の検討、またエンティティの特性に応じたロジックの使い分けなどが考えられます。 また、提案手法ではユーザーの発話に対し、一回の処理で候補集合の絞り込みを行うことを主眼に置きましたが、発話内容によっては十分に絞りきれていないケースが見受けられました。そのような場合に対する、更なる絞り込みのロジックやインタラクションの検討も行う必要がありそうです。 インターンを振り返って 今回、インターンに参加するにあたって自分の中での目標が二つありました。 一つは、「(機械学習)エンジニアとして働くとはどういうことかを体感する」ということ。もう一つは、「技術的に成長する」ということです。 一つ目に関しては、確実に達成することができました。 プロダクトの開発に関連した各種ミーティングに同席させていただいて、実際にプロダクトをよりよくするための議論に参加することができました。 もちろん、技術にまつわる全ての議論についていけたわけではないですが、それでも、プロダクトの問題点や改善点を見つけ出すプロセスやそれに対するアプローチ・考え方は、とても勉強になりました。 二つ目に関しては、達成率は50%程度だと感じています。 今回のインターンでは、今まで学んでいた知識や技術を生かすことはできたと思っています。この経験は自分のこれまでの学びに対して自信を持てる良いきっかけになったと思います。 しかし、インターン期間中に技術的に新しいスキルを獲得したり、新たな分野の知見を獲得するようなことはもっともっとやりたかったと感じています。 最後になりますが、インターン期間中はAI Shiftの皆さんがとても親切にしてくださって、楽しくインターン期間を過ごすことができました。 ここまで読んでいただきありがとうございました!! 参考 [1] https://softmatcha.github.io/ [2] https://aclanthology.org/W04-3230/ 投稿 エンティティリンキングの性能改善のための効果的な絞り込み手法の検証 は 株式会社AI Shift に最初に表示されました。
動画
該当するコンテンツが見つかりませんでした







