ChatGPTに独自の知識を埋め込む方法
前項で解説した通り、ChatGPTは基本的にステートレスであるため、知識を保存しておくということができません。そのため、必要な情報は全てシステムプロンプトや一連の会話履歴の中に記述しておかなければなりません。しかし、例えばこれをAIヘルプデスクとして活用しようと思った時に、膨大な社内情報を会話の度に全て送信するというわけにもいきません。質問文の意図に対し、関連性の高い情報だけをピックアップしてシステムプロンプトの中に記述するという作業が必要になります。
では、質問文とそれぞれの情報との意味的な繋がりはどのように計算すればよいのでしょうか? OpenAIが提供する「Embedding API」を使えばそれが可能となります。
エンベディングの概要と仕組み
自然言語処理における「エンベディング」とは、単語や文章の意味や関係性を捉えて、それをベクトルに変換する技術を指します。
OpenAIが提供するEmbedding APIに何か文章を送信すると、モデルがその文章を解釈した結果が1000次元を超える長さの巨大なベクトルに変換されて返ってきます。そのような文章の意味が内包されたベクトルデータのことを、文章の分散表現と呼びます。
この分散表現というものは非常に便利で、類似判定や分類、レコメンドなどといった、文字の状態のままでは難しい数学的な処理もベクトル演算を使って簡単に行なえるようになります。そして、それら演算結果の精度は、元の文章の意味がどれだけ的確にベクトルへと反映されているかに大きく依存します。
OpenAIが公開しているEmbeddingモデルは、同社の研究と開発の実績に基づいていると考えればその信頼性は非常に高いと言えるでしょう。
詳しい仕組みの説明には専門的な内容が必要となるため割愛しますが、ここでは以下の2点だけ押さえておけば問題ありません。
- Embedding APIを使えば文章をベクトルに変換できる
- ベクトル同士の類似度を計算すると、元の文章の類似度もわかる
Embedding APIを活用してChatGPTに外部知識を与える方法
ChatGPTに知識を与える流れは、以下のようになります。
- 事前準備として、ChatGPTに参照させたい情報を細かく分けて、一つずつEmbedding APIでベクトル化して保存しておく
- ユーザーから質問を受けたら、その内容をベクトル化する
- 質問のベクトルと、各情報のベクトルとの類似度を計算し、上位いくつかをピックアップする
- ピックアップした情報をシステムプロンプトの一部に記載し、会話を続行する
この中で重要な箇所を具体的なコードで説明します。こちらは、APIを利用して文章をベクトルに変換するコードです。「client」は冒頭で作成したようなOpenAIクラスのインスタンスです。
response = client.embeddings.create(
input="ベクトル化する文字列",
model="text-embedding-ada-002"
)
inputにはベクトル化する文章を、modelには使用するモデル名を入力します。チャットAPI同様、Embeddingにも複数のモデルがありますが、執筆時点では「text-embedding-ada-002」という第二世代モデルが公開されており、このモデルは全てのタスクで前世代の性能を上回っているとされているため、用途を問わずこのモデル一択で良いでしょう(新しいモデルが公開されている場合は必要に応じてそちらに変更してください)。
その他の詳しい仕様や、レスポンスの形式はChatGPT APIのドキュメントをご確認ください。
こちらは2つのベクトルの類似度を算出する関数のコードです。
import numpy as np def cosine_similarity(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
Embedding処理におけるベクトル同士の類似度の計算にもいくつか方法がありますが、コサイン類似度と呼ばれる指標を用いるのが最も一般的です。
コサイン類似度は、対象となる二つのベクトルの内積を、それぞれの大きさの積で割ることで算出できます。それをNumpyという算術演算ライブラリを使用して記述すると、上記のようになります。NumpyはColabに標準搭載されているため、Colab上では特に何もしなくてもimportできますが、ローカルで実行する際などはpipコマンドでインストールが必要です。
なお、Embeddingに関してAPIを利用するのは、文章をベクトルに変換する部分だけで、類似度の計算や、それをプロンプトに埋め込む作業はこちらの手元で行います。
実装方法
それでは、上記のコードを使用して必要な関数を実装していきます。まずは知識データを準備しましょう。
今回サンプルとして扱うのは、ChatGPTに考案してもらった「ウルトラリアルVR」という架空のサービスです。以下の図はウルトラリアルVRに搭載されている特徴的な機能の一覧です。
実際には存在しないサービスなので、この情報はChatGPTを含む誰も知りません。これらを埋め込んだ状態でChatGPTに質問をし、矛盾のない回答が返ってくるかどうか検証してみましょう。
上記のデータはCSV形式で本記事からサンプルファイルとしてダウンロードできます。ダウンロードしたCSVファイルは、下図の手順でColaboratoryにアップロードしてください。
事前準備
ユーザーからの質問文はその都度ベクトル化する必要がありますが、参照する知識データについては内容を変更する必要がない限り事前に一度ベクトル化して保存しておけば事足ります。まずは事前準備としてそちらを行いましょう。
以下のコードは、CSVファイルの中身を読み込んでベクトルデータを生成し、本文と共に保存しておく処理です。CSVファイルをアップロードした上で、こちらを新しいセルに貼り付けて実行してみてください。
import csv
# CSVファイルの内容にベクトルデータを追加して構造化する関数
def make_knowledge_base(filename):
# (1) CSVファイルからデータを読み込む
with open(filename) as file:
product_data = list(csv.reader(file))
knowledges = []
for product in product_data:
# (2) CSVの要素からベクトル化する内容を抽出
input_text = product[0] + "\n" + product[1] # タイトルと本文
# (3) APIを使用し、文章をベクトルを変換
response = client.embeddings.create(
input=input_text,
model="text-embedding-ada-002"
)
# (4) ベクトルデータを取得
embedding = response.data[0].embedding
# (5) ベクトルとデータの内容と紐付けてリストに追加
item = {
"title": product[0],
"content": product[1],
"url": product[2],
"embedding": embedding
}
knowledges.append(item)
return knowledges
# (6) ベクトルデータを含む各種情報を保存
# 今回は簡易的な実装のため、グローバル変数に格納
vector_store = make_knowledge_base("ultrarealvr.csv")
今回ベクトル化する内容として、機能名と本文を改行で連結したものをEmbedding APIに渡しており、URLはベクトルからは除外しています。
また、今回は簡易的な実装として、最終的にvector_storeというグローバル変数に知識データを保存しています。
知識データの中から質問文に関連する項目を検索する
次に、ユーザーの質問文から最も関連性の高い情報を抽出する関数を定義します。こちらも新しいセルに貼り付けて実行してみてください。
import numpy as np
# コサイン類似度を算出する関数
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def get_related_knowledge(query, knowledges):
# (1) 質問文をベクトルに変換
response = client.embeddings.create(
input=query,
model="text-embedding-ada-002"
)
# (2) ベクトルデータを取得
emb_query = response.data[0].embedding
# (3) 保存した知識データを全て参照し、質問文との類似度を算出
results = []
for knowledge in knowledges:
# 類似度を算出
similarity = cosine_similarity(emb_query, knowledge["embedding"])
# 類似度とデータ内容を紐づけてリストに追加
item = {"similarity":similarity, "data":knowledge}
results.append(item)
# (4) 類似度の高い順に知識をソートし、上位3件を取得
num_knowledges = 3
results = sorted(results, key=lambda x: x["similarity"], reverse=True)
results = results[:num_knowledges]
# (5) 抽出したデータをプロンプトに組み込むための文章として成形
template = "機能名:{title}\n説明:{content}\nURL:{url}\n\n"
prompt = ""
for result in results:
data = result["data"]
prompt += template.format(title=data['title'], content=data['content'], url=data['url'])
return prompt
この関数では引数で受け取った質問文をベクトル化し、全ての知識データのベクトルとのコサイン類似度を算出しながら、最も類似度が高い3件を抽出しています。
最後に、抽出したデータを文章として成形した上で返却しています。
今回はこのような形式で3件分の記述が連なるようにしています。
機能名: タイトル 説明: 本文 URL: 参照先URL [空行]
会話プログラムに知識を埋め込む
それでは今回作成した関数を、前の章で作成した会話プログラムに組み込んでみましょう。変更するのは(1)システムプロンプトを作成の部分だけです。当該箇所を以下のように修正してみてください。
# (1) システムプロンプトを作成
prompt_template = """
あなたは「ウルトラリアルVR」というサービスのカスタマーサポートです。
与えられた関連知識を元に、ユーザーからの問い合わせに適切な回答を行なってください。
# 制約事項
* ウルトラリアルVRに関する事以外は答えないでください。
* 関連知識に記述されていない機能や情報を答える事は固く禁止します。
* 参照した知識にURLが付与されている場合は提示してください。
# 関連知識
{knowledge}
"""
# 関連知識を取得し、プロンプトに埋め込む
knowledge = get_related_knowledge(message_user, vector_store)
prompt = prompt_template.format(knowledge=knowledge)
system = {'role':'system', 'content':prompt}
関連知識を埋め込むにあたり、システムプロンプトの内容も改良しました。
チャットBotに「ウルトラリアルVRのカスタマーサポート」という役割を与え、存在しない情報を勝手に創作しないよう制約事項を明記しています。本来であれば、この内容は英語で記述した方がトークン量の削減や性能の向上に繋がります。そうした際は、返答も英語で返ってきてしまうことがあるので、必ず日本語で返答するという旨を制約に加えた方がよいでしょう。
プロンプトのベースを定義したあと、先ほど実装した関数を使ってユーザーの質問文から関連知識を取得し、{knowledge}の部分に埋め込んでいます。
この状態でセルを実行して関数を上書きしてください。その後、改修前に会話を行なったセルを再度実行し、ウルトラリアルVRに関する質問を行なってみましょう。
埋め込んだ知識について質問してみる
以下、こちらで質問してみた例です。
ウルトラリアルVRではどんなアバターが使えますか?
AI > ウルトラリアルVRでは、ユーザー専用のAIアバターを使用することができます。このアバターは、ユーザー自身の知識、好み、個性に基づいて学習し、仮想空間内で活動します。アバターはユーザーの興味に合わせた情報を収集し、新しい体験を提案することもできます。詳細は以下のURLをご参照ください。
[ユーザー専用AIアバターの詳細](https://sample.ultrarealvr.com/user-exclusive-ai-avatar)
アバターに関する質問では、関連項目を正しく読み取れていることがわかります。
ウルトラリアルVRで収益を得る方法はありますか?
AI > はい、ウルトラリアルVRではユーザー生成コンテンツの市場機能を通じて収益を得ることができます。この機能では、ユーザーが自分自身で作成した仮想現実コンテンツを他のユーザーと共有・販売することができます。ユーザーは独自の仮想体験、ゲーム、アート、教育プログラムを制作し、仮想空間内で市場に公開することができます。詳細については、以下のURLを参照してください。https://sample.ultrarealvr.com/user-generated-marketplace
データに直接含まれていない「収益」という単語を使って質問しましたが、与えられた情報の中で正しい回答が行えています。
ウルトラリアルVRの利用料金を教えてください。
AI > 申し訳ありませんが、ウルトラリアルVRの利用料金に関する情報は提供できません。利用料金については、公式ウェブサイトやサービス提供元にお問い合わせいただくか、利用規約をご確認ください。
データ中にまったく言及がない情報について質問しましたが、返答できない旨を伝えているので今回のデータ内容においては正しい回答です。
ウルトラリアルVRを医療分野に活用することはできますか?
AI > はい、ウルトラリアルVRは医療分野でも活用することができます。医療分野では、以下のような活用例が考えられます。
- 手術トレーニング: ウルトラリアルVRを使用して、医師や看護師の手術トレーニングを行うことができます。仮想現実内で手術をシミュレーションし、リアルな手術環境を再現することで、実際の手術に備えることができます。
- 痛みの軽減: ウルトラリアルVRを使用して、患者が痛みを感じにくくすることができます。例えば、仮想現実内で美しい風景やリラックスできる場所を体験することで、患者の痛みや不快感を軽減することができます。
- 心理療法: ウルトラリアルVRを使用して、心理療法を行うことができます。例えば、特定の恐怖症やトラウマを再現し、患者が安全な環境でそれに直面することで、克服する手助けをすることができます。
- リハビリテーション: ウルトラリアルVRを使用して、リハビリテーションを行うことができます。例えば、脳卒中の患者が仮想現実内で日常生活の動作を練習することで、機能回復を促進することができます。
以上のように、ウルトラリアルVRは医療分野で様々な活用が期待されています。
データには含まれていないものの、知識の解釈次第で答えを導き出せるような質問をしてみました。カスタマーサポートがここまでの内容を勝手に答えて良いかについては議論の余地がありますが、データとの矛盾はないように思えます。
このように、本来ChatGPTが知らないような情報でも、Embeddingを用いて知識を埋め込むことで正しい受け答えができるようになります。
実運用における知識データの保存先について
今回のコードでは、ベクトル化した知識データをグローバル変数に入れて保管していましたが、実運用では何らかの永続的なストレージに保存しておく必要があります。
少量のデータであれば、互換性を考慮してJSON形式で保存したり、またはPickleというPythonの標準モジュールを使ってバイナリデータで保存したりするのもよいでしょう。
しかしながら、OpenAIのEmbeddingモデルで生成される巨大なベクトル(今回使用したモデルでは1536次元)は、1件あたりでもそこそこのデータ量となりますし、そこに本文の内容も加わるとなると、ファイル読み込み時のメモリへの負荷を無視できません。また、会話の度に全てデータを走査しながら類似度判定を行わなければならないため、データの数が増えれば増えるほどパフォーマンスは確実に低下していきます。そういった事情を考慮すると、実践的に充分な量のデータでそれらの手段を取ることはあまりオススメできません。
これらのデータの最適な保存先として近年注目を集めているのが、ベクターストアと呼ばれる特殊なデータストアです。
ベクターストアはそれぞれのデータにベクトルを紐づけて保管しておくことを前提として、今回手元で行なった「関連性の高い上位n件の項目を取得する」というような処理をストア側で高速に行なってくれます。それを利用すれば、手元に落ちてくるデータは常に最低限の量で済むようになるため、前述のように知識データのトータル件数を気にする必要はほぼなくなるでしょう。
ベクターストアを提供しているサービスはいくつかあるので、ご自身の環境に合わせて選択してください。筆者の周りでは、PineconeまたはAzure AI Searchの利用が多い印象です。
OpenAIの公式のクックブックにもサービスごとに実装方法が公開されていますので、ご利用の際はそちらを参考にしてみてください。
まとめ
今回の記事では、ChatGPT APIの基本的な使い方と、独自データの埋め込み方についてご紹介しました。掲載したコードは解説の都合で簡略化されていますので、アレンジや改良の余地が豊富に含まれています。ぜひ、ご自身の工夫を取り入れて実践してみてください。
ChatGPTの活用方法に関する研究は日々進化しており、新しいツールやテクニックが続々と登場しています。最新情報をチェックすることでさらに多くの知見を得られるでしょう。
次回の記事では、チャットBotをさらに進化させ、AIが独自の判断で必要なツールを実行できるようにする方法を紹介する予定です。
