AI文本检测:从破折号误判到困惑度与突发性分析
2026/9/22 2:34:04 网站建设 项目流程

AI生成文本检测是近期内容社区讨论得很热闹的一个话题。你经常能看到“一眼识别AI文本”的分享贴,其中一个流传很广的梗是:AI喜欢用 em dash,也就是英文里的长破折号 “—”,中文语境下对应全角破折号“——”。于是很多人把“破折号出现频率”当成判断AI文本的硬指标,甚至做成脚本,统计到几个破折号就自动标记为疑似AI生成。

这个判断如果放在英文博客、营销文案里,可能有一点道理,但如果真的拿它当检测依据,误判率会高得惊人。提示词一变,模型输出的标点习惯就跟着变;作者文风不同,人类写作里的破折号也可能非常多。更关键的是,破折号属于“浅层风格特征”,它只能反映某个模型的某一个侧面,并不能代表AI文本的普遍规律。

真正值得关注的,是文本内部的概率分布和统计特征。这篇文章不从“看起来像”出发,而是从“可计算、可验证”的角度,拆解哪些信号对AI生成文本更有区分度,并给出一个可以本地运行的Python检测小原型。读完你可以理解 perlexity、burstiness、词汇多样性这些指标的实际含义,也能自己跑一版检测流程,搞清楚为什么单一特征会误判。

1. 为什么“破折号检测法”不可靠

先说清楚 em dash 是怎么变成“AI文本试金石”的。很多大语言模型在长文本生成时,倾向于使用破折号来表达插入语、补充说明和语气转折。这可能是训练数据里高质量英文文本的常见风格,也可能是模型在长序列建模时偏好的一种“安全”连接方式。于是人们发现:AI生成的英文文章里破折号很多。

但问题在于,这个规律并不稳定。你可以在提示词里直接要求“不要使用任何破折号”,也可以要求“拆成短句”,模型输出会立刻变化。不同模型对破折号的偏好也不一样,新版本模型经过对齐和风格控制后,这类浅层特征往往会被修正。更现实的问题是人也会大量使用破折号。部分作者、编辑、学术写作者本身就有使用长破折号的习惯,尤其在英文非虚构写作里,em dash 是表达插入语和强调的常见工具。

从统计角度看,一个特征要成为检测依据,至少需要满足两个条件:区分度要足够大,稳定性要足够好。破折号在两个条件上都表现不佳。对于一段中文文本,情况更复杂——全角破折号到底是刻意使用还是输入法自动生成,很难从标点数量里判断。因此,破折号可以作为一个观察窗口,但不能作为检测证据。真正更可靠的信号藏在更深的统计分布里。

2. 更可靠的信号:概率分布、突发性与语义模式

要把“AI文本一眼假”这种主观感觉变成可测量、可计算的指标,需要从语言生成机制说起。大语言模型生成文本的过程,本质上是不断从概率分布中采样下一个 token。AI生成的文本倾向于选择高概率词,整体路径比较“安全”,所以模型对这段文本的预测难度较低;人类写作则不同,作者会引入口语、生僻词、隐喻、特殊术语,这些内容会让语言模型的预测难度明显上升。

这个差异对应的概念就是 perplexity,中文常译作困惑度。通俗解释是:模型看到一段文本时,它有多“意外”。如果模型很熟悉这段文本的模式,困惑度就低;如果文本充满意外,困惑度就高。AI生成文本整体困惑度偏低,因为模型生成时就在走自己最擅长的路。

另一个更重要的指标是 burstiness,中文可以理解为突发性或波动性。人类写作有张弛,有些句子信息密度高,使用的词汇意外性强,模型预测起来很吃力;有些句子则是连接部分,比较平淡。因此人类文本的句子级困惑度波动较大。AI生成的文本往往从头到尾保持“平稳质量”,句子之间的困惑度差异很小。这个“平滑感”其实是比平均困惑度更有辨识度的信号。

除了概率统计层面的特征,语义层面也有一些可观察的模式:

维度人类写作常见表现AI生成文本常见表现能否用提示词改变
词汇多样性因作者功底差异较大,会出现特定领域的术语和口语用词正确但重复率高,偏好通用词汇可以部分改变,但本质概率路径难完全改变
过渡与套话过渡自然,偶尔使用,不会密集出现频繁使用“首先”“其次”“总而言之”“值得注意的是”等模板可以改变,但默认输出概率较高
句子结构长短句交错,节奏变化明显句式均匀,信息密度稳定,缺少节奏起伏可以改变,但需要较强的提示控制
认知立场有个人判断、不确定性、限定条件回避立场,追求面面俱到,缺少真实观点可以改变,但模型本身倾向安全表述
具体细节包含真实数字、时间、地点、动作细节细节偏泛化,常用“一些”“许多”“各种”等模糊量词可以被提示,但模型缺乏真实感知,细节多为结构化的假细节

