evex-2
ある Discord サーバーの過去ログ だけ をゼロから学習した 5.87M パラメータの言語モデル。 既存モデルからの継続学習ではなく、tokenizer も含めて全部そのログから作った。 このモデルにとって世界はそのログだけで、一般知識は一切持っていない。
evex-1 の第2世代。コーパスも語彙も構成も 同じで、変えたのは2つだけ。
evex-1 から変えたこと
1. 学習率 3e-4 → 1e-3
evex-1 は 10 epoch 通して val loss が単調に下がり続けていた = 収束していなかった。 上げて 12 epoch 回したら val 4.2404 → 4.0384 (同じ尺度で測った値)。
2. 正規化記号を損失から外した
evex-1 の一番大きい問題は、返答の 38.5% が <url> や <file> だけだったこと。
正規化で作った記号なので発言ではなく、bot は画像もリンクも実際には出せない。
原因は密度ではなく1トークンであること。添付だけの発言 (コーパスの 3.6%) が <file>
1個に潰れていたので、発言の先頭で確率が1点に集中していた。
そこで <file> <url> <mention> <channel> <time> を損失から外して学習した。
文脈からは消していない — 「誰かが画像を貼って、他の人が反応する」流れは学ばせたいので、
入力には残したまま、その位置の予測だけ評価しない。
<nl> と <code> は外していない。改行もコードブロックもモデルが書いて良いもの。
効果
同じチェックポイント同士を同じ尺度で測り直した結果 (12 epoch / 96 生成 / プロンプトと乱数を固定):
| 素 val | マスク val | 記号だけの返答 | |
|---|---|---|---|
| マスクなし | 4.0384 | 4.0465 | 38.5% |
| マスクあり (これ) | 4.0970 | 4.0330 | 0.0% |
- 素 val …
<url><file>も予測対象に含める尺度 - マスク val … 記号を損失から外す尺度 = 実際に読まれる語だけの尺度
素の尺度で 0.059 負けているのは、その尺度が <url> を当てることを点数にしている
から。出してほしくないトークンなので、そこで負けるのは払って良いコスト。実際に読まれる
語だけで測れば勝っていて、記号だけの返答は消えた。
推論時に記号を禁止する必要がなくなった。 evex-1 では ban_ids で潰して 38% → 12%
に抑えるしかなかったが、あれは高確率のトークンを削って再正規化するので出てくる第二候補が
歪む。損失から外せば確率の質量が最初から実際の語に乗る。
evex-1 のカードに載せた seed をそのまま引き直すと、記号だけだった3本が全部発言になる
(プロンプトは <|conv|><|s3|>これバグってる?<|other|>)。
| seed | evex-1 | evex-2 |
|---|---|---|
| 1000 | これでいいです |
それでも、自分のこと言えば、それは正しいかも |
| 1001 | <url> |
なんかあれはcodexがバグってる |
| 1002 | おしえて / おk / <url> |
おしえて / おk / お疲れ様です |
| 7 | <file><file><file> |
いやー / あーそれはそっか |
中身が正しくなったわけではない。 5.87M なので固有名詞は形だけ真似て、長く出させれば 崩れる。消えたのは「発言ですらないもの」だけ。
使い方
transformers の Auto クラスには当てはまらない構成なので、同梱の modeling_evex.py を使う。
tokenizer は evex-1 と同一 (tok.model の md5 が一致) なので、既に evex-1 を持って
いるならそちらを使い回せる。
pip install torch safetensors sentencepiece huggingface_hub
hf download tako080614/evex-2 --local-dir evex-2
git clone で取るなら git-lfs が必要。入れずに clone すると model.safetensors が
133 バイトのポインタになり、読み込もうとしても壊れる。ls -la して 22MB あるか確かめる。
import json, sys, torch, sentencepiece as spm
from safetensors.torch import load_file
sys.path.insert(0, "evex-2")
from modeling_evex import Config, MicroLM
cfg_json = json.load(open("evex-2/config.json"))
cfg = Config(
vocab_size=cfg_json["vocab_size"], n_layers=cfg_json["n_layers"],
d_model=cfg_json["d_model"], n_heads=cfg_json["n_heads"],
context=cfg_json["context"], dropout=0.0, attn_dropout=0.0,
)
model = MicroLM(cfg)
state = load_file("evex-2/model.safetensors")
state["head.weight"] = state["embed.weight"] # weight tying を結び直す
model.load_state_dict(state)
model.eval()
sp = spm.SentencePieceProcessor(model_file="evex-2/tok.model")
end_id = sp.piece_to_id("<|end|>")
prompt = "<|conv|><|s3|>これバグってる?<|other|>"
ids = torch.tensor([sp.encode(prompt, out_type=int)])
out = model.generate(ids, max_new_tokens=60, temperature=0.9, top_k=40, stop_id=end_id)
print(sp.decode(out[0].tolist()))
head.weight は入っていない。weight tying で embed.weight と同じテンソルを指しており、
safetensors はストレージを共有したテンソルを保存できないので落としてある。上のように結び直す。
ban_ids は要らない。渡しても害は無いが、記号だけの返答は既に出ない。
プロンプトの形
学習データと同じ直列化でないと、モデルは一度も見ていない形を受け取って崩れる。
<|conv|><|s3|>今日ひま?<|s7|><|re|>ひま<|end|>
| トークン | 意味 |
|---|---|
<|conv|> |
会話の開始 |
<|end|> |
会話の終了 |
<|s0|>〜<|s47|> |
話者。発言数の多い上位48人 (人間の発言の 85.3% を被覆) |
<|other|> |
それ以外の 2,599 人 |
<|re|> |
直前の誰かへの返信 |
<nl> |
発言内の改行 |
<url> <mention> <channel> <time> <file> |
正規化した URL / メンション / チャンネル / 時刻 / 添付。このモデルはこれらを出さない |
<code> </code> |
コードブロック |
末尾に話者トークンを置くと、その話者として続きを書く。speakers.json に各話者の
発言数と表示名が入っている。
Discord の user ID は入れていない (モデルの動作に要らず、実アカウントへの手がかりになる)。
同じ話者トークンが続けて出ることがある。 学習データでは話者の塊のうち 27.4% が 2連続以上なので、モデルもそう書く。1発言だけ欲しいなら最初の話者トークンで切る。
絵文字・草・www・顔文字は正規化せず残してあるので、そのまま出る。
数字
| evex-2 | evex-1 | |
|---|---|---|
| パラメータ | 5,868,800 | 5,868,800 |
| 学習トークン | 6,685,152 | 6,685,152 |
| 語彙 | 4,096 (SentencePiece BPE / byte fallback) | 同じ |
| context | 512 | 512 |
| 構成 | decoder-only / 6 層 / d_model 256 / 4 head / d_ff 704 RoPE + RMSNorm + SwiGLU + weight tying |
同じ |
| 学習 | 12 epoch / lr 1e-3 / AdamW / cosine / CPU のみ | 10 epoch / lr 3e-4 |
| 損失マスク | <file> <url> <mention> <channel> <time> |
なし |
| train / val loss | 3.5535 / 4.0559 (マスク尺度) | 3.8685 / 4.2404 (素) |
| 記号だけの返答 | 0.0% | 38.5% |
データは足りていない。 Chinchilla 最適 (20 トークン/パラメータ) はこの規模だと 33万パラメータで、5.87M は最適の 6%。val は 12 epoch でもまだ下がり続けているので、 学習の余地は残っている。
できること / できないこと
できる: チャットの口調、短い応答、ネットスラング、そのサーバー特有の語彙と話題、 話者ごとの癖 (ある話者は「〜にゃい」と書くが、それを再現する)
できない: 一般常識、推論、数学、コード生成、長い整合性、知らない話題への応答
固有名詞は形だけ真似て中身が合わない。Cloudflare Codex AGP Core MT6589 のように、
見たことのある語を組み替えたものが出る。事実として読むものではない。
出どころと制限
学習データは同意を明示的に取っていない実在の人物の会話。 逐語での再生は 20 文字以上の完全一致で 0 箇所だったが、669万トークンを12周している ので実際の発言に近いものが出る可能性は残る。
- 生成物を事実として扱ってはいけない
- 特定の人物の発言として扱ってはいけない
- 話者トークンと表示名の対応は
speakers.jsonで公開している。Discord の user ID は公開していない - 学習に使ったログそのものは公開していない
- 表示名で名指しできる形になっているので、特定の人が書いたものとして引用してはいけない
- Downloads last month
- 380