evex-2 / README.md
tako080614's picture
README: 話者トークンと表示名の対応を公開したことを書く
820d0c8 verified
|
Raw
History Blame Contribute Delete
9.35 kB
---
language:
- ja
license: mit
library_name: pytorch
pipeline_tag: text-generation
tags:
- causal-lm
- japanese
- from-scratch
- tiny
---
# evex-2
ある Discord サーバーの過去ログ **だけ** をゼロから学習した 5.87M パラメータの言語モデル。
既存モデルからの継続学習ではなく、tokenizer も含めて全部そのログから作った。
**このモデルにとって世界はそのログだけで、一般知識は一切持っていない。**
[evex-1](https://huggingface.co/tako080614/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 を持って
いるならそちらを使い回せる。
```bash
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 あるか確かめる。
```python
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<br>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 は公開していない**
- 学習に使ったログそのものは公開していない
- 表示名で名指しできる形になっているので、**特定の人が書いたものとして引用してはいけない**