把这些信号组合起来,得到的不是某个单一的“AI嫌疑值”,而是一份文本分布画像。检测工具要做的事情,就是提取这些可计算特征,再根据一组文本的分布去判断它们更像人类写作还是AI生成。

3. 实验准备:环境、数据与评估思路

为了让讨论落地,下面实现一个最小可运行的检测流程。这个流程不是工业级检测系统,而是帮助你理解核心指标的教学原型。

3.1 环境准备

推荐使用 Python 3.8 或更高版本,安装依赖:

pip install transformers torch scipy

transformers 负责加载语言模型,torch 提供推理框架,scipy 用于统计计算。本文示例使用gpt2作为困惑度计算模型,因为它是开箱即用的小型因果语言模型,适合演示。如果处理中文文本,建议换用支持中文的因果语言模型,效果会更好,具体模型名称请在运行时从模型库中选择,不同版本可用性会有差异。

3.2 数据准备

准备两组文本:一组是人工撰写的文章或笔记,一组是AI生成的文本。每组 5 到 10 篇即可演示流程。注意文本来源必须合法合规,仅用于个人学习、内容管理或学术研究。不要用这套方法去对他人文本做公开定性,检测只能作为辅助手段。

3.3 评估思路

先对每篇文本分别计算多个特征,再比较两组文本在特征分布上的差异。如果某个特征在两组之间有明显分离,说明它有区分度;如果分布重叠严重,说明它不适合作为判断依据。这种“先看分布,再定阈值”的思路,远比拍脑袋定一个“破折号超过3个就是AI”要可靠。

4. 特征量化:用 Python 统计文本风格

先从最容易实现的统计特征开始。创建一个features.py文件,包含破折号计数、平均句长、词汇多样性、过渡短语频率等函数。

# 文件:features.py import re def count_em_dashes(text: str) -> int: """统计长破折号:英文 em dash、中文全角破折号""" return len(re.findall(r"—|—|–", text)) def avg_sentence_length(text: str) -> float: """按常见句末标点切分句子,返回平均句长(词数)""" sentences = re.split(r'[.!?。!?]', text) sentences = [s.strip() for s in sentences if s.strip()] if not sentences: return 0.0 return sum(len(s.split()) for s in sentences) / len(sentences) def type_token_ratio(text: str) -> float: """词汇多样性:不同词数 / 总词数,简称 TTR""" words = re.findall(r"\b\w+\b", text.lower()) if not words: return 0.0 return len(set(words)) / len(words) TRANSITION_PHRASES = [ "值得注意的是", "总而言之", "首先", "其次", "最后", "综上所述", "需要指出的是", "不难发现", "in conclusion", "notably", "moreover", "furthermore", "additionally", ] def transition_frequency(text: str) -> float: """统计常见过渡短语出现次数,除以文本长度做归一化""" count = 0 lower_text = text.lower() for phrase in TRANSITION_PHRASES: count += lower_text.count(phrase.lower()) return count / max(1, len(text))

这段代码的核心价值不是“高深”,而是把“模板化”“套话多”这种主观感受变成可比较的数值。count_em_dashes用来观察破折号频率,avg_sentence_length能反映句子节奏,type_token_ratio可以衡量文本用词的丰富程度,transition_frequency则用来捕捉那些最容易被AI默认输出的过渡套话。

在实际使用中,你不需要迷信任何一个指标。比如type_token_ratio和技术类文章之间就可能存在冲突:技术文章本来就会反复使用术语,TTR天然偏低,但这不代表它是AI生成的。所以这些特征更适合作为多维输入,而不是单点判据。

5. Perplexity 与 Burstiness 计算

基础统计特征容易实现,但它们很容易被人类作者、文体差异干扰。更具区分度的是基于语言模型计算的困惑度和突发性。

5.1 计算文本困惑度

创建perplexity.py,加载一个因果语言模型,计算整段文本的困惑度。

# 文件:perplexity.py import torch from transformers import AutoTokenizer, AutoModelForCausalLM MODEL_NAME = "gpt2" tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) model = AutoModelForCausalLM.from_pretrained(MODEL_NAME) model.eval() def ppl_for_text(text: str) -> float: """计算一段整体文本的困惑度(perplexity)""" encodings = tokenizer(text, return_tensors="pt") input_ids = encodings["input_ids"] with torch.no_grad(): outputs = model(input_ids, labels=input_ids) loss = outputs.loss return torch.exp(loss).item()

