OpenAIでEmbeddingを生成する
text-embedding-3-smallとtext-embedding-3-largeモデルを使って文、段落、ドキュメントをembeddingし、品質とコストのトレードオフを比較します。
「OpenAIでEmbeddingを生成する」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。
OpenAI埋め込みモデルの概要
OpenAIは本番環境向けの埋め込みモデルとして、text-embedding-3-smallとtext-embedding-3-largeの2つを提供しています。smallモデルは1536次元のベクトルを生成し、トークンあたりの料金は5分の1です。一方、largeモデルは3072次元のベクトルを生成し、ベンチマークでより高い精度を示します。ほとんどのRAGアプリケーションでは、smallモデルから始めるのが適切です。
最初の埋め込み呼び出しを行う
client.embeddings.create()メソッドは、model名とinput文字列またはリストを受け取ります。返されるレスポンスオブジェクトにはdataリストが含まれ、各要素にはベクトルをPythonの浮動小数点数リストとして格納したembedding属性があります。
APIキーは必ず環境変数に保存し、ソースファイルにハードコードしないでください。
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])
response = client.embeddings.create(
model='text-embedding-3-small',
input='What is retrieval-augmented generation?'
)
vector = response.data[0].embedding
print(f'Vector length: {len(vector)}') # 1536
print(f'Type: {type(vector[0])}') # float
print(f'Sample values: {vector[:3]}') # [-0.02, 0.01, ...]埋め込みのバッチ処理を効率化する
inputに文字列のリストを渡すと、すべてを1回のAPI往復で埋め込むことができます。これはドキュメントコーパスのインデックスを作成する際に推奨される方法です。レスポンスのdataリストは元の順序を維持するため、ルックアップ辞書を簡単に構築できます。
1回のリクエストで送信できるバッチは2048項目までで、すべての入力の合計トークン数はモデルのトークン制限内に収める必要があります。
from openai import OpenAI
client = OpenAI()
documents = [
'RAG stands for Retrieval-Augmented Generation.',
'Embeddings convert text to numerical vectors.',
'Pinecone is a managed vector database service.'
]
response = client.embeddings.create(
model='text-embedding-3-small',
input=documents
)
embeddings = [item.embedding for item in response.data]
print(f'Got {len(embeddings)} embeddings')
print(f'Each has {len(embeddings[0])} dimensions')埋め込みの次元数を削減する
text-embedding-3の2つのモデルは、出力ベクトルを切り詰めるdimensionsパラメータに対応しています。たとえば、dimensions=256を設定すると、1536要素ではなく256要素のベクトルが返されます。短いベクトルはストレージ容量と計算量を削減できますが、精度がわずかに低下する可能性があります。
これは、すばやく試行したい場合や、最高の検索品質よりもストレージコストを重視する場合に便利です。
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model='text-embedding-3-small',
input='Shorter vectors save storage and query time.',
dimensions=256 # truncate from 1536 to 256
)
print(len(response.data[0].embedding)) # 256エンコード形式:Base64と浮動小数点数リスト
デフォルトでは、APIは埋め込みを浮動小数点数のJSON配列(encoding_format='float')として返します。encoding_format='base64'を指定すると、コンパクトなBase64エンコード済みバイナリデータを受け取れます。大規模なバッチでは、ネットワーク経由でより高速に転送できます。
Base64を使用する場合は、NumPyでデコードする必要があります。np.frombuffer(base64.b64decode(b64_str), dtype='float32')を使用してください。
import base64
import numpy as np
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model='text-embedding-3-small',
input='Base64 encoding transfers faster.',
encoding_format='base64'
)
b64 = response.data[0].embedding
vector = np.frombuffer(base64.b64decode(b64), dtype='float32')
print(f'Decoded {len(vector)} floats')トークン使用量とコストを追跡する
すべての埋め込みレスポンスには、prompt_tokensを含むusageオブジェクトがあります。料金はトークン単位で発生し、text-embedding-3-smallは100万トークンあたり$0.02、text-embedding-3-largeは100万トークンあたり$0.13です(2024年半ば時点)。
使用量を追跡すると、本番環境で実行する前に、コーパス全体のインデックス作成にかかるコストを見積もることができます。
from openai import OpenAI
client = OpenAI()
texts = ['Document one content here.', 'Document two content here.']
response = client.embeddings.create(
model='text-embedding-3-small',
input=texts
)
tokens_used = response.usage.prompt_tokens
cost_usd = tokens_used * 0.00000002 # $0.02 per 1M tokens
print(f'Tokens: {tokens_used}, Cost: ${cost_usd:.6f}')text-embedding-3-smallとlargeの比較
smallとlargeのどちらを選ぶかは、必要な精度と予算によって決まります。
- text-embedding-3-small:1536次元、高速、5分の1の料金、ほとんどのRAGワークロードに最適
- text-embedding-3-large:3072次元、MTEBベンチマークのスコアが高く、多言語コンテンツや複雑な意味タスクに適している
一般的には、smallモデルで検索品質のプロトタイプ作成と検証を行い、評価指標が目標を下回る場合にのみlargeモデルへ切り替えます。
内積検索のために埋め込みを正規化する
OpenAIはL2正規化された埋め込みを返します。つまり、各ベクトルの大きさはすでに1です。そのため、追加の正規化処理を行わずに、内積をコサイン類似度の高速な近似として使用できます。数百万のベクトルを検索する場合、こうした細かな最適化の積み重ねが重要になります。
import numpy as np
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model='text-embedding-3-small',
input='Are OpenAI embeddings normalized?'
)
vec = np.array(response.data[0].embedding)
magnitude = np.linalg.norm(vec)
print(f'Vector magnitude: {magnitude:.6f}') # very close to 1.0大規模なドキュメントを扱う
text-embedding-3-smallモデルは、1つの入力につき最大8191トークンを受け付けます。ドキュメントがこの上限を超えると、APIはエラーを返します。標準的な解決策は、まずドキュメントをチャンクに分割し(通常は1チャンクあたり200~500トークン)、各チャンクを個別に埋め込むことです。
このチャンク分割は単なる技術的要件ではありません。小さく焦点を絞った部分を埋め込むほうが、ページ全体を埋め込むよりも精度の高い結果が得られるため、検索性能も向上します。
import tiktoken
enc = tiktoken.encoding_for_model('text-embedding-3-small')
def count_tokens(text):
return len(enc.encode(text))
max_tokens = 8191
text = 'Very long document content...' # pretend this is huge
if count_tokens(text) > max_tokens:
print('Document too long — chunk before embedding')
else:
print(f'Safe to embed: {count_tokens(text)} tokens')高スループットのための非同期埋め込み
数千件のドキュメントをインデックス化する場合は、async OpenAI clientとasyncio.gatherを使用して、複数の埋め込みリクエストを同時に送信します。ボトルネックはCPUではなくネットワークの遅延であるため、逐次的に呼び出すよりも大幅に高速です。
同時リクエストを実行する際は、tokens-per-minuteの上限に達しないよう、必ずレート制限への対応として指数バックオフを追加してください。
import asyncio
from openai import AsyncOpenAI
async def embed_batch(texts):
client = AsyncOpenAI()
response = await client.embeddings.create(
model='text-embedding-3-small',
input=texts
)
return [item.embedding for item in response.data]
async def main():
batches = [['doc1', 'doc2'], ['doc3', 'doc4']]
results = await asyncio.gather(*[embed_batch(b) for b in batches])
all_embeddings = [emb for batch in results for emb in batch]
print(f'Embedded {len(all_embeddings)} documents')
asyncio.run(main())コスト削減のための埋め込みのキャッシュ
同じテキストに対して何度も埋め込みを生成すると、費用と時間が無駄になります。テキスト文字列(またはそのハッシュ)をキーとする辞書に埋め込みをキャッシュし、セッション間でも利用できるようにディスクへ保存してください。
本番システムでは、ドキュメントIDによって重複排除できるベクトルデータベースに埋め込みを保存します。すべてのインデックス作成時に再埋め込みするのではなく、内容が実際に変更された場合にのみドキュメントを再埋め込みしてください。
import json, hashlib, os
from openai import OpenAI
CACHE_FILE = '/tmp/embedding_cache.json'
client = OpenAI()
try:
cache = json.load(open(CACHE_FILE))
except FileNotFoundError:
cache = {}
def get_embedding(text):
key = hashlib.md5(text.encode()).hexdigest()
if key not in cache:
r = client.embeddings.create(model='text-embedding-3-small', input=text)
cache[key] = r.data[0].embedding
json.dump(cache, open(CACHE_FILE, 'w'))
return cache[key]
vec = get_embedding('Caching saves API costs.')
print(f'Retrieved {len(vec)}-dim embedding')クイックチェック
このレッスンで学んだAIエンジニアリングの概念を理解できているか確認しましょう。
レッスンのまとめ
このレッスンでは、text-embedding-3-smallは、ほとんどのRAGワークロードに適した費用対効果の高い選択肢であること、1回のAPI呼び出しで複数のテキストをまとめて埋め込むほうが、逐次的に呼び出すより効率的であること、そしてdimensionsパラメーターによってベクトルサイズを小さくし、精度とストレージ使用量をトレードオフできることを学びました。次は、これらの埋め込みとNumPyを使ってセマンティック検索システムを構築します。
AI チューターと学ぶ Python — 無料
ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。
- コース
- 30
- レッスン
- 120
よくある質問
「OpenAIでEmbeddingを生成する」レッスンは無料ですか?
はい。「OpenAIでEmbeddingを生成する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。
「OpenAIでEmbeddingを生成する」で何を学びますか?
text-embedding-3-smallとtext-embedding-3-largeモデルを使って文、段落、ドキュメントをembeddingし、品質とコストのトレードオフを比較します。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Engineering Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「OpenAIでEmbeddingを生成する」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Engineering Academyレッスンでコードを書いて実行できますか?
はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Vector Embeddingとは何か
- OpenAIでEmbeddingを生成する
- NumPyによるSemantic Search
- Embeddingのクラスタリングと可視化