使用“经典”机器学习检测LLM生成的文本

Hacker News Top 工具

摘要

一位开发者探索使用经典机器学习来检测LLM生成的网络小说,创建了一个开源演示和模型,单句准确率约为85%。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/16 16:52

# 使用“经典”机器学习检测 LLM 生成的小说文本(AIGC 文本检测) 来源:https://blog.lyc8503.net/en/post/llm-classifier/ 本文目前是实验性的机器翻译,可能存在错误。若有不清晰之处,请参考原始中文版本。我正持续改进翻译。 ## https://blog.lyc8503.net/en/post/llm-classifier/#TL-DR-DemoTL;DR 与演示 截至 2026 年初,主流 LLM 生成的文本表现出强烈的统计规律,可以使用传统机器学习模型有效区分人工撰写内容。我怀疑很多所谓的“AI 查重工具”底层就是这么工作的。 在线演示:https://lyc8503.github.io/AITextDetector/ 本演示所用的模型**并非在通用数据上训练**,也未经过严格的优化和迭代。其在测试集上的单句检测准确率约为 85%。使用前请通读本文,了解潜在局限性。 核心代码(草稿)和训练好的模型文件可在 GitHub 获取:lyc8503/AITextDetector (https://github.com/lyc8503/AITextDetector) ## https://blog.lyc8503.net/en/post/llm-classifier/#Background-aka-Useless-Rambling背景(又名废话连篇) 半年前还在学校写论文的时候,就已经有关于查重 AIGC(AI 生成内容)的传闻了。我测试了几个平台——知网、万方以及一些第三方 AIGC 检测服务——发现它们确实能比较准确地分辨我手写的文本和 LLM 生成的文本。 这激起了我的好奇心:AIGC 检测到底是怎么工作的?(以及如何绕过它。) 但当时我在太多事情上分身乏术——沉迷于无线电 (https://blog.lyc8503.net/categories/%E7%A1%AC%E4%BB%B6%E3%81%AE%E7%A0%94%E7%A9%B6/%E6%97%A0%E7%BA%BF%E5%B0%84%E9%A2%91/)、Minecraft、东方 Project——尝试了几次失败后,我就搁置了这个想法。 最终我混过了论文,生活继续。但最近逛 Lofter 的时候,我发现有些标签页整个被低质量、严重 OOC(Out of Character)的 AI 同人文淹没了。 我怎么一眼看出是 AI 写的?嗯,有些人(或女士)甚至懒得在发帖前清理 Markdown 格式或 AI 生成的章节标题——然后还把一半内容放在付费墙后面 😓 不过,大多数 AI 生成的文本更难发现——它们混杂在多样的写作风格、不同的提示词中,不那么明显。等你觉得不对劲时,已经晚了。有些文本几乎无法证明是 AI 写的,让我疑神疑鬼。在咽下几篇 AI 写的垃圾文后,我终于受够了。Lofter 浏览到此为止——打开 VS Code! 没错,我就是这样重启了我的周末项目:构建一个 AI 文本检测器…… ## https://blog.lyc8503.net/en/post/llm-classifier/#Research-Attempt-%E2%80%93-No-Luck研究尝试——运气不佳 现在网上搜索 AIGC 检测,几乎全是广告。每个结果都是另一家论文 AI 改写服务。当时我费尽心思,找到了一个叫做文本困惑度 (https://zhuanlan.zhihu.com/p/114432097) 的东西。 思路很简单:用现有的 LLM 来估计每个词在给定句子中出现的概率。如果几乎每个词在 LLM 的预测中排名都很高(Top-N),那么这个句子很可能是 AI 生成的。反之,如果很多词出乎意料,则更可能是人类写的。 听起来很有希望?我花了一些时间尝试这个方法,但结果令人失望——假阳性和假阴性都很多,而且无法设定合理的阈值。此外还有实际问题:推理成本高、跨模型泛化能力差、难以本地部署大型模型、闭源权重难以集成。总的来说,这种方法既不优雅也不可靠。 失败尝试——被一堆软广“教程”骗了失败尝试——被一堆软广“教程”骗了 ## https://blog.lyc8503.net/en/post/llm-classifier/#A-Somewhat-Successful-Attempt-%E2%80%93-scikit-learn-SVM一个(某种程度上)成功的尝试——scikit-learn SVM 既然网上资源没用,那就回归传统炼丹。 Scikit-learn,启动!按照它的路线图 (https://scikit-learn.org/stable/machine_learning_map.html),我们可以直接选择 Linear SVC 和 Naive Bayes 作为我们分类任务的良好起点。 (小声说:这符合我的直觉——LLM 有可检测的用词模式;即使是朴素贝叶斯分类器也能捕捉到。我只是没想到信号这么强。) ### https://blog.lyc8503.net/en/post/llm-classifier/#Data-Generation数据生成 传统炼丹——传统分类器需要标注数据——所以我们需要人类写的文本和确认是 LLM 生成的文本用于训练。 我的方法:我拉取了 2023 年从某个类似福特和类似河流的平台上爬取的数据,筛选出 2010–2022 年(ChatGPT 之前)发表的文章。我只过滤掉了互动极低或篇幅极短的作品,然后随机抽样了近 10000 篇数千字的长文作为人类样本。 然后,我用 LLM 生成这些文本的章节目录,再将目录喂回 LLM,让它重新生成完整的文章。这样就得到了数量大致相等的 LLM 生成样本,体裁多样,且与原始人类内容高度匹配。 理论上如此。但 LLM API 很贵,我可不想在周末项目上花几千块。所以我动用了点小聪明——打了擦边球——利用了多个低成本或免费的 API 渠道: - **Gemini**:使用 CLIProxyAPI (https://github.com/router-for-me/CLIProxyAPI) 将 Antigravity/Gemini CLI 配额转换成 API 访问——只需花约 20 美元买个 AI Pro 账户 - **Qwen**:qwen-code (https://github.com/QwenLM/qwen-code) 可以逆向工程 Qwen Plus API——免费 - **GLM-5**:时机恰到好处——OpenRouter 当时免费提供 GLM-5 公测版(Pony Alpha) - **Kimi、Deepseek、Doubao、GLM-4.7**:在某编程推广计划期间注册——首月 8.9 美元,解锁 API 访问 免责声明:这不是推荐。这些行为违反平台服务条款,可能导致封号。但平台忙着搞营销造势,没空管我,而我也不打算付全价。 许多面向编程的 LLM API 按调用次数收费,但我们可以“优化”一下:把任务批处理成大输入,迫使 LLM 一次生成更多内容。于是…… 你以为我用了超过 3 亿 Gemini tokens,按全价得花 2000 美元?!你以为我用了超过 3 亿 Gemini tokens,按全价得花 2000 美元?! 最终,我使用 `gemini-3-flash` 生成摘要,并用七个不同的模型(`gemini-3-pro`、`qwen-coder-plus`、`glm-5`、`glm-4.7`、`kimi-k2.5`、`doubao-seed-code`、`deepseek-v3.2`)生成七组 LLM 样本。 生成的文件生成的文件 ### https://blog.lyc8503.net/en/post/llm-classifier/#Training训练 数据生成到一半的时候,我忍不住开始训练了。 我让 Claude 写分类器代码,它天真地把整段原始文本直接丢进模型——达到了可疑的 99.45% 准确率……等等,真的吗? Claude 不行,我自己来。训练时,我使用中文标点把所有文本切分成句子,清理非中英文字符,然后应用 scikit-learn 的 `TF-IDF` → `LinearSVC`。清理掉一些噪声后,句子级别的分类仍然达到了约 85% 的准确率! 即使是这个有 bug 的初版也达到了 88% 准确率(后来优化到 85%)即使是这个有 bug 的初版也达到了 88% 准确率(后来优化到 85%) 单个句子包含的信息有限,但 85% 的句子级准确率意味着对于较长的文章,我们可以非常有信心地判断它是否是 AI 生成的。这个表现远超我的预期。传统 ML 还是能打——比那些只是问 LLM“嘿,这文本是 AI 写的吗?”的傻缺在线工具强多了。 完成所有数据后,我尝试训练一个 8 分类模型(人类 + 7 种 AI),但 LLM 之间似乎太相似了——可能互相蒸馏过——所以分类很乱,准确率只有约 50%。 多分类结果——显然分不开。可能是我的模型烂,但无所谓,不重要多分类结果——显然分不开。可能是我的模型烂,但无所谓,不重要 最后,我训练了七个独立的二分类器,并使用多数投票:如果 ≥2 个模型检测为 AI,则该句子被标记为 AI。 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ``` ``` loaded 8536 samples train chapter size: 6820, test chapter size: 1716 [gemini] Train: 917,374 Test: 228,051 [gemini] full TF-IDF + SVC ... 3,336,446 features -> acc=0.8809 f1=0.8082 [tn=143688 fp=10650 fn=16503 tp=57210] [qwen] Train: 1,315,338 Test: 328,636 [qwen] full TF-IDF + SVC ... 3,989,603 features -> acc=0.8911 f1=0.8974 [tn=136293 fp=18045 fn=17739 tp=156559] [pony] Train: 1,128,044 Test: 278,663 [pony] full TF-IDF + SVC ... 3,688,143 features -> acc=0.8493 f1=0.8286 [tn=135085 fp=19253 fn=22755 tp=101570] [kimi25] Train: 1,088,007 Test: 269,567 [kimi25] full TF-IDF + SVC ... 3,976,027 features -> acc=0.8721 f1=0.8473 [tn=139390 fp=14948 fn=19534 tp=95695] [glm47] Train: 1,124,430 Test: 279,109 [glm47] full TF-IDF + SVC ... 3,980,772 features -> acc=0.8436 f1=0.8222 [tn=134461 fp=19877 fn=23786 tp=100985] [doubao] Train: 1,063,395 Test: 264,121 [doubao] full TF-IDF + SVC ... 4,243,728 features -> acc=0.8940 f1=0.8700 [tn=142420 fp=11918 fn=16089 tp=93694] [deepseekv32] Train: 1,176,294 Test: 289,042 [deepseekv32] full TF-IDF + SVC ... 4,361,691 features -> acc=0.8529 f1=0.8403 [tn=134625 fp=19713 fn=22819 tp=111885] ===================================== SUMMARY ===================================== 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% 的二分类准确率。不细说了。 ### https://blog.lyc8503.net/en/post/llm-classifier/#JS-Implementation-for-Web-DemoWeb 演示的 JS 实现 到这一步,我完全可以发布一下仓库就走人。但每次都启动 Python 实在 *太* 不方便了。我可以托管一个 Python API,但那意味着要维护服务器——违反了我严格的 Serverless 原则。 我最初的计划:将模型导出为 ONNX,通过 ONNX Web Runtime 在 Wasm 中运行推理。但当我让我的硅基仆人 Claude 帮忙时,我没说清楚——它跑偏了,修剪并导出了模型为 JSON……然后在浏览器中用 JavaScript 实现了完整的 TF-IDF + SVM 推理。 嗯……其实也不算坏主意。我在 100 万字符的文本上测试了——在我的机器上大约花了 10 秒,可以接受。对于典型的几千字符输入,几乎是瞬间完成。 好吧,既然这只是个演示,而且 JS 实现更透明,我就保留这个 *有点蠢* 的实现。(怪 Claude,不怪我。) 至于准确率:我测试了不同的特征数量限制。最终为了性能优先,保留了 50 万个特征。保存为 JSON 后,体积臃肿到 107MB(不过服务端用 gzip 压缩后约 38MB)。更小的版本(5万–8万)只损失了 3–4% 的准确率,但最终的 AI 检测率变化很大——尤其是在人类文本上,相对差异达到了 ±50%,导致假阳性。所以我坚持用 50 万。 最终准确率下降约 1%,如下所示: ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ``` ``` ============================================================ SUMMARY 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 AVG 0.8592 0.8348 MIN acc: 0.8311 ============================================================ ``` ### https://blog.lyc8503.net/en/post/llm-classifier/#Testing-Performance性能测试 以下所有测试均使用精简后的网页版本,其性能应与完整的 joblib 模型接近。 当前逻辑:将输入文本按句子切分,清理后使用所有 7 个二分类模型进行分类。如果 ≥2 个模型标记某个句子,则标记为疑似 AI 并高亮。最终 AI 得分是被标记字符的比例。分类如下: - <50%:人类 - 50–70%:可能人类 - > 70%:可能 AI 首先测试常见模型如 Doubao 和 Deepseek 的检测率——它们都在训练数据中。提示词为:“给我写一个 3000 字的故事。”轻松捕获: Deepseek V3.2:78.4%Deepseek V3.2:78.4% Doubao Seed Code:93.0%Doubao Seed Code:93.0% 现在测试未见过的模型——泛化能力如何? Claude Sonnet 4.6:71.9%Claude Sonnet 4.6:71.9% GPT 5.2:73.3%GPT 5.2:73.3% 我测试了其他几个不在训练集中的模型(MiMo-V2、Doubao-Seed-2.0、GPT-4o)——全部检测到约 70%,有的甚至 >90%。很扎实。 还测试了更复杂的提示词——例如,给 LLM 喂 20 章人类写的文本,要求它模仿风格继续写。检测率略有下降至 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 随机抽样了 10000 篇高互动量(浏览量 >5000)、长篇幅(字数 >2000)的同人文,全部发布于 2022 年之前。它们的 AI 检测率分布(使用 7 模型投票,≥2 票): ``` 1 ``` ``` 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 安卓端热门标签页(周榜)前 20 个标签中的所有文章,按长度筛选后运行检测: ``` 1 ``` ``` 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 |

相似文章

不要让LLM说话,直接探测它(8分钟阅读)

TLDR AI

本文介绍了一种技术,该技术从LLM的最后一个提示标记处提取隐藏状态,无需文本生成即可进行分类,使用一个小型MLP读取模型的内部决策,从而实现快速且廉价的零样本分类器。

检测议会文本中未披露的LLM生成内容

arXiv cs.CL

本文通过训练一个可解释的文本分类器,评估了英国和瑞典议会文本中未披露的LLM生成内容的程度,发现自2022年以来未披露的LLM使用量稳步增加。

LLM生成文本中的文学非风格

arXiv cs.CL

本文使用n-gram分布分析LLM生成文本中的统计模式,揭示了其风格缺陷,并表明风格与语义不可分离。