メインコンテンツへスキップ

7.8.2 プロジェクト:垂直領域のファインチューニング

この節の位置づけ

垂直領域のファインチューニング・プロジェクトは、いちばん「一言で終わる説明」になりがちです。

  • 「業界の専門家モデルを作る」

でも、ポートフォリオとして価値のあるプロジェクトは、たいてい次のような形です。

範囲が明確で、境界がはっきりしていて、評価できる領域 QA タスクを、before/after の差がよく分かるシステムにする。

この節では、その流れをきちんと整理します。

学習目標

  • 「領域ファインチューニング」の題材を、実行可能なプロジェクトまで絞り込めるようになる
  • 元の知識を整理して、SFT データと評価セットを作れるようになる
  • 本当に説得力のある baseline 比較ができるようになる
  • このプロジェクトを作品レベルのページとして見せられるようになる

一、なぜプロジェクトの題材は先に絞る必要があるのか?

大きすぎる題材は、ほとんど進められない

たとえば:

  • 業界の専門家 LLM を作る

このような題材は境界が広すぎて、
次の点をはっきりさせにくいです。

  • 入力は何か
  • 出力は何か
  • 何をもって正解とするか

ポートフォリオに向いている題材

たとえば:

EC のアフターサービス方針アシスタント:返金、住所変更、請求書、アフターサービス手続きの 4 種類の質問に絞る。

この題材の良いところは:

  • 範囲が狭い
  • 意味が安定している
  • 評価基準を設計しやすい

二、作品レベルのファインチューニング・プロジェクトの最小ループは?

  1. タスクの境界を決める
  2. 知識と会話サンプルを整理する
  3. baseline を作る
  4. SFT データを整理する
  5. 評価セットを作る
  6. 学習して before/after 比較を行う

この 6 ステップがはっきりしていれば、プロジェクトはかなり説得力のあるものになります。

垂直領域ファインチューニング・プロジェクトの閉ループ図

図の見方

この図は上から下へ読んでください。まずタスクを絞り、元知識を SFT サンプルに変え、学習前に baseline を比較し、固定評価セットで確認し、そのうえでファインチューニングの追加コストに見合うかを判断します。

三、おすすめの進め方

初心者にとって、より安定しやすい順番は通常こうです。

  1. まず題材の範囲を絞る
  2. 次に Prompt / 検索 baseline を作る
  3. その後で SFT データを整理する
  4. 最後にファインチューニングと before/after 比較を行う

こうすると、プロジェクトが「何となく微調整したもの」ではなく、「判断した上で微調整したもの」に見えます。

コードを読む前に押さえたいプロジェクト用語

用語初心者向けの意味この節での役割
LLMLarge Language Model、大規模言語モデル。token を1つずつ予測しながら文章を生成するモデルこのプロジェクトでは、LLM の領域タスクでの振る舞いを調整する
Prompt推論時にモデルへ渡す指示、文脈、制約训练より低コストなので、最初の baseline になる
RAGRetrieval-Augmented Generation、回答前に外部資料を検索してから生成する方法最新知識や社内資料が足りないときに役立つ
ファインチューニングタスク専用サンプルでモデルを追加学習すること文体、形式、判断パターンを安定させたいときに価値がある
SFTSupervised Fine-Tuning、整理済みの入力/出力例で行う教師あり微調整モデルに「良い回答の形」を教える
baseline高度な方法の前に作る、最も簡単な比較対象根拠なしに「改善した」と言わないために必要
評価セット学習には使わない固定のテスト質問セット新しい方法が未見のケースにも効くかを確認する
coverage必須ポリシー点をどれだけ回答に含めたか「良さそう」を、より測れるスコアに変える

四、まずは、より完全なデータと baseline の例を見てみよう

次の例では、以下を同時に示します。

  • 元データ
  • SFT サンプル
  • 2 種類の baseline
  • 評価ルール

このコードは Python 標準ライブラリだけで動きます。domain_finetune_demo.py として保存し、python domain_finetune_demo.py で実行できます。

raw_records = [
{
"intent": "refund_unshipped",
"question": "注文がまだ発送されていませんが、そのまま返金できますか?",
"policy_points": ["未発送なら直接返金申請できる", "返金は元の支払い方法に戻る", "着金は通常 3〜7 営業日"],
"evaluation_keywords": [["未発送", "発送されていない"], ["元の支払い方法"], ["3〜7", "営業日"]],
"answer": "はい。注文がまだ発送されていない場合は、そのまま返金を申請できます。代金は元の支払い方法に返金され、通常 3〜7 営業日で反映されます。",
},
{
"intent": "change_address",
"question": "受け取り住所を間違えました。変更できますか?",
"policy_points": ["出庫前なら住所変更可能", "出庫後は有人サポートに連絡"],
"evaluation_keywords": [["出庫前"], ["有人サポート", "出庫済み"]],
"answer": "注文がまだ出庫前であれば、注文詳細ページから住所を変更できます。すでに出庫済みの場合は、有人サポートまでご連絡ください。",
},
{
"intent": "invoice",
"question": "請求書はいつ発行できますか?",
"policy_points": ["注文完了後に申請可能", "電子請求書はメール送付"],
"evaluation_keywords": [["注文完了"], ["電子請求書", "メール"]],
"answer": "注文完了後に請求書センターから発行を申請できます。電子請求書は登録済みのメールアドレスに送信されます。",
},
]


