evex-5.3
ある Discord サーバーのログだけからゼロから学習した日本語の会話モデル。 既存の重みからの派生ではなく、tokenizer も含めて全部このサーバーのために作ってある。
- パラメータ 25,763,264 (8 層 / d_model 384 / 6 head / context 1024)
- 語彙 12,288 (sentencepiece BPE / byte_fallback)
- decoder-only transformer (RoPE + RMSNorm + SwiGLU) / weight tying
- PLE (Per-Layer Embeddings, d_ple=64) — Gemma 3n と同じ、トークンごと・層ごとの補助ベクトル。行列積ではなく引き表なので、容量は +36% なのに 1 トークンあたりの計算量は +3% しか増えません (CPU 推論で行列積律速なモデルには効きます)
- QK-norm — q/k を RoPE の前に RMSNorm
- val loss 6.9018 (epoch 8)
⚠ プロンプトの形が独特です
この節を読まずに使うと、まともな出力は出ません。普通の instruct モデルでも
chat template でもなく、記号だけで会話の構造を表す独自の形です。
<|conv|> から始めて <|end|> で終わる 1 行が 1 会話になります。
<|c2|><|conv|><|s0|>これバグってる?<|hi|><|s3|><|re|><|s0|>どこで止まってる?<|end|>
トークンの意味
| トークン | 個数 | 意味 |
|---|---|---|
| `< | conv | >` |
| `< | end | >` |
| `< | c0 | >..< |
| `< | cx | >` |
| `< | s0 | >..< |
| `< | a | >..< |
| `< | z | >` |
| `< | re | >` |
| `< | hi | >` |
<nl> |
1 | 発言の中の改行 |
<url> <file> <mention> <channel> <time> |
5 | 正規化した中身。学習で損失から外してあるので、モデルは自分では書きません |
<code> </code> |
2 | コードブロックの囲み |
組み立ての規則
- 話者トークンと本文を交互に、区切り文字なしで繋げる。空白も改行も入れません
- 同じ人が続けて喋るときは、そのつどトークンを置きます (
<|a|>今日ひま?<|a|>あ、やっぱいいや) - 返信は
<話者><|re|><返信先>本文の順。自分への返信には付けません - チャンネルは
<|conv|>の前。学習データは 100% がこれで始まるので、省くと分布の外になります
続きを書かせたい人のトークンを末尾に置くと、その人の発言として続きます:
<|c2|><|conv|><|a|>日本の首都どこ<|hi|><|s0|>
使い方
model.py と tok.model がこのリポジトリに入っています。
import json
import torch
import sentencepiece as spm
from safetensors.torch import load_file
from model import Config, MicroLM # このリポジトリの model.py
cfg = Config(**{k: v for k, v in json.load(open('config.json')).items()
if k in Config.__dataclass_fields__})
model = MicroLM(cfg)
state = load_file('model.safetensors')
state['head.weight'] = state['embed.weight'] # weight tying。head は保存していない
model.load_state_dict(state)
model.eval()
sp = spm.SentencePieceProcessor(model_file='tok.model')
prompt = '<|c2|><|conv|><|a|>日本の首都どこ<|hi|><|s0|>'
ids = torch.tensor([sp.encode(prompt, out_type=int)])
got = model.generate(ids, max_new_tokens=80, temperature=0.9, top_k=40,
stop_id=sp.piece_to_id('<|end|>'))
print(sp.decode(got[0].tolist())[len(prompt):])
生成は <|end|> か、次の話者トークンが出たところで切ってください。
<|hi|> <|conv|> <|cN|> は生成から禁止してください。プロンプト側が置くもので、本文として出るものではありません (禁止しないと 草<|hi|> のように漏れます)。
切らずに流すと、他の人の発言まで作り続けます。
学習
多段で学習しています。外部データで日本語と会話の土台を作り、最後はこのサーバーのログだけで仕上げるという順序です。
| 段 | 中身 | 量 |
|---|---|---|
| 段1 | 外部の会話 + このサーバーの素の会話 (evex 30.0%) | 601,794 会話 / 489,523,351 字 |
| 段2 | このサーバーだけ (窓の水増しと切り出し込み) | 1,314,843 会話 / 238,803,759 字 |
段1 は evex-5.2 のものをそのまま使っています。語彙も同じなので、5.2 との違いは 段2 のデータと損失の当て方だけです。初期値・形・最適化 (AdamW + cosine)・ 学習率 (4.2e-4 / batch 24)・epoch 数 (8) まで揃えてあります。
この世代 (5.3) で変えたところ
指名した人らしさ (/as) を強くするために、話者ごとの切り出しを 2.9 倍にしました。
5.2 は「1人あたり 2,500 本まで」という上限を掛けていたので、
- 発言の多い人 (55,964 件) は 2,500 本で頭打ち = 手持ちの 4.5% しか使っていない
- 発言の少ない人 (203 件) は 4 倍の 812 本止まり
という、多い人は絞られ、少ない人は伸びていない形でした。上限を外し、少ない人だけ 繰り返しで底上げする (8 倍まで) ように変えています。
| 5.2 | 5.3 | |
|---|---|---|
| 話者ごとの切り出し | 296,324 本 / 38.4M字 | 867,353 本 / 86.0M字 |
| 発言の少ない人 (203 件) | 812 本 | 1,624 本 |
| 発言の多い人 (55,964 件) | 2,500 本 | 55,964 本 |
あわせて、切り出しの中では「その人の発言」だけを損失に入れています。切り出しは
[前の2発言] + [その人の発言] の形なので、そのままだと勾配の 2/3 が他人の話し方を
学ぶのに使われます。対象の発言だけ残すと、同じ本数で勾配が約 3 倍濃くなります
(段2 全体では 78.2% のトークンが損失に入ります)。
効果の実測
val にある本物の発言を、本人の話者トークンで条件付けたときと他人のトークンのときで loss を比べ、「本人の方が低い」割合を数えました (368 件 / 偶然なら 50%)。 生成を挟まないので決定的に測れます。
| evex-5.2 | evex-5.3 | |
|---|---|---|
| 全体 | 72.3% (8.5σ) | 75.0% (9.6σ) |
| 発言の多い上位 | 75.8% | 78.8% |
| 発言の少ない下位 | 65.6% (3.5σ) | 68.0% (4.1σ) |
| 逐語コピー (20字以上) | 0.0% | 0.0% |
上位・下位・全体すべてが同じ向きに動いています。繰り返しを 8 倍 (8 epoch で最大 64 回) にしても逐語コピーは 0.0% のままで、丸暗記には至っていません。
一方で話題の噛み合いは 30.0% → 25.0% に下がっています。素の会話の窓が段2 に占める 割合が 48.7% → 39.2% に落ちたぶんだと思われます (絶対量は据え置き)。 一問一答よりも「その人らしさ」に寄せた世代です。
話者トークンは発言数の多い順に 147 人ぶん (全発言の 96.6% を被覆)。 件数は 最多 55,964 / 最少 203 で、275 倍の開きがあります。 順位が小さいほどよく学習されています。
出典とライセンス
段1 に使った外部データ:
| データ | ライセンス |
|---|---|
| OmniAICreator/Japanese-Roleplay-Dialogues | Apache-2.0 |
| nntsuzu/JESC | CC-BY-4.0 |
| p1atdev/open2ch (元: 1never/open2ch-dialogue-corpus) | Apache-2.0 |
注意
- 話者トークン
<|sN|>は匿名の順位で、userId は含まれていません。ただし発言の癖は重みに入っているので、特定の順位で生成した文がその人の書き方に似ることはあります - 表示名は tokenizer の語彙に残っています。
tok.modelはこのサーバーのログから学習しているので、会話の中でよく呼ばれる名前はBPE が 1 トークンにまとめます。実測で 147 人中 16 人 (上位20人では 7 人) の表示名がそのまま語彙にあり、列挙すれば読めます。<|sN|>の番号と名前の対応は含まれていませんが、「このサーバーによく居る人の呼び名」は分かります - 学習元は特定のサーバーの雑談なので、俗語と内輪の話題に強く偏っています。汎用の日本語モデルではありません
- 出力の正しさは保証しません。事実確認の用途には使えません
- 有害表現のフィルタは掛けていません。学習データにも推論にもありません。 以前は外部データにだけ 12 語の正規表現を掛けていましたが、実測すると外部は 0.09% を除去して残り 0%、一方でこのサーバー自身のログはフィルタ無しで 0.59% が残るという逆立ちした状態でした。学習の重心はサーバー側 (計算量の 80%) なので実質的に何も守れておらず、「機械的に除去している」という説明だけが誤解を招くため廃止しました
- Downloads last month
- -