埋め込み(Embeddings)は、テキストを意味を捉えた数値ベクトルに変換します。ベクトルストアと組み合わせることで、強力なセマンティック検索が可能になり、RAGアプリケーションの基盤を形成します。
埋め込みとは?
埋め込みは、テキストを密なベクトルに変換し、類似した意味がベクトル空間で近くなります。
flowchart LR
A["テキスト: 'こんにちは'"] --> B["埋め込みモデル"]
B --> C["[0.12, -0.34, 0.56, ...]"]
D["テキスト: 'やあ'"] --> B
B --> E["[0.11, -0.33, 0.55, ...]"]
style B fill:#8b5cf6,color:#fff
主な特性
| 特性 | 説明 |
|---|---|
| 次元数 | ベクトルサイズ(例:1024、1536) |
| 意味的類似性 | 類似テキスト → 類似ベクトル |
| 距離指標 | コサイン類似度、ユークリッド距離 |
Titan Embeddings
Amazon Titanは、検索とRAGに最適化された埋め込みモデルを提供します。
利用可能なモデル
| モデル | 次元数 | 最大トークン | ユースケース |
|---|---|---|---|
| Titan Embeddings G1 - Text | 1536 | 8192 | 汎用テキスト |
| Titan Multimodal Embeddings G1 | 1024/384/256 | 128 | テキスト + 画像 |
テキスト埋め込みの生成
import boto3
import json
client = boto3.client('bedrock-runtime', region_name='us-east-1')
def get_embedding(text: str) -> list:
response = client.invoke_model(
modelId='amazon.titan-embed-text-v1',
body=json.dumps({
"inputText": text
})
)
result = json.loads(response['body'].read())
return result['embedding']
# 埋め込みを生成
embedding = get_embedding("クラウドコンピューティングとは何ですか?")
print(f"次元数: {len(embedding)}") # 1536
バッチ埋め込み
def get_embeddings_batch(texts: list) -> list:
embeddings = []
for text in texts:
embedding = get_embedding(text)
embeddings.append(embedding)
return embeddings
documents = [
"クラウドコンピューティングはインターネット経由でITリソースを提供します。",
"機械学習はコンピュータがデータから学習することを可能にします。",
"Kubernetesはコンテナ化されたアプリケーションをオーケストレーションします。"
]
embeddings = get_embeddings_batch(documents)
マルチモーダル埋め込み
import base64
def get_multimodal_embedding(text: str = None, image_path: str = None):
body = {}
if text:
body["inputText"] = text
if image_path:
with open(image_path, "rb") as f:
body["inputImage"] = base64.b64encode(f.read()).decode()
response = client.invoke_model(
modelId='amazon.titan-embed-image-v1',
body=json.dumps(body)
)
result = json.loads(response['body'].read())
return result['embedding']
# テキスト埋め込み
text_emb = get_multimodal_embedding(text="ソファに座っている猫")
# 画像埋め込み
image_emb = get_multimodal_embedding(image_path="cat.jpg")
# 複合埋め込み
combined_emb = get_multimodal_embedding(
text="ソファに座っている猫",
image_path="cat.jpg"
)
ベクトル類似度
コサイン類似度
import numpy as np
def cosine_similarity(a: list, b: list) -> float:
a = np.array(a)
b = np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 埋め込みを比較
emb1 = get_embedding("機械学習とは何ですか?")
emb2 = get_embedding("MLアルゴリズムを説明してください")
emb3 = get_embedding("パスタの作り方")
print(f"ML質問同士: {cosine_similarity(emb1, emb2):.4f}") # ~0.85
print(f"ML vs 料理: {cosine_similarity(emb1, emb3):.4f}") # ~0.30
AWSのベクトルストア
Amazon OpenSearch Serverless
フルマネージドのベクトル検索サービス。
from opensearchpy import OpenSearch, RequestsHttpConnection
from requests_aws4auth import AWS4Auth
import boto3
# クライアント設定
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(
credentials.access_key,
credentials.secret_key,
'us-east-1',
'aoss',
session_token=credentials.token
)
client = OpenSearch(
hosts=[{'host': 'your-collection.us-east-1.aoss.amazonaws.com', 'port': 443}],
http_auth=awsauth,
use_ssl=True,
connection_class=RequestsHttpConnection
)
# ベクトルフィールド付きインデックスを作成
index_body = {
"settings": {
"index": {
"knn": True
}
},
"mappings": {
"properties": {
"embedding": {
"type": "knn_vector",
"dimension": 1536,
"method": {
"name": "hnsw",
"space_type": "cosinesimil",
"engine": "nmslib"
}
},
"text": {"type": "text"},
"metadata": {"type": "object"}
}
}
}
client.indices.create(index="documents", body=index_body)
ドキュメントのインデックス
def index_document(doc_id: str, text: str, metadata: dict = None):
embedding = get_embedding(text)
document = {
"embedding": embedding,
"text": text,
"metadata": metadata or {}
}
client.index(
index="documents",
id=doc_id,
body=document
)
# ドキュメントをインデックス
index_document("doc1", "クラウドコンピューティング概要", {"category": "cloud"})
index_document("doc2", "機械学習の基礎", {"category": "ml"})
ベクトル検索
def search_similar(query: str, k: int = 5) -> list:
query_embedding = get_embedding(query)
search_body = {
"size": k,
"query": {
"knn": {
"embedding": {
"vector": query_embedding,
"k": k
}
}
}
}
response = client.search(
index="documents",
body=search_body
)
return [
{
"text": hit["_source"]["text"],
"score": hit["_score"],
"metadata": hit["_source"].get("metadata", {})
}
for hit in response["hits"]["hits"]
]
# 検索
results = search_similar("AWSとは何ですか?")
for result in results:
print(f"{result['score']:.4f}: {result['text']}")
Amazon Aurora PostgreSQL(pgvector)
import psycopg2
conn = psycopg2.connect(
host="your-aurora-cluster.region.rds.amazonaws.com",
database="vectordb",
user="admin",
password="password"
)
cur = conn.cursor()
# pgvector拡張を有効化
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
# ベクトル列付きテーブルを作成
cur.execute("""
CREATE TABLE IF NOT EXISTS documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536),
metadata JSONB
)
""")
# 高速類似検索用インデックスを作成
cur.execute("""
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100)
""")
conn.commit()
pgvectorでの挿入と検索
def insert_document(content: str, metadata: dict = None):
embedding = get_embedding(content)
cur.execute(
"""INSERT INTO documents (content, embedding, metadata)
VALUES (%s, %s, %s)""",
(content, embedding, json.dumps(metadata or {}))
)
conn.commit()
def search_documents(query: str, limit: int = 5):
query_embedding = get_embedding(query)
cur.execute(
"""SELECT content, metadata,
1 - (embedding <=> %s::vector) as similarity
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT %s""",
(query_embedding, query_embedding, limit)
)
return cur.fetchall()
ベクトルストア比較
| 機能 | OpenSearch Serverless | Aurora pgvector | Neptune Analytics |
|---|---|---|---|
| マネージド | フル | 部分的 | フル |
| スケール | 自動 | 手動 | 自動 |
| ハイブリッド検索 | あり | 限定的 | グラフ + ベクトル |
| 最適な用途 | 汎用検索 | 既存PostgreSQL | グラフ関係 |
完全な検索アプリケーション
import boto3
import json
from typing import List, Dict
class SemanticSearch:
def __init__(self, opensearch_client, index_name: str):
self.bedrock = boto3.client('bedrock-runtime')
self.opensearch = opensearch_client
self.index_name = index_name
def get_embedding(self, text: str) -> List[float]:
response = self.bedrock.invoke_model(
modelId='amazon.titan-embed-text-v1',
body=json.dumps({"inputText": text})
)
return json.loads(response['body'].read())['embedding']
def index(self, doc_id: str, text: str, metadata: Dict = None):
self.opensearch.index(
index=self.index_name,
id=doc_id,
body={
"embedding": self.get_embedding(text),
"text": text,
"metadata": metadata or {}
}
)
def search(self, query: str, k: int = 5) -> List[Dict]:
response = self.opensearch.search(
index=self.index_name,
body={
"size": k,
"query": {
"knn": {
"embedding": {
"vector": self.get_embedding(query),
"k": k
}
}
}
}
)
return [
{
"text": hit["_source"]["text"],
"score": hit["_score"],
"metadata": hit["_source"].get("metadata", {})
}
for hit in response["hits"]["hits"]
]
# 使用例
search = SemanticSearch(opensearch_client, "documents")
search.index("1", "AWSはクラウドコンピューティングサービスを提供します")
results = search.search("Amazonはどんなクラウドサービスを提供していますか?")
ベストプラクティス
| プラクティス | 推奨事項 |
|---|---|
| ドキュメントをチャンク | 大きなドキュメントを小さな断片に分割 |
| 埋め込みを正規化 | 一貫した類似度スコアを確保 |
| メタデータフィルターを使用 | ベクトル検索とフィルターを組み合わせ |
| レイテンシを監視 | 埋め込み生成時間を追跡 |
| 埋め込みをキャッシュ | 計算済み埋め込みを保存 |
重要なポイント
- 埋め込みは意味を捉える - 類似テキストは類似ベクトル
- Titanはテキストとマルチモーダルを提供 - ユースケースに応じて選択
- 複数のベクトルストアオプション - OpenSearch、Aurora、Neptune
- コサイン類似度が標準 - 埋め込みの比較に
- RAGの基盤 - 埋め込みは検索システムを支える