用"古典"机器学习检测 LLM 生成的网络小说(AIGC 文本检测)
截至 2026 年初,主流 LLM 生成的文本表现出强烈的统计模式,使用传统机器学习模型可以有效地将其与人类撰写的内容区分开来。我怀疑这就是许多所谓的"AI 查重工具"背后的实际工作原理。
在线 Demo:https://lyc8503.github.io/AITextDetector/
此 Demo 使用的模型并非基于通用数据训练,也未经过严格的优化或迭代。其在测试集上的单句检测准确率约为 85%。使用前请阅读本文以了解潜在限制。
核心代码(草稿)和训练好的模型文件可在 GitHub 上获取:lyc8503/AITextDetector
背景(又名碎碎念)
半年前在学校写论文的时候,就已经有各种关于 AIGC 检测查重的传言。我测试了几个平台——知网、万方和一些第三方 AIGC 检测服务——发现它们确实能以不错的准确率区分我手写的文本和 LLM 生成的文本。
这激发了我对 AIGC 检测实际工作原理(以及如何绕过它)的好奇心。
但当时我手头的事情太多了——沉迷于无线电、Minecraft、东方 Project——在几次失败的尝试之后,我把这个想法搁置了。
最终我糊弄完了论文,生活继续。但最近刷 Lofter 的时候,我偶然发现整个标签都被低质量、严重角色崩坏的 AI 同人小说淹没了。
怎么一眼看出它们是 AI 写的?嗯,有些人连 Markdown 格式或 AI 生成的小标题都懒得清理就直接发出来了——然后还把文章的一半内容放在付费墙后面😓
然而大多数 AI 生成的文本更难被发现——它们混杂在各种写作风格、不同的提示词之中,并不那么明显。当你觉得不对劲的时候已经晚了。有些文本几乎无法证明是 AI 写的,让我疑神疑鬼。在被迫吞下太多 AI 生成的"劣质品"之后,我终于受够了。Lofter 到此为止——打开 VS Code 吧!
是的,这就是为什么我重启了这个周末项目:构建一个 AI 文本检测器……
研究尝试——碰壁
如今搜索 AIGC 检测,互联网几乎完全被广告污染了。每个搜索结果都是论文 AI 改写服务。那时候,我在垃圾信息中找到了一个叫文本困惑度(perplexity)的东西。
思路很简单:用一个现有的 LLM 来估算给定句子中每个单词出现的概率。如果几乎所有单词在 LLM 的预测中都排名靠前(Top-N),那么这句话很可能是 AI 生成的。反之,如果很多词是出人意料的,则更可能是人类写的。
听起来不错对吧?我花了一些时间尝试这个方法,但结果令人失望——大量误报和漏报,而且无法设定合理的阈值。此外还有实际工程问题:推理成本高、跨模型泛化能力差、大模型本地部署困难、闭源权重模型难以集成。总的来说,这种方法既不优雅也不可靠。
第一次尝试失败——被一堆软广告"教程"骗了
一个(还算)成功的尝试——scikit-learn SVM
既然网上资源没用,那就回归传统炼金术。
scikit-learn,启动!按照它的路线图,我们可以直接选择 Linear SVC 和 Naive Bayes 作为分类任务的良好起点。
(小声说:这也符合我的直觉——LLM 有可检测的用词模式;即使是 Naive Bayes 分类器也应该能捕捉到。只是我没料到信号会这么强。)
数据生成
传统炼金术传统分类器需要标注数据——所以我们需要人类撰写的文本和确认为 LLM 生成的文本进行训练。
我的做法是:我提取了 2023 年从某个类 Ford 和类 River 平台爬取的数据,筛选出 2010-2022 年间(ChatGPT 之前)发布的文章。我只过滤了极端短篇。然后用 7 种不同 LLM 生成同等数量的同领域/同人样本。提示词也多样化——一些是"写一篇 3000 字的故事",一些是模仿特定作者的写作风格。最终得到约 100,000 个样本,总字符数约 1 亿。
此外,我还从 Hugging Face 下载了一些中文 LLM 生成数据集,用作额外正样本。
数据生成的 API 费用大概 50 美元(豆包和 DeepSeek 便宜,Gemini 有免费额度,Kimi 把我账号封了……)。
Gemini API 使用量恐怖——只有它能在可接受的时间内处理长篇中文文本
数据量对比
训练
首先,用 CountVectorizer(基于字级别的 n-gram,n=2-4)对语料进行分词,然后通过 TfidfTransformer 将词袋转为 TF-IDF 特征。训练一个 LinearSVC 分类器。
是的,就是这么简单。没有特殊的预处理,没有停用词表,没有词性标注——只有一个玩具级别的 scikit-learn 流程。
# 典型的技术实现
第一阶段:
1. 用 CountVectorizer(ngram_range=(2,4), analyzer='char') 分词
2. 用 TfidfTransformer() 转为 TF-IDF
3. 训练 LinearSVC()
这在 scikit-learn 中大约十行代码,
而且精度惊人。
我尝试了 7 个不同的 LLM 作为正样本源:Gemini 2.5 Pro、Qwen 2.5 Max、Pony、Kimi K 2.5、GLM 4.7、Doubao Seed Code、Deepseek V3.2。
每个 LLM 单独训练一个二分类器。这是为了防止模型过拟合某个 AI 的特定写作风格(大家应该记得早期 ChatGPT 机器翻译那种一眼就能认出的特征)。
初步结果很不错:
加载 8536 个样本
训练集大小: 6820, 测试集大小: 1716
[gemini] 训练: 917,374 测试: 228,051
[gemini] 完整 TF-IDF + SVC ...
3,336,446 个特征 -> acc=0.8809 f1=0.8082 [tn=143688 fp=10650 fn=16503 tp=57210]
[qwen] 训练: 1,315,338 测试: 328,636
[qwen] 完整 TF-IDF + SVC ...
3,989,603 个特征 -> acc=0.8911 f1=0.8974 [tn=136293 fp=18045 fn=17739 tp=156559]
[pony] 训练: 1,128,044 测试: 278,663
[pony] 完整 TF-IDF + SVC ...
3,688,143 个特征 -> acc=0.8493 f1=0.8286 [tn=135085 fp=19253 fn=22755 tp=101570]
[kimi25] 训练: 1,088,007 测试: 269,567
[kimi25] 完整 TF-IDF + SVC ...
3,976,027 个特征 -> acc=0.8721 f1=0.8473 [tn=139390 fp=14948 fn=19534 tp=95695]
[glm47] 训练: 1,124,430 测试: 279,109
[glm47] 完整 TF-IDF + SVC ...
3,980,772 个特征 -> acc=0.8436 f1=0.8222 [tn=134461 fp=19877 fn=23786 tp=100985]
[doubao] 训练: 1,063,395 测试: 264,121
[doubao] 完整 TF-IDF + SVC ...
4,243,728 个特征 -> acc=0.8940 f1=0.8700 [tn=142420 fp=11918 fn=16089 tp=93694]
[deepseekv32] 训练: 1,176,294 测试: 289,042
[deepseekv32] 完整 TF-IDF + SVC ...
4,361,691 个特征 -> acc=0.8529 f1=0.8403 [tn=134625 fp=19713 fn=22819 tp=111885]
=====================================
总结
=====================================
model s1 acc s1 f1
gemini 0.8809 0.8082
qwen 0.8911 0.8974
pony 0.8493 0.8286
kimi25 0.8721 0.8473
glm47 0.8436 0.8222
doubao 0.8940 0.8700
deepseekv32 0.8529 0.8403
所有模型都达到了超过 85% 的准确率和超过 80% 的 F1 分数——相当不错!我还注意到 AI 生成的文本通常会被多个模型同时标记,所以投票机制非常合理。
我也试了 MultinomialNB 和 SGDClassifier,但准确率有所下降。BERT 有轻微提升但需要太多 GPU 时间——放弃了。甚至试了 AutoGluon,它不知怎么只有 53% 的二分类准确率。这些就不深入了。
完成所有数据后,我尝试训练了一个 8 分类模型(人类 + 7 种 AI),但各个 LLM 之间的差异似乎太小——可能因为它们都是互相蒸馏出来的——所以分类效果很差,只有约 50% 的准确率。
多分类结果——显然不可分。也许是我模型太烂,但无所谓,不重要
最终我训练了七个独立的二分类器,使用多数投票:如果一个句子被 ≥2 个模型标记为 AI,则判定为 AI。
=====================================
总结 top-500,000 C=1.0
=====================================
model s1 acc s2 acc Δacc s1 f1 s2 f1 Δf1
gemini 0.8809 0.8721 -0.0088 0.8082 0.7986 -0.0096
qwen 0.8911 0.8819 -0.0092 0.8974 0.8886 -0.0088
pony 0.8493 0.8383 -0.0109 0.8286 0.8173 -0.0113
kimi25 0.8721 0.8623 -0.0098 0.8473 0.8376 -0.0097
glm47 0.8436 0.8311 -0.0125 0.8222 0.8097 -0.0125
doubao 0.8940 0.8869 -0.0071 0.8700 0.8624 -0.0076
deepseekv32 0.8529 0.8419 -0.0110 0.8403 0.8291 -0.0112
平均 0.8592 0.8348
最低准确率: 0.8311
=====================================
Web Demo 的 JS 实现
到这一步,我本可以把仓库一发就完事了。但每次启动 Python 实在太不方便了。我也可以搭建一个 Python API 服务,但这意味着服务器维护——违反了我不留服务器的严格原则。
我最初的计划:将模型导出为 ONNX,通过 ONNX Web Runtime 在 Wasm 中推理。但当我的硅基仆人 Claude 帮忙时,我没说清楚——它跑偏了,把模型裁剪并导出为 JSON……然后在 JavaScript 中完整实现了 TF-IDF + SVM,在浏览器中进行推理。
嗯……其实也不算坏主意。我在 100 万字符的文本上测试了一下——在我的机器上大约花了 10 秒,可以接受。对于典型的几千字符输入,几乎是即时的。
好吧,既然这只是个 Demo,而且 JS 方法更透明,我就保留这个有点蠢的实现吧。(都怪 Claude,不怪我。)
至于准确率:我测试了不同的特征数量限制。最终优先考虑性能,保留了 50 万个特征。以 JSON 存储,体积膨胀到 107MB(不过通过服务端 gzip 压缩后约 38MB)。更小的版本(5万-8万特征)只损失了 3-4% 的准确率,但最终的 AI 检测率波动很大——尤其是对人类文本,有 ±50% 的相对差异,导致了误报。所以我还是用了 50 万特征。
最终准确率下降:约 1%,如下所示:
============================================================
总结 top-500,000 C=1.0
============================================================
model s1 acc s2 acc Δacc s1 f1 s2 f1 Δf1
gemini 0.8809 0.8721 -0.0088 0.8082 0.7986 -0.0096
qwen 0.8911 0.8819 -0.0092 0.8974 0.8886 -0.0088
pony 0.8493 0.8383 -0.0109 0.8286 0.8173 -0.0113
kimi25 0.8721 0.8623 -0.0098 0.8473 0.8376 -0.0097
glm47 0.8436 0.8311 -0.0125 0.8222 0.8097 -0.0125
doubao 0.8940 0.8869 -0.0071 0.8700 0.8624 -0.0076
deepseekv32 0.8529 0.8419 -0.0110 0.8403 0.8291 -0.0112
平均 0.8592 0.8348
最低准确率: 0.8311
============================================================
测试效果
以下所有测试均使用裁剪后的 Web 版本,其性能应与完整 joblib 模型相近。
当前逻辑:将输入文本拆分为句子,清洗后使用全部 7 个二分类模型进行分类。如果 ≥2 个模型标记某句为 AI,则将其标记为疑似 AI 并高亮显示。最终 AI 分数是标记字符的比例。分类如下:
低于 50%:人类
50-70%:可能是人类
高于 70%:可能是 AI
首先,测试常见模型如豆包和 DeepSeek 的检测率——它们都在训练数据中。提示词:write me a 3000-word story。轻松识别:
DeepSeek V3.2:78.4%
豆包 Seed Code:93.0%
现在测试未见过的模型——泛化能力如何?
Claude Sonnet 4.6:71.9%
GPT 5.2:73.3%
我还测试了其他几个不在训练集中的模型(MiMo-V2、豆包 Seed 2.0、GPT-4o)——全部在大约 70% 的水平被检测到,有些甚至超过 90%。效果扎实。
还测试了更复杂的提示词——例如,喂入 20 章人类撰写的文本,要求 LLM 模仿风格续写。检测率略有下降至 67.8%(但记住,我们的训练数据中也包含复杂提示词)。由于篇幅原因不展示结果。
然后我从订阅列表中挑选了 10 部完本网络小说(2022 年之前)——涵盖不同题材、作者和时期,很可能不在训练数据中。
它们的 AI 检测率为:22.7%、24.2%、25.0%、24.5%、19.0%、13.7%、29.1%、4.9%、27.3%、19.2%——全部低于 30%。我还随机采样了 Lofter 上的同人作品;由于它们更偏向随性写作,检测率通常低于 10%。但当我把怀疑是 AI 生成的文本输入时,检测率飙升至 83.4%,强烈表明存在未经声明使用 LLM 的情况。
[2026 年 3 月 5 日更新] 为了更严格的测试,我从 Lofter 中随机采样了 10,000 篇高互动量(阅读量 >5000)、长篇幅(字数 >2000)的同人作品,均发布于 2022 年之前。它们的 AI 检测率分布(使用 7 模型投票,≥2 票)如下:
0-5%:313 | 5-10%:1945 | 10-15%:3016 | 15-20%:2033 | 20-25%:1355 | 25-30%:594 | 30-35%:492 | 35-40%:123 | 40-45%:34 | 45-50%:62 | 50-55%:24 | 55-60%:5 | 60-65%:3 | 65-70%:1
以 60% 为阈值 → 误报率:0.04%
以 70% 为阈值 → 误报率:<0.01%(实际上为零)
超过 60% 的四篇文本均为合集索引,并非实际小说——因链接过多被标记。
即使在 50% 阈值下,误报率也只有 0.33%。
然后,我爬取了 Lofter Android 端前 20 个热门标签(周榜)的所有文章,按长度筛选后运行检测:
0-5%:27 | 5-10%:138 | 10-15%:231 | 15-20%:245 | 20-25%:238 | 25-30%:137 | 30-35%:112 | 35-40%:87 | 40-45%:116 | 45-50%:112 | 50-55%:118 | 55-60%:157 | 60-65%:118 | 65-70%:109 | 70-75%:75 | 75-80%:56 | 80-85%:28 | 85-90%:15 | 90-95%:10
32.22% 的文章评分超过 50% AI——很可能部分或全部由 AI 生成……还有人类在写吗??而且没有任何一篇主动声明使用 AI 生成内容。
"末法时代,末法时代,,,"——群友语
攻防对抗——绕过检测(?)
好了,我们建了一个 AIGC 检测器。现在该建一个反检测器了。
不,开玩笑的。我还没那么无聊。
不过让我们测试一些常见的反 AIGC 检测技巧:
经典翻译法
Google 翻译来回(中→英→中):89.9% → 85.0%
有道翻译(中→英→中):89.9% → 79.2%
搜狗翻译(中→英→中):89.9% → 86.0%
轻微下降,但仍然明显被标记。
LLM 提示词法
用"魔法"提示词让 LLM 输出不那么"AI 味"——听起来就很扯!
测试了一行提示词:Rewrite the above article to minimize AI flavor:89.9% → 83.0%
还试了更复杂的提示词:89.9% → 79.3%
略有改善,但实际中仍然毫无意义。
这个检测方法太强了!(flag 已立)
如果真的想绕过它,我唯一能想到的方法是:在大量人类文本上微调一个 LLM,或者构建一个庞大的基于规则的系统来精准破坏 SVM 匹配的特征。但这超出了本文的范围。不知道是否可行。或者也许有更好的方法——留给读者作为练习。
结语
现在,开始收尾的碎碎念。
我真的没想到这个分类任务如此简单——简单到用 scikit-learn 的"Hello World"级脚本,加上极少的迭代和一些硬编码规则,就能产出一个相当鲁棒且准确的检测器。大部分时间都花在等 LLM 生成数据上……
有志的读者可以按照这种方法为其他领域训练检测器——比如学术论文 AIGC 检测。再加一个花哨的前端,你就有了一个可以卖给绝望的大学生的工具。要是赚到钱了别忘了捐点。
另一个想法:检测 AI 生成的图片。但有了 Stable Diffusion 和便捷的 LoRA 微调,视觉风格的多样性远高于文本——这个任务会困难得多。写到这里,我的三分钟热度已经耗尽了。下次再说吧。
最后,对 AI 生成内容说几句。我不接受将 AI 生成的娱乐内容视为真正的创作。就像 AI 编码工具会生成臃肿、不可维护的代码一样,AI 生成的文本、图像和音频乍一看似乎还不错,但仔细看就露馅了——重复、肤浅、如此可预测以至于连词频统计都能抓住它们。这种模式从根本上不适合真正的创作,作为一名读者,我深感不满。我开始怀疑 LLM 所谓的"创意写作"不过是一堆训练数据被不断地重新组合和复读。
但话又说回来,"世界应该怎样"从来就不等于"世界实际怎样"。虽然 LLM 带来了创新和生产力提升,但滥用和误用正在每个行业中无情蔓延。而且由于 LLM 经过微调以利用人类感知,谁知道它们到底是"理解"了什么还是只是记忆了模式呢?在修复了无数像"3.9 和 3.11 哪个大"或"要不要开 50 米去洗车"这样的 bug 之后,我们还能说模型真的理解世界吗?
每个人都在争论中迷失:LLM 到底是什么?它将如何颠覆我的行业?AI 会把人类带向何方?没有人有答案。
至少我很庆幸自己在 AI 时代之前学会了编程。否则,我可能意识不到今天的"氛围编程"软件有多蠢。至于未来?要么生成式 AI 把人类社会秩序砸个粉碎,要么 AI 泡沫破裂、记忆体变得一文不值。无论哪种,听起来都不错,不是吗?