← 返回导航站首页

AI味检测与去AI化改写实战指南:零依赖检测脚本 + 4份真实文本实测

"AI味"通常被当成玄学:说不清、只能凭感觉改。我把它拆成可以数的东西(句子长度起伏、具体数字密度、模板词命中),写了一个零依赖的检测脚本 deai_lint.py,然后拿 4 份真实文本(2 份模型输出、2 份职业媒体稿)去跑。
本轮(v0.4.0)实测:模型原生输出 68 分过不了门;同一模型按 4 条要求改写后 90 分;一份通讯社特稿和一份气象通报都是 100 分。比分数更值得看的,是两条规则被样本否掉的过程,以及一条判据在不同取文方式下会漂多少。
文中所有分数、指标、命令输出都是本机实测原样贴出,脚本和两份模型样本都在仓库里可以直接跑;另外两份是公开稿件,下文给了地址和取文方式。


一、先把"AI味"拆成三层,不然没法量化

把"像不像人写的"当成一件事,永远只能靠感觉。拆成三层之后,只有第一层是玄学:

层 问的问题 能不能测 谁在乎
词表层 有没有"首先/其次/最后""值得注意的是""让我们一起"这类模板词 能测,确定性 读者(读三行就烦)
节奏层 句子长短有没有起伏、段落是不是一样长 能测,统计量 读者(读起来"打点机"感)
事实层 通篇有没有可核对的东西:数字、时间、地点、名字、金额 能测,计数 读者+平台(决定有没有人看下去)

第三层最容易被忽略,但它决定了内容是否有人看。样本 S1(模型原生输出)实测 449 字、具体数字密度 0.0/千字——整篇没有一个可核对的事实。这种文本的问题不是"像 AI 写的",而是读完什么都没剩下,换个真人来写同样没人看。

结论前置:去AI味不是改文风,是补信息。 词表和节奏是"别让人一眼看出是模板",事实密度才是"让人愿意读完"。


二、检测器怎么做:4 个统计量 + 6 类词表

脚本 deai_lint.py(零依赖、纯标准库、不调用任何模型,同一份文本永远得同一个分数)分两路读数:

统计量(不依赖词表,改词改不掉)

指标 怎么算 说明
句长变异系数 CV 句长标准差 ÷ 句均长 越低=句子越整齐
段落长度离散度 段长标准差 ÷ 段均长 越低=每段一样长,像清单
具体数字密度 数字/时间/金额/量词计数 ÷ 千字 事实层的读数
句首最高重复次数 相同前两字的句子数 起手式重复

词表/句式命中(每条都带行号和改法)

类别 例子 扣分权重
空转连接词 首先、其次、最后、值得注意的是、综上所述、由此可见 4
升华口号 让我们一起、未来可期、赋能、助力、深耕、打磨、闭环、抓手 6
空洞形容 极大地、显著提升、意义重大、淋漓尽致 5
无源数据 研究表明、数据显示、据统计、专家表示 7
元话语说教 你有没有想过、相信很多人、我们不难发现 5
直呼读者 你的存款、你的房子、你的生活 3
句式模板 不是…而是…、既…又…还…、三段式递进、破折号密度 3~5

计分规则(就是代码里那几行,没有隐藏项):

score = 100 − penalty,clamp 到 [0, 100]

penalty = Σ(词表命中次数 × 权重),每条的次数按 6 次封顶
        + Σ(句式模板命中次数 × 权重)
        + 句首重复(同一开头 ≥3 次)→ 4 × (重复次数 − 2)
        + 破折号密度 > 3/千字 → 3 × int(密度 ÷ 3)
        + CV < 0.30 → 10 分
        + 段落长度离散度 < 0.3 → 8 分
        + 数字密度 < 3/千字 → 10 分
        − 加分:数字密度 ≥ 12/千字 → 8 分;CV ≥ 0.7 → 6 分

两点设计取舍值得先说明:扣分按次数封顶(同一句口头禅出现 20 次和 6 次扣一样多),避免长文被单一维度压死;加分项存在意味着"总分"不是扣分的单调函数——这一点在第三节和第五节都会咬人。


三、实测:4 份真实文本,一次跑完(v0.4.0)

样本来源(四份都是真实文本,不是我自己造的对照):

