Bedrock Fine-tuning本番6ヶ月で直面した、データ準備・コスト・精度の現実的な課題
BedrockのFine-tuning機能を実際に本番運用して6ヶ月。ドキュメントに書いてないデータセット準備の落とし穴、想定外のコスト、推論精度の失敗を赤裸々に共有します。
Bedrock Fine-tuningに手を出した時点で気づくべきだった
うちのチームがBedrock Flows本番運用を始めて3ヶ月経った時点で、プロダクト側から「Claude の精度が足りない。カスタムモデル作ってくれ」という依頼が来た。そこから地獄が始まった。
Bedrock Fine-tuningが2025年後半に正式化されて、2026年の今はかなり安定してるんだけど、実際に本番で6ヶ月運用してみると、ドキュメントには書いてない落とし穴だらけなんですよね。データセット準備の手間、思ったより高いコスト、推論時の想定外の遅延…ぜんぶ経験した。
実際にやってみた失敗と工夫を、ありのままに書く。
データセット準備が本当の地獄だった話
まず最初の落とし穴は「準備」。ドキュメントだと「高品質な例100個あれば十分」と書いてあるけど、うちの場合は全然足りなかった。
最初、営業チーム向けの提案生成モデルを作ろうとして、既存のCRM履歴から適当に100件抽出してFine-tuningした。結果は最悪。推論時に「前のモデルより悪くなった」という状況に陥ったんだ。
後で気づいたのは、単なるサンプル数の問題じゃなくて、データの多様性と質が完全に欠落してたということ。営業がCRMに入力したテキストって、実は品質がバラバラ。機械的な記入漏れもあるし、営業個人のくせも強い。そこを無視してFine-tuningすると、モデルが「営業のクセを学習」してしまう。結果は推察の通り、最悪な提案ばかり生成される羽目になった。
結果的に、うちが準備に使った労力は以下の通り。正直、見積もりの3倍は覚悟した方がいい:
- データクリーニング(3週間) - 既存データから使えそうなものをフィルタリング。機械的な削除ルールとスポット確認を組み合わせた
- 手作業での生成例作成(2週間) - 営業マネージャー3人が理想的なプロンプト・レスポンスのペアを500個作成。一番つらい工程
- 品質ラベリング(1週間) - 外部QA業者で品質チェック。これがないと「品質」の定義が主観になる
- バージョン管理(進行中) - データセットの変更履歴を追跡。あとで「あの例、なんで入れたんだ?」って問題が出る
ドキュメントでは触れられていないけど、Fine-tuning用のデータセット準備ツール群が2026年でもまだ成熟していない。BigQueryやSageMaker Data Wranglerを組み合わせるしかない状況だ。正直、ここが一番の改善ポイントだと思う。
実装的には、JSONLフォーマットで以下の構成に統一した:
{"prompt": "顧客プロフィール:SaaS企業、年商5億円、現在ERP導入検討中", "completion": " 提案のポイント:\n1. 既存システムとの統合コスト削減\n2. 導入期間3ヶ月を保証\n3. 初期6ヶ月の無料サポート"}
{"prompt": "顧客プロフィール:製造業、工場3拠点、IoT化を進めたい", "completion": " 提案のポイント:\n1. リアルタイム監視ダッシュボード\n2. 既存PLC との互換性確保\n3. オンサイトサポート体制"}
Bedrock側の仕様として、プロンプトと完成例の長さのバランスが重要。うちが最初失敗したのは、promptが短すぎて、「完成例」のバリエーションが多すぎたこと。同じプロンプトに対して異なる回答を学習させるのは避けるべき。1プロンプト = 1ベストな完成例 の原則を守らないと、モデルが混乱して、むしろ性能が落ちる。地味だけど、ここが本当に重要。
コストが想定の3倍になった罠
次の驚きはコスト。公式ドキュメントで「Fine-tuning用の計算コストは通常推論の3倍」と書いてあるけど、実際にはそれより複雑だ。
Bedrock Fine-tuningの課金体系(2026年時点)を整理するとこんな感じ:
| 項目 | 単価 | 説明 |
|---|---|---|
| Fine-tuning(1トークン) | $0.000075 | 学習データセット全体の処理 |
| 推論(1出力トークン) | $0.0006 | カスタムモデルの推論 |
| ストレージ(モデル1個/月) | $5.00 | モデルアーティファクト保管 |
初回Fine-tuningの試算だと、500個の例×平均800トークンで約40万トークン、単価$0.000075なので $3/回。月に5回新しいバージョンをテストするなら月$15。そして本番推論で月300万トークン出力するなら月$1,800。「あれ、合計で月$1,820?」って計算になる。
実は違うんだ。計算が複雑なのは、データセット内の重複排除ロジックがBlack Boxだから。うちの場合、営業データから生成した例には似たパターンが多かった。Bedrockはこれを検出して「有効トークン数」を圧縮する(ドキュメントより推測)。結果的に実績は月$800程度。まあ、許容範囲か。
でも落とし穴がもう1つある。Fine-tuning後のモデルは、公開モデル(Claude 3.5 Sonnet)より推論が遅い。平均レイテンシが2倍になるケースもあるんですよね。そこからコスト圧力が生まれる:
- 遅いなら並列度を上げたい→DynamoDBのプロビジョニング容量アップ→コスト増
- または、キャッシュ層(Redis)を入れる→インフラコスト増
うちはこっちを選んだ。ElastiCacheにRedisを追加して、推論結果の30分キャッシュを実装した。これで実効的には月$2,500程度のコスト増で抑えられたけど、当初見積もりからは+$1,700。予算ヒアリングの段階で「想定と違う」と言われた。
実装アーキテクチャ図
graph TB
subgraph Client["クライアント層"]
API["REST API<br/>FastAPI"]
end
subgraph Inference["推論層"]
Cache["ElastiCache Redis<br/>30分キャッシュ"]
Gateway["API Gateway"]
Lambda["Lambda<br/>推論呼び出し"]
end
subgraph Bedrock["AWS Bedrock"]
CustomModel["Fine-tuned Claude<br/>v1.2"]
PublicModel["Claude 3.5 Sonnet"]
end
subgraph DataPipeline["データパイプライン"]
S3Raw["S3 Raw Data<br/>営業CRM抽出"]
Glue["AWS Glue<br/>クリーニング"]
S3Train["S3 Training Data<br/>JSONLフォーマット"]
Bedrock2["Bedrock Fine-tuning<br/>月2回実行"]
end
subgraph Monitoring["監視・評価"]
CloudWatch["CloudWatch<br/>レイテンシ監視"]
Lambda2["Lambda<br/>品質評価"]
SQS["SQS<br/>評価キュー"]
end
API -->|キャッシュ確認| Cache
Cache -->|MISS| Lambda
Lambda -->|推論リクエスト| Gateway
Gateway -->|カスタムモデル優先| CustomModel
Gateway -->|フォールバック| PublicModel
CustomModel -->|結果キャッシュ| Cache
PublicModel -->|結果キャッシュ| Cache
S3Raw --> Glue
Glue --> S3Train
S3Train --> Bedrock2
Bedrock2 --> CustomModel
Lambda2 -->|監視対象| CloudWatch
Lambda -->|品質スコア計算| SQS
SQS -->|評価実行| Lambda2
推論精度とフォールバックの設計が実は重要
Fine-tuningしたモデルが「必ず元モデルより良い」とは限らない。これが本番運用で一番つらいポイントなんですよね。
うちの営業提案生成モデルの場合:
- Claude 3.5 Sonnet(オリジナル) - 提案内容は汎用的で安全。だが顧客特性への適応が薄い
- Fine-tuned Claude - 営業スタイルに合わせた提案が出るけど、たまに営業クセを強く反映して「これはちょっと…」という案が出る
評価指標としては、営業マネージャーが手動で100件抽出して「どっちの提案の方が実際に契約まで行きやすいか」を採点した結果は以下:
- Fine-tuned:73点
- オリジナル:68点
「5点差」。統計的に有意か微妙なライン。ただし営業チームの主観としては「Fine-tunedの方が使える」らしい。データに基づく評価より、営業の実感が優先されるのが現実だ。
この曖昧さを処理するために、うちが実装したのはスコアベースのルーティング。正直、この設計があるとないで、本番運用の心の安定度が全然違う:
from enum import Enum
from typing import Literal
import json
import hashlib
class ModelSelection(Enum):
FINE_TUNED = "fine-tuned-claude-v1.2"
PUBLIC = "claude-3-5-sonnet"
async def invoke_with_routing(
prompt: str,
customer_profile: dict,
use_fine_tuned: bool = True
) -> dict:
"""
Fine-tuned と Public を動的にルーティング
"""
# 1. キャッシュ確認
cache_key = hashlib.md5(
f"{prompt}{json.dumps(customer_profile)}".encode()
).hexdigest()
cached = await redis_client.get(f"bedrock:{cache_key}")
if cached:
return json.loads(cached)
# 2. モデル選択
model_id = (
ModelSelection.FINE_TUNED.value
if use_fine_tuned
else ModelSelection.PUBLIC.value
)
# 3. 推論実行
response = await bedrock_runtime.invoke_model(
modelId=model_id,
body=json.dumps({
"prompt": prompt,
"max_tokens": 500,
"temperature": 0.7
})
)
result = json.loads(response["body"].read())
# 4. 品質スコア計算(簡易版)
quality_score = await calculate_quality_score(
result["completion"],
customer_profile
)
# Fine-tunedで品質スコアが低い場合、Public モデルで再実行
if use_fine_tuned and quality_score < 0.6:
logger.warning(
f"Fine-tuned score low ({quality_score}), fallback to public"
)
result = await invoke_with_routing(
prompt,
customer_profile,
use_fine_tuned=False
)
result["fallback_used"] = True
# 5. キャッシュ保存
await redis_client.setex(
f"bedrock:{cache_key}",
1800, # 30分
json.dumps(result)
)
return result
async def calculate_quality_score(completion: str, context: dict) -> float:
"""
簡易的な品質スコア計算
- 顧客セグメント関連キーワードの有無
- 提案の長さ(短すぎたり長すぎたりしないか)
- 営業トーンの検出
"""
score = 0.5 # ベーススコア
# 顧客タイプ別キーワード
keywords = {
"saas": ["統合", "導入期間", "サポート"],
"manufacturing": ["リアルタイム", "監視", "互換性"],
"retail": ["POS連携", "在庫", "顧客体験"]
}
if context.get("industry") in keywords:
for keyword in keywords[context["industry"]]:
if keyword in completion:
score += 0.1
# 長さチェック(200-500文字が理想)
if 200 <= len(completion) <= 500:
score += 0.2
return min(score, 1.0)
実運用で気づいたのは、フォールバック設計がないと「Fine-tuningしたのに古いモデルを使わせてくれない」という営業からの要望が来ることだ。人間は新しいツールへの期待値が高い。でも現実は「新しい = 必ず良い」ではない。なぜなら、Fine-tuningは営業データの弱点まで学習してしまうから。
本番運用で見えた3つの地味だけど重い課題
1. モデルのドリフト(時間とともに性能が落ちる)
3ヶ月ごとに営業データが更新されるんだけど、古いFine-tuningモデルを使い続けると徐々に性能が落ちる。顧客セグメントが変わったり、提案パターンが進化するからなんですよね。
うちは月1回の定期更新と、四半期ごとの完全再学習をやってる。費用は上がるけど、精度を維持するにはこれが必須。最初のドキュメントには書いてないけど、実運用では「継続的な再学習」が前提になる。
2. 推論レイテンシの予測不可能性
Fine-tuned モデルの推論時間にはバラつきがある。平均500msだけど、たまに2秒かかることもある。公開モデルより安定性が低い。原因は不明。Bedrockのドキュメントにもこれ以上の説明がない。
対策として、タイムアウト設定を短めに(1秒)にして、それを超えたら自動的に公開モデルにフォールバックさせてる。正直、この対策がなかったら本番でオーバーロード状態になってた。
3. A/Bテストがしたくても難しい
Fine-tuningして本番デプロイすると、本当にそれが良かったのか評価するのが困難。理由は「営業の使い方が変わっちゃう」こと。新しいモデルだから営業も工夫して使うようになる。その結果、成約率が上がったのは「モデルのおかげ」なのか「営業の使い方のおかげ」なのか不明確になる。統計的に厳密なA/Bテストを設計するなら、ランダム化が必須だけど、営業から「いや、新しいモデル使いたいんですけど…」という反発が来る。
2026年時点で「やっておけばよかった」こと
実装系だけじゃなく、組織的な準備も大事だった。改めて思う4つのポイント:
-
データセット管理の自動化 - JSONLファイルを手動で管理してると、バージョン管理が地獄。S3+Glueで自動パイプライン化しておくべきだった。あとから「この例、いつ誰が入れたの?」って問い合わせが来るから
-
品質評価メトリクスの事前定義 - Fine-tuning前に「成功の定義」を数値化しておくべき。営業の主観だけに頼ると後から「実は微妙…」ってなる。うちは定義が甘かったから、本番後に「もっと厳密に測定しろ」と言われた
-
フォールバック設計 - 最初からFine-tuning一本に絞らず、2モデルの共存を前提に設計。予期しない品質低下時の逃げ道が重要。これがないと本番障害時に手の打ちようがない
-
推論キャッシュ層 - 後付けするより、最初から入れておいた方が楽。営業提案は同じパターンの繰り返しが多いから、キャッシュ効率が高い。うちは後付けしたから設計をやり直す羽目になった
まとめ
Bedrock Fine-tuningは確かに強力だけど、「単にデータセット用意して学習させればOK」みたいなシンプルなものではない。本番運用6ヶ月で学んだことを整理すると:
-
データセット準備が7割 - 手作業が半端ない。品質ラベリングなし無理。見積もりの3倍は覚悟した方がいい
-
コストは想定より複雑 - 学習コスト、推論コスト、推論遅延による間接コスト(キャッシュ層、インフラ増強)を総合で考える必要がある
-
フォールバック設計は必須 - Fine-tuningモデルが常に最適とは限らない。2モデル共存の仕組みを作っておく
-
継続的な再学習が現実的 - 一度やったら終わりではなく、月1回程度の更新が必要。ドキュメントに「セットアンドフォーゲット」みたいな記述があったら信じるな
-
評価指標を事前に定義 - 営業の主観に頼ると後から「実は微妙」ってなる。定量的な成功基準がない状態で本番化するな
ぶっちゃけ、いきなり本番にぶち込まずに、まずは内部でのテスト期間(3ヶ月)を作るべき。データ準備だけで2-3ヶ月は見積もっておくのが現実的だ。
IAOSP Top 10の脆弱性対策と同じで、AI/ML基盤でもセキュリティとデータ品質は後付けできない。最初から設計に組み込んでおくことが、後々の心の安定につながる。