ベクトル検索で欲しい情報が得られないときの問題点と改良方法を考えてみた

2023.07.16

はじめに

新規事業部 山本です。

ChatGPT(OpenAI API)をはじめとしたAIの言語モデル(Large Language Model:以下、LLM)を使用して、チャットボットを構築するケースが増えています。通常、LLMが学習したときのデータに含まれている内容以外に関する質問には回答ができません。そのため、例えばある社内システムに関するチャットボットを作成しようとしても、素のLLMでは質問に対してわからないという回答や異なる知識に基づいた回答が(当然ながら)得られてしまいます。

この問題を解決する方法として、Retrieval Augmented Generation(以下、RAG)という手法がよく使用されます。RAGでは、ユーザからの質問に回答するために必要そうな内容が書かれた文章を検索し、その文章をLLMへの入力(プロンプト)に付け加えて渡すことで、ユーザが欲しい情報に関して回答させることができます。

前回の記事では、RAGの文章検索でよく使用されるベクトル検索において、クエリの条件にあった文章を判定できるかを調べるために、「以外」「数字の大小」を例として簡単な実験を行い、結果として難しそうであるという印象をもちました。

ベクトル検索で「以外」や「数字の大小」を試してみた | DevelopersIO

今回は、ベクトル検索において欲しい情報が得られないときに、そもそも原因となっていそうな問題点を取り上げ、それぞれへの対策方法と概要について記載します。

(以前にも、RAGについて考えた内容や、RAGを使って取り組んだ内容がありますので、こちらも合わせてご覧ください)

(専門の分野ではなく調べ始めたばかりであるため、不足している点・間違っている点などがあるかもしれませんので、ご了承いただければ幸いです)

状況の整理・言葉の定義

今回は以下のような状況を想定しています。また、使用する言葉は以下の内容を指します。

  • 処理フロー
    • 単純なRAG
      • ドキュメント内の検索:ベクトル検索で利用する
      • 回答の生成:取り出したドキュメントのテキストと質問のテキストからプロンプトを作成し、LLMに入力する
  • 入力データ
    • ドキュメント:検索したい対象、大量にデータがある、マニュアルやルールなどの説明が書かれていることを想定
    • ユーザの質問:システムを利用したいユーザの質問、ドキュメントに関して知らないことを想定
  • データベース:ドキュメントのテキストを前処理し、検索しやすい形にしたもの

この記事では、こうしたシステムを作成する際に、思ったような回答が生成されなかった場合、考えられる問題点やさらに改良できそうな点について検討します。

考えられる問題点・改良点

文章を検索して欲しい情報を取り出したい(&その情報をもとに回答をLLMに考えさせたい)ときに、単純なベクトル検索で上手く行かない場合、以下の部分が原因の候補として考えられそうです。

ベクトル検索の使い方の問題

単純なベクトル検索では、「ユーザの質問のベクトル」と「ドキュメントを分割したテキストのベクトル」を比較することで類似度を計算しますが、以下の点が問題になる可能性があります。

  • 比較する文章同士の種類があっていない

    類似度を計算するということは、比較するもの同士の種類が合っていることが前提になるはずです。説明文と説明文の比較はできるはずですが、ユーザの質問とドキュメント内の説明文の比較は少しズレた種類を比較していると言え、精度が下がる原因になりそうです。

    例えば、質問のテキストでは「〇〇についてわからないのですが、どうしたら良いか教えてください」のように書かれ、社内文章などの文章では「〇〇の方法」「〇〇の際は以下から手続きしてください」のように書かれていることが想定され、文体などが異なることが多そうです。

    (→ 比較する文章を揃える方が良い)

  • 類似度の使用方法が適切ではない

    こうしたベクトル検索で学習で使われるデータセットにおいて、テキスト同士の類似度は「2つの文章が同じ話題であるか」を人間が評価しているケースが多いです。つまり、上記のようなケースで行いたい「ある文章がある質問の回答になっているか」とは少しずれているため、本来の使い方とは異なっていると言え、これも精度が下がる原因になりそうです。

    例えば、「少年が砂場でお城をつくっています」と「子供が公園で遊んでいます」という2つの文章が類似していることはわかりますが、「少年はどこで何をしていますか?」と「子供が公園で遊んでいます」が類似しているかは(データセットに無いため)わかりません。おそらく後者のペアの類似度はある程度高くなりますが、前者のペアほど高くならないことが予想されます。

    (→ 「文章が質問の回答に使用できるか」を出力できるようにモデルを変える方が良い)

ベクトル検索自体の問題

