大規模言語モデルをつかったベクター検索ベースの検索・回答できる仕組みを作ってみました
こんにちは。カウシェで機械学習エンジニアをしている tatsuya(白川)です。
GPT-4、すごいですよね。このようなものが出てくるのは既定路線だったと思うのですが、想像より早くでてきた上に公開までされてしまい、驚きました。これからは大規模言語モデル(LLM)ありきで何ができるかを考える必要があるなぁと感じています。自分の仕事ももっと実現したい世界観・体験に寄せていけるような気がしており、これからが楽しみです。
ところで、カウシェではいま検索システムのフルリプレイスを検討しています(これまでは Shopify の検索機能をそのまま使っていました)。これからゼロベースで検索システムを作るならどういう仕組みにするのが良いかを検討しており、機械学習とまとめてしまうのならいっそのことベクター検索ベースの検索システムはどうか…などと考えていたところ、下記の Tweet をみつけたので、そちらを真似しながら LLM ベースの簡易な検索なしくみ(というか、任意のテキストリソースを参照して回答してくれる Bot)をつくって検証してみました。
なお、ChatGPT を使った検索では風間さんによる下記のすばらしい記事がすでにあります。
風間さんの方法では、検索対象の文章を要約させておくことでドキュメントの文脈を考慮した検索を実現されています。(最後の部分は推測ですが)要約生成→要約のベクター化→ベクター検索→要約をプロンプトに仕込み回答の生成というような手順を取っているようです。非常に面白いです。
今回やったこと
風間さんの方法も試してみたかったのですが、要約を経由することで欠落する情報もありそうだったのと単純な仕組みでの限界を知っておきたかったので、ナイーブにテキストをチャンキングしてベクター検索する方向で検証してみました。具体的には、下記のようなことをしています。
LLM を使った文章の Embedding を使ったテキストのベクター検索をしてみた
得られた検索結果を LLM に要約させてみた
上記すべて OpenAI の API + ローカル Python 環境だけで完結させて作ってみた
カウシェのデータブログを検索+要約させてみた
若干省略している部分はありますが、基本的には gpt4-pdf-chatbot-langchain の流れに従っています(はず)。
gpt4-pdf-chatbot-langchain がやっていること
先程の Tweet で紹介されている GPT ベースの検索システムです。大雑把には下記のような構成になっています。
■ ベクター検索システムへの登録
テキストソースからテキストの抽出(図だとPDF)
テキストを一定のサイズのチャンクに分割
チャンクごとに Embedding を取得
適当なメタ情報とともに Embedding を Vector Store に登録
■ 検索
質問文とそれにまつわるテキストコンテキスト(chat history)を取得
LLM に上記を食わせて、スタンドアローンな質問文を生成
2のスタンドアローンな質問文をテキスト Embedding モデルに入力し Embedding を取得
3 の Embedding をクエリーとしてベクター検索システムに問い合わせ、関連性の高いベクター+そのメタ情報を取得
得られた検索結果を 2 のスタンドアローンな質問文と組み合わせる
5 のテキストを LLM に入力し要約させ、回答とする
面白いなと思ったのは、コンテキストと質問からスタンドアローンな質問を作るところと、検索するシステムと回答を生成するシステムを分離しているところです。過去にも BERT が出たばかりのときにベクター検索ベースの自然文を入力クエリーとした検索システムを作ってみたりもしたこともあるのですが、そのときは可能性は感じたものの、学習なしでは取りこぼしや文体の類似性に引きづられる部分が多くそのまま使うのは厳しそうでした。一方で学習させようとするとQ にたいして良い A を紐付けたデータをそれなりに容易しなければならず、ゼロショット的に始めるのは難しい感覚を持ちました。
LLM でやってみたらどうなるでしょう?
今回作った仕組み
今回作った仕組みは基本的には gpt4-pdf-chatbot-langchain とほぼ同じ仕組みです。ただ、gpt4-pdf-chatbot-langchain の絵をみてなるほどなと思い、コードを読み込まずに一気に作った部分のあるので、細かいところでは gpt4-pdf-chatbot-langchain と異なる部分もあるかもしれませんのでご了承ください。gpt4-pdf-chatbot-langchain とは少なくとも下記の部分を変えています。
langchainは使っていません。よりコントローラブルで生っぽい処理を体験しておきたかったためです。
純粋な検索システムとしての利用を想定したので今回コンテキストは抜きました。そのため、スタンドアローンな質問はそのまま入力された質問にしています。質問文にコンテキストを含めれば良いだけなので、あとでコンテキストを追加するのは容易です。
テキストソースを PDF ではなく Web ページにしました(原理的にテキストを抽出できればソースはなんであっても大丈夫です)。Webの場合、余計なテキスト情報も混じりがちなので、trafilatura を使って本文抽出をしました。本文抽出は地味に難しいタスクなので、完璧ではないもののこういったツールがあるのはありがたいです。
テキストのチャンク単位は文にしました。文分割は GiNZA で行っています。短文だと回答を生成するときに心もとないので、ただし検索結果の出力としては、ヒットした文の前後 2 文を含めています。
ベクター検索には faiss を使いました(gpt4-pdf-chatbot-langchain は Pinecone というサービスを使っています)。ただし、今回検証したくらいのサイズでは faiss を使う必要は全くありません。あくまで今後の拡張のためです。そもそも簡単に使得るツールなので使っています。
全体的に Python で実装しました。
ヒットした文章を要約して回答を作るための LLM への指示は下記のような感じにしています(検索で取得する文章は 10 件にしています)。
{question}
上記の質問に関連する文章としてたいして下記の{n_hits}箇所が見つかりました。
書かれている内容を整理して、質問に対する回答をまとめてください。
なお、関連度は0から1までの関連性を表す値です。その説明をそのまま使いたいので、
説明に関係ない余計な情報はつけないでください。
#1 (関連度: {関連度1})
{ヒットした文章1}
#2 (関連度: {関連度2})
{ヒットした文章2}
...
機械学習エンジニア(自分)が後でローカルでいろいろ弄ることを前提に作ってみました。
試してみた
カウシェのデータブログに投稿されている 9 本の記事を対象にしてみました。
データに関する情報は下記のとおりです。このくらいであればこの仕組みで実現する必要はないかもしれないですが、しくみの検証ということでご容赦ください。
本当は GPT-4 を使いたかったのですが、GPT-4 の API 利用を申請しているものの waiting list に入ったままなので、API としては gpt-3.5-turbo を使っています。
また、回答を得られた後に 2 回「続けてください。」と入力して、回答の追加を促しています。結果的に、回答を上からストレートに読んでいくと理解がだんだんと深まるようになればよいなと期待しました。
以下、いくつか質問・回答例を上げてみます。
Q. カウシェではどんなデータ活用をしていますか?
切り貼りして作った感はあるのと、活用ではなく採用の話が出てくるところ、技術的な部分が少なめなのがちょっと微妙です。質問自体をもう少し具体化させたほうが良かったのかもしれません。
Q. tatsuyaはどんな人ですか?
社内にはメルペイ出身の方はいますが、自分はメルペイにいたことないんですけどね。。。検索結果の要約を作る際の入力はもっと工夫が必要な感じがあります。後は GPT-3 だと回答にちょっとチグハグ感があるので、GPT-4 にしたときにどうなるのか気になります。
というわけで、GPT-4 でも試してみました。
アイシア=ソリッドを運営しているのは弊社をお手伝いいただいている杉山さんです。色んな人の情報が混ざってしまっているようです。「データ基盤の構築やデータ分析に驚くべきスキルを持っています」なんて言われると照れちゃいますね。
それはさておき、やはりそもそも検索結果を作るところに大きな課題があるようです。チャンクに分割して独立に Embedding を作るところに問題がありそうです。ただ、現在の OpenAI の API では長い文章を入力できる機能は提供されていないので、自前でなにか工夫をする必要がありそうです。
Q. 門奈剣平はどんな人ですか?
※ 弊社の代表です。カウシェのデータブログ中では言及はないのでどういう結果になるか試してみました。
実はベクターサーチでは関連度 0.9 以上でヒットした文章が複数見つかっていたので(下記)、それを要約して「その人に関する情報は見つかりませんでした」的な回答をしてくれるのはすごいです。
ベクターサーチ部分には課題が残ります。現状の関連度(cosine similarity)の数値はそんなに良い指標になっていないかもしれません。そもそも BERT で試したときもそうだったのですが、質問と回答ではモデルを変えたりする必要があるのかもしれません(BERT などの場合そのための fine tune などはよくやられているはずです)。リッチなLLMあればそこすら指示の仕方の工夫で解消できるかもしれないので、今後研究してみたいです。
Q. カウシェではどんな人を募集していますか?
ということなので、カウシェにご興味を持った方がいらっしゃいましたら Twitter @s_tat1204 もしくは YOUTRUSTのカジュアル面談 などでお気軽にお声がけください。とくにデータエンジニアの方!お待ちしています!!その他職種の方もお待ちしています!!!
まとめ
任意のテキストソースを検索し、それをもとに回答してもらえる仕組みを作ってみました。下記などはまだまだ研究の余地があるなと感じており、引き続き検証してみたいと思います。
テキストチャンクの単位。今回は文にしてみましたが、もっと大きめのチャンクにしたほうが良さそうです。
ドキュメントのコンテキストを考慮したテキストチャンクのベクター構築方法の確立。要約+チャンクでベクター化するなどしたほうが良いかもしれません。
検索対象・クエリーをベクター化する際の LLM への問い合わせ方。ベクター検索の精度がいまいちなので、質問に対する関連性がより高くなるようにしたいです。LLM への問い合わせ方を工夫するだけである程度実現できるのではと期待しています。レコメンドでよく使う two tower model のような、問い合わせ側と検索される側で embedding を独立のモデルで作ってうまくマッチするようにチューニングする部分が、LLM だと問い合わせ方を変えるだけで実現できたら楽しそうです。
質問の内容に具体性を欠く場合にユーザーに情報を補足させるようにする仕組みがあったほうが体験として良いかもしれません。「質問内容を明確化するために何を知りたいですか?」などを ChatBot に聞けば良さそうです。
検索結果のより良い要約・表現の仕方。回答が生成されてしまう仕組みなので、なぜその回答が得られたのかが気になってしまうと思います。回答の品質次第な部分は大いにあると思いますが、今回作った雑な仕組みだとやはり欲しくなります。
そもそも非常に遅い(1 つの質問にたいして 20 秒くらいかかっています)ので、高速化したいです。
工夫しながら手軽に遊べるのは素晴らしいですね!