汇总(deai_lint.py v0.4.0,及格线 75,判定以四条硬门槛为准):

样本 类型 分数 判定 字数 句长CV 数字密度 短词列举 词表/句式命中
S1 模型原生 68 REWRITE 449 0.422 0.0/千字 0.0/千字 研究表明×1(-7)、首先/其次/最后×3(-12)、破折号 4.45/千字(-3)
S2 同模型改写 90 PASS 329 0.273 39.51/千字 0.0/千字 无
S3 通讯社特稿 100 PASS 977 0.44 9.21/千字 0.0/千字 无
S4 气象通报 100 PASS 1866 0.709 34.83/千字 8.57/千字 句首重复×4(-8)、三项排比×1(-4)

S1 的真实报告(原样输出):

=== tools/samples/A1_llm_baseline.txt · AI味分数 68/100 · REWRITE(及格线 75,deai_lint v0.4.0)
字数 449 · 句子 18 · 句均长 24.4 · 变异系数 0.422 · 数字密度 0.0/千字 · 短词列举 0.0/千字
-- 硬门槛(不可互相补偿,任一不过即返工)--
  FAIL 总分 ≥ 75(当前 68)
  FAIL 具体数字密度 ≥ 3/千字(当前 0.0)
  PASS 无「升华口号」命中
  FAIL 无「无源数据」命中
-- 统计量提示 --
  · 句长变异系数 0.422 偏均匀(仅作参考读数,未扣分——实测专业记者稿也在 0.33 附近)
  · 具体数字密度 0.0/千字 < 3:通篇没有可核对的事实,是空泛的主要来源
-- 词表/句式命中(按扣分排序)--
  [无源数据] 「研究表明」×1(-7) 行5
      → 补出处或改成你自己跑出来的数(本机实测/官方通报/文件名+页码)
  [空转连接词] 「首先」×1(-4) 行3
  [空转连接词] 「其次」×1(-4) 行5
  [空转连接词] 「最后」×1(-4) 行9
  [句式模板] 「破折号密度 4.45/千字」×4(-3) 行5

最有用的一条结论与模型能力无关:

同一模型,只改对它的要求:分数 68 → 90,数字密度 0.0 → 39.51/千字,模板词清零。
模型不是不会具体,是默认输出会漂向模板;把"要具体"写成硬要求,它就能具体。

S4 那一行还藏了个细节:它命中了两条句式模板(-12 分),最终仍然 100 分,因为 CV ≥ 0.7 加回 6 分、数字密度 ≥ 12/千字 加回 8 分。加分项能把扣分盖过去——这正是第五节坚持用硬门槛定生死的理由。

顺手也把本文自己喂进去跑了一遍:判定 PASS,命中的只有「句首重复×6(-16)」和「三项排比×1(-4)」。总分却不稳:同一篇文本,这句话加上之前 94 分、加上之后 88 分、再润一次又是 94 分——句长变异系数就在 ≥0.7 那条加分线上下晃(同一稿三次实测 0.697 / 0.703 / 0.705),一句话就能让 6 分加分出现或消失。这就是第五节的活例:加分项既能盖住扣分,也会因为一句话就消失。


四、两条被样本否掉的规则(都能复算)

规则库第一版里有两条判据,跑完这四份样本就被否了。两条的否证过程今天都能用仓库里的脚本和文本复算一遍。

规则 1:把"列举"当成"三连排比"

第一版的三项排比规则是 各项 2~8 字的顿号三连。它在 S4 这份气象通报上命中的是这种东西:

重庆、湖北、安徽、
辽宁东南部、湖北西北部、重庆西南部和北部、
河南东南部、安徽、江苏南部、

规则分不清排比和列举:排比是修辞(长词组、有节奏、可删),列举是信息(短词、必要、删了就没内容)。同一条正则套到 S4 上,命中 30 次。

复算(今天的脚本 + 今天的样本,一条正则就能验证):

OLD = r"[^,。;:、\n]{2,8}、[^,。;:、\n]{2,8}、[^,。;:、\n]{2,8}、"    # 第一版
NEW = r"[^,。;:、\n]{5,14}、[^,。;:、\n]{5,14}、[^,。;:、\n]{5,14}、"  # 现在
# 同一段 S4 文本:OLD 命中 30 次,NEW 命中 1 次