def build_sft_record(row):
system = "あなたは EC のアフターサービス方針アシスタントです。丁寧で正確、かつプラットフォーム規則に沿ってユーザーの質問に答えてください。"
return {
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": row["question"]},
{"role": "assistant", "content": row["answer"]},
],
"intent": row["intent"],
"policy_points": row["policy_points"],
}


def generic_baseline(question):
if "返金" in question:
return "一般的には返金申請は可能です。詳細は注文状況によります。"
if "住所" in question:
return "住所の問題は、できるだけ早くサポートへ連絡することをおすすめします。"
if "請求書" in question:
return "請求書は通常申請できます。詳しくは画面の案内をご確認ください。"
return "サポート窓口へお問い合わせください。"


def retrieval_baseline(question, records):
best = max(records, key=lambda row: overlap(question, row["question"]))
return best["answer"]


def tokenize(text):
punctuation = "、。!?;:「」『』()()[]{}"
words = [char.strip(punctuation) for char in text]
return {word for word in words if word}


def overlap(a, b):
return len(tokenize(a) & tokenize(b))


def coverage(answer, required_keyword_groups):
matched = [
group
for group in required_keyword_groups
if any(keyword in answer for keyword in group)
]
return round(len(matched) / len(required_keyword_groups), 3)


sft_dataset = [build_sft_record(row) for row in raw_records]
sample = raw_records[0]

generic_answer = generic_baseline(sample["question"])
retrieval_answer = retrieval_baseline(sample["question"], raw_records)

print("question:", sample["question"])
print("generic :", generic_answer, "coverage=", coverage(generic_answer, sample["evaluation_keywords"]))
print("retrieval:", retrieval_answer, "coverage=", coverage(retrieval_answer, sample["evaluation_keywords"]))
print("sft_sample:", sft_dataset[0])

期待される出力は次のとおりです。

question: 注文がまだ発送されていませんが、そのまま返金できますか?
generic : 一般的には返金申請は可能です。詳細は注文状況によります。 coverage= 0.0
retrieval: はい。注文がまだ発送されていない場合は、そのまま返金を申請できます。代金は元の支払い方法に返金され、通常 3〜7 営業日で反映されます。 coverage= 1.0
sft_sample: {'messages': [{'role': 'system', 'content': 'あなたは EC のアフターサービス方針アシスタントです。丁寧で正確、かつプラットフォーム規則に沿ってユーザーの質問に答えてください。'}, {'role': 'user', 'content': '注文がまだ発送されていませんが、そのまま返金できますか?'}, {'role': 'assistant', 'content': 'はい。注文がまだ発送されていない場合は、そのまま返金を申請できます。代金は元の支払い方法に返金され、通常 3〜7 営業日で反映されます。'}], 'intent': 'refund_unshipped', 'policy_points': ['未発送なら直接返金申請できる', '返金は元の支払い方法に戻る', '着金は通常 3〜7 営業日']}

この小さなコードの目的は、本番レベルの検索システムを作ることではありません。比較を見える形にすることです。汎用回答は丁寧に聞こえても、必須のポリシー詳細を落とすことがあります。一方、領域に合った回答は、固定した必須点に照らして確認できます。

この例が、単なる「プロジェクト案」より価値がある理由

なぜなら、プロジェクトで本当に重要な 4 つが、すでに入っているからです。

  1. 元データがどんな形か
  2. SFT サンプルがどんな形か
  3. baseline の結果が何か
  4. 評価ルールが何か

これは、実際のファインチューニング・プロジェクトの核にかなり近い形です。

垂直領域ファインチューニング・プロジェクト評価ボード図

図の見方

この図は、ポートフォリオの視点で読むのがおすすめです。まずタスク境界を絞り、SFT データは出典と形式を見せられるようにし、baseline は必ず先に動かし、評価にはポリシー点のカバー率、禁止事項の約束、文体の一貫性、before/after 比較を入れます。プロジェクトの価値は「ファインチューニングしたこと」そのものではなく、なぜ微調整する価値があるのか、どこが良くなったのかを証明できる点にあります。

なぜ最初に 2 つの baseline を作るのか?

少なくとも、次の比較をおすすめします。

  1. そのままの Prompt / 汎用回答
  2. 検索 or 簡単な領域マッチ
  3. ファインチューニング後のシステム

