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

📅 2026/7/10 13:23:51
内网离线环境下自研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_tensorspt).to(model.device) with torch.no_grad(): outputs model(**inputs, labelsinputs[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:int200) - float: chars list(text) ratios [] for i in range(0, len(chars), window_size//2): window chars[i:iwindow_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_mapauto, use_safetensorsTrue )第二个优化做批量推理每次喂32条短文本进去推理利用GPU的并行能力单卡A10就可以做到每秒处理超过30万字的文本。 第三个优化提前过滤短文本低于200字的文档直接跳过检测这类短文本没有足够的特征支撑判断误判率极高本来就没有检测的意义。优化完之后我们扫完仓库里全部37万份存量研发文档只用了不到2个小时完全符合预期。 之前测试的时候发现有一批文档被批量误判排查了很久才找到原因这批文档是研发用Markdown写的里面全是代码片段。我们在特征提取之前必须先用正则把所有的包裹的代码块全部过滤掉不能放进检测流程里不然代码的字符分布完全不符合自然语言特征会直接误判。 还有就是要过滤掉表格内容、自动生成的接口文档注释这类机器生成的文本本身就不是自然语言混入之后会直接拉高误判率。最后还要做白名单机制把老员工的常用写作习惯做小样本微调单独校准阈值。 比如有的老工程师就爱用短句逗号用的少单独调完之后就不会被误判整体体验顺滑了很多。这次折腾下来最大的感受就是很多场景根本不需要追新用什么千亿大模型做检测把基础的统计特征摸透轻量方案反而效果更好。 完全不用花大价钱采购商用服务小团队照着这个流程搭一套一周就能跑通数据完全留在内网里适配性比外部方案强不少。