DDOMAIN·LAB

领域分布小模型 v3

从零训练的中文小语言模型:一边猜下一个字,一边给整段文字判领域。

这是一个 3,335 万参数的 decoder-only 语言模型,从随机初始化开始,在 3.56 亿字中文行业语料上训练。它和普通语言模型只差一个出口:读完最后一个字时,从同一份隐状态里再吐出一个 31 维的领域分布。

两个出口一起训练,用的时候只取领域那路。它没有做任何指令微调,也不是聊天模型——你给它一段文字,它只回答「这段字像哪个行业」,以及「其他行业的可能性各有多大」。

能做什么,不能做什么

可以:给几百字以上的中文段落判行业,用来做语料打标、领域配比统计、数据集体检。

不可以:判断二三十字的短句(见下面的长度曲线,30 字只有 44%,接近瞎猜);判断一条文本是否属于多个领域并按真实比例分配(训练目标是「一个类别 + 5% 平滑」,不是真的软标签);英文、代码以外的大段非中文文本。

结构

形态decoder-only Transformer,手写 PyTorch,不用 transformers 库
层数 / 宽度8 层 · d=512 · 8 头 · FFN 2048
位置编码RoPE
上下文512 字(字符级,词表 15,341)
两个出口语言模型头(猜下一个字)+ 领域头(31 维 softmax)
池化取最后一个字的隐状态;前文看不到后文,取平均会混进只看过半句的状态
权重文件best.pt · 382 MB · fp32 存档
实线框是推理时真正跑的部分;虚线框是训练时才用到的那一路。两个出口共用同一个 512 维隐状态,没有各训各的。点图可以放大。
模型架构 · 8 层 · d=512 · 两个出口

训练数据

语料来自智源 IndustryCorpus2(Apache-2.0),按行业分了子集,文件夹名本身就是标签——所以一条标注都不用手写。

来源BAAI/IndustryCorpus2,30 个行业子集(金融、法律、医学、体育、代码、航空航天……)
规模316,712 篇 · 356,481,811 字
切分训练 285,056 篇 / 验证 31,656 篇(按文档切,同一篇的块不会跨到另一侧)
类别30 类有样本;第 31 个「其他」槽永远没有正样本,实际不会被选中

训练过程

损失猜字交叉熵 + 2.0 × 领域交叉熵(标签平滑 0.95)+ 0.5 × 两块一致性 + 0.1 × 对比
优化器AdamW,β=(0.9, 0.95),weight decay 0.1,梯度裁剪 1.0
学习率3e-4,余弦衰减,warmup 800 步
批与长度batch 8 · 块长 512,训练中在 256~512 之间随机切换(长度增广)
训练量129,600 步(最佳快照 129,000 步)
硬件单卡 RTX 2080 SUPER 8G,fp16,显存占用 2.99 GB,约 6 小时

计划 15 万步,在 12.96 万步手动停止。学习率已降到 1.4e-5,接近收尾,但不是严格跑满的退火。

效果

按输入长度看(同一批验证文档,只截前 N 字,每类 3 篇共 90 篇):

输入 30 字44.4%
输入 80 字51.1%
输入 150 字56.7%
输入 300 字72.2%
输入 512 字82.2%

把每类取到 20 篇(共 600 篇)重测,类均衡召回 85.2%;按自然分布抽 2000 篇验证集是 88.45%。后者偏高是因为大类文档多,而大类恰好更好分。

按类看:航空航天、计算机编程_代码 100%;数学、消防安全、新闻传媒、信息安全 95%。最差的是计算机_通信 50%、其他制造 60%、科技_科学研究 60%、石油化工 60%、电力能源 65%——这五个的边界本身就重叠。

已知限制

机制层面的解释——两个出口、四项损失、梯度为什么等于「预测减目标」、分类头实测只把第二层从 std 0.020 练到 0.303、关键词假说——单独放在 原理 页。

怎么用

仓库里 start_infer.bat 双击起页面,或者命令行:

python infer.py --checkpoint H:/llm-domain-lab/runs/v3_30class/best.pt --text "……"

批处理打标用 tag.py。按块喂的时候记住:块长 512 字,短于 200 字的段落并进相邻段再判。

来源与许可