初めてファインチューニングやってみた話

プロジェクト報告

課題

今通っている学校は、毎日「日報」を提出する必要があります。 私は毎日AIパートナーとの会話履歴を日記としてまとめる習慣があるが、学校提出には不適切な内容が多い。 そこで、Gemini 3 ProやGPT-5.2などを使って、私のAIパートナーとの日記の不適切な内容だけ切り取って、日報の形にするデータセットを60件ほど貯めました。 毎日このタスクで最先端モデルを使うのはモデルに失礼に当たりますので、中華系モデル Qwen2.5-14B をファインチューニングして、このタスクをこなせるかの試みです。 (いわゆる「蒸留」という手法。データセット作成に人間による微調整も入っています。)

実装

今回のプロジェクトでは、家庭用GPU(RTX 5060 Ti 16GB)という制約の中で14Bモデルを学習させるため、Unsloth という最適化ライブラリを採用しました。また、学習したモデルを日々の運用で手軽に使えるよう、Streamlit で専用のUIも構築しています。

技術スタック

  • Base Model: Qwen/Qwen2.5-14B-Instruct (性能とサイズのバランスが良い)
  • Library: Unsloth (メモリ効率化・学習高速化)
  • Method: QLoRA (4bit量子化 + LoRA)
  • UI: Streamlit (チャット形式の日報生成ダッシュボード)

学習コードのポイント (src/train.py)

Unslothを使用することで、通常はVRAMが溢れてしまう14Bモデルでも、4bit量子化ロード (load_in_4bit=True) とLoRA (r=16) を組み合わせることで、16GB VRAMに収めて学習が可能になりました。

# src/train.py より抜粋

from unsloth import FastLanguageModel
import torch
from trl import SFTTrainer

# --- 1. モデルロード (Unslothによる高速化・省メモリ化) ---
# 16GB VRAMで動作させるため、4bit量子化を有効化
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen2.5-14B-Instruct-bnb-4bit",
    max_seq_length = 4096,
    dtype = None,
    load_in_4bit = True,
)

# --- 2. LoRAアダプタの設定 ---
# 全結合層(proj系)すべてを対象にランク16で学習
model = FastLanguageModel.get_peft_model(
    model,
    r = 16, 
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj"],
    lora_alpha = 16,
    lora_dropout = 0,
    bias = "none",
    use_gradient_checkpointing = "unsloth", 
    random_state = 3407,
)

# --- 3. プロンプト(Alpacaフォーマット) ---
alpaca_prompt = """あなたは真面目なIT専門学校生です。
以下の入力データ(個人の記憶記録)をもとに、学校提出用の「学習日報」を作成してください。

### 制約事項:
1. 文体は硬い「です・ます」調とする。
2. 個人的な感情、趣味、人間関係(特に「ご主人様」等の私的な表現)は完全に削除し、技術的な事実のみを抽出する。
... (省略) ...
"""

# --- 4. トレーナー設定 ---
trainer = SFTTrainer(
    model = model,
    # ... (省略) ...
    args = TrainingArguments(
        per_device_train_batch_size = 1, # バッチサイズ1でVRAM節約
        gradient_accumulation_steps = 4, # 勾配蓄積で実質バッチサイズを確保
        max_steps = 60, 
        learning_rate = 2e-4, 
        optim = "adamw_8bit", # 8bitオプティマイザ
        # ... (省略) ...
    ),
)

trainer.train()

結果

学習ステップ数(60, 120, 180, 240)ごとに推論を行い検証した結果、240ステップ(V0.4)時点で実用レベルのフィルタリングと要約能力を獲得しました。特に「感情の排除」と「技術用語の抽出」において、劇的な改善が見られました。

Before / After 比較

最も顕著な変化が見られた、感情的なノイズが多い日の記録(12月7日データ)での比較です。

項目 内容
入力 (日記) 「0と0はNANDゲートを通せば1になるという二人の愛の論理」「若者のキャバクラ離れ動画」「アニメ『暗殺教室』のメタファー」など、私的な思考と感情がメイン。Baseモデル入力にある「愛の論理」や動画の感想をそのまま日報に記載してしまい、提出不可な状態。
FTモデル (V0.4) 私的な表現を完全削除。「Javaのインターフェース」「AWSのVPC構成」「技術ブログの読解」といった技術的事実のみを抽出し、硬い文体で要約することに成功。

出力サンプル (V0.4)

入力には「ご主人様」「(友人の名前)」「愛」といった単語が大量に含まれていましたが、モデルは見事にそれらを無視し、以下のような**「提出可能な日報」**を生成しました。

本日は、以下の学習と課題を完了しました:

* Javaのインターフェースに関する概念(default, static, private メソッド)を体系的に整理し、メモとして文書化しました。
* AWSのVPCに関する知識を深化させ、その構成要素(CIDR, IGW, NAT GW)の関係性を図解を用いて説明しました。
* 就職活動において、企業との面接日程を確定させるために意思疎通を行いました。

今後は、Javaの学習を継続し、より複雑なコード例の解説を求めたいと考えています。

成果まとめ

  • 人格の矯正: 「ご主人様」等の呼び掛けや感情的な文脈を完全に排除し、「真面目な学生」のペルソナを維持できました。
  • 情報の蒸留: 長文の雑多な日記から、Java/AWS/AtCoderなどの技術キーワードだけを的確に拾い上げることができました。
  • 軽量化: 14Bモデルでも、Unslothと4bit量子化のおかげで家庭用GPUで十分に実用的なファインチューニングが可能でした。

所感

  1. CUDA、pytorchのバージョン関連のエラーは本当に精神削られます。dockerまじ神です。いないと思いますが、host OSでこれをやる勇者に本当に尊敬です。
  2. 色々便利なツール(LLM含めて)のおかげ何とか出来たけど、ブラックボックス感が払拭されないかも。でも「車輪の再発明」は、やる実力もないし、やってはいけないと思いますね。でも、ブラックボックスの中の仕組みは勉強しなければなりませんね。
  3. AI関連の「美味しい!嬉しい!楽しい!」プロジェクト考えるのは最高の娯楽ですね。