这里的原理是:把文本输入模型,同时把原文本作为标签,模型会计算每个 token 的交叉熵损失,取均值后再做指数运算,得到困惑度。数值越小,代表模型对这段文本的预测越轻松,也就意味着文本在统计上更接近模型的“舒适区”。AI生成文本通常会有偏低的困惑度。

5.2 计算句子级波动

由于 AI 文本的“平稳感”更重要,需要计算句子级困惑度的标准差,即 burstiness。创建burstiness.py

# 文件:burstiness.py import re import statistics from perplexity import ppl_for_text def sentence_ppl(text: str) -> list: """将文本按句子切分,计算每个句子的困惑度""" sentences = re.split(r'[.!?。!?]', text) sentences = [s.strip() for s in sentences if s.strip()] return [ppl_for_text(s) for s in sentences] def burstiness(text: str) -> float: """句子困惑度的标准差,衡量文本张弛度""" ppls = sentence_ppl(text) if len(ppls) < 2: return 0.0 return statistics.pstdev(ppls)

这里有个需要注意的细节:短句子的困惑度往往非常不稳定,单个短句可能因为缺少上下文,分数波动很大。实际使用中,建议把文本切分成合理的窗口,比如每 2 到 3 个句子合并成一个小片段,再计算困惑度序列。这样能减少碎句噪声,让 burstiness 更有参考价值。

另外,gpt2对中文文本的支持并不好。如果处理中文,需要换成中英文都支持或纯中文的预训练模型。这个替换不会改变整体流程,只影响模型加载那一行代码。

6. 综合评分:一个可运行的 AI 文本检测原型

有了基础特征、困惑度、突发性之后,可以把它们组合成一个简单的检测函数。创建一个detector.py,输出多维特征,并给出一个从 0 到 100 的倾向性分数。分数越高,代表文本在统计上越接近 AI 生成文本。

# 文件:detector.py from features import count_em_dashes, avg_sentence_length, type_token_ratio, transition_frequency from perplexity import ppl_for_text from burstiness import burstiness def detect(text: str) -> dict: """提取多维文本特征,并输出 AI 倾向分数""" ppl = ppl_for_text(text) bst = burstiness(text) ttr = type_token_ratio(text) trans = transition_frequency(text) score = 0 reasons = [] # 困惑度越低,AI 嫌疑越高 if ppl < 25: score += 25 reasons.append("overall_perplexity_low") elif ppl < 40: score += 15 reasons.append("perplexity_moderate") # 突发性越低,AI 嫌疑越高 if bst < 5: score += 25 reasons.append("burstiness_low") elif bst < 10: score += 15 reasons.append("burstiness_moderate") # 词汇多样性越低,AI 嫌疑越高 if ttr < 0.45: score += 15 reasons.append("ttr_low") elif ttr < 0.55: score += 8 reasons.append("ttr_moderate") # 过渡套话越密集,AI 嫌疑越高 if trans > 0.003: score += 20 reasons.append("template_transitions") elif trans > 0.001: score += 10 reasons.append("some_transitions") # 破折号只作为参考,不明显影响总分 dash_count = count_em_dashes(text) if dash_count > 5: score += 5 reasons.append("many_dashes") return { "score": min(100, score), "perplexity": round(ppl, 2), "burstiness": round(bst, 2), "type_token_ratio": round(ttr, 3), "em_dash_count": dash_count, "reasons": reasons, }

这个评分函数的阈值只是示例,不应该直接写死到生产环境。不同模型生成的文本、不同语言、不同领域的人工文本,在困惑度和突发性上差别很大。正确做法是先准备一批有代表性的样本,分别计算特征分布,再根据分布确定阈值。

值得强调的是,破折号在这个综合模型里只占很小的权重。这是有意为之:破折号本身区分度不稳定,强行提高它的权重只会增加误判率。

7. 运行结果与判断框架

在命令行里运行检测函数,可以用一个简单的脚本读取文本文件并输出特征。这里不写复杂了,直接演示调用:

# 文件:run_detector.py import sys from detector import detect text = sys.stdin.read() result = detect(text) print(result)

运行命令:

python run_detector.py < sample_ai_text.txt

预期会看到类似这样的输出:

{ "score": 62, "perplexity": 18.43, "burstiness": 4.1, "type_token_ratio": 0.483, "em_dash_count": 6, "reasons": ["overall_perplexity_low", "burstiness_low", "ttr_moderate", "some_transitions", "many_dashes"] }

