
上周组会导师直接塞给我3沓学生提交的课程小论文说最近好几份作业的实验思路撞得离谱让我两天内搭个内部能用的AIGC内容原创度鉴定查询工具绝对不能把学生未公开的实验数据传到公网接口。我当时正盯着3090显卡的监控面板等BERT预训练的第7轮epoch跑完直接被打断差点误删训练数据集的缓存文件。核心需求拆解不能只靠公网接口凑数一开始我以为找几个在线检测工具把文本批量丢进去打标凑个简单的规则就能交差结果翻了组里之前的历史需求文档才发现要求的准确率必须达到85%以上还要支持docx批量上传、按段落展示得分、完全离线运行不能调用任何外部公网接口返回结果。我手头上攒的只有1200份之前人工标注好的计算机领域学术文本数据集其中一半是人类撰写的课程作业另一半是不同版本大模型生成的内容之前直接用开源的基础检测模型跑测试集准确率只有72%离要求差了一大截。为了先给这批标注数据打更细的特征标签我最开始挨个试了一批公网的检测站点和开源工具某国内头部查重平台的AI检测子模块返回的标签只有“高风险”三个字连分位数都不给完全没法用来做训练集标注某开源的detectGPT本地版本跑长文本的时候断句错误很多直接把引用的公式块识别成AI生成内容某海外的OpenAI官方检测器2023年就下线了现在能找到的第三方镜像准确率跌到40%不到团象AI检测用了一下没太搞懂它的逻辑某高校实验室开源的LLM生成内容检测工具需要上传全文件才能返回结果没法直接丢片段进去打标某小众的文本指纹检测站点只能检测GPT3.5生成的内容对GPT4的输出基本全漏判某用Perplexity输出特征训练的小模型对带大量自定义实验数据的文本误判率特别高。前前后后传了120多篇标注好的样本文档跑了快两个小时最后收集到的标注结果有接近30%和人工标注对不上等于白忙活一半。我只好转方向先做本地的文本预处理模块把所有上传的docx文件的页眉页脚、参考文献列表、页码标记全部过滤掉避免无关内容干扰后续的特征计算。下面是我写的核心提取函数用的python-docx 1.1.0版本跑在我本地的开发环境上最开始直接跑的时候有近10%的文本被误删了参考文献后的正文内容。import docx import re def extract_pure_text(doc_path): doc docx.Document(doc_path) full_text [] for para in doc.paragraphs: # 跳过纯数字页码和参考文献标题行 if re.match(r^\d$, para.text.strip()) or re.match(r^参考文献|^References, para.text.strip()): continue # 过滤掉长度小于5的空行 if len(para.text.strip()) 5: continue full_text.append(para.text) return \n.join(full_text)等等我说反了最开始我是把所有以[数字]开头的行全删掉直接把正文里的引用标记全部过滤掉后来才发现AI生成的内容很少插入带标号的引用手动写的反而会大量用删掉反而会丢失重要的分布特征才改成现在的判断逻辑最后把预处理的误操作率降到了3%以内。特征工程设计不止统计token分布差异之前的开源检测模型准确率低的核心问题是只用到了困惑度和字符熵两个基础特征现在的大模型基本都自带top-k采样、温度系数调整、重复惩罚参数调整的功能生成的文本困惑度分布和人类写的已经有大量重叠直接用单阈值划分的方法根本扛不住现在的内容改写手段。我之前做过对照实验用中文GPT2-base模型直接算GPT4生成的100篇学术文本的困惑度得到的数值和人类撰写的样本重合度超过40%根本没法画出清晰的分类边界。为了把准确率提上去我在原有两个基础特征的基础上新增了三个针对性的学术文本专属特征把所有特征拼接成5维的向量输入浅层MLP分类器**最终测试集准确率直接冲到了87.2%**刚好超过了导师要求的阈值。新增的三个特征分别是常用连接词的出现间隔方差人类写文本的时候“因此”“然而”这类连接词的出现间隔离散度很高AI生成的内容这类词的间隔几乎稳定在30-50个token之间标点符号的占比分布人类写长句的时候逗号的占比会随句子长度动态变化AI生成的文本逗号占比几乎稳定在7%左右还有术语共现得分我用之前爬的10万篇计算机领域核心论文构建的共现矩阵计算每段文本里专业术语的搭配合理性AI生成的内容很容易出现离谱的跨领域术语拼接得分远低于人类撰写的文本。import jieba import numpy as np def cal_text_features(text, ngram_cofreq_matrix, ppl_model): # 滑动窗口计算平均困惑度避免长文本截断偏差 windows [text[i:i256] for i in range(0, len(text), 128)] ppl_list [ppl_model.cal_perplexity(window) for window in windows] avg_ppl np.mean(ppl_list) if ppl_list else 0 # 计算分词后的香农熵 tokens jieba.lcut(text) token_cnt len(tokens) if token_cnt 0: ent 0 else: ent -sum([np.log(tokens.count(tok)/token_cnt) * tokens.count(tok)/token_cnt for tok in set(tokens)]) # 计算常用连接词的间隔方差 common_connect_words [因此, 所以, 然而, 不过, 而且, 此外] intervals [] for word in common_connect_words: idx_list [i for i, tok in enumerate(tokens) if tok word] for i in range(1, len(idx_list)): intervals.append(idx_list[i] - idx_list[i-1]) interval_var np.var(intervals) if len(intervals) 2 else 0 # 统计标点占比和术语共现得分 punct_cnt len([c for c in text if c in 。、]) punct_ratio punct_cnt / len(text) if len(text) 0 else 0 cofreq_score cal_cofreq_match(text, ngram_cofreq_matrix) return [avg_ppl, ent, interval_var, punct_ratio, cofreq_score]我把提前算好的ngram共现矩阵存成了npz格式的压缩文件全量加载到内存只需要1.2秒完全不会拖慢单篇文档的处理速度。不过这个特征目前只适配计算机领域的学术文本换到文学或者医学领域会不会直接失效我还没做实验验证暂时还不太确定通用性。落地部署与查询逻辑优化最开始我图省事直接用FastAPI写了个同步处理的接口前端做了个简单的上传页面结果测试的时候同时上传10篇超过1万字的论文CPU直接被特征计算占满所有后续请求全部超时根本没法多用户同时用。后来我换成了Celery 5.3.4做异步任务队列后台起4个独立的worker进程处理特征提取和模型推理前端上传完文件之后直接返回任务ID定时轮询任务状态就算同时20个用户上传长文档服务也不会出现卡顿崩溃的情况。关于AIGC内容原创度鉴定查询的返回逻辑我特意没做非黑即白的二元标签而是直接返回0到100的原创度得分得分越高代表该段内容是人类撰写的概率越高同时把每一段的得分在返回结果里用不同深浅的黄色高亮标注出来还附带每一项特征的具体数值方便使用者手动核对可疑片段避免出现完全无理由的误判。之前我把这篇实现手记的全量文本丢进去跑得到的整体得分是89把GPT4直接生成的同主题文本丢进去跑得分只有21两个样本的区分度非常明显。中间踩了个特别隐蔽的坑我最开始调用中文GPT2模型计算困惑度的时候直接把max_length参数设成了512所有超过长度的文本直接从尾部截断导致大段的实验结果和数据分析部分被直接丢弃算出来的特征完全失真有12篇纯人工撰写的课程论文被误判成了高AI概率原创度得分低于30。后来排查了快两个小时才定位到问题换成滑动窗口的平均困惑度计算方案每256个token为一个窗口步长设为128重叠计算最后取所有窗口的平均值才彻底解决了长文本截断带来的偏差问题。更准确地说我当时为了省推理时间特意加的截断逻辑反而成了最大的误判来源完全是搬起石头砸自己的脚。现在这个小工具已经在组里内部跑了快两周收上来的32份课程小论文里有7份整体得分低于40后续人工核对确认这些文本有超过70%的内容是直接用大模型生成的目前还没出现过明确的漏判案例。不过现在我还没找到很好的检测方法对付那些把AI生成内容逐句手动改写、替换所有连接词和专业术语的样本这类样本的所有特征几乎和普通人类撰写的文本完全重合暂时还没想出合适的突破口。本来两天要交的活前前后后折腾了快一周差点赶不上导师定的下一个数据集标注的ddl也算变相攒了点没用的奇怪经验。