AI 生成内容大量涌入之后,“抄袭”这个词的含义已经完全变样了。以前查重系统比的是 n-gram 重叠和向量余弦相似度,对付复制粘贴足够,但对付“把来源扔给大模型帮我改写一遍”这种操作,几乎无能为力。很多时候,一段文字既没有连续公共子串,嵌入距离也远,可它就是从某个来源洗出来的。这也是生成式抄袭检测(Generative Plagiarism Detection)最近被反复提起的根本原因:“像不像”不再是好指标,“能不能低成本生成出来”才是。
最近读到一篇论文,标题里有一个很值得琢磨的提法:Source-Conditioned Description-Length Gain,直译过来就是“基于来源条件的描述长度增益”。它不直接比文本相似度,而是换了一个信息论视角:给定某个候选来源,待检测文本的描述难度下降了多少。下降越多,说明这段文本越依赖这个来源生成,也就越可能是从它改写来的。这个思路同时还可以用在一个非常具体的工程问题上——候选来源重排序(Candidate Source Reranking):当检索系统给你返回了多个疑似来源,如何判断哪个才是“真正的源头”。
这篇文章会把我的理解拆开讲清楚:先看生成式抄袭检测为什么不能只靠表征相似性,再剖析描述长度增益的计算逻辑,然后给出一个可运行的最小实现,最后聊一聊这套方法在工程落地时会遇到的坑和最佳实践。
1. 这篇文章真正要解决的问题
先说结论:生成式抄袭检测的核心难点不是“找相似”,而是“判断来源依赖”。
传统反抄袭系统大部分建立在向量检索和文本对齐上。先对文档做分句或分块,再用嵌入模型把每段文字转成向量,然后用余弦相似度或编辑距离找重复。这套流程的核心假设是:抄袭会留下表层痕迹,比如连续 token 重复、句子结构趋同、同义改写痕迹。这个假设在“复制粘贴”时代成立,但在大模型时代正在快速失效。
原因很简单。一个学生或写手可以把源文档翻译成另一种语言,再让 LLM 做摘要、扩写、换风格、调整结构,最后得到的文本与来源在表面上没有任何公共片段。嵌入模型虽然能捕捉语义,但高级改写和意译仍然会产生较大的表示偏移,尤其在多来源拼凑场景下,一段话可能同时参考了三四个来源,任何单一来源的向量相似度都不高,可它确实是“基于这些来源生成的抄袭文本”。
这就引出了本文真正想解决的三件事:
- 为什么表征相似性(Representational Similarity)不足以覆盖生成式抄袭。
- 如何用 Source-Conditioned Description-Length Gain 衡量“来源依赖程度”。
- 如何把这一指标用于候选来源重排序,让检测系统在多个疑似来源中找到真正被参考的那一个。
如果你在做大模型内容审核、学术诚信工具、版权保护、企业内容溯源,这篇论文的思路值得仔细看。即便你不做安全方向,理解“描述长度增益”这个评估视角,也会对“如何衡量两段文本之间的生成关系”有新的认识。
2. 核心概念:表征相似性与描述长度增益
2.1 表征相似性(Representational Similarity)
表征相似性是一个很宽泛的说法,指通过模型把文本映射到向量空间,然后计算两个向量之间的相似度。常见做法包括:
- 句子嵌入余弦相似度;
- 基于 BM25 或 tf-idf 的检索得分;
- n-gram 重叠率;
- 基于对比学习的语义召回得分。
这些方法计算快、工程成熟、容易扩展,也是现有查重产品的基础。它们衡量的是“两个文本最终呈现出来的面貌有多接近”。
2.2 生成式抄袭(Generative Plagiarism)
生成式抄袭不是单纯指“用 AI 写文章”,而是指在参考一个或多个来源的情况下,用生成模型重写、翻译、摘要或融合,从而得到新的文本。与普通 AI 写作不同,这里的核心特征是“内容来源于特定候选来源”,但表层形式被大幅改造。
这种抄袭最难检测的点在于:改写后的文本在语义上与来源相关,但因为经过了生成模型的高维语义变换,表层结构完全改变。它已经不是“查重”问题,而是“溯源关联”问题。
2.3 描述长度增益(Description-Length Gain)
描述长度(Description Length)来自信息论与最小描述长度(MDL)原则:一段数据可以被压缩到多短,取决于我们给它多少先验信息。
放到抄袭检测场景中:
- 无条件描述长度:在没有任何外部来源信息时,用生成模型编码待检测文本
D,得到一个“生成难度”或“编码成本”; - 条件描述长度:在给定候选来源
S时,同样编码/生成D,得到另一个“生成难度”; - 增益 = 无条件描述长度 − 有条件下描述长度,表示候选来源
S在多大程度上帮助降低了对D的描述成本。
如果S真的是D的来源,那么在已知S的情况下,“读懂”并“生成”D会变得更容易,条件描述长度会明显下降,增益变大;如果S与D无关,条件信息基本没有帮助,甚至可能造成干扰,增益趋近于零或为负。
用大白话说就是:如果你告诉我某个来源,我可以用明显更短的描述来复现这段文本,那这段文本很可能就是从来源改写出来的。
2.4 三个概念的关系
| 概念 | 衡量对象 | 典型方法 | 核心弱点 |
|---|---|---|---|
| 表征相似性 | 文本表面的语义距离 | 嵌入模型、检索得分 | 高级改写与多源拼凑下失效 |
| 生成式抄袭 | 文本是否由模型基于来源生成 | 需要判断生成依赖 | 缺乏现成的统一指标 |
| 描述长度增益 | 来源对生成文本的“帮助程度” | 条件生成模型计算 | 算力成本高,依赖生成模型质量 |
三者不是互相替代,而是分层关系:表征相似性可以作为粗召回,描述长度增益作为精排和判定。
3. 为什么“描述长度增益”比“相似度”更适合检测生成式抄袭
一个关键判断:相似度是静态比较,描述长度增益是生成过程比较。
我们用一个具体例子来看。
假设来源S是:
研究人员发现,长期睡眠不足会导致认知功能下降,并增加患阿尔茨海默病的风险。待检测文本D是:
这项新的研究指出,如果一个人长期睡不够,大脑的认知水平会出现明显滑坡,而且未来得阿尔茨海默病的可能性也会上升。D明显是由S改写而来的。但如果你把这两句分别用嵌入模型编码,余弦相似度可能只有 0.7 左右,如果句子更长、改写更充分,降到 0.5 以下也很常见。n-gram 重叠可能只有个别词。仅靠表面相似度,这个案例很容易漏判。
但如果我们用一个生成模型来计算:
- 无条件生成
D:P(D)相对较低,因为这是一句全新的表述; - 条件生成
D,已知S:P(D|S)会明显更高,因为生成模型能够利用S提供的语义骨架,把“睡眠不足、认知下降、阿尔茨海默”这些关键信息映射到目标句子上。
于是:
DLG = log P(D | S) - log P(D)这里log表示对数,通常用自然对数。P(D|S)大于P(D)时,DLG 为正;这个正值越大,说明S对D的贡献越强。
如果S换成毫不相干的文本,比如一段关于天气的报道,那么P(D|S)甚至可能低于P(D),因为模型会被无关信息干扰,DLG 变成负值或接近 0。
这就是论文所提方法与传统方法的本质区别:它把“是否抄袭”定义成“来源是否显著降低了生成成本”。这个定义天然与语义生成能力绑定,高级改写很难绕开,因为只要语义骨架还在,条件信息就有作用。
从信息论角度,这个增益其实可以看成一种点互信息(PMI)的文本级版本。它衡量的是候选来源与待检测文本之间的“关联强度”,而不是“外观相似度”。这也解释了为什么它可以做候选来源重排序:在多个候选来源中,DLG 最高的那个,通常就是被参考最多的那个。
4. Source-Conditioned Description-Length Gain 的计算框架
4.1 问题定义
输入:
- 待检测文本
D; - 候选来源集合
Candidate = {S1, S2, ..., Sk}。
输出:
- 每个候选来源的 DLG 分数;
- 重排序后的候选来源列表;
- 最终判定
D是否属于基于来源的生成式抄袭。
4.2 用生成模型近似描述长度
真实描述长度要求我们度量理想编码器下的最短编码长度,这在现实中不可计算。论文和工程实践都会用一个可训练的生成模型M来近似。
最直接的做法是使用语言模型的条件概率:
L(D) = -log P_M(D) L(D | S) = -log P_M(D | S)其中P_M(D)是模型独立生成D的似然,P_M(D|S)是把S作为条件前缀后生成D的似然。这两个值通常用交叉熵近似,实际计算时是逐 token 求和。
因此:
DLG(D, S) = L(D) - L(D | S)当你使用基于 Transformer 的模型时,P_M(D)可以这样估算:
- 直接输入
D,让模型做自回归预测,累加每个 token 的负对数概率; P_M(D|S)则输入S + D,让模型在看完来源文本之后预测D的每个 token。
4.3 为什么使用对数概率而不是困惑度
困惑度(Perplexity)是对数概率的指数化平均,虽然更直观,但把它用在差值计算上并不方便。原因在于:
- 困惑度对文本长度敏感,两个文本 token 数不同时,直接比较困惑度会失真;
- 我们关心的是“条件与无条件之间的相对差异”,对数似然差值可以直接对应概率比,数学上更干净;
- 用序列长度做归一化之后,DLG 就变成了“每个 token 可以从来源中获得多少概率增益”的单位,更适合设置跨文本阈值。
论文标题里专门强调Source-Conditioned,本质是说:这个增益必须放在某个候选来源的上下文里计算,不能像传统表征相似性那样把两段文本各自编码后再做点积。
4.4 一个需要注意的细节:模型对来源长度的依赖
条件前缀越长,模型可利用的信息越多。如果S本身包含大量噪声文本,即使D只参考了其中一句话,P(D|S)也可能偏高,因为模型可以从长文本里找出相关的语义片段。所以工程上需要做分段或过滤,而不是直接拿一整篇文档做条件前缀。
5. 候选来源重排序:从“检索出来”到“确认源头”
在真实系统中,候选来源通常来自两个环节:
- 上游召回:用 BM25、向量检索从大型语料库中召回 Top-K 疑似来源;
- 下游确认:判断哪些召回结果真正与待检测文本存在“生成依赖”。
传统做法是继续用向量相似度做精排,选出相似度最高的来源。但正如前面所说,改写会压低表面相似度,多来源拼凑会让每个候选来源的相似度都偏低。这时 DLG 的价值就体现出来了。
重排序流程可以这样设计:
- 对候选来源集合中的每个
Si,截取与D在语义上可能相关的若干片段; - 计算
DLG(D, Si); - 按 DLG 从高到低排列候选来源;
- 设定阈值
T,DLG 超过T的来源视为“疑似真实来源”; - 如果没有任何来源超过阈值,则判定
D不依赖当前候选集合。
这个流程本质上是一种“检索-精排-判定”三段式架构。DLG 在其中承担了精排和判定的双重角色。
与传统精排相比,DLG 有一个很独特的优势:它不需要训练一个专门的重排序模型。只要有一个质量尚可的生成模型,就能直接用条件概率计算出相对分数。这在冷启动场景下非常有用。
6. 环境准备与最小示例实现
虽然论文中一定会用复杂的数据集和评测流程,但我们完全可以先用一个最小脚本跑通核心逻辑。下面用 HuggingFace Transformers 和 PyTorch 做一个简单演示。
6.1 环境依赖
建议 Python 3.9 以上,安装:
pip install torch transformers这里的transformers用来加载生成模型,torch用来计算张量。示例只用于演示思路,不指定具体模型版本;实际使用时,请根据你的算力和语言场景选择合适的模型。
6.2 计算条件概率与 DLG
# 文件路径:dlg_demo.py import torch from transformers import AutoModelForCausalLM, AutoTokenizer model_name = "your-base-model" # 例如中文场景下可换成合适的中文模型 tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name) model.eval() def sequence_log_prob(model, tokenizer, text): """ 计算一个文本序列在给定模型下的对数似然。 这里使用自回归方式逐 token 累加,不依赖外部标签。 """ inputs = tokenizer(text, return_tensors="pt", truncation=True) input_ids = inputs["input_ids"] with torch.no_grad(): outputs = model(input_ids=input_ids, labels=input_ids) # loss 是平均负对数似然,乘以序列长度得到总负对数似然 total_nll = outputs.loss * input_ids.shape[1] return -total_nll.item() # 返回 log P(text) def conditional_log_prob(model, tokenizer, source, target): """ 计算在给定 source 的条件下,模型生成 target 的对数似然。 实现方式:把两个文本拼接,计算拼接后的总似然, 再减去 source 部分的对数似然。 """ full_text = source + "\n" + target full_ids = tokenizer(full_text, return_tensors="pt", truncation=True)["input_ids"] source_ids = tokenizer(source + "\n", return_tensors="pt", truncation=True)["input_ids"] target_len = full_ids.shape[1] - source_ids.shape[1] with torch.no_grad(): outputs = model(input_ids=full_ids, labels=full_ids) # 先按整段算总 NLL full_nll = outputs.loss * full_ids.shape[1] # 再计算 source 部分的 NLL src_out = model(input_ids=source_ids, labels=source_ids) source_nll = src_out.loss * source_ids.shape[1] # 剩余部分就是 target 的条件对数似然 return -(full_nll - source_nll).item()6.3 计算 DLG 并重排序
# 文件路径:rerank.py from dlg_demo import sequence_log_prob, conditional_log_prob def dlg_score(model, tokenizer, source, target): log_p_target = sequence_log_prob(model, tokenizer, target) log_p_target_given_source = conditional_log_prob(model, tokenizer, source, target) return log_p_target_given_source - log_p_target def rerank_candidates(model, tokenizer, target, candidates): scored = [] for idx, source in enumerate(candidates): score = dlg_score(model, tokenizer, source, target) scored.append((idx, score, source)) # 按 DLG 从大到小排序 scored.sort(key=lambda x: x[1], reverse=True) return scored if __name__ == "__main__": target_text = "长期睡眠不足会损害认知能力,并且提高阿尔茨海默病的患病风险。" candidate_sources = [ "研究表明,持续的睡眠缺乏与认知功能退化显著相关。", "天气晴朗,适合户外运动,但要注意防晒。", "睡眠是大脑清除代谢废物的重要机制,长期不足可能诱发神经退行性疾病。", ] results = rerank_candidates(model, tokenizer, target_text, candidate_sources) for rank, (idx, score, src) in enumerate(results, 1): print(f"Rank {rank}: DLG={score:.3f}, source={src[:20]}...")这段代码的意图非常明确:
sequence_log_prob用于计算无条件文本的log P(D);conditional_log_prob使用拼接文本间接计算条件log P(D|S);dlg_score做差得到增益;rerank_candidates把所有候选来源按 DLG 降序排列。
6.4 运行结果与验证
如果你用一个质量不错的模型运行,通常会看到类似输出:
Rank 1: DLG=3.451, source=睡眠是大脑清除代谢废物的重要机制... Rank 2: DLG=0.892, source=研究表明,持续的睡眠缺乏与认知功能退化显著相关。 Rank 3: DLG=-0.124, source=天气晴朗,适合户外运动,但要注意防晒。排名第一的候选来源在语义上与目标最相关,DLG 显著为正;无关来源则接近 0 或为负。
如果运行失败,优先检查三件事:
tokenizer截断问题:长文本被截断会导致概率估算不稳定,可以调整truncation和max_length;- 模型加载太慢:选一个小型号先跑通流程;
- 结果不符合预期:很可能是模型太小,或者目标文本与来源文本语言不一致,可以换参数量更大的模型再试。
7. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| DLG 普遍偏低 | 模型太小,语义理解能力不足 | 检查模型参数量,尝试更大模型 | 更换更强生成模型 |
| DLG 对无关来源也偏高 | 条件前缀过长,模型被噪声干扰 | 检查 source 是否包含大量无关段落 | 对来源做分段或相关性过滤 |
| 中英文混合场景效果差 | 单语模型无法建模跨语言改写 | 调查目标语言是否被模型覆盖 | 使用多语言生成模型 |
| 结果不稳定,重复运行得分波动 | 输入顺序被随机采样影响 | 确认推理时是否开启采样 | 设置do_sample=False |
| 短文本出现明显误判 | token 数太少,概率估计方差大 | 查看文本长度分布 | 短文本场景辅助 n-gram 验证 |
| 计算速度太慢 | 每个候选来源都要走一次前向 | 检查候选来源数量 | 先粗召回,再对 Top-20 做精排 |
这些问题是实际落地时最常遇到的。核心教训是:DLG 是一个“精排+判定”工具,不适合在最原始的海量文本上做全量计算。上游必须有一个高效的召回层把候选来源数量压下来。
8. 最佳实践与工程建议
8.1 先用相似检索粗召回,再用 DLG 精排
这是目前最稳妥的工程架构。向量检索或者 BM25 负责从百万级文档中找出 Top-20 或 Top-50 候选,DLG 只在这几个候选上计算。这样既控制算力,又避免漏掉真正来源。
8.2 对来源文本做分段处理
实际操作中,一个候选来源可能是一整篇论文或网页。直接把整篇作为条件前缀,会导致模型在长文本中“寻找”与目标相关的片段,即使目标只参考了其中一句,也可能得到虚高分数。
建议按段落或窗口切分来源,切分后分别计算 DLG,取最大值作为该来源的最终得分。这样更符合真实抄袭场景。
8.3 设置合理的判定阈值
DLG 阈值不能拍脑袋定。建议做法是准备一个规模不小的人工标注数据集,里面包含:
- 正样本:确实基于来源生成的文本;
- 负样本:与来源无关,但语义接近的文本。
在这个数据集上绘制 PR 曲线或 ROC 曲线,选择满足业务要求的阈值。如果追求低误报,阈值要调高;如果追求高召回,阈值要降下来。
8.4 与现有相似度指标融合
DLG 不需要完全替代向量相似度。更推荐的做法是把两者融合成一个综合分数:
final_score = alpha * vector_similarity + beta * normalized_dlg两者的权重可以基于验证集调优。这种融合能避免单一指标的盲区:向量相似度对表面改写敏感,DLG 对来源依赖敏感,互相补充。
8.5 注意模型偏差与语言差异
DLG 依赖生成模型本身的能力。模型如果只在英文数据上训练,面对中文改写场景时条件概率估计会不准确。使用前要确认生成模型在目标语言上的基本质量,或者在目标语言语料上做微调。
8.6 建立人工复核闭环
任何自动检测算法都存在误报风险。建议把 DLG 分数高的样本作为“重点复核”推送给人工作业,而不是直接自动判罚。系统上线初期尤其要这样,等阈值和历史案例稳定后再考虑更大范围的自动化。
8.7 安全与合规提醒
抄袭检测系统在学术诚信、版权保护、内容溯源等场景中都有应用,但使用时必须遵守数据合规要求:
- 检测对象和语料库必须有合法来源和授权;
- 不要随意把私人文档作为候选来源公开;
- 判定结论只能作为辅助证据,不能替代人工判断;
- 涉及学生作品、商业代码、内部文档时,要做好数据脱敏和访问控制。
9. 总结与后续学习方向
这篇论文给我的核心启发是:在生成模型时代,判断“是否抄袭”应该询问生成过程,而不只是询问语义向量。
Source-Conditioned Description-Length Gain把两个文本之间的依赖关系转化成“来源能否降低目标文本的描述成本”,这个思路比单纯计算相似度更贴近生成式抄袭的本质。它尤其适合用在候选来源重排序和源头判定环节,与现有检索架构天然互补。
如果你接下来想深入实践,建议按这个顺序推进:
- 先在不同语言、不同改写强度的数据上测试 DLG 的稳定性;
- 再验证它在多来源拼凑场景下的表现,尤其是多个候选来源共享 DLG 时如何判定“主来源”;
- 最后把它接入你自己的反抄袭或内容溯源系统,用标注数据调阈值,观察误报率。
除此之外,还可以顺着两条线继续看:一条是“描述长度”理论在文本归属与作者分析中的应用;另一条是更高效的条件概率估算方法,比如用对比解码或者小模型蒸馏来降低推理成本,让 DLG 在工业级规模下真正可落地。
对于正在做内容安全或学术诚信工具的人来说,这个方向值得盯住:它会成为继向量检索之后,又一个检测生成式抄袭的重要技术路径。