MiniMind — 小さな言語モデルを最初から最後まで自分で学習する
MiniMind の学習ループで300ステップ事前学習し、損失曲線を読む
目標
MiniMindのtrain_pretrain.pyの骨組み(AdamW・MiniMindのコサイン学習率・勾配クリッピング1.0)をCPUの大きさに縮小した学習ループを自分で書き、シードを固定して再現されるかを確認してから、300学習ステップを学習します。損失曲線を数字で読み、学習率が大きすぎると何が壊れるかを見ます。
なぜ重要なのか
学習ループの核心は2行ですが、結果を分けるのは、その周りの決定です。学習率とそのスケジュール、勾配クリッピング、シード、検証損失を測る間隔です。この決定が間違っていると、数時間回したあとでようやく気づきます。そのため、数分の学習で、曲線の形を先に確認する習慣が必要です。 損失曲線は、モデルが何を先に学ぶのかも見せてくれます。最初の数十学習ステップではトークンの頻度を、そのあとは文脈に応じた規則を学びます。文脈なしで頻度だけを知っているモデルの損失(ユニグラムエントロピー)と比べれば、曲線のどの部分が「文脈を使いはじめた」区間なのかがわかります。
ステップ
- スクリプト(/root/mm/pre/train.py)を書き(引数は
--steps・--lr・--seed・--eval-every・--out・--log)、同じシードで20学習ステップを2回回して、記録を残してください(出力先: /root/mm/pre/log_a.csv・/root/mm/pre/log_b.csv)。 - 300学習ステップ(バッチ8・長さ128・lr 3e-3)を学習して、重みと記録を残してください(重み: /root/mm/pre/ckpt.pth、記録: /root/mm/pre/log.csv)。検証損失が1.6を下回る必要があります。
log.csvから曲線の形を取り出して書いてください(出力先: /root/mm/pre/curve.json)。- 学習配列のユニグラムエントロピーを書いてください(出力先: /root/mm/pre/unigram.json)。
- lr 0.05で50学習ステップ回して、記録を残してください(出力先: /root/mm/pre/log_high.csv)。
ckpt.pthを使い、韓国語の文字列「ガラム村の」の後ろを、16トークンぶんグリーディ生成で書き続けさせて、保存してください(出力先: /root/mm/pre/sample.txt)。## 손실 곡선## 학습률## 이어 쓰기の3つのセクションを書き、最後の検証損失とユニグラムエントロピーを数字で入れてください(出力先: /root/mm/pre/report.md)。韓国語の見出しは、順に「損失曲線」「学習率」「続きを書く」という意味です。
参考
- 記録の列は
step,train_loss,val_loss,lrです。検証損失を測っていない行は、val_lossを空にしておきます。検証損失は、mmkit.lm_loss(model, val, n_batches=4)で測れば、採点ツールと同じ方法です。 - 学習率は、
mmkit.minimind_lr(step, 전체 걸음, lr)が、MiniMindのget_lrと同じ式を返してくれます(プレースホルダーは全学習ステップ数です)。学習ステップ数は1から数えます。 - 300学習ステップは、ノードで20秒前後です(Podに割り当てられたCPUは2個です)。Podが混んでいるノードに起動すると、数倍かかることがあります。
- よくある間違い: シードをモデルの初期化にだけ設定して、バッチの選択には設定しないこと、
model.eval()に切り替えて検証したあとmodel.train()に戻さないこと、torch.save(model)のようにモデルのオブジェクトごと保存すること(state_dict()を保存してください)。 - 原典: train_pretrain.py · trainer_utils.py · PyTorch AdamW · clip_grad_norm_
同じシードなら同じ損失になる
スクリプト(/root/mm/pre/train.py)を書いてください。/opt/mm/ref/config.jsonでモデルを作り、/opt/mm/ref/train.npyから任意の位置の128トークンをバッチ8個ずつ取り出して学習し、学習ステップごとにstep,train_loss,val_loss,lrを、--logのパスのCSVに記録します。--steps 20で2回回して、2つの記録を作ってください(出力先: /root/mm/pre/log_a.csv・/root/mm/pre/log_b.csv)。
再現するには、2種類の乱数をどちらも固定する必要があります。重みの初期化(mmkit.seed_all(시드)を、モデルを作る前に)と、バッチの選択(torch.Generator().manual_seed(시드)をrandintに渡す)です(プレースホルダーは、どちらもシードです)。最初の学習ステップの損失は、ln 1024 ≈ 6.93の近くになる必要があります。
300学習ステップの事前学習
train.pyで300学習ステップ(バッチ8・長さ128・lr 3e-3・シード42、50学習ステップごとに検証)を学習してください(重み(model.state_dict())の出力先: /root/mm/pre/ckpt.pth、記録の出力先: /root/mm/pre/log.csv)。
学習率は、MiniMindのコサイン(mmkit.minimind_lr)で、最初の学習ステップに3e-3、最後に3e-4になる必要があります。採点ツールは、ckpt.pthを読み込んで検証損失を測り直し、1.6を下回っているか、そして記録の最後のval_lossと同じかを見ます。
曲線の形を数字で取り出す
log.csvから、最初の損失(first_loss)、学習損失が初めて3を下回った学習ステップ(step_below_3)、最後の学習損失(final_train)、最後の検証損失と最低の検証損失(final_val・best_val)を書いてください(出力先: /root/mm/pre/curve.json)。
急に下がる区間が何学習ステップで終わるかを見てください。そのあとは、同じ量だけ下げるのに、はるかに多くの学習ステップが必要です。
頻度だけを知るモデルの損失を計算する
/opt/mm/ref/train.npyのトークン頻度で、ユニグラムエントロピー(−Σ p·ln p、ナット)を計算し、unigram_entropyとして書いてください(出力先: /root/mm/pre/unigram.json)。
文脈をまったく見ず、いつも同じ分布(トークン頻度)を出すモデルのクロスエントロピーが、この値です。曲線がこの値より下に下がれば、モデルが前のトークンを見て、次を絞り込みはじめたという意味です。numpy.bincountが速いです。
学習率が大きすぎると何が起きるかを見る
train.pyを--lr 0.05 --steps 50で回して、記録を残してください(出力先: /root/mm/pre/log_high.csv)。同じシードなので、ステップ2の記録の41–50学習ステップと比べられます。
大きな学習率は、最初の数学習ステップは速く下がるように見えても、一度大きく跳ねたあと、ある値に張りついて下がってきません。勾配クリッピング(1.0)も、1学習ステップの大きさまでは止められません。1学習ステップの大きさは、lrが決めます。
学習したもので続きを書かせる
ckpt.pthを読み込み、[bos] + '가람 마을의'(韓国語で「ガラム村の」を意味する語です)の後ろを、16トークン、グリーディ(do_sample=False, top_k=0, top_p=1.0, eos_token_id=2)で書き続けさせ、bosを除いた全文を保存してください(出力先: /root/mm/pre/sample.txt)。
mmkit.load_model(경로)で読み込み(プレースホルダーはパスです)、MiniMindのmodel.generateを呼べばよいです。グリーディ生成は、同じ重みなら常に同じ文章を出すので、採点ツールが、作成したckpt.pthで再生成して比べます。事前学習だけをしたモデルは、質問に答えず、コーパスのように続きを書きます。
曲線を読んだ記録を書く
## 손실 곡선 ## 학습률 ## 이어 쓰기の3つのセクションを書き、ステップ3のfinal_valとステップ4のunigram_entropyを数字で入れてください(出力先: /root/mm/pre/report.md)。韓国語の見出しは、順に「損失曲線」「学習率」「続きを書く」という意味です。
曲線のどの区間が頻度を、どの区間が文脈を学んだものなのかを、ユニグラムエントロピーを基準に1行書いてください。学習率のセクションには、ステップ5で見たことを書きます。