Fine Tuning

ファインチューニング

Fine Tuning を分かりやすく

Fine Tuning(ファインチューニング)は、既に訓練された AI モデルを、あなたの特定の用途に合わせてカスタマイズする技術です。

例え話をしましょう。あなたが料理人を雇うとします:

ゼロから訓練する(Pre-training): 料理を全く知らない人に、基本から教える

  • 包丁の使い方から始める
  • 様々な料理の作り方を教える
  • 時間: 数年
  • コスト: 非常に高い

Fine Tuning: すでにプロの料理人を雇って、あなたのレストランのメニューを教える

  • 既に料理の基本は知っている
  • あなたのレストランの特別なレシピやスタイルだけを教える
  • 時間: 数週間
  • コスト: 比較的安い

AI のFine Tuning も同じです。GPT-4 や Llama などの大規模モデルは、膨大なデータで訓練されています(Pre-training)。これをベースに、あなたの会社の文書スタイル、製品知識、業界用語などを追加で学習させる(Fine Tuning)ことで、特化したAIを作れます。

Fine Tuning が必要なケース

  1. 特定のドメイン知識: 医療、法律、金融など専門分野
  2. 独自のスタイル: 会社の文章スタイル、トーン
  3. カスタム機能: 特定のタスクに特化した性能向上
  4. コスト削減: 小さなモデルで大きなモデルと同等の性能

Fine Tuning vs RAG vs Prompt Engineering

どの手法を使うべきか迷うかもしれません。比較してみましょう:

// lib/comparison.ts
type AICustomizationMethod = {
  name: string
  cost: 'Low' | 'Medium' | 'High'
  complexity: 'Low' | 'Medium' | 'High'
  updateEase: 'Easy' | 'Medium' | 'Hard'
  useCases: string[]
}

const methods: AICustomizationMethod[] = [
  {
    name: 'Prompt Engineering',
    cost: 'Low',
    complexity: 'Low',
    updateEase: 'Easy',
    useCases: [
      '一般的なタスク',
      'プロトタイピング',
      '頻繁に要件が変わる場合',
    ],
  },
  {
    name: 'RAG (Retrieval Augmented Generation)',
    cost: 'Medium',
    complexity: 'Medium',
    updateEase: 'Easy',
    useCases: [
      '最新情報が必要',
      '社内文書の活用',
      '頻繁に更新されるデータ',
    ],
  },
  {
    name: 'Fine Tuning',
    cost: 'High',
    complexity: 'High',
    updateEase: 'Hard',
    useCases: [
      '特定のスタイルやトーンが必要',
      '高度な専門知識',
      '安定した要件',
    ],
  },
]

Next.js × TypeScript で Fine Tuning を実装する

ステップ 1: 訓練データの準備

Fine Tuning には、高品質な訓練データが必要です。

// lib/fine-tuning/prepare-data.ts

export type TrainingExample = {
  messages: Array<{
    role: 'system' | 'user' | 'assistant'
    content: string
  }>
}

// カスタマーサポート用の訓練データを作成
export function prepareCustomerSupportData(): TrainingExample[] {
  return [
    {
      messages: [
        {
          role: 'system',
          content:
            'あなたは〇〇社のカスタマーサポートです。丁寧で親切に対応してください。',
        },
        {
          role: 'user',
          content: '商品の返品方法を教えてください',
        },
        {
          role: 'assistant',
          content:
            'ご購入いただきありがとうございます。返品は購入から30日以内であれば承っております。以下の手順で返品をお願いいたします:\n\n1. マイページにログイン\n2. 注文履歴から該当商品を選択\n3. 「返品申請」ボタンをクリック\n4. 返品理由を選択して送信\n\n返品送料は弊社負担となります。ご不明な点がございましたら、お気軽にお問い合わせください。',
        },
      ],
    },
    // ... さらに多くの例
  ]
}

// JSONL 形式に変換(OpenAI の形式)
export function convertToJSONL(examples: TrainingExample[]): string {
  return examples.map((example) => JSON.stringify(example)).join('\n')
}

ステップ 2: OpenAI でFine Tuning を実行

// lib/fine-tuning/openai.ts
import OpenAI from 'openai'
import fs from 'fs'
import path from 'path'

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
})

// 訓練ファイルをアップロード
export async function uploadTrainingFile(filePath: string) {
  const file = await openai.files.create({
    file: fs.createReadStream(filePath),
    purpose: 'fine-tune',
  })

  return file.id
}

// Fine Tuning ジョブを開始
export async function startFineTuning(
  trainingFileId: string,
  model: 'gpt-3.5-turbo' | 'gpt-4' = 'gpt-3.5-turbo',
  suffix?: string
) {
  const fineTune = await openai.fineTuning.jobs.create({
    training_file: trainingFileId,
    model,
    suffix, // カスタムモデル名のサフィックス
  })

  return fineTune
}