これがないと、あとで次のことを説明しにくくなります。

  • ファインチューニングで、何がどれだけ改善したのか

検索がすでに強いなら、いつファインチューニングする価値があるのか?

検索が答えるのは「モデルにどの知識を見せるべきか」です。ファインチューニングが答えるのは別の問いで、「入力を見たあと、モデルがどんな安定した振る舞いをするべきか」です。検索で正しいポリシーを見つけられる場合でも、文体を常にそろえる、意図分類を安定させる、厳密な JSON schema を出す、同じ推論パターンを何度も使う、といった場面ではファインチューニングが価値を持つことがあります。

状況まず選びたい方法理由
回答が社内資料や頻繁に変わる知識に依存するRAG文書更新のほうが再学習より安全
回答の文体や構造を安定させたいファインチューニングモデルが繰り返し出る出力パターンを学ぶ
タスク定義そのものがあいまい先にタスクと Prompt を書き直すあいまいなサンプルで学習すると、混乱を固定してしまう
出力をコードで安定して解析したいまず Prompt + schema、その後必要ならファインチューニングschema 制約で、問題が表現なのか振る舞いなのか見えやすくなる
baseline がすでに評価を通っているより単純な方法を残す単純で信頼できるシステムのほうが保守しやすい

五、ファインチューニング・プロジェクトで最も大切な評価は、「専門家っぽく見えるか」だけではない

構造化された評価ポイント

少なくとも、次を含めるべきです。

  • 重要なポリシー点をカバーしているか
  • 禁止された約束をしていないか
  • 文体が一貫しているか
  • 質問にきちんと答えているか

作品レベルらしい見せ方

最もおすすめなのは、次の形で見せることです。

  • 同じ質問セット
  • baseline の回答
  • ファインチューニング後の回答
  • 1 つずつ差分を説明する

失敗例はとても重要

たとえば:

  • 方針を勝手に作ってしまう
  • 細かい点を間違える
  • 口調が安定しない

こうした例は、成功例だけを見せるよりも、ずっと実際のプロジェクトらしく見えます。


六、このプロジェクトを作品レベルのページにするには?

おすすめのページ構成

  1. タスクの境界
  2. データの作り方
  3. baseline 比較
  4. SFT サンプル例
  5. before / after
  6. 失敗例

とても評価が高いポイント

「ポリシー点カバー率」のような、明確なルールを出すことです。
これにより、主観だけに頼らず、プロジェクトがかなりしっかりしたものに見えます。


七、いちばんハマりやすい落とし穴

最初から大きすぎる題材を選ぶ

これをやると、評価もデータも一緒に拡散してしまいます。

baseline がない

比較対象がなければ、ファインチューニング・プロジェクトはほとんど成立しません。

モデル学習だけを見せて、タスク判断を見せない

プロジェクト提出時に、できれば追加したい内容

  • タスク境界の表を 1 枚
  • baseline 比較の表を 1 枚
  • before / after の QA サンプルを 1 セット
  • 失敗例と原因分析を 1 セット
  • 「なぜここは RAG / Prompt だけではなく、ファインチューニングする価値があるのか」の判断説明

プロジェクトが本当に価値を持つのは、次の部分です。

  • 題材の定義
  • データの整理
  • 評価設計

まとめ

この節で最も大切なのは、作品レベルの判断軸を作ることです。

垂直領域のファインチューニング・プロジェクトの本当の価値は、「モデルをファインチューニングしたこと」そのものではなく、タスク境界、SFT データ、baseline、評価ルール、before/after 比較を 1 本のきれいな閉ループとして説明できるかどうかです。

このループがきちんと見えていれば、このプロジェクトは展示用としてとても向いています。


バージョンの進め方のおすすめ

バージョン目標仕上げの重点
基礎版最小ループを動かす入力できる、処理できる、出力できる、サンプルを 1 組残す
標準版見せられるプロジェクトにする設定、ログ、エラー処理、README、スクリーンショットを追加する
チャレンジ版ポートフォリオ品質に近づける評価、比較実験、失敗サンプル分析、次の方針を追加する

まずは基礎版を完成させるのがおすすめです。最初から全部入りを目指さなくて大丈夫です。バージョンを 1 つ上げるたびに、「何が増えたのか、どう検証したのか、まだ何が課題か」を README に書きましょう。

練習問題

  1. 元データにさらに 5 件サンプルを追加して、4 つの intent がもっと均等になるようにしてみましょう。
  2. 考えてみましょう:Retrieval baseline がすでにかなり良い場合、それでもファインチューニングをする価値があるのは、どんなときでしょうか?
  3. なぜ「ポリシー点カバー率」は、「なんとなく人間っぽい」という感覚より、プロジェクト評価に向いているのでしょうか?
  4. もしこのプロジェクトをポートフォリオにするなら、最も見せる価値がある before/after の例は、どの 4 つでしょうか?