Geminiのファインチューニング:ドメイン特化型モデルの構築
汎用の大規模言語モデルは一般的なタスクに優れています。しかし、ドメイン固有の言語パターン、業界用語、または専門的な推論理解が必要な場合、ファインチューニングは不可欠になります。Geminiのファインチューニング APIを使用すれば、あなたのフィールドの専門家のように動作するモデルを構築できます。
このガイドでは、データセット準備からモデル評価、本番環境へのデプロイまで、全体の過程をカバーしています。実際に機能する実践的なパターンに焦点を当てています。
ファインチューニングを理解する:いつ、なぜ
ファインチューニングは、事前学習済みモデルの重みをあなたのデータを使って調整します。これはプロンプトエンジニアリングやRAGとは異なり、これらはモデルをそのまま使用します。ファインチューニングはモデルの動作を永続的に変更します。
RAG、少数ショットプロンプティング、または基本的なツール統合でより良く解決できるタスクには効果が限定的です。
ファインチューニング vs. 代替案
# シナリオ:コンプライアンス文書から構造化インサイトを抽出
# オプション1:少数ショットプロンプティング(速い、制限あり)
def extract_with_fewshot(document: str) -> dict:
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=[{
"role": "user",
"content": f"""
この文書からコンプライアンス違反を抽出してください。
例1:
入力: "システムが2024年3月14日に4時間ダウンした"
出力: {{"violation": "SLAブリーチ", "duration_hours": 4, "date": "2024-03-14"}}
以下から抽出してください:{document}
"""
}]
)
return json.loads(response.content[0].text)
# オプション2:ドメイン文書を使用したRAG(検索中心)
def extract_with_rag(document: str) -> dict:
# ナレッジベースから同様の例を検索
examples = retrieve_examples(document, top_k=3)
# 検索した例をプロンプトで使用
pass
# オプション3:ファインチューニングされたモデル(専門的)
def extract_with_finetuned(document: str) -> dict:
response = client.messages.create(
model="compliance-extractor-v1", # ファインチューニングされたモデル
max_tokens=1000,
messages=[{
"role": "user",
"content": f"違反を抽出してください:{document}"
}]
)
return json.loads(response.content[0].text)何千もの文書を処理し、一貫した専門的な抽出が必要なコンプライアンス抽出では、ファインチューニングは投資に値します。
データセット準備:基礎
ファインチューニングの品質は、あなたのデータに完全に依存しています。「ゴミを入れればゴミが出る」はここにおいて、どこよりも適用されます。
データ形式と構造
Geminiは特定の形式でトレーニングデータを期待します。各訓練例には以下が含まれるべきです:
from dataclasses import dataclass
from typing import List, Optional
import json
@dataclass
class TrainingExample:
"""ファインチューニング用の単一の訓練例。"""
input_text: str
output_text: str
metadata: Optional[dict] = None
class DatasetPreparer:
def __init__(self, output_format: str = "jsonl"):
self.output_format = output_format
self.examples: List[TrainingExample] = []
def add_example(
self,
input_text: str,
output_text: str,
metadata: dict = None
):
"""訓練例を追加します。"""
example = TrainingExample(
input_text=input_text,
output_text=output_text,
metadata=metadata or {}
)
self.examples.append(example)
def validate_examples(self) -> dict:
"""訓練前にデータセット品質を検証します。"""
validation_report = {
"total_examples": len(self.examples),
"avg_input_length": 0,
"avg_output_length": 0,
"length_distribution": {},
"potential_issues": [],
}
if len(self.examples) < 100:
validation_report["potential_issues"].append(
"推奨される最小値(100例)より小さいデータセット"
)
input_lengths = []
output_lengths = []
for example in self.examples:
input_len = len(example.input_text.split())
output_len = len(example.output_text.split())
input_lengths.append(input_len)
output_lengths.append(output_len)
validation_report["avg_input_length"] = sum(input_lengths) / len(input_lengths)
validation_report["avg_output_length"] = sum(output_lengths) / len(output_lengths)
# 重複をチェック
unique_inputs = len(set(e.input_text for e in self.examples))
if unique_inputs < len(self.examples) * 0.9:
validation_report["potential_issues"].append(
f"高い重複率:{1 - unique_inputs/len(self.examples):.1%}"
)
# 出力の一貫性をチェック
if max(output_lengths) > 4000:
validation_report["potential_issues"].append(
"推奨される最大長(4000トークン)を超える出力があります"
)
return validation_report
def export_training_data(
self,
filepath: str,
include_metadata: bool = True,
train_split: float = 0.8
):
"""
訓練データをJSONL形式でGemini APIにエクスポートします。
自動的な訓練/評価分割を含みます。
"""
import random
random.shuffle(self.examples)
split_idx = int(len(self.examples) * train_split)
train_examples = self.examples[:split_idx]
eval_examples = self.examples[split_idx:]
# 訓練セットをエクスポート
with open(f"{filepath}.train.jsonl", "w") as f:
for example in train_examples:
record = {
"messages": [
{
"role": "user",
"content": example.input_text,
},
{
"role": "assistant",
"content": example.output_text,
}
]
}
if include_metadata and example.metadata:
record["metadata"] = example.metadata
f.write(json.dumps(record) + "\n")
# 評価セットをエクスポート
with open(f"{filepath}.eval.jsonl", "w") as f:
for example in eval_examples:
record = {
"messages": [
{
"role": "user",
"content": example.input_text,
},
{
"role": "assistant",
"content": example.output_text,
}
]
}
if include_metadata and example.metadata:
record["metadata"] = example.metadata
f.write(json.dumps(record) + "\n")
return {
"train_examples": len(train_examples),
"eval_examples": len(eval_examples),
"train_file": f"{filepath}.train.jsonl",
"eval_file": f"{filepath}.eval.jsonl",
}
# 実世界の例:法律文書分析
preparer = DatasetPreparer()
# 例1:契約分析
preparer.add_example(
input_text="本契約は2024年1月1日より有効です。"
"ライセンサーは、内部ビジネス目的に限定したソフトウェアの使用に関して、"
"非独占的な世界規模のライセンスを付与します。すべての保証は放棄されます。",
output_text=json.dumps({
"contract_type": "ソフトウェアライセンス",
"effective_date": "2024-01-01",
"scope": "非独占的、世界規模",
"usage": "内部ビジネス目的",
"warranty": "放棄",
"key_clauses": ["ライセンス付与", "保証放棄"]
}),
metadata={"document_id": "contract_001", "category": "license"}
)
# 例2:リスク識別
preparer.add_example(
input_text="責任は、過去12カ月間に支払われた総手数料を超えない直接損害に限定されます。"
"どちらの当事者も、間接的、付随的、または結果的損害について責任を負いません。",
output_text=json.dumps({
"liability_cap": "直接損害のみ",
"cap_amount": "総手数料(12ヶ月)",
"excluded_damages": ["間接的", "付随的", "結果的"],
"risk_level": "低"
}),
metadata={"document_id": "contract_001", "category": "liability"}
)
# 検証とエクスポート
report = preparer.validate_examples()
print("検証レポート:", report)
export_info = preparer.export_training_data(
"legal_analysis_dataset",
train_split=0.8
)
print("エクスポート情報:", export_info)より小さく、クリーンなデータセットは、より大きく、ノイズのあるものより優れていることが多いです。
訓練設定とハイパーパラメータ
データが準備できたら、訓練ジョブを設定します。Geminiは主要なハイパーパラメータを公開しています:
import anthropic
import json
from typing import Optional
class GeminiFineTuner:
def __init__(self, api_key: str = None):
self.client = anthropic.Anthropic(api_key=api_key)
def create_fine_tuning_job(
self,
training_file_path: str,
eval_file_path: str,
model_id: str = "compliance-extractor-v1",
display_name: str = "コンプライアンス文書抽出器",
learning_rate: float = 0.001,
num_epochs: int = 3,
batch_size: int = 32,
weight_decay: float = 0.01,
warmup_steps: int = 100,
) -> dict:
"""
指定されたハイパーパラメータでファインチューニングジョブを作成します。
Args:
training_file_path: JSONL訓練データへのパス
eval_file_path: JSONL評価データへのパス
model_id: ファインチューニングされたモデルの識別子
display_name: モデルの表示名
learning_rate: 最適化の学習率
num_epochs: 訓練エポック数
batch_size: 訓練用バッチサイズ
weight_decay: L2正則化の強さ
warmup_steps: 学習率ウォームアップステップ
Returns:
job_idとステータスを含むジョブメタデータ
"""
# 訓練データをアップロード
with open(training_file_path, "rb") as f:
train_response = self.client.beta.files.upload(
file=f,
)
train_file_id = train_response.id
# 評価データをアップロード
with open(eval_file_path, "rb") as f:
eval_response = self.client.beta.files.upload(
file=f,
)
eval_file_id = eval_response.id
# ファインチューニングジョブを作成
job_response = self.client.beta.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{
"role": "user",
"content": (
f"以下のパラメータを使用してファインチューニングジョブを作成してください:\n"
f"モデルID: {model_id}\n"
f"表示名: {display_name}\n"
f"訓練ファイル: {train_file_id}\n"
f"評価ファイル: {eval_file_id}\n"
f"学習率: {learning_rate}\n"
f"エポック: {num_epochs}\n"
f"バッチサイズ: {batch_size}\n"
f"重み減衰: {weight_decay}\n"
f"ウォームアップステップ: {warmup_steps}"
),
}
],
)
return {
"job_id": model_id,
"display_name": display_name,
"training_file_id": train_file_id,
"eval_file_id": eval_file_id,
"hyperparameters": {
"learning_rate": learning_rate,
"num_epochs": num_epochs,
"batch_size": batch_size,
"weight_decay": weight_decay,
"warmup_steps": warmup_steps,
},
"status": "submitted",
}
def monitor_training(self, job_id: str) -> dict:
"""
訓練ジョブの進行状況を監視します。
メトリクスと現在のステータスを返します。
"""
# 実装では訓練APIをポーリングし、
# 進捗メトリクスを返します
return {
"job_id": job_id,
"status": "training",
"epoch": 2,
"total_epochs": 3,
"loss": 0.245,
"eval_loss": 0.312,
"estimated_time_remaining": 3600,
}ハイパーパラメータ調整戦略
class HyperparameterOptimizer:
def __init__(self, base_model: str = "gemini-pro"):
self.base_model = base_model
self.trials = []
def grid_search(
self,
training_file: str,
eval_file: str,
param_grid: dict,
) -> list:
"""
異なるハイパーパラメータで複数の訓練ジョブを実行します。
ドメインの最適な設定を見つけるのに便利です。
"""
trials = []
for learning_rate in param_grid.get("learning_rate", [0.001]):
for batch_size in param_grid.get("batch_size", [32]):
for num_epochs in param_grid.get("num_epochs", [3]):
trial = {
"learning_rate": learning_rate,
"batch_size": batch_size,
"num_epochs": num_epochs,
"status": "pending",
}
trials.append(trial)
# すべてのトライアルを送信
for trial in trials:
# 訓練ジョブを送信
pass
return trials
def get_best_hyperparameters(
self,
metric: str = "eval_f1"
) -> dict:
"""
完了したトライアルを分析して、最適なハイパーパラメータを返します。
"""
completed = [t for t in self.trials if t["status"] == "completed"]
if not completed:
return None
best = max(completed, key=lambda t: t.get(metric, 0))
return best評価:モデル品質の測定
デプロイ前に、ファインチューニングされたモデルを厳密に評価します。汎用メトリクス(損失、精度)は領域特化メトリクスほど重要ではありません。
from typing import List, Dict
import json
from dataclasses import dataclass
@dataclass
class EvaluationMetrics:
precision: float
recall: float
f1: float
accuracy: float
custom_metrics: Dict[str, float] = None
class ModelEvaluator:
def __init__(self, model_id: str, client):
self.model_id = model_id
self.client = client
self.predictions = []
self.ground_truth = []
def evaluate_on_dataset(
self,
eval_file_path: str,
extraction_task: bool = True,
) -> EvaluationMetrics:
"""
評価データセットでモデルを評価します。
構造化抽出および分類タスクに対応しています。
"""
# 評価データを読み込む
eval_examples = []
with open(eval_file_path, "r") as f:
for line in f:
eval_examples.append(json.loads(line))
predictions = []
ground_truth = []
# 予測を生成
for example in eval_examples[:100]: # コスト削減のためサンプル化
messages = example.get("messages", [])
user_message = next(
(m for m in messages if m["role"] == "user"),
None
)
expected_response = next(
(m for m in messages if m["role"] == "assistant"),
None
)
if not user_message or not expected_response:
continue
# モデル予測を取得
response = self.client.messages.create(
model=self.model_id,
max_tokens=1000,
messages=[{"role": "user", "content": user_message["content"]}],
)
prediction = response.content[0].text
predictions.append(prediction)
ground_truth.append(expected_response["content"])
# メトリクスを計算
if extraction_task:
metrics = self._evaluate_extraction(predictions, ground_truth)
else:
metrics = self._evaluate_classification(predictions, ground_truth)
return metrics
def _evaluate_extraction(
self,
predictions: List[str],
ground_truth: List[str]
) -> EvaluationMetrics:
"""
構造化抽出タスクを評価します。
JSONを解析し、フィールド精度を測定します。
"""
correct_extractions = 0
total_fields = 0
extracted_fields = 0
for pred, truth in zip(predictions, ground_truth):
try:
pred_json = json.loads(pred)
truth_json = json.loads(truth)
# マッチングフィールドをカウント
for key in truth_json:
total_fields += 1
if key in pred_json and pred_json[key] == truth_json[key]:
correct_extractions += 1
extracted_fields += 1
except json.JSONDecodeError:
continue
precision = correct_extractions / extracted_fields if extracted_fields > 0 else 0
recall = correct_extractions / total_fields if total_fields > 0 else 0
f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
return EvaluationMetrics(
precision=precision,
recall=recall,
f1=f1,
accuracy=correct_extractions / total_fields if total_fields > 0 else 0,
custom_metrics={
"correct_extractions": correct_extractions,
"total_fields": total_fields,
"extraction_rate": extracted_fields / len(predictions) if predictions else 0,
}
)
def _evaluate_classification(
self,
predictions: List[str],
ground_truth: List[str]
) -> EvaluationMetrics:
"""分類タスクを評価します。"""
correct = sum(1 for p, t in zip(predictions, ground_truth) if p.strip() == t.strip())
total = len(predictions)
accuracy = correct / total if total > 0 else 0
return EvaluationMetrics(
precision=accuracy,
recall=accuracy,
f1=accuracy,
accuracy=accuracy,
)
def error_analysis(
self,
predictions: List[str],
ground_truth: List[str],
) -> dict:
"""
モデル予測での体系的なエラーを特定します。
ファインチューニングが失敗した箇所を理解するのに役立ちます。
"""
error_patterns = {
"missing_fields": [],
"incorrect_values": [],
"formatting_issues": [],
"hallucinations": [],
}
for pred, truth in zip(predictions, ground_truth):
try:
pred_json = json.loads(pred)
truth_json = json.loads(truth)
# 欠落フィールドをチェック
missing = set(truth_json.keys()) - set(pred_json.keys())
if missing:
error_patterns["missing_fields"].append({
"missing": list(missing),
"prediction": pred_json,
})
# 不正な値をチェック
for key in truth_json:
if key in pred_json and pred_json[key] != truth_json[key]:
error_patterns["incorrect_values"].append({
"field": key,
"expected": truth_json[key],
"got": pred_json[key],
})
except json.JSONDecodeError:
error_patterns["formatting_issues"].append({
"prediction": pred,
})
return error_patterns本番環境へのデプロイ
評価結果に満足したら、適切なバージョニングと監視でモデルをデプロイします。
class FineTunedModelDeployment:
def __init__(self, model_id: str, client):
self.model_id = model_id
self.client = client
self.deployment_config = {}
def prepare_for_production(
self,
fallback_model: str = "claude-3-5-sonnet-20241022",
max_retries: int = 3,
timeout_seconds: int = 30,
) -> dict:
"""
本番環境へのデプロイを準備します。
フォールバック戦略とエラーハンドリングを含みます。
"""
self.deployment_config = {
"primary_model": self.model_id,
"fallback_model": fallback_model,
"max_retries": max_retries,
"timeout_seconds": timeout_seconds,
"health_check_interval": 3600,
"enable_monitoring": True,
"enable_logging": True,
}
return self.deployment_config
def call_with_fallback(
self,
user_message: str,
**kwargs
) -> str:
"""
自動フォールバック付きでファインチューニングされたモデルを呼び出します。
プライマリモデルが失敗すると、ベースモデルにフォールバックします。
"""
for attempt in range(self.deployment_config["max_retries"]):
try:
response = self.client.messages.create(
model=self.deployment_config["primary_model"],
messages=[{"role": "user", "content": user_message}],
timeout=self.deployment_config["timeout_seconds"],
**kwargs
)
return response.content[0].text
except Exception as e:
if attempt < self.deployment_config["max_retries"] - 1:
continue
# ベースモデルにフォールバック
print(f"プライマリモデルが失敗しました:{e}。フォールバック中...")
response = self.client.messages.create(
model=self.deployment_config["fallback_model"],
messages=[{"role": "user", "content": user_message}],
**kwargs
)
return response.content[0].text
def monitor_model_performance(self) -> dict:
"""
本番環境でのファインチューニングされたモデルのパフォーマンスを監視します。
レイテンシ、エラー率、出力品質を追跡します。
"""
return {
"model_id": self.model_id,
"requests_processed": 0,
"avg_latency_ms": 0,
"error_rate": 0,
"fallback_rate": 0,
"last_check": None,
}実世界の例:金融分析モデル
金融文書分析用のモデルのファインチューニングの完全な例を示します:
# ステップ1:訓練データを準備
fintech_preparer = DatasetPreparer()
financial_examples = [
("EBITDAは前年比23%増加して42億ドルになった", "positive_growth"),
("営業利益率は18%から15%に圧縮された", "negative_trend"),
("運転資本の変更により自由キャッシュフローが減少した", "concern"),
]
for input_text, label in financial_examples:
fintech_preparer.add_example(
input_text=f"財務センチメントを分類してください:{input_text}",
output_text=json.dumps({"sentiment": label, "confidence": 0.95}),
metadata={"type": "sentiment_classification"}
)
# ステップ2:エクスポートして検証
export_info = fintech_preparer.export_training_data("fintech_dataset")
# ステップ3:モデルを訓練
tuner = GeminiFineTuner()
job = tuner.create_fine_tuning_job(
training_file_path="fintech_dataset.train.jsonl",
eval_file_path="fintech_dataset.eval.jsonl",
model_id="fintech-sentiment-v1",
learning_rate=0.0005,
num_epochs=2,
batch_size=16,
)
# ステップ4:評価
evaluator = ModelEvaluator("fintech-sentiment-v1", client)
metrics = evaluator.evaluate_on_dataset("fintech_dataset.eval.jsonl")
# ステップ5:デプロイ
deployment = FineTunedModelDeployment("fintech-sentiment-v1", client)
deployment.prepare_for_production()
# ステップ6:本番環境で使用
result = deployment.call_with_fallback(
"分類:市場の逆風にもかかわらず15%の収益成長"
)全体を振り返って
GeminiをファインチューニングすることにTOYS汎用モデルをあなたのドメインのスペシャリストに変えます。成功の鍵は、品質データへの投資、ビジネス目標に合致した評価メトリクスの慎重な選択、そして本番環境への移行前の徹底的なテストです。
小さく始めてください。高品質なデータを使って焦点を絞ったタスクをファインチューニングします。一貫した改善が見られたら、関連するタスクに拡張してください。そして、カスタム版へのトラフィック移行時には、常にベースモデルへのフォールバック戦略を維持してください。