// Fine Tuning の進捗を確認
export async function checkFineTuningStatus(jobId: string) {
  const job = await openai.fineTuning.jobs.retrieve(jobId)
  return job
}

// Fine Tuning されたモデルを使用
export async function useFineTunedModel(
  modelName: string,
  messages: Array<{ role: string; content: string }>
) {
  const response = await openai.chat.completions.create({
    model: modelName, // 例: "ft:gpt-3.5-turbo:my-org:custom-suffix:id"
    messages,
  })

  return response.choices[0].message.content
}

ステップ 3: Fine Tuning 管理 API

// app/api/fine-tuning/create/route.ts
import { prepareCustomerSupportData, convertToJSONL } from '@/lib/fine-tuning/prepare-data'
import { uploadTrainingFile, startFineTuning } from '@/lib/fine-tuning/openai'
import fs from 'fs'
import path from 'path'

export async function POST(req: Request) {
  try {
    // 訓練データを準備
    const trainingData = prepareCustomerSupportData()
    const jsonlContent = convertToJSONL(trainingData)

    // 一時ファイルに保存
    const tempFilePath = path.join(process.cwd(), 'temp', 'training-data.jsonl')
    fs.writeFileSync(tempFilePath, jsonlContent)

    // OpenAI にアップロード
    const fileId = await uploadTrainingFile(tempFilePath)

    // Fine Tuning を開始
    const fineTune = await startFineTuning(fileId, 'gpt-3.5-turbo', 'customer-support')

    // 一時ファイルを削除
    fs.unlinkSync(tempFilePath)

    return Response.json({
      success: true,
      jobId: fineTune.id,
      status: fineTune.status,
    })
  } catch (error) {
    console.error('Fine Tuning Error:', error)
    return new Response('Fine Tuning の開始に失敗しました', { status: 500 })
  }
}
// app/api/fine-tuning/status/route.ts
import { checkFineTuningStatus } from '@/lib/fine-tuning/openai'

export async function GET(req: Request) {
  try {
    const { searchParams } = new URL(req.url)
    const jobId = searchParams.get('jobId')

    if (!jobId) {
      return new Response('jobId が必要です', { status: 400 })
    }

    const status = await checkFineTuningStatus(jobId)

    return Response.json({
      jobId: status.id,
      status: status.status,
      model: status.fine_tuned_model,
      createdAt: status.created_at,
      finishedAt: status.finished_at,
    })
  } catch (error) {
    console.error('Status Check Error:', error)
    return new Response('ステータスの確認に失敗しました', { status: 500 })
  }
}

ステップ 4: Fine Tuned モデルの使用

// app/api/chat/fine-tuned/route.ts
import { useFineTunedModel } from '@/lib/fine-tuning/openai'

export async function POST(req: Request) {
  try {
    const { messages, model } = await req.json()

    if (!model) {
      return new Response('model が必要です', { status: 400 })
    }

    const response = await useFineTunedModel(model, messages)

    return Response.json({
      message: response,
    })
  } catch (error) {
    console.error('Fine Tuned Model Error:', error)
    return new Response('エラーが発生しました', { status: 500 })
  }
}

ステップ 5: Fine Tuning 管理 UI

// app/fine-tuning/page.tsx
'use client'

import { useState } from 'react'

type FineTuningJob = {
  jobId: string
  status: string
  model: string | null
  createdAt: number
  finishedAt: number | null
}