在现在的计分公式下,30 次命中 = −120 分,S4 的账变成 100 − (120 + 12 − 14) = max(0, −18) = 0 分。也就是说,只要沿用第一版的字长阈值,这份职业媒体稿会被判 0 分——"越规范的官方通报,分数越低"。

修正三条:

  1. 规则改成"长词组顿号三连":每项最小长度提到 5 个字(排比才成立),短词列举不再扣分;
  2. 短词列举单独统计成对照读数(不扣分),S4 的短词列举 8.57/千字 一眼可见——既能看见它,又不会拿它扣分;
  3. 加回归断言:重庆、湖北、安徽、江苏、浙江等地 不触发排比扣分、且被计入对照读数(selftest 里的第 8、9 项)。

教训:任何词表类检测器都会把"规范写法"当成"模板腔"。必须用正常人写的文本做负样本回归,否则它会逼着作者把正确写法改掉,那比不改还糟。

规则 2:把"句长整齐"当硬门槛

第一版门槛里有一条"句长变异系数 ≥ 0.45"。四份样本的实测 CV:

样本 类型 句长CV
S1 模型原生 0.422
S2 模型改写 0.273
S3 通讯社特稿 0.44
S4 气象通报 0.709

模型稿落在 0.273~0.422,人写稿落在 0.44~0.709。看上去还能分开,但两段只差 0.018,而这条读数对取文方式比对"人机差异"更敏感。同一篇 S3,只改段落切分:

S3 的取法 句长CV
网页抽出的 16 段(含标题行、图注行) 0.44
去掉标题与图注后的 13 段 0.433
段间全并成一段 0.471

同源文本因切分不同漂了 0.038,比"模型稿与人写稿"的差距(0.018)还大。CV 在 4 份样本上分不开人机,只够说明这条规则不能用。

修正:CV 从硬门槛降级为参考读数——低于 0.30 时警告并扣 10 分(句子长短几乎没有起伏确实是机器腔),0.30~0.45 只提示不扣分,≥ 0.7 反而加 6 分(长短句混排像人写的)。

教训:4 份样本只够筛掉失效的规则,不够标定阈值。 阈值标定要上百份带标签样本,这是本工具的已知边界,也是所有"AI 检测器"宣称准确率时的同一个空档。


五、硬门槛为什么是 4 条,而不是 1 个总分

总分是加权和,加权和可以被单一维度刷:数字塞多点、词表一个不犯,总分就能堆上去(S4 是活例:命中两条句式模板,靠加分项回到 100 分)。所以最终判定不看总分,看 4 条彼此独立、不许互相补偿的门槛:

总分 ≥ 75
具体数字密度 ≥ 3/千字
无「升华口号」命中
无「无源数据」命中

推而广之,任何"评分卡式"的内容门禁都该这么设计:总分用于知道该改哪儿,硬门槛用于决定能不能发。 多维度加权求和再卡一个线,等于告诉生产端"你可以用某一个维度换另一个维度"。


六、改写对照表(都来自实测命中)

AI 腔 改成
首先…其次…最后… 删掉连接词,句子之间靠内容顺序衔接
值得注意的是 / 综上所述 直接写那件事;要收尾就用最后一句事实收
研究表明 / 数据显示 换成你自己跑出来的数(本机实测/官方通报/文件名+页码)
让我们一起…未来可期! 删掉。用一句具体的事、一个数字或一句原话结尾
显著提升 / 极大地 换成"从 X 到 Y"(时间、金额、人数、次数)
赋能 / 助力 / 深耕 / 打磨 / 闭环 换动词:做了什么、对谁、结果是什么
破折号解释 改句号或括号;每千字超过 3 个就该查
三连排比(长词组) 留一项,剩下两项换句式
你有没有想过 把结论放进场景或数据,让读者自己得出
你的存款 / 你的房子 用画面和数字让读者代入,别对读者喊话

七、必须说清的边界:文字层 ≠ 行为层

这是本工具最重要的一条边界,因为它关系到你会不会把"改了文风"错当成"会有流量"。

