8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【松尾研コンペ】非エンジニアのLLMファインチューニング記録 ── 前編

8
Last updated at Posted at 2026-02-06

はじめに

昨年、松尾研のLLM講座を受講して初めてLLMの世界に触れました。今年は2年目。コンペも2回目の挑戦です。

今回のテーマは構造化データへの変換性能に特化したローカルLLMの開発。JSON、XML、YAML、TOML、CSVといった構造化データ(決まったルールに従って整理されたデータ形式)を正確に出力できるモデルを作ります。

「LLMの微調整なんてやったことないぞ…」という状態からのスタート。運営が指定するベンチマーク(LLMの性能をチェックする試験のようなもの)で高得点を目指すのですが、合格ラインは0.7。最初は0.68で不合格……そこから6回の実験を重ねて、ようやく0.73で合格ラインを突破できました。この記事では、その過程で得た知見を備忘録としてまとめます。

スクリーンショット 2026-02-06 23.21.52.png

コンペのルールでLLMによるデータセット生成は禁止されていますが、コーディングや分析での利用は問題ありません。

このコンペでは推論時のプロンプト(モデルへの指示文)は運営が指定したもので修正禁止です。つまり、プロンプトを工夫してスコアを上げる「プロンプトエンジニアリング」はできない。勝負どころはデータセット(学習に使うお手本データ)の選び方学習パラメータ(学習の速さや強さを決める設定値)の調整に絞られます。

CursorやClaudeを使い、まず固定された推論プロンプトの中身を一緒に読み解いて、「このプロンプトが求めている出力形式は何か」「どんなデータセットで学習すれば相性がいいか」を分析する。そこから学習パラメータの方針を相談して、実験結果が出たら出力を一緒に検証する──こんな感じで作戦を立てる「相棒」としてフル活用しています。初学者にとって、壁打ち相手がいるのは本当に心強いですね。

同じように初めてSFTに挑戦する方の参考になれば嬉しいです。

コンペの概要 ── 構造化データ変換って何?

このコンペでは、自然言語の指示に従って構造化データを正確に生成・変換できるLLMを作ります。

たとえばこんなタスクです。

  • 「以下のテキストからユーザー情報をJSON形式で抽出してください」
  • 「このXMLをYAMLに変換してください」
  • 「以下の条件でCSVデータを生成してください」

作ったモデルは運営が用意したベンチマークで採点されます。いわばLLMのための「試験」ですね。評価基準は構造の正確性(JSONの括弧が正しく閉じているか、キー名が合っているかなど)とキーワードマッチング(必要な値がちゃんと含まれているか)。フォーマットが壊れていたり、必要な項目が抜けていたりすると大幅に減点されます。

合格ラインは0.7。これを超えるのが最初の目標です。

使用するベースモデル

ベースモデル(微調整のベースとなる既存のLLM)も運営から指定されていて、今回は Qwen3-4B-Instruct-2507 です。「4B」はパラメータ(モデルの脳細胞のようなもの)が40億個という意味で、LLMとしては小型な部類。Google ColabのGPUで学習できるサイズ感です。それだけでなく、ローカルのPCでも動かせるくらい軽量です。実は普段から実験的にQwen3-4Bを使っていたりするので、わりと馴染みのあるモデルでした。

環境構築 ── Colab + Unsloth + QLoRA

技術スタック

項目 内容
実行環境 Google Colab Pro+(T4 / L4 GPU)
微調整ライブラリ Unsloth
手法 QLoRA(4bit量子化LoRA)
学習フレームワーク TRL(Transformers Reinforcement Learning)

各ツールをざっくり説明

ここで出てくる用語をざっくり解説しておきます。

**SFT(教師あり微調整)**は、お手本データを見せて「こう答えてね」とモデルに教える学習方法です。人間が問題集を解いて勉強するのに近いイメージですね。

Unslothは、このSFTを高速化するライブラリ。通常の2〜5倍速く学習できると言われています。

QLoRAは、メモリ節約の技術です。モデル全体を書き換えるのではなく、ごく一部だけを効率よく調整するLoRAという手法に加えて、モデルを4bitに圧縮(量子化)することで、少ないGPUメモリでも大きなモデルを扱えるようにしています。

TRLは、Hugging Faceが提供する学習フレームワーク。SFTの学習を簡単に実行できるトレーナーが入っています。

