【実践】GPT-3.5ファインチューニングで検索精度92.7%達成|過学習問題を解決し、$4で10.9%の性能改善を実現した方法
TL;DR - 3行でわかる成果
✅ GPT-3.5ファインチューニングで検索精度81.8%→92.7%に改善(+10.9%)
✅ 過学習問題を診断・解決し、わずか$4.13で高い成果を達成
✅ 再現可能な実装・ドキュメント完備で、チーム展開可能
- 投資: $4.13、開発期間1週間
- 成果: 検索成功率+10.9%、品質スコア+13.4%、年間コスト削減$3,240
- 技術: OpenAI Fine-tuning API, Python, 自作パイプライン
- 再現性: GitHub公開、詳細ドキュメント完備
GitHub: arxiv-query-expander-finetuning
ポートフォリオ: Portfolio Summary | Experiment Report
目次
- プロジェクト概要とビジネス価値
- 直面した3つの課題
- 課題1: 過学習による性能悪化(88.9% → 61.1%)
- 課題2: データ品質問題(引用符による検索失敗)
- 課題3: API制限とコスト最適化
- 最終成果と投資対効果(ROI)
- 実装の再現性とチーム展開
- 技術スタックと設計判断
- 学んだこと・今後の展開
プロジェクト概要とビジネス価値
ビジネス背景
arXiv論文検索システムの開発において、日本語クエリから英語の検索クエリへの拡張が課題でした。
課題:
- GPT-4oによるクエリ拡張は精度が高いがコストが高い(1リクエスト約$0.03)
- 月間10,000クエリで**$300のコスト**
- スケールするとコストが急増
目標:
- GPT-3.5へのファインチューニングでコスト削減(1/10)
- 精度を維持または向上
- プロダクション環境での安定稼働
ビジネスインパクト
定量的成果:
| 指標 | Before | After | 改善 |
|---|---|---|---|
| 検索成功率 | 81.8% | 92.7% | +10.9% ✅ |
| 検索品質スコア | 0.7776 | 0.8815 | +13.4% ✅ |
| 月間コスト(10k queries) | $300 | $30 | -90% ✅ |
| 年間コスト削減 | - | $3,240 | - |
定性的価値:
- ✅ ユーザー満足度の向上(検索成功率向上)
- ✅ スケーラビリティの確保(低コスト)
- ✅ 継続的改善の基盤構築(自動化パイプライン)
システム構成
[ユーザー入力]
↓
[GPT-3.5 Fine-tuned Model] ← 本プロジェクトの成果
↓
[拡張クエリ生成]
↓
[arXiv検索API]
↓
[Cohere Rerank API]
↓
[上位3件の関連論文]
技術選定の理由
なぜGPT-3.5のファインチューニングか?
| 項目 | GPT-4o (プロンプトEng) |
GPT-3.5 Fine-tuning |
Llama LoRA |
ルール ベース |
|---|---|---|---|---|
| コスト | 高($300/月) | 低($30/月) ✅ | 最低 | 最低 |
| 精度 | 高 | 高 ✅ | 中 | 低 |
| 保守性 | 高 | 高 ✅ | 中 | 低 |
| レイテンシ | 中 | 高速 ✅ | 高速 | 最速 |
| カスタマイズ性 | 低 | 高 ✅ | 高 | 高 |
選定理由:
- コスト: GPT-4oの1/10で運用可能
- 精度: ドメイン特化により高精度を実現
- 実績: OpenAIの安定したAPI基盤
直面した3つの課題
本プロジェクトでは、以下の3つの主要課題に直面しました。
| 課題 | 影響 | 解決策 | 成果 |
|---|---|---|---|
| 1. 過学習 | 成功率88.9%→61.1%に悪化 | データセット3.15倍拡大 | 92.7%に改善 |
| 2. データ品質 | 28.8%のサンプルで検索失敗 | 引用符の自動削除 | 失敗率7.3%に低減 |
| 3. API制限 | 25.5%で評価失敗 | 遅延機構の実装 | 100%成功 |
各課題の詳細を以下で説明します。
課題1: 過学習による性能悪化
問題の発見
初回ファインチューニングの結果:
期待: 「GPT-3.5をファインチューニングすれば精度向上するだろう」
現実: 成功率が88.9%から61.1%に大幅悪化 😱
# 初回実験の設定
training_samples = 80
validation_samples = 17
test_samples = 18
n_epochs = 3 # OpenAI default
# 結果
baseline_success_rate = 88.9% # GPT-3.5 (no fine-tuning)
finetuned_success_rate = 61.1% # ❌ 悪化!
| モデル | 成功率 | Avg Rerank Score |
|---|---|---|
| Baseline (GPT-3.5) | 88.9% | 0.7820 |
| Fine-tuned v1 | 61.1% ❌ | 0.5211 ❌ |
ここでのアピールポイント:
- ❌ 失敗を隠さず、正直に報告する姿勢
- ✅ 問題を定量的に把握
- ✅ 原因究明に進む判断力
原因分析のプロセス
ステップ1: トレーニングログの確認
OpenAI Fine-tuning APIのログを確認したところ、異常な値を発見:
# OpenAI Fine-tuning Job のログ
Step 240/240: training_loss=0.00, validation_loss=0.00
問題の特定:
- Training loss = 0.00 → トレーニングデータを完全暗記
- Validation loss = 0.00 → 検証データも暗記
- これは典型的な過学習(Overfitting)のサイン
ステップ2: 失敗例の詳細分析
# 学習データにあったパターン → 成功
Input: "Transformerを用いた機械翻訳"
Output: "Transformer AND machine translation" # ✅
# 未知のパターン(テストデータ) → 失敗
Input: "BERTによるテキスト分類"
Output: "BERT text classification" # ❌ AND が抜けている
気づき:
- 学習データのパターンは完璧に再現
- 少しでも異なるパターンには対応できない
- → モデルが「理解」ではなく「暗記」している
ステップ3: 根本原因の特定
仮説検証アプローチ:
| 仮説 | 検証方法 | 結論 |
|---|---|---|
| 仮説1: データ不足 | 80サンプル × 3エポック = 240ステップで完全暗記 | ✅ データが少なすぎる |
| 仮説2: エポック数過多 | 3エポックでloss=0.00に到達 | ✅ 学習しすぎ |
| 仮説3: 多様性不足 | 類似パターンが多い | ✅ バリエーション不足 |
診断コードの実装:
# scripts/analyze_dataset.py
import json
from collections import Counter
def analyze_training_data(file_path):
"""トレーニングデータの多様性を分析"""
with open(file_path) as f:
data = [json.loads(line) for line in f]
patterns = Counter()
for example in data:
assistant_msg = example['messages'][-1]['content']
# "Method AND Task" パターンを抽出
if ' AND ' in assistant_msg:
parts = assistant_msg.split(' AND ')
pattern_type = f"{len(parts)}_parts"
patterns[pattern_type] += 1
return {
'total_samples': len(data),
'unique_patterns': len(patterns),
'pattern_distribution': patterns
}
# 実行結果
result = analyze_training_data('training_data/openai_train.jsonl')
print(result)
# Output:
# {
# 'total_samples': 80,
# 'unique_patterns': 12, # ← 少ない!
# 'pattern_distribution': {'2_parts': 68, '3_parts': 12}
# }
結論:
- データ量が不足(80サンプル)
- パターンの多様性が不足(12種類のみ)
- エポック数が多すぎる(3エポック)
解決策: データセット拡大戦略
目標設定
SMART目標:
- Specific: サンプル数を250+、パターン数を100+に増やす
- Measurable: 成功率90%以上を維持
- Achievable: 1週間で実現可能
- Relevant: ビジネス価値に直結
- Time-bound: 1週間以内
数値目標:
サンプル数: 80 → 250+ (3倍以上)
パターン数: 12 → 100+ (8倍以上)
データ収集成功率: 90%以上
コスト: $5以内
Phase 1: クエリパターン設計(7種類)
設計方針:
- 実際の検索ニーズを分析
- 多様性を確保するため7つのパターンを設計
- 使用頻度に応じて重み付け
# 7つのクエリパターン
patterns = {
"method_task": {
"count": 300,
"template": "{method}を用いた{task}",
"example": "LoRAを用いたalignment",
"expanded": "LoRA AND alignment",
"weight": 42% # 最も使われるパターン
},
"method_challenge": {
"count": 150,
"template": "{method}による{challenge}の対処法",
"example": "BERTによるlow resourceの対処法",
"expanded": "BERT AND low resource",
"weight": 21%
},
"method_domain": {
"count": 120,
"template": "{method}を{domain}に応用",
"example": "GPTを医療分野に応用",
"expanded": "GPT AND healthcare",
"weight": 17%
},
"task_metric": {
"count": 50,
"template": "{task}における{metric}の改善",
"example": "翻訳におけるBLEUの改善",
"expanded": "translation AND BLEU",
"weight": 7%
},
"simple_topic": {
"count": 40,
"template": "{topic}",
"example": "neural architecture search",
"expanded": "neural architecture search",
"weight": 6%
},
"survey": {
"count": 17,
"template": "{topic}のサーベイ",
"example": "Transformerのサーベイ",
"expanded": "survey AND transformers",
"weight": 2%
},
"method_combination": {
"count": 30,
"template": "{method1}と{method2}の組み合わせ",
"example": "LoRAとRLHFの組み合わせ",
"expanded": "LoRA AND RLHF",
"weight": 4%
}
}
# Total: 707 queries
ビジネス判断:
- 最も使われる
method_taskに重点配分(42%) - 長期的なニーズ(survey, comparison)も考慮
- バランスの取れたポートフォリオ
Phase 2: クエリ生成の改善(v1 → v2 → v3)
失敗からの学び:
# v1(失敗): 成功率45%
query_v1 = f'"{method}" AND "{task}" year:2020-2024'
# 問題: 引用符と年フィルタでarXiv検索が失敗
# v2(改善): 成功率55%
query_v2 = f'"{method}" AND "{task}"'
# 問題: 引用符がまだ残っている
# v3(成功): 成功率90.25% ✅
query_v3 = f"{method}を用いた{task}"
# 拡張例: "LoRA AND alignment"
# 改善: 引用符を完全削除、シンプルなAND結合のみ
v3の改善ポイント:
- ✅ 引用符を完全削除
- ✅ 年フィルタを削除(arXivの制限を回避)
- ✅ シンプルなAND結合のみ使用
- ✅ 日本語から英語への自然な変換
実装例:
# scripts/generate_diverse_queries_v3.py
import itertools
import json
# 定義
ML_METHODS = [
"Transformer", "BERT", "GPT", "LoRA", "RLHF",
"DPO", "ViT", "CNN", "RNN", "GAN", "VAE",
"Diffusion", "Adapter", "QLoRA", "PPO", "Attention",
"Cross-Attention", "Self-Attention", "LSTM", "GRU"
]
TASKS = [
"classification", "translation", "generation",
"summarization", "question answering", "dialogue",
"sentiment analysis", "named entity recognition",
"relation extraction", "text completion",
"image captioning", "semantic segmentation",
"object detection", "image generation", "style transfer"
]
# Pattern 1: Method AND Task (300クエリ)
queries = []
for method, task in itertools.product(ML_METHODS[:20], TASKS[:15]):
query = {
"id": f"q_{len(queries)+1}",
"goal": "研究調査",
"query": f"{method}を用いた{task}",
"pattern": "method_task"
}
queries.append(query)
print(f"Generated {len(queries)} queries")
# Output: Generated 300 queries
# 保存
with open('scripts/queries_v3.json', 'w', encoding='utf-8') as f:
json.dump(queries, f, ensure_ascii=False, indent=2)
Phase 3: バッチ処理パイプラインの構築
課題:
- 707クエリを一度に実行すると時間がかかる(12時間以上)
- エラー発生時の再実行が困難
- 進捗管理が難しい
解決策: バッチ分割
# 707クエリを8バッチに分割
batch_size = 100
batches = [
queries[i:i+batch_size]
for i in range(0, len(queries), batch_size)
]
# → batch1.json (100), batch2.json (100), ..., batch8.json (7)
実装:
# scripts/collect_training_data.py
import time
from typing import List, Dict
from arxiv_researcher.searcher.arxiv_searcher import ArxivSearcher
class DataCollectionPipeline:
"""データ収集パイプライン"""
def __init__(self, batch_size=100, delay=2.0):
self.batch_size = batch_size
self.delay = delay
self.searcher = ArxivSearcher()
def collect_batch(self, queries: List[Dict]) -> List[Dict]:
"""バッチ処理でデータ収集"""
results = []
for i, query_data in enumerate(queries):
try:
query = query_data['query']
goal = query_data['goal']
# 1. クエリ拡張(GPT-4o使用)
expanded = self.expand_query(query, goal)
# 2. arXiv検索
papers = self.search_arxiv(expanded)
# 3. Cohereでrerank
ranked = self.rerank_papers(papers, query)
# 4. 品質スコア計算
avg_score = self.calc_avg_score(ranked[:3])
# 5. ログ記録
log_entry = {
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'goal': goal,
'original_query': query,
'expanded_query': expanded,
'num_results': len(ranked),
'avg_rerank_score': avg_score,
'success': len(ranked) >= 3 and avg_score >= 0.7,
'top_papers': [p['title'] for p in ranked[:3]]
}
results.append(log_entry)
# 進捗表示
if (i + 1) % 10 == 0:
print(f"Progress: {i+1}/{len(queries)} queries processed")
# Rate limit対策
time.sleep(self.delay)
except Exception as e:
print(f"Error processing query '{query}': {e}")
# エラーも記録
results.append({
'original_query': query,
'success': False,
'error': str(e)
})
return results
実行例:
# Batch 1実行
python scripts/collect_training_data.py \
--queries scripts/queries_v3_batch1.json \
--batch-size 100 \
--log-path training_data/query_logs_v3.jsonl
# 進捗確認(別ターミナル)
tail -f training_data/query_logs_v3.jsonl
チーム展開のポイント:
- ✅ エラーハンドリングを実装(try-except)
- ✅ 進捗ログを出力(デバッグしやすい)
- ✅ 中断・再開可能な設計(バッチ分割)
- ✅ 設定可能なパラメータ(batch_size, delay)
Phase 4: データ収集結果
実行結果(Batch 1-4):
Executed: 400 queries
Success: 361 samples (90.25%) # ✅ 目標達成!
Failed: 39 samples (9.75%)
品質分布:
Excellent (>0.9): 293 (81%)
Good (0.8-0.9): 47 (13%)
Acceptable (0.7-0.8): 21 (6%)
最終データセット:
# scripts/prepare_dataset.py で分割
Total: 361 samples
Train: 252 samples (70%)
Val: 54 samples (15%)
Test: 55 samples (15%)
成果のまとめ
定量的成果:
- ✅ データ量: 80 → 252サンプル(3.15倍)
- ✅ パターン数: 12 → 100+(8倍以上)
- ✅ 収集成功率: 90.25%(目標達成)
- ✅ コスト: $4(予算内)
- ✅ 期間: 2日間(計画通り)
ビジネスインパクト:
- 低コスト($4)で高品質データを大量取得
- 自動化により再現性を確保
- チーム展開可能な設計
採用担当者へのアピール:
- ✅ データ駆動の意思決定
- ✅ 自動化による効率化
- ✅ コスト意識($4で252サンプル)
- ✅ 品質管理の徹底
- ✅ チーム貢献(再現可能な実装)
課題2: データ品質問題
問題の発見プロセス
再ファインチューニング後の評価(v2):
期待: 「データを3倍にしたから性能向上するはず!」
結果: 成功率69.1%(ベースライン81.8%より悪い) 😱
# v2 Fine-tuning(epochs=3, データ拡大版)
Training samples: 252 # 3.15倍に増やした
Test success rate: 69.1% (38/55) # ❌ まだ悪い
Avg rerank score: 0.6477
# なぜ?データを増やしたのに...
ステップ1: 失敗例の詳細分析
# 失敗したクエリを抽出して分析
failures = [
{
'original': 'LoRAを用いたquestion answering',
'expanded': 'LoRA AND "question answering"', # ❌ 引用符
'results': 0,
'reason': '引用符が原因でarXiv検索が失敗'
},
{
'original': 'GANによるdata efficiencyの対処法',
'expanded': 'GAN AND "data efficiency"', # ❌ 引用符
'results': 0,
'reason': '引用符が原因でarXiv検索が失敗'
},
{
'original': 'Transformerによるimbalanced dataの対処法',
'expanded': 'Transformer AND "imbalanced data"', # ❌ 引用符
'results': 0,
'reason': '引用符が原因でarXiv検索が失敗'
}
]
print(f"Total failures: {len(failures)}")
print(f"Failures with quotes: {sum(1 for f in failures if '\"' in f['expanded'])}")
# Output:
# Total failures: 17
# Failures with quotes: 12 # 71%が引用符が原因!
重要な気づき:
- 失敗の多くに引用符が含まれている
- arXiv検索は引用符を厳密にマッチングするため、完全一致しないと0件
- 引用符を削除すれば成功する可能性が高い
ステップ2: トレーニングデータの監査
品質監査スクリプトの作成:
# scripts/analyze_dataset.py
import json
def audit_training_data(file_path):
"""トレーニングデータの品質監査"""
with open(file_path, encoding='utf-8') as f:
data = [json.loads(line) for line in f]
total = len(data)
with_quotes = 0
examples = []
for example in data:
# Assistantメッセージ(拡張クエリ)を抽出
for msg in example['messages']:
if msg['role'] == 'assistant':
content = msg['content']
if '"' in content or "'" in content:
with_quotes += 1
examples.append(content)
return {
'total': total,
'with_quotes': with_quotes,
'rate': with_quotes / total if total > 0 else 0,
'examples': examples[:10] # 最初の10件
}
# 実行
result = audit_training_data('training_data/expanded/openai_train.jsonl')
print(f"Total: {result['total']}")
print(f"With quotes: {result['with_quotes']} ({result['rate']*100:.1f}%)")
print("\nExamples:")
for ex in result['examples']:
print(f" - {ex}")
実行結果:
Total: 252
With quotes: 66 (26.2%) # ❌ 問題発見!
Examples:
- LoRA AND "question answering"
- GAN AND "data efficiency"
- Diffusion AND "memory efficiency"
- GPT AND "noisy data"
- CNN AND "memory efficiency"
- BERT AND "low resource"
- ViT AND "catastrophic forgetting"
- LLM AND "memory efficiency"
- Transformer AND "imbalanced data"
- RLHF AND "question answering"
根本原因の特定:
- 元のGPT-3.5プロンプトエンジニアリングが複数語のフレーズに引用符を付けていた
- v3クエリ生成では削除したが、既存の収集済みデータに残存
- 252サンプル中66サンプル(26.2%)が引用符付き
解決策: 自動クリーンアップパイプライン
設計判断
選択肢の比較:
| 選択肢 | コスト | 時間 | 品質 | 再現性 | 判断 |
|---|---|---|---|---|---|
| データ再収集 | 高($4) | 長(2日) | 高 | 中 | ❌ |
| 手動修正 | 低 | 中(数時間) | 中 | 低 | ❌ |
| 自動スクリプト | 低 | 短(1時間) | 高 | 高 | ✅ |
判断理由:
- ✅ 再現性が高い(同じ処理を何度でも実行可能)
- ✅ 短時間で実行可能(1時間以内)
- ✅ チーム展開しやすい(スクリプト化)
- ✅ テスト可能(品質保証)
実装
# scripts/clean_training_data.py
import json
from pathlib import Path
def clean_quotes(text: str) -> str:
"""引用符を削除
Args:
text: クリーニング対象のテキスト
Returns:
引用符を削除したテキスト
"""
# ダブルクォートとシングルクォートを削除
cleaned = text.replace('"', '').replace("'", '')
return cleaned
def clean_dataset(input_path: str, output_path: str) -> dict:
"""データセット全体をクリーンアップ
Args:
input_path: 入力JSONLファイルパス
output_path: 出力JSONLファイルパス
Returns:
クリーンアップの統計情報
"""
cleaned_data = []
modified_count = 0
total = 0
# 入力ファイル読み込み
with open(input_path, 'r', encoding='utf-8') as f:
for line in f:
if not line.strip():
continue
example = json.loads(line)
total += 1
# Assistantメッセージ(拡張クエリ)をクリーン
for msg in example['messages']:
if msg['role'] == 'assistant':
original = msg['content']
cleaned = clean_quotes(original)
if original != cleaned:
modified_count += 1
msg['content'] = cleaned
cleaned_data.append(example)
# 出力ファイル書き込み
output_file = Path(output_path)
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
for example in cleaned_data:
f.write(json.dumps(example, ensure_ascii=False) + '\n')
return {
'total': total,
'modified': modified_count,
'modified_rate': modified_count / total if total > 0 else 0
}
if __name__ == '__main__':
# Train, Val, Test をクリーンアップ
datasets = [
('training_data/expanded/openai_train.jsonl',
'training_data/cleaned/openai_train.jsonl'),
('training_data/expanded/openai_val.jsonl',
'training_data/cleaned/openai_val.jsonl'),
('training_data/expanded/openai_test.jsonl',
'training_data/cleaned/openai_test.jsonl'),
]
for input_path, output_path in datasets:
stats = clean_dataset(input_path, output_path)
print(f"{input_path}:")
print(f" Modified: {stats['modified']}/{stats['total']} "
f"({stats['modified_rate']*100:.1f}%)")
実行結果:
$ python scripts/clean_training_data.py
training_data/expanded/openai_train.jsonl:
Modified: 66/252 (26.2%)
training_data/expanded/openai_val.jsonl:
Modified: 21/54 (38.9%)
training_data/expanded/openai_test.jsonl:
Modified: 17/55 (30.9%)
Total examples: 361
Total modified: 104 (28.8%)
品質保証
Before/After検証:
# Before
{
"messages": [
{
"role": "user",
"content": "目標: 研究調査\nクエリ: LoRAを用いたquestion answering"
},
{
"role": "assistant",
"content": "LoRA AND \"question answering\"" # ❌ 引用符あり
}
]
}
# After
{
"messages": [
{
"role": "user",
"content": "目標: 研究調査\nクエリ: LoRAを用いたquestion answering"
},
{
"role": "assistant",
"content": "LoRA AND question answering" # ✅ 引用符削除
}
]
}
自動テストの実装:
def test_no_quotes_in_cleaned_data():
"""クリーンアップ後のデータに引用符がないことを確認"""
with open('training_data/cleaned/openai_train.jsonl', encoding='utf-8') as f:
data = [json.loads(line) for line in f]
for i, example in enumerate(data):
for msg in example['messages']:
if msg['role'] == 'assistant':
content = msg['content']
assert '"' not in content, f"Example {i}: Found quotes in {content}"
assert "'" not in content, f"Example {i}: Found quotes in {content}"
print(f"✅ Test passed: No quotes found in {len(data)} samples")
# 実行
test_no_quotes_in_cleaned_data()
# Output: ✅ Test passed: No quotes found in 252 samples
成果
クリーンアップ結果:
Total examples: 361
Modified: 104 (28.8%)
- Train: 66/252 (26.2%)
- Val: 21/54 (38.9%)
- Test: 17/55 (30.9%)
After cleanup:
- Quotes: 0% ✅
- Quality score: 維持
ビジネスインパクト:
- ✅ 実装時間: 1時間
- ✅ コスト: $0(再収集不要)
- ✅ 効果: 後述の再評価で大幅改善
採用担当者へのアピール:
- ✅ データ品質への強い意識
- ✅ 自動化による再現性確保
- ✅ テストによる品質保証
- ✅ ドキュメント化(チーム共有可能)
- ✅ 問題発見から解決までの体系的アプローチ
課題3: API制限とコスト最適化
問題: Cohere Rate Limit
評価中の障害:
Cohere Trial Key制限: 10 API calls/分
評価対象: 55サンプル
→ 14サンプルでrerank失敗 (25.5%)
→ Avg Rerank Scoreが不正確 (0.7041)
ビジネスインパクト:
- ❌ 正確な性能評価ができない
- ❌ 本番環境でも同様の問題が発生する懸念
- ❌ ユーザー体験の悪化
解決策: 遅延機構の実装
設計方針
要件定義:
- Rate limitを確実に回避
- 設定可能な遅延時間(環境に応じて調整)
- バックグラウンド実行対応
- 進捗表示
実装:
# scripts/evaluate_finetuned_model.py
import argparse
import time
def evaluate_finetuned_model(
test_path: str,
model_name: str,
delay: float = 7.0
):
"""ファインチューニング済みモデルを評価
Args:
test_path: テストデータパス
model_name: モデル名
delay: サンプル間の遅延(秒)
"""
# テストデータ読み込み
test_data = load_test_data(test_path)
results = []
for i, example in enumerate(test_data):
print(f"[{i+1}/{len(test_data)}] Evaluating...")
# 評価処理
result = evaluate_sample(example, model_name)
results.append(result)
# Rate limit対策(最後のサンプル以外)
if delay > 0 and i < len(test_data) - 1:
time.sleep(delay)
return results
# コマンドライン引数
parser = argparse.ArgumentParser()
parser.add_argument(
"--delay",
type=float,
default=7.0,
help="Delay between samples to avoid rate limits (seconds)"
)
トレードオフ分析
遅延時間の最適化:
| 設定 | 評価時間 | Rerank成功率 | コスト | 判断 |
|---|---|---|---|---|
| delay=0 | 3分 | 74.5% | $0 | ❌ 不正確 |
| delay=5 | 5分 | 90% | $0 | △ やや不足 |
| delay=7 | 6.5分 | 100% | $0 | ✅ 最適 |
| delay=10 | 9分 | 100% | $0 | △ 過剰 |
判断理由:
- Cohere Trial: 10 calls/分 → 6秒間隔が理論値
- 余裕を持って7秒に設定
- 成功率100%を達成しながら、評価時間は許容範囲
実装例
# 評価実行(Rate limit回避)
python scripts/evaluate_finetuned_model.py \
--model-type gpt35 \
--model-name ft:gpt-3.5-turbo-0125:cappa:arxiv-query-expander-v3-clean:CSYip7QF \
--test-path training_data/cleaned/openai_test.jsonl \
--delay 7 \
--output-dir results/cleaned
# 実行結果
# [1/55] Evaluating...
# [2/55] Evaluating...
# ...
# [55/55] Evaluating...
# ✅ Evaluation completed: 100% success (55/55)
改善結果:
| 指標 | delay=0 (Before) | delay=7 (After) | 改善 |
|---|---|---|---|
| Rerank成功率 | 74.5% (41/55) | 100% (55/55) | +25.5% ✅ |
| Avg Rerank Score | 0.7041 | 0.8815 | +25.2% ✅ |
| 評価時間 | ~3分 | ~6.5分 | +3.5分 |
コスト最適化
全体コスト分析
プロジェクト全体のコスト:
1. データ収集(GPT-4o for query expansion): $4.00
- 400 queries × $0.01/query
2. ファインチューニング(OpenAI API): $0.12
- Training tokens: 15,155
- Rate: $0.008/1K tokens
3. 評価(推論)(GPT-3.5 Fine-tuned): $0.01
- 55 queries × ~$0.0002/query
合計: $4.13
ROI計算
月間10,000クエリの場合:
Before(GPT-4o):
コスト: $300/月(10,000 queries × $0.03)
年間: $3,600
After(GPT-3.5 Fine-tuned):
コスト: $30/月(10,000 queries × $0.003)
年間: $360
削減額: $3,240/年(90%削減)
投資対効果(ROI):
初期投資: $4.13
年間削減: $3,240
投資回収期間: 即時(初月で回収)
ROI: 78,400% (= $3,240 / $4.13 × 100)
スケーラビリティ分析
月間クエリ数別のコスト比較:
| 月間クエリ数 | GPT-4o | GPT-3.5 FT | 削減額/年 |
|---|---|---|---|
| 1,000 | $360 | $36 | $324 |
| 10,000 | $3,600 | $360 | $3,240 |
| 100,000 | $36,000 | $3,600 | $32,400 |
| 1,000,000 | $360,000 | $36,000 | $324,000 |
洞察:
- ✅ スケールすればするほど効果が大きい
- ✅ 初期投資$4.13は非常に小さい
- ✅ プロダクション環境で即座に価値を発揮
採用担当者へのアピール:
- ✅ 本番環境を想定した実装
- ✅ トレードオフの明確な分析
- ✅ 設定可能な設計(柔軟性)
- ✅ ビジネス価値の定量化
- ✅ ROIの明確化
最終成果と投資対効果
定量的成果
パフォーマンス改善の全体像:
| 指標 | Baseline (GPT-3.5) |
v1 (失敗) |
v2 (品質問題) |
v3-clean (成功) |
最終改善 |
|---|---|---|---|---|---|
| 成功率 | 81.8% (45/55) |
61.1% (11/18) ❌ |
69.1% (38/55) ❌ |
92.7% (51/55) ✅ |
+10.9% |
| Avg Rerank Score | 0.7776 | 0.5211 ❌ | 0.6477 ❌ | 0.8815 ✅ | +13.4% |
| データセット | - | 80 | 252 | 252 | 3.15倍 |
| Training Loss | - | 0.00(過学習) | 0.00(過学習) | 0.00 | - |
| Validation Loss | - | 0.00(過学習) | 0.00(過学習) | 0.08 ✅ | 過学習軽減 |
| エポック数 | - | 3 | 3 | 1 ✅ | - |
| 引用符率 | - | 不明 | 28.8% | 0% ✅ | 完全削除 |
成功例と失敗例
成功例(引用符削除の効果)
Before(引用符あり - 失敗):
Input: "BERTによるlow resourceの対処法"
Baseline: "BERT AND \"low resource\"" # 0件
Fine-tuned v2: "BERT AND \"low resource\"" # 0件
After(引用符なし - 成功):
Input: "BERTによるlow resourceの対処法"
Fine-tuned v3: "BERT AND low resource" # 3件 ✅
Papers:
1. "Low-Resource BERT for Sequence Labeling" (Score: 0.92)
2. "BERT in Low-Resource Scenarios" (Score: 0.85)
3. "Efficient BERT for Limited Data" (Score: 0.81)
Avg Rerank Score: 0.8580
その他の成功例:
1. Query: "Transformerによるimbalanced dataの対処法"
Before: "Transformer AND \"imbalanced data\"" # 0件
After: "Transformer AND imbalanced data" # 3件 ✅
Score: 0.8387
2. Query: "GANによるcomputational efficiencyの対処法"
Before: "GAN AND \"computational efficiency\"" # 0件
After: "GAN AND computational efficiency" # 1件 ✅
Score: 0.7293
3. Query: "LLMによるcatastrophic forgettingの対処法"
Before: "LLM AND \"catastrophic forgetting\"" # 0件
After: "LLM AND catastrophic forgetting" # 3件 ✅
Score: 0.9985(非常に高い関連性)
残る失敗例(4/55 = 7.3%)
失敗パターン1: 引用符が残存(3件)
# モデルがまだ一部のパターンで引用符を付ける
Query: "ViTを用いたquestion answering"
Expanded: "ViT AND \"question answering\"" # ❌ 引用符が残存
Results: 0件
# 原因分析:
# - トレーニングデータからは削除済み
# - しかしモデルが複数語フレーズに引用符を付ける傾向を学習
# - 特に"question answering"のような一般的なフレーズ
# 対策案:
# 1. システムプロンプトに「引用符を絶対に使わない」を明記
# 2. Post-processingで強制削除
# 3. Few-shot examplesを追加
失敗パターン2: arXivに該当論文なし(1件)
Query: "BERTによるsample efficiencyの対処法"
Expanded: "BERT AND sample efficiency" # 引用符なし
Results: 0件
# 原因分析:
# - arXivに該当する論文が存在しない可能性
# - これはモデルの問題ではなく、データベースの問題
# - 正常な動作と判断
ビジネス価値
コストパフォーマンス:
総投資: $4.13
開発期間: 1週間
成果:
- 検索精度: +10.9%
- 品質スコア: +13.4%
- 月間コスト削減: $270(10,000クエリの場合)
- 年間削減: $3,240
3年間の累積効果: $9,720
初期投資比: 2,354倍
プロダクション環境での価値:
-
スケーラビリティ:
- 月間クエリ数に依存しないコスト構造
- Fine-tunedモデルは追加コストなしで利用可能
-
保守性:
- 自動化パイプライン構築により、データ追加が容易
- ドキュメント完備でチーム引き継ぎが可能
- GitHubで管理、バージョン管理も完璧
-
拡張性:
- 他のドメイン(医療、法律など)への展開可能
- Llama LoRAなどの他手法との比較基盤
- 継続的改善のサイクル確立
ユーザー体験向上:
- ✅ 検索成功率+10.9% → より多くのユーザーが求める論文を発見
- ✅ 検索品質+13.4% → より関連性の高い論文を提示
- ✅ レスポンス時間の改善(GPT-3.5は高速)
実装の再現性とチーム展開
ドキュメント体系
3層構造のドキュメント:
portfolio/
├── PORTFOLIO_SUMMARY.md # 採用担当者向け
│ - プロジェクト概要
│ - ビジネス価値
│ - 技術的ハイライト
│
├── EXPERIMENT_REPORT_FINAL.md # エンジニア向け
│ - 詳細な実験手順
│ - ハイパーパラメータ設定
│ - 結果分析
│
└── GITHUB_PUBLICATION_GUIDE.md # 運用者向け
- 公開手順
- セキュリティチェック
- ベストプラクティス
コードの品質
保守性の確保:
# ✅ Good Practice
# 1. 明確な関数名と型ヒント
def clean_quotes(text: str) -> str:
"""引用符を削除する
Args:
text: クリーニング対象のテキスト
Returns:
引用符を削除したテキスト
Examples:
>>> clean_quotes('LoRA AND "alignment"')
'LoRA AND alignment'
"""
return text.replace('"', '').replace("'", '')
# 2. 設定可能な設計
class DataCollectionPipeline:
"""データ収集パイプライン"""
def __init__(
self,
batch_size: int = 100,
delay: float = 2.0,
min_score: float = 0.7
):
"""初期化
Args:
batch_size: バッチサイズ
delay: API呼び出し間の遅延(秒)
min_score: 最小品質スコア
"""
self.batch_size = batch_size
self.delay = delay
self.min_score = min_score
# 3. 包括的なエラーハンドリング
try:
result = self.expand_query(query)
except OpenAIError as e:
self.logger.error(f"OpenAI API error: {e}")
return None
except CohereError as e:
self.logger.error(f"Cohere API error: {e}")
return None
except Exception as e:
self.logger.error(f"Unexpected error: {e}")
raise
# 4. ロギング
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.info(f"Processing batch {batch_id}")
logger.warning(f"Low rerank score: {score}")
logger.error(f"Failed to process query: {query}")
チーム展開の実績
GitHub公開:
- リポジトリ: arxiv-query-expander-finetuning
- 公開日: 2024年10月
- 言語: Python 3.11+
- ライセンス: MIT
再現実験の手順:
# ステップ1: リポジトリクローン
git clone https://github.com/Datarchpy/arxiv-query-expander-finetuning
cd arxiv-query-expander-finetuning
# ステップ2: 環境構築
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
# ステップ3: 環境変数設定
cp .env.sample .env
# .envにAPIキーを設定:
# OPENAI_API_KEY=your_key
# COHERE_API_KEY=your_key
# ステップ4: データセット確認
ls training_data/cleaned/
# → openai_train.jsonl (252 samples)
# → openai_val.jsonl (54 samples)
# → openai_test.jsonl (55 samples)
# ステップ5: ファインチューニング実行
python scripts/finetune_gpt35.py \
--train-path training_data/cleaned/openai_train.jsonl \
--val-path training_data/cleaned/openai_val.jsonl \
--epochs 1 \
--learning-rate 0.1 \
--suffix my-arxiv-expander
# ステップ6: 評価
python scripts/evaluate_finetuned_model.py \
--model-type gpt35 \
--auto-detect \
--test-path training_data/cleaned/openai_test.jsonl \
--delay 7
採用担当者へのアピール:
- ✅ チームでの再現性を重視
- ✅ ドキュメント駆動開発
- ✅ コードレビューを意識した実装
- ✅ OSSとして公開(コントリビューション歓迎)
- ✅ 明確な手順書(オンボーディングに活用可能)
技術スタックと設計判断
技術選定の根拠
OpenAI GPT-3.5 Fine-tuning API:
| 評価項目 | スコア | 理由 |
|---|---|---|
| 安定性 | ⭐⭐⭐⭐⭐ | プロダクショングレードのAPI |
| コスト | ⭐⭐⭐⭐ | GPT-4oの1/10 |
| ドキュメント | ⭐⭐⭐⭐⭐ | 充実した公式ドキュメント |
| サポート | ⭐⭐⭐⭐⭐ | OpenAIの継続的なサポート |
| カスタマイズ性 | ⭐⭐⭐⭐ | ハイパーパラメータ調整可能 |
Python:
- ✅ ML/AI開発のデファクトスタンダード
- ✅ 豊富なライブラリ(arxiv, openai, cohere)
- ✅ チーム内の共通言語
- ✅ 高速なプロトタイピング
アーキテクチャ判断
自作パイプライン vs MLOps ツール:
| 選択肢 | メリット | デメリット | 適用フェーズ | 判断 |
|---|---|---|---|---|
| 自作スクリプト | シンプル、依存少ない、学習コスト低 | スケール時の限界 | MVP、検証 | ✅ 採用 |
| MLflow | 実験管理が強力、可視化優秀 | 学習コスト高、オーバーキル | 本格運用 | 次フェーズで検討 |
| Airflow | スケジューリング、複雑なワークフロー | セットアップ複雑、維持コスト高 | 大規模運用 | 不要 |
| Kubeflow | K8s統合、スケーラブル | 複雑度が非常に高い | エンタープライズ | 不要 |
判断理由(自作パイプライン):
- ✅ MVPフェーズに最適(迅速な検証)
- ✅ チーム全員が理解できるシンプルさ
- ✅ 必要に応じて段階的に移行可能
- ✅ オーバーエンジニアリングを避ける
モジュール設計
責任の分離(Single Responsibility Principle):
scripts/
├── generate_diverse_queries_v3.py # クエリ生成
│ 責任: 多様なクエリパターンの生成
│
├── collect_training_data.py # データ収集
│ 責任: arXiv検索とCohere rerankの実行
│
├── clean_training_data.py # データクリーンアップ
│ 責任: 引用符などの品質問題を修正
│
├── prepare_dataset.py # データセット構築
│ 責任: Train/Val/Test分割とフォーマット変換
│
├── finetune_gpt35.py # ファインチューニング
│ 責任: OpenAI APIへのファイルアップロードとジョブ作成
│
├── evaluate_baseline.py # ベースライン評価
│ 責任: ファインチューニング前の性能測定
│
└── evaluate_finetuned_model.py # モデル評価
責任: ファインチューニング後の性能測定
# 各スクリプトは独立して実行可能
# = テストしやすい、再利用しやすい、並列実行可能
依存関係の管理:
# pyproject.toml (推奨) または requirements.txt
[tool.poetry.dependencies]
python = "^3.11"
openai = "^1.0.0"
cohere = "^4.0.0"
arxiv = "^2.0.0"
python-dotenv = "^1.0.0"
[tool.poetry.dev-dependencies]
pytest = "^7.0.0"
black = "^23.0.0"
flake8 = "^6.0.0"
mypy = "^1.0.0"
採用担当者へのアピール:
- ✅ 技術選定の根拠を明確に説明
- ✅ トレードオフを理解している
- ✅ 将来の拡張性を考慮
- ✅ オーバーエンジニアリングを避ける判断力
- ✅ チーム開発を意識した設計
学んだこと・今後の展開
重要な学び
1. データ品質 > データ量
実体験から:
| フェーズ | データ量 | データ品質 | 成功率 | 結論 |
|---|---|---|---|---|
| v1 | 80 | 悪(引用符あり) | 61.1% | ❌ 失敗 |
| v2 | 252 | 悪(引用符28.8%) | 69.1% | ❌ 失敗 |
| v3 | 252 | 良(引用符0%) | 92.7% | ✅ 成功 |
教訓:
「データを増やす前に、データの質を確保せよ」
データ量を3倍に増やしても、品質が悪ければ性能は向上しない。
逆に、品質を改善すれば同じデータ量でも大幅に性能が向上する。
実務への応用:
- データ収集前に品質基準を明確化
- 定期的なデータ監査
- 自動テストによる品質保証
2. 失敗からの学び
失敗の価値:
v1: 過学習を発見
↓ 学び: データ不足、エポック数過多
↓
v2: データ品質問題を発見
↓ 学び: 引用符がarXiv検索を失敗させる
↓
v3: すべての学びを統合して成功
✅ データ拡大 + 品質改善 + ハイパーパラメータ調整
採用担当者へのメッセージ:
失敗を恐れず、迅速に問題を発見し、改善するサイクルを回すことが重要。
各失敗から具体的な学びを得て、次の改善に活かす姿勢が成功につながる。
3. ビジネス視点の重要性
技術だけでなく:
- ✅ コスト意識($4で大きな成果)
- ✅ ROIの定量化(78,400%)
- ✅ スケーラビリティの考慮
- ✅ チーム展開の考慮(再現性)
- ✅ 保守性の確保(ドキュメント)
具体例:
技術的成果:
- 検索精度+10.9%
- 過学習問題の解決
ビジネス価値:
- 年間コスト削減$3,240
- ROI 78,400%
- スケールしても低コスト維持
- チーム全体で活用可能
4. 自動化の価値
手動 vs 自動化:
| タスク | 手動 | 自動化 | 時間削減 |
|---|---|---|---|
| データクリーンアップ | 数時間 | 1分 | 99% |
| データ収集 | 1週間 | 2日 | 71% |
| 評価 | 1時間 | 6.5分 | 89% |
自動化の効果:
- ✅ 時間節約
- ✅ 再現性確保
- ✅ ヒューマンエラー削減
- ✅ チーム展開容易
今後の展開
短期(1-2週間)
1. 残る引用符問題の完全解決
# アプローチ1: システムプロンプト強化
system_prompt = """
arXiv検索クエリ最適化AI
重要なルール:
1. 引用符(")を絶対に使用しない
2. AND結合のみを使用
3. シンプルな英語フレーズを生成
悪い例: LoRA AND "question answering"
良い例: LoRA AND question answering
"""
# アプローチ2: Post-processing
def remove_quotes_postprocess(query: str) -> str:
"""推論後の後処理で引用符を強制削除"""
return query.replace('"', '').replace("'", '')
expanded = model.expand(query)
expanded = remove_quotes_postprocess(expanded) # 保険
2. データセット更なる拡大
- Batch 5-8を実行(残り307クエリ)
- 目標: 500-600サンプル
- 期待効果: 過学習のさらなる軽減、多様性向上
中期(1-2ヶ月)
3. Llama LoRA との比較実験
| 項目 | GPT-3.5 FT | Llama LoRA | 判断基準 |
|---|---|---|---|
| コスト(推論) | $0.003/query | $0(セルフホスト) | スケール時重要 |
| 精度 | 92.7% | TBD | 同等以上なら移行 |
| レイテンシ | 高速 | 中速 | ユーザー体験 |
| カスタマイズ性 | 中 | 高 | 将来の拡張性 |
| 保守性 | 高(API) | 中(セルフホスト) | 運用コスト |
実験計画:
# Llama 3.1 8B + LoRA
python scripts/finetune_llama_lora.py \
--model meta-llama/Llama-3.1-8B \
--train-path training_data/cleaned/hf_train.jsonl \
--val-path training_data/cleaned/hf_val.jsonl \
--lora-rank 16 \
--epochs 3
# 評価・比較
python scripts/compare_models.py \
--models gpt35-ft,llama-lora \
--test-path training_data/cleaned/openai_test.jsonl
4. プロダクション化
# 本番環境の要件
class ProductionRequirements:
"""本番環境要件"""
# 1. API Key管理
cohere_api_key = "Production Key" # Trial → Production
rate_limit = "1000 calls/min" # 10 → 1000
# 2. エラーハンドリング
retry_strategy = ExponentialBackoff(max_retries=3)
circuit_breaker = CircuitBreaker(threshold=5)
# 3. モニタリング
metrics = [
"success_rate",
"avg_rerank_score",
"latency_p50",
"latency_p99",
"error_rate"
]
alerting = Datadog() # or CloudWatch
# 4. A/Bテスト
traffic_split = {
"baseline": 0.1, # 10%
"finetuned": 0.9 # 90%
}
長期(3-6ヶ月)
5. ドメイン拡張
- 医療: PubMed論文検索
- 法律: 判例検索
- 特許: 特許文献検索
6. 継続的改善ループ
[ユーザーフィードバック]
↓
[データ収集・ラベリング]
↓
[モデル再トレーニング]
↓
[A/Bテスト]
↓
[デプロイ]
↓
(繰り返し)
まとめ
プロジェクトの振り返り
このプロジェクトでは、GPT-3.5のファインチューニングにより、arXiv検索のクエリ拡張精度を**81.8% → 92.7%(+10.9%)**に改善することができました。
成功の3つの鍵
1. データ品質の徹底
- 引用符削除により成功率90.25%を達成
- 品質フィルタリング(min_score=0.7)
- 継続的なデータ監査
2. 過学習対策
- データセット拡大(80 → 252サンプル、3.15倍)
- ハイパーパラメータ調整(epochs=1, lr=0.1)
- Validation lossの監視(0.00 → 0.08)
3. 実践的な問題解決
- API制限への対応(delay=7秒)
- バッチ処理パイプライン構築
- 再現可能な実装
投資対効果
数値で見る成果:
投資:
- 金額: $4.13
- 時間: 1週間
成果:
- 検索成功率: +10.9% (81.8% → 92.7%)
- 検索品質: +13.4% (0.7776 → 0.8815)
- 年間コスト削減: $3,240 (月間10,000クエリの場合)
- ROI: 78,400%
3年間の累積価値: $9,720
学んだこと
-
データ品質 > データ量
- 質の悪いデータをいくら増やしても性能は向上しない
- 品質改善が最優先
-
失敗は成功への道
- v1, v2の失敗から多くを学んだ
- 迅速なPDCAサイクルが重要
-
ビジネス視点の重要性
- 技術的成果だけでなく、ROIを定量化
- チーム展開・保守性も考慮
-
自動化の価値
- 再現性、効率性、品質の向上
- チームスケールに不可欠
採用担当者へのメッセージ
このプロジェクトを通じて、以下のスキルセットを実証しました:
技術力:
- ✅ LLMファインチューニングの実践経験
- ✅ 過学習問題の診断と解決
- ✅ データエンジニアリング(収集・クリーンアップ・管理)
- ✅ API統合とレート制限対応
- ✅ Python開発のベストプラクティス
問題解決能力:
- ✅ 複雑な問題の根本原因分析
- ✅ 仮説検証アプローチ
- ✅ データに基づく意思決定
- ✅ トレードオフの明確な判断
ビジネスセンス:
- ✅ ROIの定量化(78,400%)
- ✅ コスト削減の実現(年間$3,240)
- ✅ スケーラビリティの考慮
- ✅ 投資対効果の最大化
チーム貢献:
- ✅ 再現可能な実装(GitHub公開)
- ✅ 包括的なドキュメント整備
- ✅ ナレッジシェアの姿勢
- ✅ オープンソースへの貢献
リンク
GitHub Repository:
ドキュメント:
一緒に働きませんか?
私は現在、機械学習エンジニアとして新しいチャレンジを探しています。
このようなプロジェクトで培った「技術力×ビジネス価値」を大切にする開発を、
あなたのチームでも実現したいと考えています。
まずはカジュアルにお話しできれば嬉しいです。
Contact
📧 datarch.py2011@gmail.com
💼 GitHub
Keywords: GPT-3.5, ファインチューニング, 過学習対策, OpenAI, Fine-tuning, データ品質, 機械学習, LLM, コスト削減, ROI, arXiv, 論文検索, クエリ拡張
Discussion