当前位置: 首页 > news >正文

内网离线环境下自研AIGC生成文本痕迹识别方案,全流程可落地

上周接了公司运维侧的需求,要给内部研发文档仓库做AIGC内容筛查,所有数据不能出内网,完全禁止联网调用外部接口。

一开始图省事,测了七八款市面上现成的检测工具,什么GPTZero、Originality.ai、百度文心检测、团象AICG检测、Copyleaks、Crossplag,全试了一圈,要么要联网校验,要么本地版准确率不到60%,根本没法用。

这些工具要么强制上传文本到公网接口,要么本地部署包体积超过20G跑起来占满8卡资源,全满足不了需求,只能硬着头皮自研。

一开始我想直接套开源的roberta-base预训练模型微调,跑了一周下来,对中文长文本的准确率只有52%。

误判率高到把我存了3年的手写老研发笔记,直接标成100%AIGC生成的,完全没法落地使用。

排查了3天终于找到根因:大模型生成的文本不是靠“敏感关键词”判断,很多低阶统计特征是人类写作不会出现的,直接用语义预训练模型反而会学到大量无关特征。

我们最终放弃了重型预训练分类的思路,全部从底层统计特征入手,搭出了整套轻量方案。

第一类核心特征是Token级别的概率分布偏差。 大模型解码的时候是逐token选top k概率最高的结果,所以生成文本所有token的对数概率分布,和人类手写内容的偏差极大。

我们用开源的中文base模型chinese-llama-7b做前向推理,统计每个token的生成概率,代码如下:

