"AI味"通常被当成玄学:说不清、只能凭感觉改。我把它拆成可以数的东西(句子长度起伏、具体数字密度、模板词命中),写了一个零依赖的检测脚本
deai_lint.py,然后拿 4 份真实文本(2 份模型输出、2 份职业媒体稿)去跑。
本轮(v0.4.0)实测:模型原生输出 68 分过不了门;同一模型按 4 条要求改写后 90 分;一份通讯社特稿和一份气象通报都是 100 分。比分数更值得看的,是两条规则被样本否掉的过程,以及一条判据在不同取文方式下会漂多少。
文中所有分数、指标、命令输出都是本机实测原样贴出,脚本和两份模型样本都在仓库里可以直接跑;另外两份是公开稿件,下文给了地址和取文方式。
把"像不像人写的"当成一件事,永远只能靠感觉。拆成三层之后,只有第一层是玄学:
| 层 | 问的问题 | 能不能测 | 谁在乎 |
|---|---|---|---|
| 词表层 | 有没有"首先/其次/最后""值得注意的是""让我们一起"这类模板词 | 能测,确定性 | 读者(读三行就烦) |
| 节奏层 | 句子长短有没有起伏、段落是不是一样长 | 能测,统计量 | 读者(读起来"打点机"感) |
| 事实层 | 通篇有没有可核对的东西:数字、时间、地点、名字、金额 | 能测,计数 | 读者+平台(决定有没有人看下去) |
第三层最容易被忽略,但它决定了内容是否有人看。样本 S1(模型原生输出)实测 449 字、具体数字密度 0.0/千字——整篇没有一个可核对的事实。这种文本的问题不是"像 AI 写的",而是读完什么都没剩下,换个真人来写同样没人看。
结论前置:去AI味不是改文风,是补信息。 词表和节奏是"别让人一眼看出是模板",事实密度才是"让人愿意读完"。
脚本 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 次扣一样多),避免长文被单一维度压死;加分项存在意味着"总分"不是扣分的单调函数——这一点在第三节和第五节都会咬人。
样本来源(四份都是真实文本,不是我自己造的对照):
tools/samples/A1_llm_baseline.txt。tools/samples/A2_llm_rewritten.txt。汇总(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 分加分出现或消失。这就是第五节的活例:加分项既能盖住扣分,也会因为一句话就消失。
规则库第一版里有两条判据,跑完这四份样本就被否了。两条的否证过程今天都能用仓库里的脚本和文本复算一遍。
第一版的三项排比规则是 各项 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 分——"越规范的官方通报,分数越低"。
修正三条:
重庆、湖北、安徽、江苏、浙江等地 不触发排比扣分、且被计入对照读数(selftest 里的第 8、9 项)。教训:任何词表类检测器都会把"规范写法"当成"模板腔"。必须用正常人写的文本做负样本回归,否则它会逼着作者把正确写法改掉,那比不改还糟。
第一版门槛里有一条"句长变异系数 ≥ 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 检测器"宣称准确率时的同一个空档。
总分是加权和,加权和可以被单一维度刷:数字塞多点、词表一个不犯,总分就能堆上去(S4 是活例:命中两条句式模板,靠加分项回到 100 分)。所以最终判定不看总分,看 4 条彼此独立、不许互相补偿的门槛:
总分 ≥ 75
具体数字密度 ≥ 3/千字
无「升华口号」命中
无「无源数据」命中
推而广之,任何"评分卡式"的内容门禁都该这么设计:总分用于知道该改哪儿,硬门槛用于决定能不能发。 多维度加权求和再卡一个线,等于告诉生产端"你可以用某一个维度换另一个维度"。
| AI 腔 | 改成 |
|---|---|
| 首先…其次…最后… | 删掉连接词,句子之间靠内容顺序衔接 |
| 值得注意的是 / 综上所述 | 直接写那件事;要收尾就用最后一句事实收 |
| 研究表明 / 数据显示 | 换成你自己跑出来的数(本机实测/官方通报/文件名+页码) |
| 让我们一起…未来可期! | 删掉。用一句具体的事、一个数字或一句原话结尾 |
| 显著提升 / 极大地 | 换成"从 X 到 Y"(时间、金额、人数、次数) |
| 赋能 / 助力 / 深耕 / 打磨 / 闭环 | 换动词:做了什么、对谁、结果是什么 |
| 破折号解释 | 改句号或括号;每千字超过 3 个就该查 |
| 三连排比(长词组) | 留一项,剩下两项换句式 |
| 你有没有想过 | 把结论放进场景或数据,让读者自己得出 |
| 你的存款 / 你的房子 | 用画面和数字让读者代入,别对读者喊话 |
这是本工具最重要的一条边界,因为它关系到你会不会把"改了文风"错当成"会有流量"。
同一个账号 2026-09-01 发布的两篇图文,后台读数:展现 3536 次 / 阅读 0、展现 3540 次 / 阅读 0,两篇都没有推荐标记(未进推荐池)。稍晚发布的另一篇:71 次展现 / 1 次阅读。发布侧每一步都是绿的——接口返回成功、有内容编号、内容在后台可见;平台侧的读数是零阅读、不给推荐。
这说明两件事:
所以 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
接进流水线的三条纪律:
0/1/2 三个状态足够 CI 用;报告文本留给人看行号和改法。三档处置建议:
| 分数 | 处置 |
|---|---|
| ≥ 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 是第三方媒体的公开稿件,未随仓库分发(版权),按下面的地址和方式自行取文即可复现:
https://news.sina.com.cn/zx/gj/2026-09-23/doc-inisvumv5211565.shtmlhttps://finance.sina.com.cn/jjxw/2026-09-28/doc-initirzr3985011.shtml取文方式(正文段落怎么抽会影响统计量,所以把口径写清楚):
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(第四节那张表),所以任何"单条统计量阈值"都别信,除非它在上百份带标签样本上标定过。
selftest,且断言里既有"AI 腔必须被抓",也有"正常人写文本必须通过"--json + 退出码)📚 更多创作资源: https://afdian.com/a/felix007