问题不只在训练语料,也在词表
大语言模型(Large Language Model,LLM)并不是直接按“词语”理解文本。它处理输入时,会先把一句话切成一串 Token,也就是模型内部使用的最小文本单位。
如果一个中文短语在互联网语料里反复出现,它就可能被分词器收进词表,成为一个独立 Token。问题在于,中文互联网上有大量来自色情、网络赌博、私服游戏、盗版视频站和内容农场的垃圾页面,这些页面常常把同一批关键词重复堆叠到标题、页脚、弹窗和广告位里。
清华大学、蚂蚁集团、南洋理工大学的一项研究把这类异常中文 Token 称为污染中文词元(Polluted Chinese Tokens,PoC Tokens)。它们通常符合“3U”特征:
| 特征 | 含义 | 例子类型 |
|---|---|---|
| Undesirable | 不受欢迎,常关联违规或灰色内容 | 成人内容、赌博广告 |
| Uncommon | 在正常中文表达里不常见 | 拼接式站点词、异常品牌词 |
| Useless | 对通用语言理解几乎没有价值 | 重复广告串、乱码式短语 |
污染词元不是普通的脏词表问题。更麻烦的是:这些词元可能已经被固化进模型的基础词表,但在后续训练和安全对齐阶段又缺少正常语义学习,于是它们在推理时容易变成幻觉触发器。
参考研究:Inferring Chinese Training Data Pollution from Large Language Model Tokenizers
https://arxiv.org/abs/2508.17771
Tokenizer 为什么会把垃圾广告收进词表
大多数现代大语言模型会使用 BPE(Byte Pair Encoding,字节对编码)或类似算法训练分词器。BPE 的核心逻辑很简单:谁出现得多,谁更有资格被合并成一个 Token。
可以把 BPE 理解成一个反复合并高频片段的过程:
flowchart TD
A[收集训练语料] --> B[把文本拆成字节或基础字符]
B --> C[统计相邻片段出现频率]
C --> D{是否还有词表容量}
D -- 有 --> E[合并最高频片段]
E --> C
D -- 没有 --> F[冻结词表]
例如一个正常短语在语料中高频出现,分词器可能会逐渐学到这样的合并路径:
您 + 好 -> 您好
垃圾页面里的堆叠关键词也会经历同样的过程。BPE 并不知道某个短语是不是赌博站广告,也不知道某个人名是不是来自成人内容页面。它只看统计频率。
这会造成一个结果:
只要某些低质量短语在网页中重复得足够多,它们就可能进入词表。
词表一旦冻结,后面的模型训练通常不会再轻易改动 Token 集合。也就是说,污染词元会像“字典里的异常词条”一样留在模型底层。
高频不等于学会语义
污染词元最反直觉的地方在于:它们出现频率很高,却不一定被模型真正学会。
原因来自训练流程的分离。分词器训练和模型语义训练并不完全是一回事。
flowchart LR
A[原始网页语料] --> B[训练分词器]
B --> C[词表冻结]
C --> D[包含污染词元的 tokenizer]
A --> E[数据清洗]
E --> F[预训练]
F --> G[指令微调与安全对齐]
G --> H[最终模型]
D --> H
污染词元可能在分词器阶段被收录,因为原始网页里充满重复广告;但到了预训练、指令微调和安全对齐阶段,这些内容往往会被过滤掉,或者被安全策略压制。
这会形成一种尴尬状态:
| 阶段 | 污染词元发生了什么 |
|---|---|
| 分词器训练 | 因为高频,被收进词表 |
| 数据清洗 | 相关上下文被大量删除 |
| 预训练 | 缺少丰富、正常、可解释的语义样本 |
| 安全对齐 | 相关内容被拒答策略压制 |
| 推理阶段 | 一旦用户输入该 Token,模型不知道该如何稳定处理 |
所以,污染 Token 的“高频”更像是一种机械记忆,而不是语义理解。模型可能只学到:这个 Token 经常和另一批灰色关键词、广告词、乱码链接一起出现。它没有学到这个词在正常语言环境中的含义、用法和边界。
当用户输入污染词元时,模型的语义空间里没有足够可靠的解释路径,只能沿着早期统计相关性去补全,于是输出一些相邻污染词、无意义符号、虚构链接或无法解释的句子。
flowchart TD
A[用户输入污染词元] --> B{模型是否学到稳定语义}
B -- 是 --> C[正常解释或拒答]
B -- 否 --> D[回退到统计关联]
D --> E[输出相邻垃圾词元]
D --> F[生成虚构链接]
D --> G[出现乱码或不相关回答]
一句话概括:
污染 Token 出现频繁,不代表模型学会了它的含义;它可能只是被 tokenizer 收录,却在语义训练中长期欠训练。
一个简单例子:用 tiktoken 看模型如何切分中文
OpenAI 开源了 tiktoken,可以用来观察 GPT 系列分词器如何把文本转换成 Token ID。
pip install tiktoken
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
text = "您好,今天广州天气怎么样?"
ids = enc.encode(text)
print(ids)
print([enc.decode([i]) for i in ids])
输出会类似这样:
[...token ids...]
['您好', ',', '今天', '广州', '天气', '怎么样', '?']
实际切分结果取决于具体模型和 tokenizer。关键点不在于某句话一定怎么切,而在于:
如果某个中文片段被完整切成一个 Token,说明它已经是词表中的独立单位。
还可以粗略扫描词表里的长中文 Token:
import re
import tiktoken
enc = tiktoken.get_encoding("o200k_base")
cjk = re.compile(r"[\u4e00-\u9fff]")
long_chinese_tokens = []
# _mergeable_ranks 是 tiktoken 的内部结构,适合研究和排查,不建议作为线上稳定接口依赖
for token_bytes, rank in enc._mergeable_ranks.items():
try:
token = token_bytes.decode("utf-8")
except UnicodeDecodeError:
continue
chinese_chars = cjk.findall(token)
if len(chinese_chars) >= 2:
long_chinese_tokens.append((rank, token))
long_chinese_tokens.sort(key=lambda x: x[0])
for rank, token in long_chinese_tokens[:50]:
print(rank, token)
这个脚本只能帮你看到“哪些长中文片段进入了词表”,不能直接判断它们是不是污染词元。真正的污染检测还需要结合网页搜索结果、上下文类别、域名质量和人工校验。
POCDETECT:识别污染中文词元
研究人员设计了 POCDETECT,用来标记词表里的 PoC Tokens。它不是简单关键词过滤,因为很多污染词元表面上看起来像正常词,有些甚至像普通品牌名、软件名或人名。
一个更可靠的检测流程大致是这样:
flowchart TD
A[抽取长中文 Token] --> B[过滤明显正常词]
B --> C[搜索互联网结果]
C --> D[分析页面标题、摘要、域名和上下文]
D --> E{是否指向灰色内容}
E -- 是 --> F[标记为 PoC Token]
E -- 否 --> G[保留为正常或待定 Token]
F --> H[按成人内容、赌博、私服、盗版视频等分类]
这里有两个难点。
一个是隐蔽性。很多污染词元不是直接写出敏感词,而是使用缩写、谐音、品牌包装、拼接词或站群词。单靠敏感词列表很容易漏掉。
另一个是上下文依赖。同一个中文片段在正常语境和灰色语境里可能都出现过。判断它是不是污染词元,需要看搜索结果集中指向哪里。如果结果主要来自赌博站、成人视频站、私服推广页、盗版资源站,就说明它更可能是 PoC Token。
POCTRACE:用 Token 排名反推训练频率信号
POCTRACE 用来估计某个 Token 在 tokenizer 训练数据里的频率信号。
在 BPE 训练过程中,越早被合并的片段通常越高频。对 tiktoken 这类分词器来说,普通可合并 Token 的 rank 或 ID 可以作为一个近似信号:排名越靠前,说明它越早进入词表,也通常意味着训练分词器时出现得越多。
需要注意,这不是精确计数器。Token ID 会受到词表设计、特殊 Token、不同语言混合语料等因素影响。但在同一个 tokenizer 内比较相近类型的 Token,rank 仍然有参考价值。
研究里有一个很刺眼的估算:某成人内容相关人名作为独立 Token 的频率信号,约为中文问候语“您好”的 2.6 倍。
相关 Token ID 对比如下:
| Token 类型 | Token ID / rank 信号 | 解释 |
|---|---|---|
| 成人内容相关人名 | 185,946 | 被完整收录为独立 Token |
| “您好” | 188,633 | 常见中文礼貌问候 |
| 频率估算 | 约 2.6 倍 | 前者在 tokenizer 训练数据中的统计信号更强 |
更异常的是,这类人名不只是全名进入词表,部分子序列也可能被单独收录。对中文来说,这通常说明相关字符串在训练分词器的语料里出现过大量重复样本。
研究人员还做了一个验证:把与该人名相关的网页按一定比例混入干净数据集,重新训练 tokenizer,得到的 Token ID 和 GPT-4o 的结果非常接近。这个实验支持了一个判断:
污染词元不是偶然切分出来的,而是由训练语料里的重复低质量内容推动形成的。
各模型污染程度差异很大
污染问题不是所有模型都一样严重。研究统计了多个主流大语言模型的中文词表,关注对象主要是“包含两个以上汉字的长中文 Token”。
部分结果可以整理成这样:
| 模型或系列 | 研究中的污染比例信号 | 需要注意的解读 |
|---|---|---|
| GPT-4o 相关词表 | 长中文词元中相当一部分落入成人、赌博等类别;部分统计口径超过 23% | 新 tokenizer 扩展了多语言覆盖,也更容易把高频垃圾短语收进词表 |
| GPT 系列某统计口径 | 最高约 46.6% | 具体比例与词元长度、分类规则、词表版本有关 |
| Qwen 系列 | 约 1.00% | 中文词表污染明显更低 |
| GLM-4 | 约 0.25% | 污染词元占比较低 |
| DeepSeek-V3 | 约 0.17% | 污染词元占比较低 |
| GPT-4 / GPT-4-turbo / GPT-3.5 | 被标注的长中文 PoC Token 为 0 | 不代表训练语料完全无污染,只说明公开 tokenizer 词表中没有这类被标注长中文污染词元 |
这些数字不能直接等同于“模型整体质量”。它们更准确地反映了 tokenizer 词表里的长中文 Token 是否混入了大量低质量短语。
一个模型可能词表更干净,但回答能力未必更强;另一个模型可能能力很强,却在某些异常中文 Token 上表现不稳定。污染词元更像是底层输入表示的缺陷,而不是完整能力评测。
为什么中文更容易暴露这个问题
英文 tokenizer 常常以单词、词根、子词为单位,长 Token 往往是专业术语、技术名词、化学名词、医学名词或复合词。中文没有天然空格,BPE 会根据字符组合频率自动合并。
这会让中文高频垃圾短语更容易以“完整短语”的形式进入词表。
| 语言特点 | 对 tokenizer 的影响 |
|---|---|
| 英文有空格分词边界 | 长 Token 更常来自长单词或专业术语 |
| 中文没有天然空格 | 高频字符组合容易被合并成短语 Token |
| 中文垃圾广告常重复堆叠关键词 | 赌博、私服、成人内容短语可能被高频合并 |
| 灰色站点大量使用站群模板 | 同一批词在标题、页脚、导航、锚文本中反复出现 |
这并不意味着“中文 Prompt 天生不如英文 Prompt”。更准确的说法是:中文互联网语料的噪声分布、分词机制和数据清洗难度叠加后,会让某些问题更明显地暴露在中文 Token 里。
对实际使用有什么影响
污染词元会带来几类工程风险。
1. 幻觉更容易被异常 Token 触发
用户输入一个表面普通、实际来自灰色语境的短语时,模型可能无法稳定解释,转而输出不相关内容、乱码、虚构链接或错误定义。
这类幻觉不是普通知识缺失,而是输入表示本身已经把模型带进了低质量统计关联区域。
2. 安全策略可能被绕开
安全对齐通常依赖模型理解用户意图。如果污染 Token 在语义空间里欠训练,模型可能无法正确识别它对应的风险类别。攻击者可以利用这些罕见、隐蔽、语义不稳定的 Token 触发异常输出。
3. RAG 系统会受到低质量网页污染
RAG(Retrieval-Augmented Generation,检索增强生成)系统如果直接从搜索引擎或开放网页取资料,可能把内容农场、站群页、AI 生成垃圾内容一起召回。即使底层模型没有相关污染词元,检索上下文也会把垃圾送进提示词。
flowchart LR
A[用户问题] --> B[检索系统]
B --> C{召回来源}
C --> D[可信资料]
C --> E[内容农场]
C --> F[站群广告]
D --> G[模型生成]
E --> G
F --> G
G --> H[答案质量下降或引用错误]
4. AI 生成内容会继续污染互联网
当模型生成低质量内容并被批量发布到网页上,搜索引擎和未来训练数据又会把这些内容重新收集进去。这会形成反馈回路:
flowchart TD
A[低质量网页] --> B[训练数据]
B --> C[模型]
C --> D[批量生成低质量内容]
D --> E[发布到互联网]
E --> A
如果缺少来源标记、反垃圾机制和高质量数据筛选,这个循环会让后续模型更难获得干净语料。
模型开发侧如何降低风险
污染词元需要在 tokenizer、语料清洗、训练和评测多个环节处理,单靠安全拒答很难兜住。
| 环节 | 可行做法 | 解决的问题 |
|---|---|---|
| tokenizer 训练前 | 先清洗语料,再训练词表 | 避免垃圾短语被固化为基础 Token |
| 词表冻结前 | 扫描长中文 Token,做 PoC 标注 | 提前发现异常高频短语 |
| 数据清洗 | 结合域名质量、模板重复度、搜索结果类别 | 过滤站群、赌博、私服、盗版资源站 |
| 预训练 | 降低重复垃圾页面权重,增强高质量中文语料 | 减少统计关联偏移 |
| 对齐训练 | 加入污染词元触发测试集 | 检查模型是否会乱码、胡编或绕过安全策略 |
| 上线评测 | 做 token-level 红队测试 | 找到正常文本测试发现不了的漏洞 |
一个关键原则是:
不要等模型已经训练完,再希望安全层修复所有污染词元。
如果污染已经进入词表,并且在语义训练阶段欠训练,后期拒答策略只能缓解一部分问题。更稳妥的方式是在 tokenizer 训练前就降低污染语料权重。
应用开发侧如何防守
调用大模型的应用也可以做一些防护,尤其是面向中文开放输入的系统。
建立异常 Token 监控
可以记录用户输入被 tokenizer 切分后的 Token。如果某些请求包含大量罕见长中文 Token、异常拼接词、站点广告词,就可以进入额外审核流程。
def suspicious_token_ratio(text, enc, suspicious_token_set):
ids = enc.encode(text)
if not ids:
return 0.0
hit = sum(1 for token_id in ids if token_id in suspicious_token_set)
return hit / len(ids)
这里的 suspicious_token_set 不应该只靠敏感词表生成,更适合结合历史日志、搜索上下文、模型异常输出和人工标注持续维护。
检索增强系统要过滤来源
RAG 系统不要把“能搜到”当成“可信”。至少需要做这些过滤:
| 检查项 | 作用 |
|---|---|
| 域名白名单或信誉评分 | 降低站群和垃圾站进入上下文的概率 |
| 内容重复度检测 | 过滤模板化广告页 |
| 发布时间和引用关系 | 避免过期或互相搬运的内容 |
| 页面主体抽取 | 去掉页脚广告、侧栏推荐、弹窗文本 |
| 多来源交叉验证 | 减少单一垃圾来源误导模型 |
对异常输出做回退
如果模型输出乱码、奇怪链接、无法解释的专有词或大量无关中文短语,可以触发二次策略:
flowchart TD
A[模型输出] --> B{是否包含异常模式}
B -- 否 --> C[正常返回]
B -- 是 --> D[重新检索可信来源]
D --> E[降低温度重新生成]
E --> F{仍异常?}
F -- 否 --> C
F -- 是 --> G[返回无法确认并请求澄清]
这比直接把异常内容返回给用户更安全。
普通用户该怎么判断
用户不需要理解所有 tokenizer 细节,也可以用几个简单信号判断回答是否可能受污染影响:
| 现象 | 风险 |
|---|---|
| 模型突然输出不相关中文短语 | 可能触发了异常 Token 关联 |
| 出现打不开的网站、奇怪域名 | 可能混入垃圾网页记忆或检索结果 |
| 对某个冷门词解释得很自信但没有来源 | 高概率是幻觉 |
| 引用资料来自内容农场或 AI 聚合页 | 可信度低 |
| 同一问题换种说法后答案差异很大 | 模型对该语义不稳定 |
遇到这种情况,最好要求模型给出可验证来源,或者换用更明确的表达重新提问。对专业问题,不要只看模型回答本身,要看它引用的资料是否可靠。
关键结论
污染中文词元揭示了大语言模型一个很底层的问题:模型的“词表”并不天然干净,它也是从互联网统计中学出来的。
BPE 只关心频率,不理解内容质量。中文互联网里的垃圾广告、灰色站点和重复模板一旦规模足够大,就可能把异常短语推入 tokenizer 词表。后续训练又会清洗或压制这些内容,导致污染词元缺少正常语义学习,最终在推理时表现为乱码、幻觉和安全不稳定。
这件事的工程启示很直接:
- tokenizer 训练语料必须先清洗,不能只在模型训练阶段清洗;
- 长中文 Token 需要单独审计,尤其是高频但语义异常的短语;
- RAG 系统要控制信息来源,否则检索层会把垃圾重新送给模型;
- 中文模型评测不能只测常规问答,还要测异常 Token、站群词和灰色语境触发。
大语言模型学到的是数据分布。数据分布里有什么,词表和参数里就可能留下什么。模型能力越强,越需要对进入训练管线的中文语料做更细的质量控制。