同一个账号 2026-09-01 发布的两篇图文,后台读数:展现 3536 次 / 阅读 0、展现 3540 次 / 阅读 0,两篇都没有推荐标记(未进推荐池)。稍晚发布的另一篇:71 次展现 / 1 次阅读。发布侧每一步都是绿的——接口返回成功、有内容编号、内容在后台可见;平台侧的读数是零阅读、不给推荐。

这说明两件事:

  1. 发布成功、文风改善、平台推荐是三件独立的事,各有自己的读数和判定方。
  2. 2026 年主流平台的判定已经下沉到信息源与账号行为层(谁在什么环境下用什么方式发的、账号历史如何),不是逐句查你的形容词。把"AI味词表"改干净,能改善人的阅读体验,但不对流量做任何承诺。

所以 deai_lint.py 的定位要写清楚:它是防止把模板垃圾发出去的返工门,不是流量预测器,更不是"AI 检测器绕过工具"。它也回答不了"这段文字是不是 AI 写的"——它只回答"这段文字里有多少可数的空转成分"。

顺带一句给用检测器做审核的场景:任何"AI 生成检测"(包括我这个脚本)都只有统计意义上的区分力,误判必然存在(第四节的两条就是)。用检测器当门禁可以,用它当判决不可以。


八、接进发布流水线

# 单篇体检(退出码 0=过,1=返工,2=用法/文件错误)
python deai_lint.py draft.md

# 批量体检 + 机器可读(给流水线/看板用)
python deai_lint.py --json draft.md outro.md

# 自定义及格线
python deai_lint.py --threshold 80 draft.md

# 自检(自带样例 + 13 项断言,验证脚本本身没坏)
python deai_lint.py selftest

接进流水线的三条纪律:

  1. 门禁只拦"明显空转",不拦风格。阈值 75 是"有具体信息、没有模板词"的底线,不是"写得好"。设太高会逼着作者为了过检测硬塞数字。
  2. 退出码即结论,不要解析文字。0/1/2 三个状态足够 CI 用;报告文本留给人看行号和改法。
  3. 检测器的修改必须带回归样本。每次改词表或阈值,先把"应该通过的正常人写文本"跑一遍;第四节那两条规则问题都是这么发现的。

三档处置建议:

分数 处置
≥ 90 直接发
75~89 看命中的行号改 2~3 处再发(优先补数字、删模板词)
< 75 退回去重写。别在原文上打补丁,模板骨架改不掉

九、复现

脚本与两份模型样本都在本仓库:

# 脚本(零依赖,Python 3.8+)
tools/deai_lint.py

# 两份可复现的模型样本(S1 原生输出 / S2 按 4 条要求改写)
tools/samples/A1_llm_baseline.txt
tools/samples/A2_llm_rewritten.txt

python tools/deai_lint.py --threshold 75 tools/samples/A1_llm_baseline.txt tools/samples/A2_llm_rewritten.txt
python tools/deai_lint.py selftest

S3、S4 是第三方媒体的公开稿件,未随仓库分发(版权),按下面的地址和方式自行取文即可复现:

取文方式(正文段落怎么抽会影响统计量,所以把口径写清楚):

import re, html, urllib.request

url = "<上面任意一个地址>"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
raw = urllib.request.urlopen(req, timeout=30).read().decode("utf-8", "replace")

body = re.search(r'id="artibody"(.*?)</div>', raw, re.S).group(1)   # 新浪正文容器
paras = []
for p in re.findall(r"<p[^>]*>(.*?)</p>", body, re.S):
    t = re.sub(r"\s+", " ", html.unescape(re.sub(r"<[^>]+>", "", p))).strip()
    if len(t) >= 15:
        paras.append(t)

open("sample.txt", "w", encoding="utf-8").write("\n\n".join(paras))

引用这两份稿件的意义在于用"职业媒体稿"当负样本:检测器如果判记者稿返工,那多半是检测器的规则错了,不是记者写错了——第四节的两条修正就是这么来的。同时注意口径本身的不确定性:同一篇稿子,段落切法不同,CV 会漂 0.038(第四节那张表),所以任何"单条统计量阈值"都别信,除非它在上百份带标签样本上标定过。


十、上线检查清单


边界声明(本工具不做什么)


📚 更多创作资源: https://afdian.com/a/felix007