踩坑十多款AIGC检测工具后,我给实验室搭了套全本地的论文内容自查系统
上周学院通知毕业论文抽检新增AIGC内容溯源校验项,我们实验室要求所有送审论文必须先过一轮内部自查。
一开始图省事想找现成的公网工具测,先后试了GPTZero、Originality、团象AICG检测、Writer AI检测、零一检测,全都不好用。
要么上传10页PDF直接崩溃,要么论文核心实验数据刚传完,转头就出现在公网预收录库里,完全不敢放心用。
这些公网工具全都无法满足我们“数据绝对不出内网”的核心要求,最后我们干脆花了3天时间,自研了一套全离线的自查方案。
我们的需求定得非常明确,一共三条,没有任何冗余功能,所有开发都围绕核心目标走。
第一是全本地离线运行,所有论文数据绝对不出实验室内网,不跟任何公网服务做数据交互。
第二是支持PDF、Word、Markdown多格式解析,自动跳过公式、图表、参考文献部分,不做无效检测。
第三是检测准确率不能低于市面主流商用工具的平均水平,不能出现大量误判漏判的情况。
部署硬件用的是实验室闲置的2张RTX 3090 24G显卡,服务器系统是Ubuntu 22.04,CUDA版本11.7,PyTorch版本2.0.1。
第一步先处理依赖安装,这里要特别注意不要装最新版的transformers,不然旧版预训练权重会加载失败。
pip install transformers==4.35.2 pdfplumber==0.10.3 python-docx==1.1.0 tqdm==4.66.1这套依赖组合我们跑通了全链路流程,后续升级大版本库很容易出现不兼容的API报错。
很多人用公网AIGC检测器测出来结果不准,核心原因是没做前置内容清洗,把大量无效内容喂给了模型。
比如直接解析整份PDF的时候,会把公式的LaTeX字符、图表标注、参考文献的序号全都塞进去,干扰模型判断。
我们写的预处理逻辑,会自动先过滤所有表格、图片区域,读到“参考文献”关键词之后直接终止文本提取。
import pdfplumber def extract_valid_text(pdf_path): valid_text = [] with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: # 跳过当前页所有表格区域 for coord in page.find_tables(): page = page.outside_bbox(coord.bbox) page_text = page.extract_text() # 过滤掉参考文献之后的所有内容 if "参考文献" in page_text or "References" in page_text: break valid_text.append(page_text) return "\n".join(valid_text)这段清洗逻辑能把待检测文本的无效占比降低至少40%,从根源上减少模型误判的概率。
最开始我们想直接用开源的roberta-base-openai-detector这类预训练模型,测完直接踩了大坑。
这个模型是2023年上半年训练的,训练集全是GPT-3.5生成的内容,对现在GPT-4o、Claude 3生成的学术文本识别率不到50%。
后来我们干脆自己做微调数据集,总共攒了10万条标注样本,全部来自学术场景,没有一条泛互联网内容。
其中5万条是近3年硕博毕业生的纯手写论文片段,另外5万条是不同大模型基于相同学术主题生成的文本片段。
为了兼顾准确率和推理速度,我们用模型蒸馏的思路,把三个大模型的输出软标签融合,蒸馏出一个1.5B参数量的小模型。
三个参与蒸馏的模型分别是1.5B参数量的DeBERTa微调版、7B参数量的Llama2微调版、1.1B参数量的Qwen检测版。
最终得到的小模型,在单张RTX 3090上的推理速度能达到每秒1200字,整份100页的硕士论文不到30秒就能出结果。
推理阶段我们开了4bit量化,进一步压缩显存占用,这里的核心代码逻辑非常简单,不需要复杂的框架封装。
from transformers import AutoTokenizer, AutoModelForSequenceClassification MODEL_PATH = "/data/models/local-aigc-detector-v2" tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) model = AutoModelForSequenceClassification.from_pretrained( MODEL_PATH, device_map="auto", load_in_4bit=True ) def detect_text(text: str) -> float: inputs = tokenizer( text, return_tensors="pt", truncation=True, max_length=512 ).to(model.device) result = model(**inputs).logits.softmax(dim=-1).cpu().tolist()[0] # 返回AI生成内容的置信度百分比 return round(result[1] * 100, 2)开启4bit量化之后,1.5B的检测模型显存占用只需要8G,单张24G的3090甚至可以同时跑2个检测实例。
这个并发水平,完全够实验室几十号硕士生同时上传论文做检测,完全不会出现排队卡顿的情况。
测试阶段我们还发现了很多公网检测工具的小心机,有几篇人工深度改写过的AI论文,我们的模型报的AI置信度只有10%左右。
但拿去测某几款公网商用工具,直接打出70%以上的高AI分,一开始我们还以为是自己的模型训练出了问题。
后来翻了它们的开源协议才发现,很多公网工具根本不是跑大模型推理,是直接把上传文本和自己的收录库做字符串匹配。
只要你上传的文本片段之前被其他人上传过、收录过,工具根本不做语义判断,直接给你打高AI置信度,纯纯赚信息差。
后来我们特意在微调集里加入了1万条经过人工语序改写、替换专属实验数据的AI生成学术文本,优化模型泛化能力。
最终在我们的1万条测试集上,模型的检测准确率达到了89.7%,和我们之前测过的主流商用工具的平均准确率持平。
最后我们用FastAPI搭了个极简的Web上传页面,整个系统完全不对外暴露端口,只能通过校园网VPN访问。
所有上传的论文文件,检测完成之后30秒就会被后台异步任务自动删除,服务器本地不留任何源文件的缓存。
from fastapi import FastAPI, UploadFile, File import uuid import os app = FastAPI() # 临时文件存储路径,自动30秒后清理 TEMP_PATH = "/tmp/paper_temp" os.makedirs(TEMP_PATH, exist_ok=True) @app.post("/upload_paper") async def upload_paper(file: UploadFile = File(...)): file_suffix = os.path.splitext(file.filename)[-1] temp_file_name = f"{str(uuid.uuid4())}{file_suffix}" full_path = os.path.join(TEMP_PATH, temp_file_name) # 写临时文件 with open(full_path, "wb") as f: f.write(await file.read()) # 异步开定时任务清理文件,这里省略任务调度代码 # 调用之前的提取+检测逻辑返回结果 score = full_detect_process(full_path) return {"ai_confidence": score}后续我们还加了段落级标注功能,系统检测完成之后,会自动把AI置信度超过60%的段落标黄高亮。
大家自查的时候,只要对着标黄的部分,加入自己专属的实验细节、比如平行实验的特殊现象、原始数据的误差范围。
只需要改个三五句话,整段的AI置信度就会直接降到10%以内,完全不会被任何公开检测工具识别出来。
上周我们实验室把所有毕业生的送审论文全扫了一遍,总共筛出了3篇大段用AI润色过、没做人工调整的论文,及时打回去修改。
整个过程没有一篇论文的内容流出内网,完全不用担心论文刚写完就被提前收录、影响后续送审和答辩的问题。
现在回头看,当初试了那么多公网工具全是折腾,要么要充几十块钱的月卡,要么要上传手机号绑定信息,数据安全根本没有保障,花几天搭这套全本地的系统,后续不管是论文自查,还是项目文档的合规校验,都能一直用,性价比高太多。
