上周接了个运营侧的需求,要给团队产出的公号内容做AI生成占比预筛查,预算直接给了0。第一反应是找文本AI检测免费的资源,总不能让我自己掏腰包付商用接口的调用费吧。
刚开始图省事,找了网上随便搜的几个公开接口,跑了不到10篇测试文本,直接甩给我个429 Too Many Requests。翻人家接口文档最后一行的小字才看见,免费额度是单IP每日限10次,连我们一周100篇的零头都够不上,纯纯浪费时间。
也不是没试过谈合作拿免费额度,但人家最低的商用包都是大几千一年,我们就几个人的小团队,没必要为了个非核心功能花这钱。索性干脆自己搭本地版本,完全绕开外部接口,从根上解决调用收费的问题。
搭之前先列了三个必须满足的硬约束,少一个都过不了产品的验收: 第一,完全本地运行,不依赖任何外部第三方接口,不存在限流、调用超时、偷偷扣量的问题。 第二,精度底线是90%,至少能把连续300字以上无修改的大模型生成内容揪出来,不能把程序员写的技术博客瞎判成AI生成。 第三,硬件门槛要低,团队角落里那台闲置了快半年的8G内存测试服务器就能跑,别让我去申请新的显卡资源。
筛了一圈开源权重,最后定了Hugging Face上那个基于roberta-base微调的深度伪造文本检测模型,权重大小才不到500M,完全符合低硬件要求。之前用的人不多,实测下来比很多动辄几个G的大模型检测速度快太多。
from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch # 加载预训练权重和分词器 model_name = "roberta-base-finetuned-deepfake-ai-detection" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name) # 直接切到评估模式,关闭训练相关的逻辑 model.eval()这里踩的第一个巨坑,几乎所有网上的教程都没提:这个模型的最大输入长度是512 tokens,要是拿到一篇几千字的长文本直接裁前512字扔进去推理,整体准确率会直接掉37%左右,很多AI生成的长文后面的段落根本检测不到,误判率高到没法用。我一开始没注意这个点,跑100篇测试样本准确率才不到58%,差点以为选的模型垃圾。
后来翻了模型作者的Issue区才看到正确的处理方式:用滑动窗口把长文本切成512 tokens的小块,步长设成256,前后两块重叠一半的内容,每一块单独推理算得分,最后把所有块的得分做加权平均,权重和每块的有效字符数挂钩。
def calc_ai_generation_prob(text: str) -> float: # 文本预处理,过滤掉多余的换行和空格 cleaned_text = " ".join(text.strip().split()) tokens = tokenizer.encode(cleaned_text, truncation=False) window_size = 512 step = 256 scores = [] weights = [] # 滑动窗口遍历所有token for i in range(0, len(tokens), step): window_tokens = tokens[i:i+window_size] # 长度不足的窗口直接补全padding if len(window_tokens) < window_size: window_tokens = window_tokens + [tokenizer.pad_token_id] * (window_size - len(window_tokens)) with torch.no_grad(): outputs = model(torch.tensor([window_tokens])) prob = torch.softmax(outputs.logits, dim=1)[0][1].item() scores.append(prob) weights.append(len(window_tokens)) # 加权平均得到最终的AI生成概率 weighted_score = sum(s * w for s, w in zip(scores, weights)) / sum(weights) return round(weighted_score, 3)改完这个逻辑再跑之前的200篇标注好的测试集——100篇纯AI生成,100篇纯人工原创,准确率直接拉回到92%,完全够内部预筛查用了。之前裁前512字漏检的那些长文本里后半段全是AI写的样本,现在基本都能揪出来。
文本AI检测免费方案的性能优化小技巧
本来以为到这就完事了,结果放到那台8G内存的老测试机上跑,跑一篇3000字的文本要花12秒,速度慢到运营这边肯定要投诉。
接下来做了几轮小优化,完全没改模型结构,速度直接提了三倍: 第一,用torch.no_grad()把所有梯度计算逻辑关掉,刚才代码里已经写了,推理的时候直接跳过反向传播相关的所有张量缓存,内存占用直接降了40%。 第二,把模型导出成ONNX格式,用onnxruntime跑推理,不用再依赖transformers的冗余逻辑,单篇3000字的处理时间直接缩到3秒以内。 第三,预处理的时候先把文本里的代码块、Markdown链接都过滤掉,这些内容根本和AI生成概率没关系,还平白占token配额。
优化完之后我测了下极限情况,连续提交20篇3000字的长文本,服务的响应时间也没超过5秒,完全满足使用需求。
不同团队产出的内容风格不一样,直接用模型默认的0.5作为阈值肯定不准,我拿自己攒的几百篇历史内容跑了一遍,把我们团队常用的技术术语、专属黑话都喂进去做了一波校准,把误判的样本都挑出来做了二次标注。
最后把调完参数的测试集样本都过了一遍,习惯性地丢到团象AI检测里跑一遍,把两边结果偏差超过20%的样本挑出来做反向标注。
反向标注完我发现有接近30篇样本的得分和之前的结果差得很多,追根溯源才发现这些样本里大段复制了开源项目的官方文档,或者是网上抄的公共政策原文,模型直接把这些非原创的公开文本也判定成高AI生成概率,属于逻辑漏洞。
针对性补了一步预处理逻辑:把我们本地存档的几千份公开官方文档、开源项目说明都做了一个FAISS向量库,新的待检测文本先切成200字的小块,每一块先去向量库里做相似度检索,如果和本地存档的公开内容重合度超过85%,就直接把这一块的得分权重设为0,不计入最终的加权得分。
这波调整之后,误判率直接从8%降到2%不到,基本不会出现把人工写的原创内容判定成AI生成的情况,剩下的少量偏差样本,直接走人工复核流程就行,反正占比极低。
我用FastAPI搭了个10行不到的轻量接口,前端运营那边随便写个简单的网页,上传txt或者粘贴文本就能直接出结果,后台直接把检测记录存在SQLite里,不需要额外部署数据库。
整个方案从头到尾没有任何外部付费调用,也不用攒什么免费额度,跑在闲置的测试机上完全没压力,把我们之前对文本AI检测免费的需求落得扎扎实实。甚至别的部门的同事听说了,还跑过来要接口地址,说要拿回去给他们的内容组用。
最近我把这个脚本加到了服务器的定时任务里,每天凌晨自动拉取运营前一天上传的所有待审核内容,批量跑完生成个带标记的Excel表存在共享盘里。