Text Generation
Safetensors
Japanese
japanese
discord
from-scratch

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 コードブロックの囲み

組み立ての規則

  1. 話者トークンと本文を交互に、区切り文字なしで繋げる。空白も改行も入れません
  2. 同じ人が続けて喋るときは、そのつどトークンを置きます (<|a|>今日ひま?<|a|>あ、やっぱいいや)
  3. 返信は <話者><|re|><返信先>本文 の順。自分への返信には付けません
  4. チャンネルは <|conv|>。学習データは 100% がこれで始まるので、省くと分布の外になります

続きを書かせたい人のトークンを末尾に置くと、その人の発言として続きます:

<|c2|><|conv|><|a|>日本の首都どこ<|hi|><|s0|>

使い方

model.pytok.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
-
Safetensors
Model size
25.8M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Datasets used to train tako080614/evex-5.3