GPUは、もともとゲームの画像処理用チップですが、大量の計算を並列で高速処理できるため、AI学習にも使われています。LLMの学習にはGPUが必須です。

Colab環境の変遷 ── ブラウザ → VSCode拡張 → SSH接続

実は環境構築にもかなり試行錯誤がありました。

第1段階: ブラウザから利用(無料プラン)

最初はColabのブラウザUIでノートブックを実行していました。無料プランのT4 GPUで十分……と思っていたのですが、すぐにGPU不足に。学習を何度も回すとあっという間に使用制限に引っかかります。

第2段階: Pro+に課金 → VSCode拡張機能

これは厳しいということでColab Pro+に課金しました。L4やA100といった高性能GPUが使えるようになります。

せっかくならVSCodeから操作したいと思い、Google Colab拡張機能を導入。ところが、VSCode拡張機能からL4やA100のGPUに接続ができない。T4には繋がるのにL4/A100は選べない……原因不明です。誰かやり方知っていたら教えてください。

第3段階: SSH接続(現在)

最終的にCloudflaredトンネル経由のSSH接続に落ち着きました。これならVSCodeのRemote-SSH拡張から、L4でもA100でも問題なく接続できます。

ColabへのSSH接続手順

同じ悩みを持つ方のために、手順を残しておきます。

ローカル側の準備(初回のみ)

1. cloudflaredをインストール

brew install cloudflared

2. ~/.ssh/configに追記

Host *.trycloudflare.com
    HostName %h
    User root
    Port 22
    ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h

Colab側のセットアップ(ブラウザで毎回実行)

Colabでランタイムタイプを選択(L4やA100など)した後、ブラウザのColab上で以下のセルを順番に実行します。

# セル1: SSHトンネル起動
!pip install colab_ssh --upgrade
from colab_ssh import launch_ssh_cloudflared
launch_ssh_cloudflared(password="任意のパスワード")

実行するとxxx.trycloudflare.comというホスト名が表示されます。これをコピー。

# セル2: SSH鍵ペアの生成
!ssh-keygen -t ed25519 -f /tmp/colabkey -N "" -q && cat /tmp/colabkey.pub >> /root/.ssh/authorized_keys && cat /tmp/colabkey
# セル3: sshd設定の修正(鍵認証を有効化)
!sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
!sed -i 's/#AuthorizedKeysFile/AuthorizedKeysFile/' /etc/ssh/sshd_config
# セル4: authorized_keysのパーミッション設定 + sshd再起動
!mkdir -p /root/.ssh && cat /tmp/colabkey.pub >> /root/.ssh/authorized_keys && chmod 700 /root/.ssh && chmod 600 /root/.ssh/authorized_keys && service ssh restart
# セル5: 秘密鍵を表示(これをローカルに保存する)
!cat /tmp/colabkey

セル5で表示された秘密鍵をローカルの~/.ssh/id_colabに保存します。

# ローカルで実行
pbpaste > ~/.ssh/id_colab   # macOSの場合、コピーした鍵を貼り付け
chmod 600 ~/.ssh/id_colab

ローカルからSSH接続

ssh -i ~/.ssh/id_colab root@<表示されたホスト名>.trycloudflare.com

VSCodeならRemote-SSHで同じホスト名を指定すればOK。鍵認証なのでパスワード入力なしで繋がります。

注意: Colabのセッションが切れるとホスト名が変わります。再接続時はセル1のlaunch_ssh_cloudflaredから再実行してください。鍵ペアもセッションごとに再生成が必要です。

Colabでのセットアップ手順