export default function FineTuningPage() {
  const [jobId, setJobId] = useState('')
  const [jobStatus, setJobStatus] = useState<FineTuningJob | null>(null)
  const [isCreating, setIsCreating] = useState(false)
  const [isChecking, setIsChecking] = useState(false)

  const createFineTuning = async () => {
    setIsCreating(true)
    try {
      const response = await fetch('/api/fine-tuning/create', {
        method: 'POST',
      })

      const data = await response.json()
      setJobId(data.jobId)
      alert(`Fine Tuning ジョブを開始しました: ${data.jobId}`)
    } catch (error) {
      console.error('Error:', error)
      alert('エラーが発生しました')
    } finally {
      setIsCreating(false)
    }
  }

  const checkStatus = async () => {
    if (!jobId) return

    setIsChecking(true)
    try {
      const response = await fetch(`/api/fine-tuning/status?jobId=${jobId}`)
      const data = await response.json()
      setJobStatus(data)
    } catch (error) {
      console.error('Error:', error)
      alert('エラーが発生しました')
    } finally {
      setIsChecking(false)
    }
  }

  return (
    <div className="container mx-auto max-w-4xl p-4">
      <h1 className="text-3xl font-bold mb-6">Fine Tuning 管理</h1>

      {/* Fine Tuning の説明 */}
      <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
        <h2 className="font-semibold mb-2">Fine Tuning とは?</h2>
        <p className="text-sm">
          既存の AI モデルを、あなたの特定の用途に合わせてカスタマイズする技術です。
        </p>
        <ul className="list-disc list-inside mt-2 text-sm">
          <li>特定のドメイン知識を追加</li>
          <li>独自のスタイルやトーンを学習</li>
          <li>タスク固有の性能を向上</li>
        </ul>
      </div>

      {/* Fine Tuning 作成 */}
      <div className="border border-gray-200 rounded-lg p-6 mb-6">
        <h2 className="text-xl font-semibold mb-4">新しい Fine Tuning ジョブを作成</h2>
        <button
          onClick={createFineTuning}
          disabled={isCreating}
          className="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600 disabled:bg-gray-300"
        >
          {isCreating ? '作成中...' : 'Fine Tuning を開始'}
        </button>
      </div>

      {/* ステータス確認 */}
      <div className="border border-gray-200 rounded-lg p-6">
        <h2 className="text-xl font-semibold mb-4">Fine Tuning ステータス確認</h2>

        <div className="flex gap-2 mb-4">
          <input
            type="text"
            value={jobId}
            onChange={(e) => setJobId(e.target.value)}
            placeholder="ジョブ ID を入力..."
            className="flex-1 border border-gray-300 rounded-lg px-4 py-2"
          />
          <button
            onClick={checkStatus}
            disabled={isChecking || !jobId}
            className="bg-green-500 text-white px-6 py-2 rounded-lg hover:bg-green-600 disabled:bg-gray-300"
          >
            {isChecking ? '確認中...' : 'ステータス確認'}
          </button>
        </div>

        {jobStatus && (
          <div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
            <div className="grid grid-cols-2 gap-4">
              <div>
                <p className="text-sm text-gray-600">ジョブ ID</p>
                <p className="font-mono text-sm">{jobStatus.jobId}</p>
              </div>
              <div>
                <p className="text-sm text-gray-600">ステータス</p>
                <p className={`font-semibold ${
                  jobStatus.status === 'succeeded' ? 'text-green-600' :
                  jobStatus.status === 'failed' ? 'text-red-600' :
                  'text-yellow-600'
                }`}>
                  {jobStatus.status}
                </p>
              </div>
              {jobStatus.model && (
                <div className="col-span-2">
                  <p className="text-sm text-gray-600">モデル名</p>
                  <p className="font-mono text-sm">{jobStatus.model}</p>
                </div>
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  )
}

Fine Tuning のベストプラクティス

1. データ品質が最重要

// 良い訓練データの例
const goodExample = {
  messages: [
    { role: 'system', content: '明確な指示' },
    { role: 'user', content: '具体的な質問' },
    { role: 'assistant', content: '高品質で一貫性のある回答' },
  ],
}

// 悪い訓練データの例
const badExample = {
  messages: [
    { role: 'system', content: '' }, // 指示が不明確
    { role: 'user', content: 'あれ' }, // あいまい
    { role: 'assistant', content: 'うーん' }, // 低品質
  ],
}

2. 適切なデータ量

  • 最低: 50〜100 例
  • 推奨: 500〜1,000 例
  • 理想: 数千例以上

3. バリデーションデータで評価

// 訓練データとバリデーションデータに分割
export function splitData(data: TrainingExample[], splitRatio: number = 0.8) {
  const shuffled = data.sort(() => 0.5 - Math.random())
  const splitIndex = Math.floor(data.length * splitRatio)

  return {
    training: shuffled.slice(0, splitIndex),
    validation: shuffled.slice(splitIndex),
  }
}

まとめ

Fine Tuning は、AI モデルをあなたの特定の用途に最適化する強力な手法です。Next.js と TypeScript を組み合わせることで、Fine Tuning のプロセスを管理し、カスタムモデルを効果的に活用できます。

重要なポイント:

  1. 適切な手法選択: Prompt Engineering、RAG、Fine Tuning を適切に使い分ける
  2. データ品質: 高品質な訓練データが成功の鍵
  3. 継続的な評価: バリデーションデータでモデルの性能を評価
  4. コスト管理: Fine Tuning には時間とコストがかかることを理解
  5. バージョン管理: 複数のモデルバージョンを管理して比較

Fine Tuning を活用することで、汎用 AI モデルでは実現できなかった、高度に専門化された AI アプリケーションを構築できます。