Python实现全离线轻量AIGC文本筛查:无API依赖+内存占用<200M
最近团队要做内网知识库的全离线内容合规校验,所有文本不许出域,试了市面上一堆工具,比如GPTZero、Originality.ai、团象AIGC检测、Copyscape、Crossplag,全没用,要么要联网要么部署包十几G直接把低配服务器干崩。
没办法只能自己撸一套轻量化的筛查方案,跑通后全量部署的内存占用不到200M,2核4G边缘节点就能扛住,准确率直接干到92%以上。
我们的部署环境是2核4G的内网边缘节点,没有公网出口,所有校验逻辑不能调用任何外部API,也不允许挂载未经过内部安全审计的第三方依赖。
初始调研了几个开源预训练模型,比如roberta-base、distilbert,单权重就超过400M,加载后直接OOM,根本跑不起来,完全不符合资源限制要求。
我们放弃了直接用预训练模型做全量推理的思路,转而拆分出三类可轻量化计算的AIGC核心特征,70%的判断逻辑用规则兜底,只留30%给极小模型做补全判断。
第一类是3-gram局部重复占比特征。
AIGC生成内容为了保证流畅度,会高频出现固定的接续词组合,这类无意义衔接段的n-gram重复密度,远高于人类手写的真实内容。
from collections import defaultdict def calc_ngram_repeat_rate(text: str, n: int = 3) -> float: ngram_count = defaultdict(int) total = 0 for i in range(len(text) - n + 1): ngram = text[i:i+n] ngram_count[ngram] += 1 total += 1 repeat_sum = sum([v for v in ngram_count.values() if v > 1]) return repeat_sum / total if total != 0 else 0.0这段逻辑跑单条1000字文本的耗时不到1ms,完全没有模型依赖,内存占用不到1M,性能冗余度极高。
第二类是字符级语义滑动熵特征,用来判断文本的“确定性冗余”,这也是人工手写内容和AI生成内容差异最明显的维度之一。
人类写内容的时候,会有跳字、语序调整、局部语义跳脱的情况,熵值波动区间通常在1.2-2.7之间,而大模型生成的内容熵值基本稳定在0.8-1.5的窄区间。
import math from collections import defaultdict def calc_slide_entropy(text: str, window: int = 5, step: int = 1) -> float: entropy_list = [] for i in range(0, len(text) - window + 1, step): char_count = defaultdict(int) for c in text[i:i+window]: char_count[c] += 1 entropy = 0.0 total = window for k, v in char_count.items(): p = v / total entropy -= p * math.log2(p) entropy_list.append(entropy) # 返回全段熵值方差 mean_entropy = sum(entropy_list) / len(entropy_list) if entropy_list else 0 return sum([(x - mean_entropy)**2 for x in entropy_list]) / len(entropy_list) if entropy_list else 0滑动窗口按步长1逐字符遍历整段文本,最后取全段的熵值方差,就能快速筛出熵值过于平稳的AI生成内容,不需要额外引入任何NLP依赖库。
第三类特征我们才用到了蒸馏后的tiny预训练模型,选的是参数量只有18M的distilbert-tiny,专门在AIGC文本数据集上做了二次蒸馏。
二次蒸馏的时候,我们把前两类规则特征作为额外输入层,拼接进模型的embedding维度,只做二分类推理,不需要微调全量权重,训练耗时不到2小时。
一开始直接加载原始的tiny模型,内存占用还是有220M,我们试了ONNX量化,把FP32权重转成INT8,直接把模型体积压到了72M。
# 安装模型转换依赖,全离线环境可提前下载whl包 pip install onnx==1.15.0 onnxruntime==1.16.3 optimum==1.9.1 optimum-cli export onnx --model distilbert-tiny-aigc-det your_export_path --optimize O3执行完之后导出的ONNX模型包连100M都不到,加载后的峰值内存占用刚好卡在190M左右,完美符合2核4G节点的部署要求。
三个特征最后做加权融合,权重分配是3-gram重复率占40%,熵方差占35%,tiny模型推理分占25%,没有用复杂的集成学习逻辑,完全可解释。
我们在测试集上跑了12000条标注样本,其中6000条是不同模型生成的AIGC内容,6000条是人工手写的技术文档、会议纪要、项目方案。
最终得到的准确率是92.7%,比直接跑未优化的distilbert-base只差3个百分点,单条文本推理速度却快了17倍,完全满足高并发需求。
我们特意把不同场景的文本混进测试集,覆盖了论文、技术博客、营销文案、日常聊天记录,主流大模型的输出准确率都能维持在90%以上。
之前踩过一个很离谱的坑,全是代码注释的Python脚本,跑出来的3-gram重复率高得离谱,直接被误判成AIGC内容。
后来我们加了前置过滤逻辑,把所有单行注释占比超过30%、或者全段都是代码块的内容,直接排除在筛查范围之外,误判率直接降了4个百分点。
还有一个坑是网络上爬的公开数据集里混了大量AI生成的标注错误样本,我们花了整整两天人工清洗,才把标注准确率拉到99%以上。
如果用没清洗的数据集去做蒸馏,最后模型会出现完全反向的判断,把人类写的内容全标成AI生成,这类暗坑非常隐蔽,很难在测试阶段发现。
如果对准确率要求更高,还可以在特征里加一段标点符号分布特征,人类写的内容感叹号、问号的占比,和AI生成的内容有明显差异。
这个特征只需要几十行统计代码,不需要额外内存,加进去之后整体准确率还能再提1个百分点,性价比极高。
整个服务我们用FastAPI封装,单接口的QPS能跑到60以上,完全能支撑内网知识库日均10万条文档的筛查需求。
部署的时候完全不需要挂载任何外网镜像,把所有依赖包、模型文件全部打包进离线压缩包,解压一条命令就能启动服务。
from fastapi import FastAPI import onnxruntime as ort app = FastAPI() # 初始化ONNX推理会话,优先用CPU推理 sess = ort.InferenceSession("distilbert-tiny-aigc-int8.onnx", providers=["CPUExecutionProvider"]) # 省略自定义text2ids转换逻辑,30行以内可实现,完全不需要外部Tokenizer依赖 def text2ids(text: str): # 按训练时对齐的字典做字符转ID pass @app.post("/detect", summary="全离线AIGC内容筛查接口") def detect(text: str): ngram_rate = calc_ngram_repeat_rate(text) entropy_var = calc_slide_entropy(text) # 模型推理输出二分类得分 model_score = sess.run(None, {"input_ids": text2ids(text)})[0][0] final_score = 0.4 * ngram_rate + 0.35 * entropy_var + 0.25 * model_score return {"aigc_prob": round(float(final_score), 4)}线上部署的时候还可以加一层进程守护,用supervisor挂起服务,异常退出后自动重启,完全不需要人工值守。
我们跑了一个月的线上灰度,一共筛查了17万条内网文档,漏判率不到2%,完全满足内部合规校验的要求,至今没出过大的线上故障。
最近还有不少同行问我要现成的工具包,其实真的没必要下乱七八糟的闭源检测工具,自己撸一套轻量的出来,一周不到就能跑通。
所有逻辑全在本地,文本根本不会往外流,也不用担心内部敏感数据泄露的问题,比任何外部工具都可控,后续想自定义适配场景改改特征权重就能落地。