ChatGPTで独自データを扱うためのエンべディング
【2023/11/7追記】
OpenAI Dev Dayにて、開発者向けの大型アップデートが発表されました。この記事で紹介している手法は、Retrieval-Augmented Generation(RAG)と呼ばれてきましたが、今回のアップデートでコンテクスト長(やりとりできるテキストの長さの上限)がこれまでの8Kから128K(12万8千トークン)に大幅にアップするため、一般的な本の内容は1冊分丸ごと渡すことができるようになります。独自データベースとの連携という意味では、ここで紹介している手法も引き続き有効な手法ですが、API関連でも様々な機能が追加されているので、リリースやSam Altmanによるキーノートは要チェックです。
ChatGPTは、膨大な量のテキストを学習してはいますが、天気予報のような最新の情報や、ある特定の本の内容や、特定のサービスの詳細についてはじめから知っているわけではありません。独自のサービスやアプリにChatGPTを取り入れようと思うと、プロンプトに収まらない長い文章や独自データをChatGPTに何らかの方法で教えたり学習させる必要があります。今回はその手法として、エンべディングについて説明してみたいと思います。
その前にまず、ChatGPTをサービスやアプリに組み込むサンプルとして前回の記事で紹介した天気予報AIについて、改めて何をしていたのか振り返ってみます。
「東京の明日の天気は?」
ChatGPTの特徴は、こうした自然言語の質問や指示を正しく理解出来るところです。自然な会話文から何を聞かれているかをきちんとわかってくれる訳です。最近だいぶ慣れてきてしまいましたが、改めて考えるとその能力には驚かされます。
ただ、何を聞かれているかわかっても明日の天気予報の情報を持っていなければ答えることはできません。”素の”ChatGPTは、膨大な知識を学習していても、明日の天気はわからないのです。
ChatGPTが知らない情報を教える
ユーザーがChatGPTへ入力するプロンプトには質問だけでなく、さまざまな指示や付加情報を与えることが出来ます。例えば「あなたは優秀なアシスタントです。」といった役割を与えたり、「小学生にもわかるように回答してください。」といった表現形式を指示したり、アイデア次第でいろいろな工夫ができるのですが、プロンプトでChatGPTが知らないことを事前に伝えておくこともできます。
つまり、必要な天気予報のデータをどこからか取ってきて事前に伝えることができれば、その情報にもとづいて自然な言葉で明日の天気をユーザーに伝えることができるというわけです。
前回の記事では、天気予報のデータを取得することができるWeatherAPIというサービスを利用しました。WeatherAPIは、例えば次のようにURLに都市名や日付をつけると、天気予報のデータがJSONという形式で返ってきます。
http://api.weatherapi.com/v1/forecast.json?...q=tokyo&dt=2023-04-02
{
"maxtemp_c": 14.3,
...
"condition": {
"text": "Patchy rain possible",
...
"code": 1063
}
}
そして、返ってくる天気予報のJSONデータをそのままプロンプトに突っ込んでも、いい感じに天気を読み取ってくれることもわかりました。
これは一つの例ですが、独自のサービスやアプリにChatGPTを活用するイメージがなんとなく持てたでしょうか?
「アメリカから注文できますか?」
今日の本題はここからです。
今回は、ECサイトのカスタマーサポートにChatGPTを使うことを考えてみましょう。アメリカの住んでいるユーザーから「アメリカから注文できますか?」という質問が来たとします。当たり前ですが、"素の"ChatGPTはあなたのECサイトが海外からの注文に対応しているかどうかは知りません。
まず思いつく一番雑な方法は、サイトにあるFAQのデータをまるごとChatGPTに教えることですが、少なくとも現状ではプロンプトの文字数(トークン数)に制限があり、大量のデータをまるごとプロンプトに詰め込むことはできません。さてどうしましょう、、?
エンべディングを使ってプロンプトをつくる
そこで登場するのが、大量の情報からユーザーの入力に関連する情報を抽出するためのエンべディング(Embedding)という手法です。
では、「質問に関連した内容」を大量の独自データから取り出すにはどうしたらいいでしょう?
そこで活用されているのがベクトルデータベースと呼ばれるものです。やや専門的になってきますが、ベクトルデータベースとは、ざっくり言えば下の図のように各データを数値化(ベクトル化)し、空間上にマッピングしたようなデータベースです。下の図では2次元空間ですが、実際には自然言語のベクトルデータベースは1,000次元以上(!)の多次元空間上にマッピングされています。
このベクトルデータベースに対して、ユーザーが入力した文章も同じように数値化(ベクトル化)して同じ空間上にマッピングすることで、空間上で近くにある情報(=関連する情報)を抜き出してくることができるわけです。
エンべディングと言われる手法が何をやっているか、少しイメージできたでしょうか?
ここまで見てきたように、独自データのエンべディングは、プロンプトに含められる文字数(トークン数)に制限がある中で、ChatGPTが独自データを扱えるようにするための手法とも言えます。
そして図からわかることは、エンべディングによってChatGPTが独自データがうまく扱えるようになるかどうかは、実はChatGPTのモデルの性能だけでなく、その手前で独自データから精度の高いベクトルデータベースをつくれるか、ユーザーの入力文をもとにそのベクトルデータベースから精度高く関連情報を抽出できるかがとても重要になるということです。
ユーザーの入力文に対して、ベクトルデータベースから見当違いな情報しか抽出できなければ、どんなにChatGPTが優秀でも満足のいく回答は得られないのです。
LlamaIndexを使う
では、具体的にはどうすれば独自データのエンべディングを行うことができるでしょうか。
実は独自データをベクトル化するためのエンべディング用モデルもOpenAI から提供されており、直接そのAPIを使うことも可能ですが、使い勝手のよいツールもいくつか開発されています。その中の一つがLlamaIndexと呼ばれるライブラリです。LlamaIndexを使うと、
テキストファイルなどの独自データをベクトルデータベース化する
ユーザーの入力文から、関連するデータを抽出する
抽出したデータをプロンプトに追加した上でユーザーの入力文をChatGPTに渡す
といったことが比較的簡単にできるようになっています。
また、テキストファイルだけでなく、ウェブサイトやPDFなど様々なフォーマットのデータをベクトル化するテンプレートが、Llama Hubというサイトで公開されており、ライブラリから簡単に使えることも特徴です。
「ChatGPTはコンヴィヴィアル・テクノロジーと言えますか?」
最後に、LlamaIndexを使った具体的なコードを紹介します。
拙著『コンヴィヴィアル・テクノロジー』の原稿テキストデータを使って、「『コンヴィヴィアル・テクノロジー』を読んだChatGPT」をつくってみます。
以下のコードは、Google ColabなどのPython実行環境で試すことができます。まずは、OpenAIとLlamaIndexのライブラリをインストールします。
!pip install openai llama-index
import os
os.environ["OPENAI_API_KEY"] = "your_openai_api_key"
そして次のように、文章のベクトルデータベース化、ユーザーの入力と類似度が高い部分の抽出し、その部分を追加したプロンプトの生成を短いコードで行うことができます。
from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader
# dataというフォルダ内のテキストファイル(原稿データ)をベクトルデータベース化
documents = SimpleDirectoryReader("data").load_data()
index = GPTVectorStoreIndex.from_documents(documents)
# ユーザーの入力文を処理するエンジンの作成
query_engine = index.as_query_engine()
# ユーザーの入力文に関連する部分を抽出し、プロンプトに追加した上でユーザーの入力文をChatGPTに渡す
response = query_engine.query("ChatGPTはコンヴィヴィアル・テクノロジーと言えますか?"))
print(response)
このままでも動きますが、LlamaIndex側でデフォルトのプロンプトテンプレートが使われていたり、ChatGPTのモデルもGPT-3ベースのものだったりするので、少しカスタマイズします。
from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, LLMPredictor, PromptHelper, ServiceContext, QuestionAnswerPrompt, download_loader
from langchain import OpenAI
# dataというフォルダ内のテキストファイル(原稿)をベクトルデータベース化
documents = SimpleDirectoryReader("data").load_data()
# 使用するLLMやパラメータをカスタマイズする
llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="gpt-4"))
max_input_size = 4096
num_output = 256
max_chunk_overlap = 20
prompt_helper = PromptHelper(max_input_size, num_output, max_chunk_overlap)
service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor, prompt_helper=prompt_helper)
index = GPTVectorStoreIndex.from_documents(
documents, service_context=service_context
)
# プロンプトのテンプレートをカスタマイズする
QA_PROMPT_TMPL = (
"関連する情報は以下の通りです。"
"---------------------\n"
"{context_str}"
"\n---------------------\n"
"{query_str}\n"
)
QA_PROMPT = QuestionAnswerPrompt(QA_PROMPT_TMPL)
query_engine = index.as_query_engine(
text_qa_template=QA_PROMPT
)
# ユーザーの入力文に関連する部分を抽出し、プロンプトに追加した上でユーザーの入力文をChatGPTに渡す
response = query_engine.query("ChatGPTはコンヴィヴィアル・テクノロジーと言えますか?")
print(response)
特に、デフォルトのプロンプトテンプレートは次のようになっていて、
DEFAULT_TEXT_QA_PROMPT_TMPL = (
"Context information is below. \n"
"---------------------\n"
"{context_str}"
"\n---------------------\n"
"Given the context information and not prior knowledge, "
"answer the question: {query_str}\n"
)
そのままだと、プロンプトが英語なのでたまに英語で回答が返ってきたり、”Given the context information and not prior knowledge,(提供されたコンテキスト情報に基づき、先行知識を用いずに) "となっていて、与えられた情報だけを使うような条件付けがされているので、(本に書かれていない)ChatGPTについて聞いても「ChatGPTについての情報がないので回答できません。」といった回答が返ってきてしまいます。さきほどのようなカスタマーサポートに使おうと思ったらその方が余計なことを言わないので安心ですが、今回は素直に「ユーザーの入力文に関連した独自データ」+「ユーザーの入力文」をChatGPTに渡すようにしてみました。
果たして『コンヴィヴィアル・テクノロジー』を読んだChatGPTは「ChatGPTはコンヴィヴィアル・テクノロジーと言えますか?」という問いにうまく答えてくれるでしょうか?
回答は以下のようなものでした。
ちょっと優等生すぎる気もしますが、なかなかの回答です。レポートで提出されたら次第点は取れるといったところでしょうか。私がイリイチの思想に依拠しつつまとめたコンヴィヴィアル・テクノロジーに向けた6つの問いをうまく抽出して、それぞれの問いに照らし合わせながら回答していて、確かに本を読んでくれていると感じます。
さてこの時、実際にはどんな情報がプロンプトに渡されているでしょうか?
実行中のログを表示するようにしてみます。
!pip install openai llama-index
import os
os.environ["OPENAI_API_KEY"] = "your_openai_api_key"
# ログを出力する
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True)
実際にChatGPTに渡されているプロンプトは以下のようになっていました。
確かにエンべディングによって、「ChatGPTはコンヴィヴィアル・テクノロジーと言えますか?」という入力文に関連した文章が抽出されていると言えなくはないですが、ベクトルデータベース化した6万字程度の原稿に対して、類似度が高いと抽出されたのは第6章の冒頭1300字程度。細かく言うと、第6章の冒頭の2つのチャンク(文章のかたまり)が抽出されていて、max_chunk_overlap=20と指定しているので2つのチャンクは若干文章がオーバーラップしていて、なおかつ単に類似度が高い順に並べているので文章としては順序が逆になっています。
「ChatGPTはコンヴィヴィアル・テクノロジーと言えますか?」という質問にこの程度の回答をするためには、とりあえず第6章の冒頭を1000字くらい読めばいいのか、、と著者としてはちょっと複雑な気持ちになりますが、いずれにしても、エンべディングによってChatGPTが本の全文を読むというわけではなく、ChatGPTはあくまで最終的に生成されたこのプロンプトを受け取って回答しているだけなのだということが、詳しく中身を見てみるとわかります。
ちなみに、拙著『コンヴィヴィアル・テクノロジー』の第6章は、ウェブサイトで全文公開しています。発売からもうすぐ2年経ちますが、改めてこうしたAIの登場によって「人間とテクノロジーが共に生きる社会」が現実味を帯びていると感じています。ぜひご興味あれば読んでみてください。
解像度を上げて中身を知ること
以上のように、エンべディングという手法を使うと、ChatGPTなどの大規模言語モデル(LLM)を独自のサービスやアプリと連携させることができますが、前述したように、ベクトルデータベースからユーザーの入力文に関連した情報を抽出する部分がかなり重要であるということがわかりますし、そこはChatGPTとは別の仕組みで動いている点も知っておくべきでしょう。厳密に言えば、ChatGPTが与えた独自データを学習しているわけではないのです。
エンべディングとは別に、「プロンプトとそれに対して期待される回答」のセットを独自データとして用意して追加学習させるファインチューニングと呼ばれる手法もありますが、現時点(2023/5/12)では、少なくともOpenAIが提供するモデルでファインチューニングが可能なのはGPT-3のベースモデル(text-davinci-003など)に限られるので、GPT-3.5やGPT-4の能力を引き出すことができません。GPT-3をファインチューニングするか?エンべディングを使ってGPT-3.5やGPT-4を使うか?という選択になり悩ましいところです。
また、OpenAI以外にもたくさんの大規模言語モデル(LLM)が日々登場しているので、今後は様々な選択肢から用途目的に応じて適したモデルや手法を選んでいくことになるのだと思います。
ただいずれにしても言えることは、「ChatGPTをカスタマイズする」「独自のサービスやアプリにChatGPTを活用する」と言う時、具体的にどんな仕組みが使われているのか、少し解像度を上げて中身を知っておくことは、AIを活用したサービスやアプリを考える上でも、またAIに使われずにAIを使っていく上でも重要だと思います。
【追記】「埋め込み」ってどっちの話?
自然言語のような複雑で大量のデータから類似した特徴を抽出するための「エンべディング(Embedding)=埋め込み」と、抽出した情報をプロンプトへ「埋め込む」という表現が混在してしまっていたので文言を修正しました。(ご指摘ありがとうございます。)
Google Cloudの説明にもあるように、機械学習における「エンべディング(Embedding)=埋め込み」とは、あくまで「高次元ベクトルの低次元空間への変換」を意味します。