evex-3
ある Discord サーバーの過去ログ 94万件だけで、tokenizer からゼロで作った 15.74M パラメータの言語モデル。事前学習済みの重みは1つも使っていない。
同じ系統の兄弟 — evex-1 / evex-2(どちらも 5.87M)。 Qwen3-0.6B に同じログを追加学習した別系統もある (evex-ft-1 / ft-2 / ft-3)。
⚠ 147人の話者が重みに入っている
<|s0|> から <|s146|> は実在の人ひとりひとりに対応する。
つまりこのモデルは特定の人になりきって書ける。
- 出力をその人が書いたものとして扱ってはいけない
- 学習データは同意を明示的に取っていない会話。ログそのものと Discord の
user ID・表示名は公開していない(
speakers.jsonは順位と発言数だけ) - evex-1 は 48人だった。147人に増やしたので、なりきれる範囲が広がっている
evex-2 から変えたこと
evex-2 のコーパスには、evex-ft-2 / ft-3 で測って効いた工夫がひとつも入っていなかった。 あちらは全部コーパス側の話なので、記号に翻訳してそのまま持ち込んだ。
| evex-2 | evex-3 | |
|---|---|---|
| 話者 | 48人(発言の 85.3%) | 147人(96.6%) |
| 返信 | <|re|> の真偽だけ(相手を捨てていた) |
<|re|><|sM|> で相手も |
| 窓 | 15分 / 20件 / 1200字 | 60分 / 60件 / 3600字 × 3通りの切り方 |
| 噛み合いの重み付け | なし | train の 12.5% |
| 長い発言の重み付け | なし | train の 24.5% |
| context | 512 | 1024 |
| 学習トークン | 6,479,221 | 23,950,617(3.70倍) |
| パラメータ | 5,865,984 | 15,735,168 |
返信先を残したのが一番大きい。 113,001 件の返信のうち 94,729 件で相手が分かる。 賑やかなチャンネルでは「誰に答えたか」が噛み合いの信号そのもので、evex-2 は そこを捨てていた。
窓の切り方を3通りにして 2.08 倍に増やしてある。新しい情報は増えない (同じ発言が違う位置・違う文脈長で出るだけ)ので、1 epoch が evex-2 の約2 epoch にあたる。開始位置をずらす方式は効かなかった(36,647 窓のうち 35,858 が同じ本文。 窓は主に沈黙時間で切れるので、読み始める位置を変えても境界が動かない)。
効果と代償(実測)
同じコーパスで 5.87M も回してある(evex-2 と同じ形)。良くなった分がコーパス由来か 大きさ由来かを分けるため。このモデル自身の val を同じ尺度で測った値を地の値にする。
匿名の役 <|b|>(bot の既定経路 / n=60)
| 対照 5.87M | evex-3 15.74M | 地の値 | |
|---|---|---|---|
| 噛み合い(質問の語が返答に出る) | 38.3% | 41.7% | — |
| 20字以上で返す | 65.0% | 58.3% | 31.2% |
| 平均の長さ | 39字 | 29字 | 19字 |
| 未知語 | 19.6% | 9.8% | 14.9% |
| 逐語コピー | 3.3% | 10.0% | — |
| 敬体 | 3.3% | 5.0% | 3.0% |
名前持ち <|s0|>(n=60): 噛み合い 23.3% / 20字以上 53.3% / 平均 35字 /
未知語 6.6% / 逐語コピー 15.0%
**記号だけの返答は 0%**(evex-2 と同じ。<url> や <file> を損失から外してある)。
実際に出てくるもの(samples.txt に epoch 5 のぶんが入っている):
<|s0|>rebase 疲れた<|s3|> → <|re|><|s5|>codexのレートリミット来たから
codexに課金してclaude codeのレートリミット来たから
<|a|>これバグってる?<|a|> → いやこれ普通にtry catchするからtry catchでエラー起きそうで
<|a|>TCP と UDP どっちが速いん<|b|> → TCPとかSPAの関係で外部環境を動かそうとしたら
Chromebookが起動しなくて良いよ...
返信先を自分で書く。<|re|><|s5|> のように「誰に返しているか」を出力に含める。
読む側は <|re|> と続く話者トークンを落としてから本文を取ること
(相手のトークンを「次の話者」と読むと本文が丸ごと落ちる)。
大きくした効果はほとんど無かった
噛み合いは +3.4pt だが、逐語コピーが 3 倍になった。24M トークンに対する Chinchilla 最適は 1.21M パラメータなので、5.87M で既に 5 倍・15.74M で 13 倍過剰。 効いたのはコーパスであってサイズではない。
参考: 同じ質問・同じ匿名の役で Qwen3-0.6B 追加学習の系統は ft-1 20% / ft-2 34% / ft-3 30%。 38倍小さいこのモデルの方が噛み合っている(ただし事実の正しさは別の話で、 どちらも当てにならない)。
数字
| パラメータ | 15,735,168(埋め込み 1.57M + 8層) |
| 形 | d_model 384 / 8層 / 6 head / context 1024 / RoPE + RMSNorm + SwiGLU + weight tying |
| 語彙 | 4,096(ログから作った sentencepiece BPE) |
| 学習トークン | 23,950,617 / 145,202 会話 |
| 学習 | 5 epoch / lr 1e-3 / cosine / AdamW / fp16 + fp32 master |
| val loss | 4.0741(記号を外した尺度)/ 素の val 4.1557 |
| ハードウェア | Kaggle T4 / 54,556 tok/s / 約 37 分 |
epoch ごとの val: 4.4115 → 4.1712 → 4.0927 → 4.0769 → 4.0741。 epoch 3 は逐語コピーが 11.7% と低いが、既定経路の噛み合いが 31.7% しか無いので epoch 5 を採った。
val は evex-1 / evex-2 と直接比べられない。コーパスも tokenizer も違う。 比べられるのは同じ v4 で回した 5.87M との間だけ。
コーパス v4
外部データは使っていない。全部このサーバーのログ。
| 読んだ発言 | 945,009(うち bot 357,376 を除外) |
| 会話 | 18,806 → 窓の切り方3通りで 38,106(同じ本文 17,373 は落とした) |
| 噛み合った箇所 | 23,414 を ×2 |
| 長い発言(40字以上) | 30,134 を ×2 |
| 語彙 | 4,096(制御12 + 話者147 + 役8 + バイト等259 / 2.29 文字per token) |
| val | 最後の14日 436 会話。train と本文が同じものは外してある |
切り出しの割合は evex-ft-2 / ft-3 で測って効いた値(12.3% / 25.6%)に合わせて 周回数を自動で決めている。多すぎると窓の中身が3〜4行の短い会話ばかりになる。
使い方
transformers では動かない。素の PyTorch で読む。
import json, torch, sentencepiece as spm
from safetensors.torch import load_file
from huggingface_hub import snapshot_download
path = snapshot_download("tako080614/evex-3")
cfg = json.load(open(f"{path}/config.json"))
sp = spm.SentencePieceProcessor(model_file=f"{path}/tok.model")
state = load_file(f"{path}/model.safetensors")
state["head.weight"] = state["embed.weight"] # tie_word_embeddings
# model.py は https://github.com/tako0614/sakana の scripts/llm/model.py
from model import Config, MicroLM
model = MicroLM(Config(**{k: cfg[k] for k in
["vocab_size", "n_layers", "d_model", "n_heads", "context", "dropout", "attn_dropout"]}))
model.load_state_dict(state)
model.eval()
prompt = "<|conv|><|a|>これバグってる?<|s0|>"
ids = torch.tensor([sp.encode(prompt, out_type=int)])
out = model.generate(ids, max_new_tokens=80, temperature=0.9, top_k=40,
stop_id=sp.piece_to_id("<|end|>"))
print(sp.decode(out[0].tolist())[len(prompt):])
プロンプトの形
<|conv|><|s0|>これバグってる?<|s3|><|re|><|s0|>どこで止まってる?<|s0|>
↑147人の誰か ↑別の人 ↑<|s0|> への返信 ↑ここの続きを書かせる
- 話者は
<|s0|>..<|s146|>(固有)と<|a|>..<|h|>/<|z|>(会話ごとの相対) - 名前を持たない人だけに相対の役を振る。混ぜ方を学習と変えてはいけない
- 返信は
<|re|>のあとに相手のトークンを置く。相手が会話に居ないときは<|re|>だけにする(学習でもそうしてある) <|end|>で会話が終わる。<url><file><mention><channel><time>は 正規化が作った記号で、損失から外してあるので自分では書かない
できること / できないこと
できる: このサーバーの口調、147人の読み分け、会話の交代
できない: 事実、計算、翻訳、指示に従うこと
15.74M しか無いので内容は当てにならない。狙えるのは「話を受けて、それらしい形で、 このサーバーの口調で返す」まで。
ライセンス
重みは MIT。学習データは非公開の私的な会話なので、上の「147人の話者が重みに 入っている」を必ず読むこと。
- Downloads last month
- 122