「テキストをベクトル化して2つのテキストの類似度を測る」という手法そのものに、以下のような制約(≒ 限界)がありそうです。

  • 条件に合致した箇所を正確に取り出すのは難しい

    前回の記事に書いた通り、「以外」や数字の大小などを含んでしまうと、その条件が該当する箇所を正確に取り出すのは難しそうです。

    ただ、幸いなことに、類似度が一番高いテキストの周辺に該当箇所が含まれていたり、条件が該当する箇所も類似度は(一番ではないですが)高いため、取り出し方を工夫すれば良さそうです。

    後段の処理でLLMにテキストを入力するので、そこで不要な情報を弾くことは可能ですが、逆に必要な情報が抜けてしまうのは問題です。

    (→ 条件に合致する箇所を漏らさなくするために、抽出範囲を大きくする必要がある)

  • ユーザのやりたい検索に対応できない

    ユーザがやりたい検索は、かなり条件が複雑です。キーワードで検索する、あるキーワードを除外する、日時や作成した人などのメタ情報で絞り込む、コンテキスト性が高い質問をする場合などがあります。文章ベクトルの比較は大まかに検索するには使えますが、こうした複雑な検索はそもそも対応できないと思われます。

    (→ ユーザのやりたい検索をカバーするために、ほかの検索方法も併用する方が良い)

(そもそもベクトル検索のメリットは、様々な種類のデータ(画像・音声)もベクトル化して同時に検索できる点や、近似最近傍探索を利用でき高速に検索できる点、と言われることが多いようです。高速に量を絞り込む処理を担当し、より正確な検索・判断が必要な処理は別の方法を取るという多段階の仕組みにすることで、処理時間やコストを下げるという考え方です)

検索の仕組み自体の問題

ここはベクトル検索の話ではなくなりますが、検索全体の仕組みとして、以下の点も考慮した方が良いでしょう。

  • 一回の検索では欲しい情報を取り出せない

    文章の形式や書き方にも依存しますが、該当箇所のテキストに参照リンクが貼ってあるケースや、別の箇所に書かれた2つの内容を比較した結果を知りたいケースなど、1回の検索で抽出したテキストでは情報が不十分な場合が考えられます。これには、抽出したテキストの内容を自分で把握して、不十分な要素があれば再度クエリを考えて、検索を繰り返しながら情報を集めていく仕組みが必要そうです。

    (→ 得られた情報をもとに考えながら複数回検索を繰り返す仕組みが必要)

  • ユーザの質問の情報がそもそも十分でない可能性がある

    そもそも、ユーザはデータベース内の文書の内容をわかっていない場合もあります(おそらくほとんどの場合)。こうしたケースでは、文書を特定するのに十分な情報が、1回の質問の中に入っていないことが考えられます。毎回ユーザの質問をもとにただ検索するだけだと、これは解決できないため、必要な情報をユーザに聞き返して教えてもらったり、「こういう情報ならあるよ」と伝えて質問を考えなおしてもらう、というフローにする必要がありそうです。

    (→ 会話形式にしてやり取りできる仕組みが必要)

(情報検索(Information Retrieval)という単語を調べていると、多くの場合、単に1回だけドキュメント内からクエリに関連する情報を取得することとして定義されています。ただ、今回のようなシステムでは、チャットボットというような形式にしてユーザに提供している = 自然言語かつ会話形式でやりとりができる点や、同様のインタフェースでChatGPTのような”質の高い”を体験したことあるユーザが増えている点は考慮すべきです。今後ユーザはこうしたシステムに、単なる「情報検索」だけではなく、やり取りや対話ができたり、思考したり代わりに作業もしてくれることを期待するようになり、提供する側もそれに応えることが求められるでしょう。例えばドキュメントの内容をすべて知っている頭の良い気が効く人間がいたら、以下のようなことができそうですが、情報検索・回答生成で対応できているのは一部しかありません)

(全体プロセスの問題)

少し話が大きくなってしまいますが、開発のプロセスとして以下の点も検討する余地があるかと思います。

  • ユーザがどのように使うかは予想しきれない

    開発者としてはユースケースを想定し、それに沿ったシステムを開発しますが、ユースケースに沿わない使われ方は存在します。なので、まずはプロトタイプ的に作成してみて、ユーザの使い方から不足しているケースを追加するというマインドや進め方の方が上手くいきそうです。

    (→ プロトタイピングして分析するプロセスが必要)

  • ユーザも慣れて変わっていく・データも変わる

    長くシステムを使っていくと、ユーザも慣れてきたり、データも更新・追加されてきます。あるドキュメントを追加したら、内容や形式が異なっていて精度が悪くなる、というケースも想定されます。

    (→ 定期的に改善していく仕組みが必要)