ノートブックは大きく5ステップで構成しました。

  1. Step 0: Google Driveのマウント(超重要!
  2. Step 1: 依存関係のインストール(numpy, transformers, trl, unsloth等をバージョン固定)
  3. Step 2: HuggingFaceへのログイン
  4. Step 3: SFT学習の実行
  5. Step 4: 学習成果物の確認 → LoRAアダプタをHuggingFaceにアップロード

Step 0のGoogle Driveマウントは地味ですが超重要です。Colabはセッションが切れるとローカルのファイルはすべて消えます。学習データ、チェックポイント、LoRAアダプタ……せっかく何時間もかけた成果が全部パーです。Google Driveにマウントしておけば、セッションが切れても成果物が残ります。これは最初にやっておきましょう。

ColabではGPUの種類によって設定が変わります。L4 GPUの場合はbf16=True、T4の場合はfp16=Trueを指定します。これはGPUが得意な計算精度(数値の扱い方)の違いで、間違えるとエラーになるので注意してください。

データセット選びの試行錯誤

SFTで最も重要なのはデータセットの質だと実感しました。

なお、このコンペでは使用できるデータセットは運営から指定されたものに限定されています。著作権などの問題から、LLMで生成したデータを学習に使うのはNGです。ただし、スクリプトを書いてルールベースでデータをクリーニング・前処理するのはOK。この制約の中でいかに工夫するかがポイントになります。

指定されたデータセットは大きく2系統、全9種類ありました。全部で35,815行。ここからどれを選び、どう組み合わせるかが腕の見せどころです。

u-10bei系(CoTあり・6種類)

アシスタントの回答に**Chain of Thought(推論ステップ)**──つまり「こう考えました」という思考過程──が含まれるデータセットです。こんな形式ですね。

Approach:
1. Identify the required format...
2. Determine the schema...
...
Output:
{"name": "John", "age": 30}

5ステップの推論過程のあとに構造化データが出力されます。

1-1. u-10bei/structured_data_with_cot_dataset_512_v2 ★標準コードで使用

項目 内容
サイズ 3,930行(trainのみ)
CoT 5ステップ推論
出力形式 Approach → Output(コードのみ、フェンスなし)
フォーマット JSON, XML, YAML, TOML, CSV
タスクタイプ 生成(generation)と変換(conversion)
複雑度 simple, medium, complex の3段階
URL https://huggingface.co/datasets/u-10bei/structured_data_with_cot_dataset_512_v2

1-2. u-10bei/structured_data_with_cot_dataset_512_v4

項目 内容
サイズ 5,760行(train: 4,610 / val: 575 / test: 575)
特徴 最大行数、train/val/test分割あり。メタデータにprompt/outputフィールド追加
URL https://huggingface.co/datasets/u-10bei/structured_data_with_cot_dataset_512_v4

1-3. u-10bei/structured_data_with_cot_dataset_512_v5

項目 内容
サイズ 5,680行(train: 4,550 / val: 568 / test: 568)
特徴 constraintフィールド追加(minified/sorted/null)。random_structured_dataスキーマあり
URL https://huggingface.co/datasets/u-10bei/structured_data_with_cot_dataset_512_v5

1-4. u-10bei/structured_data_with_cot_dataset_512

項目 内容
サイズ 3,445行(trainのみ)
特徴 typeフィールドあり(conversion約60%/generation約40%を明示)。512トークン上限
URL https://huggingface.co/datasets/u-10bei/structured_data_with_cot_dataset_512

1-5. u-10bei/structured_data_with_cot_dataset_v2

項目 内容
サイズ 2,500行(trainのみ)
特徴 typeフィールドなし。v1より長い回答。2.26MB
URL https://huggingface.co/datasets/u-10bei/structured_data_with_cot_dataset_v2

1-6. u-10bei/structured_data_with_cot_dataset(オリジナル)

項目 内容
サイズ 2,500行(trainのみ)
CoT 4ステップ推論(v2より1ステップ少ない)
特徴 最も基本的なバージョン。1.9MB
URL https://huggingface.co/datasets/u-10bei/structured_data_with_cot_dataset

daichira系(CoTなし・クリーン出力・3種類)

こちらはアシスタントの回答が構造化データのみ。余計な文章は一切なし、コードフェンスもなし。プロンプトにフォーマット固有の制約指示が含まれているのが特徴です(例: TOML「Do NOT use inline tables」)。

{"name": "John", "age": 30}

2-1. daichira/structured-3k-mix-sft

項目 内容
サイズ 3,000行(trainのみ)
フォーマット JSON, XML, YAML, TOML, CSV(各600、完全均等)
タスクタイプ extract(抽出)+ transform(変換)の2種類、17サブカテゴリ
特徴 5形式完全均等配分。TOML制約指示あり
URL https://huggingface.co/datasets/daichira/structured-3k-mix-sft

2-2. daichira/structured-5k-mix-sft

項目 内容
サイズ 5,000行(trainのみ)
フォーマット YAML多め(~30%), JSON/XML各20%, TOML/CSV各15%
タスクタイプ extract + transform。toml_to_yaml, toml_to_json等の追加サブカテゴリ
特徴 最大行数。形式配分は不均等(YAML偏重)。TOML制約指示あり
URL https://huggingface.co/datasets/daichira/structured-5k-mix-sft

2-3. daichira/structured-hard-sft-4k

項目 内容
サイズ 4,000行(trainのみ)
フォーマット JSON, XML, YAML, TOML(CSV少なめ)
タスクタイプ transform中心(深いネスト、多様な型の組み合わせ)
特徴 高難度特化。deterministic serialization使用
URL https://huggingface.co/datasets/daichira/structured-hard-sft-4k

データセット全体比較

ID データセット名 行数 CoT 出力スタイル TOML制約 分割
1-1 structured_data_with_cot_dataset_512_v2 3,930 ✅ 5step Approach→Output なし trainのみ
1-2 structured_data_with_cot_dataset_512_v4 5,760 ✅ 5step Approach→Output なし train/val/test
1-3 structured_data_with_cot_dataset_512_v5 5,680 ✅ 5step Approach→Output なし train/val/test
1-4 structured_data_with_cot_dataset_512 3,445 ✅ 5step Approach→Output なし trainのみ
1-5 structured_data_with_cot_dataset_v2 2,500 ✅ 5step Approach→Output なし trainのみ
1-6 structured_data_with_cot_dataset 2,500 ✅ 4step Approach→Output なし trainのみ
2-1 structured-3k-mix-sft 3,000 データのみ ✅ あり trainのみ
2-2 structured-5k-mix-sft 5,000 データのみ ✅ あり trainのみ
2-3 structured-hard-sft-4k 4,000 データのみ ✅ あり trainのみ
合計 35,815

どちらを使うべきか?

コンペのルールブックには「余計な文章を出さない(コードだけ出すのが安全)」と書かれていました。

ここで重要な比較です。

特徴 u-10bei系(CoTあり) daichira系(CoTなし)
出力スタイル 推論 + データ データのみ
ルールブック適合 △(余計な文章あり) ◎(クリーン出力)
読み取りやすさ CoT部分の解析失敗リスク そのまま安全に読み取り可能
TOML品質 制約指示なし 「Do NOT use inline tables」あり

結論から言うと、daichira系のみを使った実験(exp06)がスコア最高でした。CoTの推論ステップは一見賢そうに見えますが、出力に余計な文章が混入するリスクがあり、構造化データの正確性を求めるベンチマークでは不利に働いたのです。

実験の記録 ── 6回の試行錯誤

ここからは実際の実験結果を紹介します。試行錯誤の流れを追ってみてください。

実験1〜3: 学習率を上げれば性能は上がる?

最初の3実験では、同じデータセット構成で**学習率(Learning Rate)**を変えてみました。

学習率って何?
「お手本からどれくらいガツガツ学ぶか」を決める数値です。大きくすると一度にたくさん学ぶけど大雑把になりがち。小さくすると丁寧に学ぶけど時間がかかる。ちょうどいい値を見つけるのが重要です。

実験 学習率 練習中の成績(Val Loss) 本番の成績(スコア) 合格ライン(0.7)
exp01 1e-6(超ゆっくり) 1.3033 0.688 不合格
exp02 2e-6(ゆっくり) 1.0594 0.682 不合格
exp03 2e-5(速め) 0.5693 0.678 不合格

全部不合格。しかも面白い結果が出ました。

Val Loss(Validation Loss)って何?
「練習中の模擬試験の点数」のようなものです。値が小さいほど模試の成績は良い。ただし、模試で高得点を取っても本番の試験(ベンチマーク)で良い点が取れるとは限らないんです。ここがミソでした。

学習率を上げると模試の成績は上がるのに、本番の成績は下がるのです。

普通、模試の成績が良ければ本番もいけると思いますよね? でも違いました。

発見: 模試の成績 ≠ 本番の成績

原因を調べてみると、**速くガツガツ学びすぎて、出力が「過度に簡素化」**されていました。

たとえば「Text to CSV」タスクで10行必要なところ、exp03では1行しか生成しないケースが見つかりました。模試では「フォーマットは合ってる」から点が取れる。でも本番では「必要なデータが足りない」から大幅減点になるわけです。

学習率 1e-6(超ゆっくり)→ 模試 1.30 → 本番 0.688(合格ラインまであと0.012)
学習率 2e-6(ゆっくり)  → 模試 1.06 → 本番 0.682(むしろ下がった…)
学習率 5e-6(ふつう)    → 模試 0.46 → 本番 0.674(さらに下がった…!)
学習率 2e-5(速め)      → 模試 0.57 → 本番 0.678(全然ダメ)

結論: 学習率は1e-6(超ゆっくり)が最適。模試の成績だけ見ていてはダメ。

これは大きな学びでした。「たくさん勉強すれば成績が上がる」わけじゃなく、丁寧に学んだほうが本番に強いんですね。でも学習率をいじっているだけでは合格ライン0.7には届かない。別のアプローチが必要だと気づきました。

実験6: daichira系のみ + MAX_SEQ_LEN拡大 → スコア大幅改善!

最終的に最高スコアを叩き出したのがexp06です。2つの大きな変更を行いました。

変更1: daichira系データセットのみ使用

u-10bei系(CoTあり)を思い切って全部外し、daichira系3つだけで学習しました。合計12,000行。データ量は減りましたが、クリーンな出力だけを学習させるという戦略です。

変更2: MAX_SEQ_LENを512 → 1024に変更

正直なところ、なぜこれが効くのかは完全には理解できていません。ただ、変えてみたら点数が上がりました!

MAX_SEQ_LENって何?
「お手本データを何文字分まで読むか」の上限です。たとえば512だと、お手本が長い場合に途中でバッサリ切られてしまいます。読書感想文のお手本が途中で切れていたら、まともに学べないですよね。

512トークン(約500単語分)では、daichira系の長めの出力が途中で切れてしまい、全体の51.6%がお手本として使い物にならない状態になっていました。

1024に変更したところ、使えないデータが51.6% → 7.4%に大幅改善。有効な学習データ量が一気に増えました。

変更点 exp01 exp06 効果
データセット CoT混在 daichiraのみ 余計な文章混入なし
お手本の読み取り上限 512 1024 使えないデータ 51.6%→7.4%
有効なお手本の数 ~5,973件 9,885件 +65%増
本番スコア 0.688(不合格) 0.737(合格!) +7.2%改善

学習率は1e-6のまま。データの質と量を改善しただけで、スコアが0.688 → 0.737と大きく伸び、ついに合格ラインの0.7を突破しました!

最終結果と学んだこと

6回の実験を通じて得た知見をまとめます。

全実験結果の比較

実験 データセット 学習の速さ 模試の成績 本番スコア 判定
exp01 u-10bei×3 + daichira×1 超ゆっくり 1.3033 0.688 不合格
exp02 +hard追加(5つ) ゆっくり 1.0594 0.682 不合格
exp03 同上 速め 0.5693 0.678 不合格
exp04 重複除去+フィルタ ふつう 0.4631 0.684 不合格
exp06 daichira×3のみ 超ゆっくり 0.7057 0.737 合格!

3つの重要な学び

1. データセットの質 > 量

データ量を増やすことよりも、タスクに適した「クリーンな」データを使うことのほうが効果的でした。CoT(推論ステップ)付きのデータは一見リッチに見えますが、構造化データの正確な出力が求められるタスクでは、余計な文章が混入するリスクになりました。

2. 模試の成績だけ見ていてはダメ

練習中の模試(Val Loss)の成績が良くても、本番(ベンチマーク)の成績が逆に下がることがあります。ガツガツ学びすぎると出力が簡素化されて、必要なデータが欠落してしまうのが原因でした。最終的な本番の評価でモデルを判断することが大切です。

3. お手本の読み取り上限(MAX_SEQ_LEN)はデータに合わせて設定する

デフォルトの512トークンでは、お手本データの半分以上が途中で切れて使い物にならなくなっていました。お手本の実際の長さを確認して、上限を適切に設定するだけで、有効データ量が65%も増加しました。これは見落としがちですが、影響が非常に大きい設定値です。

まとめ

LLMの微調整は初めてでしたが、実験を繰り返すことで少しずつコツが掴めてきました。

特に印象的だったのは、直感に反する結果が出ることです。「たくさん勉強すれば成績は上がるはず」「お手本は多いほど良いはず」── こういった思い込みが、実験で覆されました。

コンペはまだ続いているので、引き続き改善を重ねていきます。この記事が、同じようにLLMの微調整に初めて挑戦する方の参考になれば幸いです。


この記事は、松尾研LLM講座のコンペティションに参加した際の備忘録です。
後編: LLMファインチューニング5つの失敗と逆転の一手

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?