作业抄袭检测:TensorFlow代码相似度比对
在高校计算机课程中,教师批改编程作业时常常面临一个令人头疼的问题:两份代码看起来变量名不同、缩进不一致,甚至函数拆分方式各异,但逻辑结构却惊人地相似。这背后往往不是巧合,而是经过“表面改写”的代码抄袭行为。传统的文本查重工具在这种场景下几乎失效——它们能发现复制粘贴,却难以识别“换汤不换药”的语义等价代码。
随着机器学习技术的发展,尤其是深度神经网络在自然语言处理领域的成功迁移,我们开始有能力从语义层面理解代码,而不仅仅是匹配字符序列。TensorFlow 作为工业级的深度学习框架,在这一转型过程中扮演了关键角色。它不仅提供了构建复杂模型的能力,更通过其生态系统支持从训练到部署的全流程自动化,使得大规模、高精度的代码相似性分析成为现实。
要让机器“读懂”代码,第一步是将源码转化为数学可处理的形式。直接使用原始文本显然不可靠——学生完全可以把i改成counter,把for循环改成while而不影响功能。因此,我们需要一种对表面差异鲁棒、又能保留程序逻辑本质的表示方法。
常见的做法是提取代码的抽象语法树(AST),然后将其节点序列化作为模型输入。例如,一段简单的循环代码:
for i in range(10): print(i * 2)其 AST 可以被展开为类似这样的标记序列:
[For, Name:i, Range, Num:10, Print, BinOp:*, Name:i, Num:2]这种表示剥离了具体命名和格式细节,突出了控制流和操作类型,更适合模型学习功能一致性。当然,也可以进一步使用子树路径或程序依赖图(PDG)来增强上下文表达能力,但在实际系统中,平衡精度与计算开销至关重要。
有了结构化输入后,接下来就是核心环节:如何用 TensorFlow 构建一个能够生成语义向量的编码器?
以下是一个基于 LSTM 的轻量级代码编码模型示例:
import tensorflow as tf from tensorflow.keras import layers, models def build_code_encoder(vocab_size=10000, embedding_dim=128, lstm_units=64): model = models.Sequential([ layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=512), layers.LSTM(lstm_units, return_sequences=False), layers.Dense(64, activation='relu'), layers.Lambda(lambda x: tf.nn.l2_normalize(x, axis=1)) # 归一化便于相似度计算 ]) return model这个模型的设计有几个值得注意的细节:
- Embedding 层将离散的语法标记映射为稠密向量空间中的点,使语义相近的操作(如
Add和Sub)在向量空间中也彼此接近; - LSTM 层捕捉代码中的长期依赖关系,比如嵌套循环、异常处理块之间的关联;
- L2 归一化确保输出向量位于单位球面上,这样两个向量的点积就等于它们的余弦相似度,无需额外计算步骤即可快速比对。
当然,这只是一个起点。为了提升判别能力,我们可以采用更高级的架构,比如孪生网络(Siamese Network),同时输入两段代码,训练模型直接预测它们是否出自同一逻辑模板。损失函数可以选择三元组损失(Triplet Loss),让模型学会拉开正负样本的距离:
anchor = encoder(code_a) positive = encoder(code_b) # 同一逻辑的不同实现 negative = encoder(code_c) # 完全不同的任务 loss = tf.reduce_mean( tf.maximum(0.0, margin + tf.square(tf.norm(anchor - negative)) - tf.square(tf.norm(anchor - positive))) )在真实教学环境中,系统的运行效率同样重要。想象一下期末周,数百名学生同时提交作业,系统需要在短时间内完成全部比对。这时,TensorFlow 的优势就显现出来了。
利用tf.function编译加速和 GPU 批量推理,单张 T4 显卡每秒可以处理超过 300 份代码片段。结合 FAISS 或 Annoy 这类近似最近邻搜索库,即使面对上万份历史作业,也能在毫秒级时间内找到最相似的候选集。整个流程可以封装为一个异步服务,学生提交后几分钟内就能收到反馈,极大地提升了教学闭环的响应速度。
但这还不够。教师真正关心的不只是“有多像”,而是“哪里像”。如果系统只能给出一个 0.97 的相似分数,却没有解释依据,仍然难以服众。为此,我们在设计中引入了可解释性机制。
一种有效的方式是结合注意力权重可视化。在 Transformer 或带 Attention 的 Seq2Seq 模型中,每个输入节点都会有一个对应的注意力得分,反映出它对最终语义表示的影响程度。当检测到高相似度时,系统可以反向投影这些权重,高亮显示两份代码中最关键的匹配区域——比如都包含“快慢指针遍历链表”或“递归终止条件判断”。
另一种策略是回溯 AST 节点对齐路径。通过动态规划算法找出两棵树之间的最大公共子结构,并用颜色标注出重复出现的模式。这种方式特别适合展示结构性抄袭,比如多个学生独立实现了相同的错误修复逻辑,而这本不该出现在标准解法中。
当然,任何技术落地都不能忽视工程实践中的现实约束。
首先是模型大小问题。教学平台通常部署在普通服务器甚至虚拟机上,无法负担 BERT-Large 这样的庞然大物。我们的经验是:对于 Python/Java/C++ 等主流语言的基础逻辑识别,一个 6 层 LSTM 或 TinyBERT 结构已经足够,参数量控制在百万级别,推理延迟低于 50ms。必要时还可使用 TensorFlow Lite 进行量化压缩,进一步降低资源消耗。
其次是隐私合规。学生的代码属于个人创作成果,必须确保数据安全。我们建议采用本地化部署方案,避免将原始代码上传至云端。TensorFlow Serving 支持 gRPC over TLS,配合 JWT 认证,可以在保证性能的同时实现端到端加密通信。所有中间特征向量在比对完成后立即清除,不留持久化痕迹。
最后是系统的持续演进能力。编程语言不断更新,新的 API 和设计模式层出不穷。如果模型几年不变,迟早会变得“看不懂新代码”。为此,我们推荐引入 MLOps 流程,借助 TensorFlow Extended (TFX) 构建自动化的再训练流水线:
- 每学期收集人工复核后的误报/漏报案例;
- 自动加入训练集并触发增量训练;
- 使用 A/B 测试验证新版模型效果;
- 通过 SavedModel 格式热更新线上服务。
这样一来,系统不仅能越用越准,还能适应不同课程的特点。比如算法课侧重控制流相似性,而 Web 开发作业则可能更多关注 API 调用序列的一致性。
说到阈值设定,这也是个容易被低估的技术点。很多人以为设个固定阈值(如 0.95)就行,但实际上不同课程、不同难度的任务应有不同的容忍度。初学者写的冒泡排序本来就很相似,强行设高标准只会造成大量误报。我们的做法是引入统计归一化机制:先计算所有作业两两之间的相似度分布,剔除高频公共模板(如标准输入读取),再基于 Z-score 动态调整判定边界。这种方法显著降低了跨班级比较时的偏差。
事实上,这套基于 TensorFlow 的代码相似性分析系统已经在一些 MOOC 平台和在线判题系统中投入使用。某国内重点高校的编程基础课数据显示,在启用该系统后,人工复查工作量减少了约 70%,同时抄袭检出率提高了近 3 倍。更重要的是,它改变了学生的心态——不再是“能不能躲过检查”,而是意识到“写出自己的代码才是真正的学习”。
展望未来,还有更大的提升空间。当前模型主要基于序列建模,而程序本质上是图结构。如果我们能用图神经网络(GNN)直接处理控制流图(CFG)或程序依赖图(PDG),有望捕捉更深层次的语义信息,比如数据流传播路径、副作用传递关系等。TensorFlow 对 GNN 的支持正在逐步完善,结合 TF-GNN 库,这类前沿探索已具备可行性。
另一个方向是多模态融合。除了语法结构,代码的注释风格、命名习惯、调试痕迹甚至编辑历史都可能成为辅助判断的线索。将这些信号与语义向量联合建模,或将形成更强的鉴别能力。
归根结底,这场技术变革的意义不止于“抓作弊”。它推动我们重新思考:在一个 AI 辅助编程的时代,什么是真正的“原创”?当 Copilot 可以自动生成完整函数时,评判标准是否应该从“写了什么”转向“如何选择与整合”?这些问题没有简单答案,但有一点是确定的:只有建立在强大、透明且可信赖的技术基础上,教育评估才能跟上时代步伐。
而 TensorFlow 正是以其稳定性、灵活性和开放性,为这场变革提供着底层支撑。