対策

各対策の方針、各方針の具体的な方法(世の中で提案されている方法、アイディア)、それによって期待される効果と、必要な作業(手間・時間・コスト)について述べます(記事が長くなってしまったので、概要についてのみ記載しました。各方法の詳しい内容については、それぞれ別の記事にする予定です)。

方針1:Embeddingで比較するテキストを揃える

方法1-1:質問を工夫する

方法として、以下の2つがありえそうです

  • 質問文をLLMに渡して、検索しやすい形に変換する
    • 内容・方法

      質問のテキストをLLMに渡して変換させる方法です

    • 期待される効果

      データベース内のテキストに近いワードや形式で検索でき、より正確に類似度が計算されることを期待できます

    • 手間・コスト

      毎回LLMを実行する分、処理時間・コストがかかります

  • 質問文をLLMに渡して、仮の回答を考えさせる

    • 内容

      質問のテキストに対して(情報がない状態で)仮に回答した場合のテキストをLLMに考えさせ、その回答を入力としてベクトル検索する方法です。

    • 期待される効果

      質問のテキストよりも仮の回答のテキストの方が、データベース内のテキストに近い形式・書き方・内容になっていて、より正確に類似度が計算されることを期待できます

    • 難しそうな点

      専門用語や一般的でない用語が含まれると、仮の文章を考えることがそもそも難しそうです

    • 手間・コスト

      毎回LLMを実行する分、処理時間・コストがかかります

    • 補足

      Hypothetical Document Embeddings(HyDE)と呼ばれる方法であり、LangChainでもchainの一つとして実装されています(https://python.langchain.com/docs/modules/chains/additional/hyde

      今回のケースでいうと、「仮の回答」というよりは「仮のドキュメント」を生成する方が、より正確な類似度の計算ができそうです

方針1-2:ドキュメント(データベース)を工夫する

  • 仮のQ&Aを作成する

    • 内容・方法

      ドキュメントのテキストをLLMに渡して、仮のQ&Aを考えさせる方法です

    • 期待される効果

      ユーザの質問と同様の質問テキストを生成できれば、類似度が高く計算されることが期待できます

    • 難しそうな点

      Q&Aがユーザの質問を網羅しきれるか不透明なところがあります。また、網羅しようと思うと大量のQ&Aを生成する必要があり、コストがかかりそうです

    • 補足

      作成したQ&Aのうち、どこをベクトルに変換するかは3通り(Qの部分のみ・Aの部分のみ、Q&A全体)あります。質問と内容を揃えるという点ではQの部分のみを変換するのがまず考えられますが、ほかの2つも試してみたり、3つの結果を組み合わせるのも良さそうです。

方針1-3:どちらも工夫する

状況によっては以下のような方法でも欲しい精度を得られるかもしれません。あまり情報がなかったのでアイディアレベルです。

  • タグ付けする
    • 内容

      ドキュメントのテキストをLLMに入力してタグを付け、タグのリストを取得します。質問のテキストも同様にLLMに入力して、どのタグに該当するかを判定させます。タグに該当するドキュメントのテキストを取り出します

    • 補足

      タグ付けだけであれば、LLMではなく、Amazon Comprehendのようなテキスト内のエンティティ(トピックとなる単語)を抽出する方法でもできるかもしれません(コストを抑えられる)

  • 要約を生成する

    • 内容

      ドキュメントのテキストをLLMに入力し、要約を生成します。質問のテキストも同様にLLMに入力して、概要(要約)を生成します。それらの類似度をベクトルで計算します。

    • 難しそうな点

      質問のテキストは短いことが多く、質問のテキストの概要を生成する(ドキュメントの要約と比較するための文章を作成する)というタスク自体が上手くいくのか不透明です

    • 補足

     ドキュメントの要約を検索に使用するという点に関しては、LlamaIndexのDocumentSummaryIndexが近い動作をしているようです(未調査)。

  • 参照するケースを生成する
    • 内容

      ドキュメントのテキストをLLMに入力し、テキストの想定される参照ケース(その文章をいつ参照したら良いか)を考えさせます。質問のテキストをLLMに入力し、その質問がどういう状況なのかを考えさせます。参照ケースと状況の類似度をベクトルで計算します

    • 難しそうな点

      質問のテキストは短いことが多く、質問のテキストの概要を生成する(ドキュメントの要約と比較するための文章を作成する)というタスク自体が上手くいくのか不透明です

方針2:「文章が質問の回答に使用できるか」を出力できるようにモデルを変える

方針2-1:ベクトル化(モデル)を改良する

https://www.youtube.com/watch?v=3giqIW2pIW4より引用

  • two-towerモデルを使う
    • 内容

      新しいモデルを利用します。質問のテキストをベクトルにする部分(上図中央左)と、ドキュメントのテキストをベクトルにする部分(上図中央右)を持っていて、質問とドキュメントのペアに対して、それらが合致するものであれば2つのベクトルが近くなるように、合致しないものであれば遠くなるように学習をするというモデルです。

      レコメンドエンジンを作成するのと同様の考え方のようです。

    • 大変そうな点

      おそらくモデルを学習するためのデータセットが必要で、質問のテキスト・該当するドキュメントのテキストのペアを作成する必要があります。また、学習させるためのスクリプトを動かす手間や、学習を実行するコストがかかりそうです。

    • 補足

      データセット(質問のテキスト・ドキュメントのテキストのペア)を作成する方法として、先に別の検索方法でシステムを作成し、どういう質問をした時どういうドキュメントにたどり着いたか、ユーザの操作を分析することで得る、というやり方もありえそうです

      QA用に学習済みのモデル(universal-sentence-encoder-multilingual-qa)も提供されているようで、これが利用できるなら学習にかかる手間やコストは不要そうです(未調査)。https://tfhub.dev/google/universal-sentence-encoder-multilingual-qa/3

      two-towerモデルで、ドキュメント(データセット)のベクトルは事前に計算しておくことができます。なので、質問分が来たときには、質問文をベクトル化する処理と、ベクトルの同士の類似度を計算する処理のみで済みます。Googleの方の発表では、この検索をVertex AI Matching Engine(ScaNN)を利用することで高速に処理できる、と述べられているのだと思います。

      調べているとGoogleの方の発表資料が多くありましたので、参考にさせていただきました

  • ほかにも、自作でモデルを作成する方法もありそうですが、調べきれていないので、今後調査する予定です。

方針3:抽出範囲を大きくする

方針3-1:文章を取り出す処理ロジックを改良する

類似度を計算した後、どのノードを取り出すかの処理ロジックを改良します(通常は、スコアが高いものからいくつかのノードを取り出します)。LlamaIndexにもPostprocessorという、これに該当するクラスがあります。

  • 前後のノードをまとめて取得する
    • 内容

      スコアが高いノードの前後に該当箇所がありそうと仮定し、その前後をまとめて取得します。LlamaIndexではPrevNextNodePostprocessorというクラスで、前後のNodeから一定数(パラメータで指定された数)を、まとめて取り出すというものです。

    • トレードオフになる点

      ドキュメントのテキストを多く取り出すことになるので、後段の処理でLLMにわたすプロンプトの量が増え、コストが増えたり、違う箇所を参考にして意図しない回答が得られる可能性が高くなります。

  • 文章の階層構造を把握できるようにして、親要素を取得する

    • 内容

      スコアが高いノードがある階層(章・節・記事)に該当箇所がありそうと仮定し、スコアが高い兄弟要素がいくつかあったら、その親要素を取得することで該当箇所も含めて取り出せる手法です。ドキュメント内の階層構造を把握するために、事前にドキュメントを解析しておく必要があります

    • 大変そうな点

      文章の階層構造を解析する処理必要がありますが、現状こうした機能に該当するライブラリがなさそうなので、自作する必要がありそうです。ドキュメントのファイルごとに書き方は異なるでしょうし、特に、ドキュメントが独自の構成をしていたり、解析が難しいファイルの種類を利用しているなど、文章の構成を解析しにくい場合は実装がさらに大変そうです。

      HTMLやMarkdownであれば階層的に書かれていると思われますが、PPTやWordのファイルは難しいことが予想されます。

    • 補足

      LlamaIndexには、TreeIndexというクラスがあるのですが、これは文章に合わせた木構造を作成するわけではなく、単にチャンク化したものを一定数ごとにまとめて木構造にするものです。

      Tree Index - LlamaIndex ? 0.7.9

方針3-2:テキストを大きく分割する

  • テキスト分割のパラメータを調整する

    • 内容

      テキストを大きく分割することで、該当箇所も含められるだろうという考え方です。テキスト分割のパラメータとしては、チャンクのサイズとオーバーラップするサイズがあります。これを大きくすることで、該当箇所を含められる可能性があります。

    • 難しそうな点

      1つのチャンクの分量が増えるので、ベクトル検索の精度が低くなる可能性があります。

    • 補足

      アイディアレベルの案です。多分難しいと思います。

方針4:ほかの検索方法も併用する

  • 内容

    ベクトル検索だけなく他の検索手法も利用する方法です

  • 大変そうな点

    ユーザとのインタフェースは会話調であることを考えると、ユーザの質問から、検索条件・クエリを考える仕組みが必要そうです。ここはLLMに考えさせてしまうのも良さそうです。

    データベースの設計・構築が大変かもしれません。自分はこうした検索サービスなどを使用したことが無いので、このあたりはまだわかっていません。

  • 補足

    クラウドサービスやOSSを使うことになると思います。クラウドサービスとしては、Azure Coginitive Search、Amazon Kendra、Google Cloud Searchといったものが挙げられます(ほかにもたくさんあります)

    複数の検索結果を統合して再度ランクをつけ直す、re-rankという仕組みもあるようです。その手法の一つとして、Cross-encoder re-rankingという手法があります。これは複数の検索結果(ドキュメントのテキスト)と、質問テキストの類似度を別の手法で計算するというものです。

    コストや処理時間が許すなら、全部の検索結果をそれぞれLLMに入力し「質問の回答として利用できるか」を考えさせる、というのもありかもしれません。

方針5:考えながら複数回検索を繰り返す

方針5-1:Agentを利用する

  • 内容

    LangChainに実装されているAgentのような、与えられたクエリ(タスクの文章)とツールをもとに、自律的に考えてながらツールを実行して結果を返す機能を利用する方法です。今回の場合、検索機能をツールとしてAgent与え、Agentはユーザからの質問に回答するのに必要なデータがあるかを自動で考え検索し、検索結果で不足している情報があれば随時検索する、というフローになります。

  • 難しそうな点

    理想的にはすべて自律的に動いて、ユーザが質問すればさまざまな検索が行われて回答が帰ってくるはずですが、実際にやってみると、思考の過程でおかしな考えになったり、ツールの実行の仕方がおかしくなり、回答がでないということが起きやすいです。

    このあたりはまだ思考プロンプトやツールの与え方や、どのモデルを利用するかなどを試行錯誤する必要がある印象です。

方針6:会話形式にして何回かやり取りできる

方針6-1:Agent + Human as a Toolを利用する

  • 内容

    LnagChainのAgent関連の機能として、Human as a Toolという考え方があります。思考の過程で、一回ユーザに質問を返したり入力を求めることで、不足している情報を追加することができます。

  • 補足

    Human as a Toolの使い方として、専門の人につなぐというのもありだと思います。例えば社内のチャットボットなら、担当部署の人にAgentがわからない部分を聞いて、その回答をもとにAgentがユーザに返すというフローになります。

どこから手を着けるか?

最初に検討するべきは、そもそもベクトル検索以外の方法を採用するかどうか(方針4)だと思います。その検索手法を試して、要件に合うコストや処理時間に収まり、精度が上がるのであれば、簡単に改良できそうです。また、別の検索手法を単体で利用して精度があまり改善されなくても、複数の検索手法を組み合わせる方式も考えられます。

ほかの検索手法が要件に合わない場合など、ベクトル検索単体でドキュメントを検索する場合は、ベクトル検索を改良する方法(方針1~3)や、Agentを利用する方法を試す(方針5~6)を試すことになると思われます。Agentを利用する場合(方針5・6)は検索機能をツールとしてわたすことになるので、検索機能の改良(方針1~3)が先の方が筋が良いでしょう。まず方針1のような簡単な工夫で済めば一番楽だと思われ、方針2・方針3は少し自作する部分が多く、手間や時間がかかりそうです。

なので、方針4・1・{2 or 3}・5・6の順かなと思います。

まとめ

ドキュメントに関して質問ができるチャットボットを作成することを想定し、文章検索の方法としてベクトル検索を使用した際に、起きそうな問題点と対策について考えた内容をまとめてみました。

今後は、今回挙げた各方法について調べたり試していこうと思っています。

参考にさせていただいたサイト・ページ

https://cloud.google.com/vertex-ai/docs/matching-engine/train-embeddings-two-tower?hl=ja

https://cloud.google.com/blog/products/ai-machine-learning/scaling-deep-retrieval-tensorflow-two-towers-architecture?hl=en

https://arxiv.org/abs/2004.12832

https://blog.reachsumit.com/posts/2023/03/two-tower-model/#fn:5

https://qdrant.tech/articles/hybrid-search/

https://cloud.google.com/vertex-ai/docs/matching-engine/train-embeddings-two-tower?hl=ja

https://www.youtube.com/watch?v=3giqIW2pIW4

https://dev.classmethod.jp/articles/google-cloud-day-2022-recommend-model-two-tower-session-report/

謝辞

中村さんじょんすみすさんに、技術まわりについての相談にのっていただきました。ありがとうございました。