import torch from transformers import LlamaTokenizer, LlamaForCausalLM def get_token_log_probs(text: str, model, tokenizer) -> list[float]: inputs = tokenizer(text, return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model(**inputs, labels=inputs["input_ids"]) logits = outputs.logits[:, :-1, :] log_probs = torch.nn.functional.log_softmax(logits, dim=-1) target_log_probs = log_probs.gather(2, inputs["input_ids"][:, 1:].unsqueeze(-1)).squeeze(-1) return target_log_probs.squeeze().cpu().numpy().tolist()

这里要注意把label设为input_ids,避免触发模型微调逻辑,只拿前向的输出概率,不需要反向传播,单条1000字文本推理耗时不到100ms。

人类写的文本会大量出现概率低于1e-6的“低概率token”,也就是我们常说的灵光一闪的个性化表达,大模型生成的文本几乎不会出现这类token。

我们统计了10万条人工标注的样本:5万纯人工写的研发文档,5万不同大模型生成的文档。 人工文本的低概率token占比均值在17.2%,AI生成文本的该指标均值仅为2.3%,两类样本的重叠占比不到4%,区分度极高。

第二类核心特征是标点与停用词的分布异常。 很多人忽略了,大模型生成文本的时候,对停用词、标点的选择偏好极其统一,和人类写作的习惯偏差很大。

比如中文里的逗号,人类写1000字平均会用32-47个,AI生成的文本逗号占比基本稳定在千分之55以上,连续三个逗号的出现概率是人类的7倍。

还有“的”“了”“在”这类高频停用词,不同的人类写作者的使用频率方差极大,但大模型生成的文本里这类词的占比几乎不会跳出千分之25-30的窄区间。

这里我踩过一个坑:不要做全局的停用词占比统计,要按200字的滑动窗口统计方差。 人类文本的窗口方差基本在0.02以上,AI生成的大多低于0.005,区分度比全局统计高3倍以上。

实现这个特征的代码非常轻量,不需要加载大模型,纯CPU就可以1s处理10万字:

from collections import Counter import torch STOP_WORDS = {"的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一"} def stop_word_var(text: str, window_size:int=200) -> float: chars = list(text) ratios = [] for i in range(0, len(chars), window_size//2): window = chars[i:i+window_size] cnt = Counter(window) total_stop = sum(cnt.get(w, 0) for w in STOP_WORDS) if len(window) == 0: continue ratios.append(total_stop / len(window)) return torch.tensor(ratios).var().item()

这段代码跑出来的方差特征,单独用就可以拿到72%的二分类准确率,配合前面的token概率特征,准确率直接拉到89%。

第三类核心特征是语义跳转的连贯性偏差。 人类写技术文档的时候,经常会出现逻辑跳转,比如写着写着突然插一句临时的注意事项,甚至有笔误的痕迹。

大模型生成的文本的语义向量的余弦相似度,在相邻段落之间的跳转极其平滑,几乎不会出现超过0.3的语义跳转变异。

这里我们不用大模型做embedding,直接用tf-idf特征统计相邻短块的向量余弦差,避免额外加载embedding模型拖慢性能。

这里踩的坑是不要用全量段落做对比,要把文本切成50字的短块,统计相邻短块的tf-idf向量的余弦值的方差,AI生成文本的该方差基本低于0.01。

把前面三个维度的特征全部抽出来之后,不需要用重型的分类模型,直接用轻量的XGBoost做二分类训练,就可以拿到远超之前预训练大模型的效果。

训练集我只用了2万条标注样本,其中正负样本1:1,训练10轮就收敛了,整个模型文件大小不到20M,不需要GPU,纯CPU部署就可以跑满每秒100篇的检测速度。

这里要做一个关键的阈值校准,不能直接用默认的0.5的分类阈值,要根据自己的业务场景调整。 我们的场景要求误判率不能超过1%,毕竟不能把研发辛辛苦苦写的文档随便标成AI生成的,所以把判定阈值调到了0.9。

调完阈值之后,我们在1万条测试集上的结果:误判率0.87%,召回率82.3%,完全满足内网文档筛查的需求。

一开始整个流程跑通的时候,单篇1万字的文本要耗时2.7s,在文档全量扫描的时候速度太慢,根本跑不完几十万存量文档。 排查了性能瓶颈,发现90%的耗时都花在了7b模型的token概率推理上,我们做了几个小优化,直接把速度提升了8倍。

第一个优化:把7b的LLaMA模型量化成4bit,用GPTQ加载,推理精度损失不到0.1%,但显存占用从14G降到了3.8G,推理速度直接翻了3倍。

from auto_gptq import AutoGPTQForCausalLM model = AutoGPTQForCausalLM.from_quantized( "chinese-llama-7b-4bit", device_map="auto", use_safetensors=True )

第二个优化,做批量推理,每次喂32条短文本进去推理,利用GPU的并行能力,单卡A10就可以做到每秒处理超过30万字的文本。 第三个优化,提前过滤短文本,低于200字的文档直接跳过检测,这类短文本没有足够的特征支撑判断,误判率极高,本来就没有检测的意义。

优化完之后,我们扫完仓库里全部37万份存量研发文档,只用了不到2个小时,完全符合预期。 之前测试的时候发现有一批文档被批量误判,排查了很久才找到原因,这批文档是研发用Markdown写的,里面全是代码片段。

我们在特征提取之前,必须先用正则把所有的```包裹的代码块全部过滤掉,不能放进检测流程里,不然代码的字符分布完全不符合自然语言特征,会直接误判。 还有就是要过滤掉表格内容、自动生成的接口文档注释,这类机器生成的文本本身就不是自然语言,混入之后会直接拉高误判率。

最后还要做白名单机制,把老员工的常用写作习惯做小样本微调,单独校准阈值。 比如有的老工程师就爱用短句,逗号用的少,单独调完之后就不会被误判,整体体验顺滑了很多。

这次折腾下来最大的感受,就是很多场景根本不需要追新用什么千亿大模型做检测,把基础的统计特征摸透,轻量方案反而效果更好。 完全不用花大价钱采购商用服务,小团队照着这个流程搭一套,一周就能跑通,数据完全留在内网里,适配性比外部方案强不少。

http://www.cnnetsun.cn/news/3263564.html

相关文章:

  • 告别激活烦恼:KMS智能激活脚本如何让你的Windows和Office重获新生
  • Vegeta HTTP 负载测试工具
  • Navicat无限试用终极指南:3种强力方案解决Mac版试用期限制
  • C# WinForm俄罗斯方块项目解析:从核心逻辑到界面实现
  • LangChain Memory 架构演进全图谱(2022–2024核心迭代路径与企业级选型决策树)
  • 5分钟掌握ModTheSpire:杀戮尖塔模组加载器终极指南
  • 抖音批量下载工具:轻松保存无水印视频与音乐的终极方案
  • YaeAchievement:原神成就数据导出与UIAF格式转换终极指南
  • 167、动态分配策略中 Anchor Ratio 的重新聚类:用 K-Means 调整 YOLOv11 默认 Anchor
  • 蓝牙5.4音频开发实战:低延迟高保真方案解析
  • Spatial-Agent:大模型驱动的地理分析工作流引擎
  • 机器学习中的觉的距离和相似度
  • eBPF 与 OpenTelemetry 融合:构建分布式追踪的 eBPF 探针
  • d3d8to9:让Direct3D 8游戏在Windows 10/11上完美运行的终极解决方案
  • 汽车行业术语
  • 玄学中的系统论:从周期律到模型训练的收敛震荡
  • 程序员必读书单
  • Windows 11 LTSC 24H2 企业版一键安装微软商店:完整解决方案指南
  • Windows 11任务栏拖放功能缺失?3分钟教你如何优雅修复
  • CAF空间原型与肿瘤微环境观察:七类基质生态位的研究启发
  • Speechless:如何用这款免费Chrome插件永久保存你的微博记忆?
  • 终极指南:如何用MCA Selector轻松管理你的Minecraft世界存档
  • JavaScript 网盘文件直链获取技术方案:重构下载体验的现代实现
  • 公司注销登报清算公告多少钱?2026各省报纸收费标准与流程
  • Wand-Enhancer:免费解锁WeMod高级功能的本地化增强方案
  • Unity全景图实现:天空盒与Shader球体技术对比与实战指南
  • Django-Admin和第三方插件Xadmin
  • 解锁B站高阶体验:3个核心场景下的Bilibili-Evolved实战指南
  • OpenCore Legacy Patcher终极指南:如何让老旧Mac焕发新生体验现代macOS
  • VisualCppRedist AIO终极指南:一键解决Windows C++运行库依赖难题