这只是一个示意输出,实际数值取决于模型版本、文本长度和语料来源。你需要重点关注的是两个问题:

第一,score是否偏高。如果一段人工撰写的文本因为某人习惯使用破折号、句子节奏稳定,就被打出高分,说明你的特征权重或阈值设置有问题。

第二,不要把score > 80当成“这就是AI写的”的依据。这个分数只能说明“该文本在统计分布上更像AI生成文本”。它适合用来做风险排序,比如内容审核场景中把高分段文本优先交给人工复核,而不是直接做自动定性。

一个更稳妥的判断框架是:先看 perplexity 和 burstiness 是否同时偏低,再看过渡套话是否密集,最后才看词汇多样性和破折号。如果前两个特征都不满足,破折号再多也说明不了太多。

8. 常见误区和排查思路

这套流程看起来简单,实际运行时很可能遇到各种问题。下面列出几个高频问题。

问题现象可能原因排查方式解决方案
人类文本被频繁误判为AI作者文风本身就平稳、套话较多对比多篇同作者文本的特征分布引入更多特征,不要只依赖困惑度和突发性
破折号数量很高但最终分数不高破折号权重被有意降低,或其他特征不匹配AI分布检查检测器返回的 reasons 字段这是预期行为,说明综合判断比单一特征可靠
perplexity 对短文本极不稳定单句缺少上下文,模型预测难度大打印句子级困惑度序列使用 2 到 3 个句子合并的滑动窗口
中文文本的分数明显失真使用 gpt2 处理中文,分词和概率分布都不适配查看 tokenizer 切分结果换用支持中文的因果语言模型
同一篇文章,不同模型计算得分差异巨大不同模型的概率分布不同对比多个模型的困惑度排名固定检测使用的模型和版本,不要混用
AI 文本被改写润色后分数下降改写破坏了原始概率分布特征分析改写后的句子结构变化明确检测边界:润色后文本可能无法可靠识别

遇到误判时,最忌讳的是继续调高某个特征的权重。更合理的方式是收集更多样本文例,观察误判文本和正确识别文本在特征空间中的分布差异,根据差异做针对性调整。

9. 工程落地建议与风险边界

如果你想把这套思路应用到真实业务中,有几点需要提前想清楚。

第一,检测工具应该定位为“人工审核的辅助”,而不是“自动定罪的法官”。内容审核、学术评审、招聘筛选这类场景中,一个统计分数不能替代人工判断,更不能成为单方面惩罚的依据。把检测结果标成“需要进一步核查”比“这是AI生成”更稳妥。

第二,持续监控模型更新和特征漂移。大模型更新频率很快,旧版本模型生成的文本特征,可能在新版本中完全消失。你需要周期性地用新样本重新校准阈值,否则检测系统会逐渐失效。

第三,注意数据来源的合法合规。建设检测系统时,用于训练的文本必须有合法来源,包括自有语料、授权使用的公开数据集等。不要使用通过非授权方式爬取的数据。

第四,不要陷入“猫鼠游戏”。有些写作者会刻意调整文本让它更“像AI”或更“不像AI”,这种对抗会影响检测效果。正确的心态是承认检测能力的边界,只把它用在合理的辅助场景。

第五,如果你是普通内容创作者,这套指标更实用的价值不在于识别AI,而在于反观自己的写作风格。低困惑度、低突发性、模板化套话密集,这些特征同样可以用来审视自己的文字是否太“平”。从这个角度说,文本统计方法完全可以当作写作质量诊断工具。

10. 总结与后续学习方向

回到文章开头的问题:什么暴露了AI生成文本?不是破折号。破折号只是某个模型在某种提示下的浅层风格偏好,而真正能提供稳定区分度的,是文本的概率分布特征,尤其是困惑度、突发性、词汇多样性和模板化表达的组合。AI生成文本并不是因为某个标点奇怪才被识别,而是因为它整体的统计分布太“平滑”,缺少人类写作中常见的低概率跳跃。

建议你亲自跑一遍上面的流程,准备 5 篇人工文本和 5 篇AI生成文本,对比两组文本在困惑度和突发性上的分布差异。只有亲手看到数值分布,你才会理解为什么“破折号检测法”不靠谱,也才能真正建立对检测工具的判断力。后续如果还想深入,可以沿着三个方向继续学习:一是用提取到的特征训练一个小型分类器;二是使用 token 级特征替代句子级特征,提高检测精度;三是把事实核查和风格检测结合起来,从“文本是否可信”的角度做更完整的分析。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询