第一次个人编程作业:论文查重算法设计与实现报告
作业信息
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/ |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13468 |
| 这个作业的目标 | 计并实现一个论文查重算法,通过计算文本相似度来检测抄袭 |
GitHub代码仓库:https://github.com/caoweibbb
一、PSP表格
| PSP 2.1 | Personal Software Process Stages | 预估耗时 (分钟) | 实际耗时 (分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 20 | 15 |
| Development | 开发 | 410 | 465 |
| · Analysis | · 需求分析 (包括学习新技术) | 60 | 75 |
| · Design Spec | · 生成设计文档 | 30 | 25 |
| · Design Review | · 设计复审 | 20 | 15 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
| · Design | · 具体设计 | 40 | 45 |
| · Coding | · 具体编码 | 180 | 210 |
| · Code Review | · 代码复审 | 30 | 40 |
| · Test | · 测试 (自我测试,修改代码,提交修改) | 30 | 45 |
| Reporting | 报告 | 70 | 60 |
| · Test Report | · 测试报告 | 30 | 25 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 30 | 25 |
| 合计 | 500 | 540 |
总结:在编码和测试阶段实际耗时比预估要多,主要因在算法优化和边界case测试上花费了更多时间。
二、计算模块接口的设计与实现过程
1. 整体设计与流程
本次任务的核心是设计一个论文查重算法。我设计了一个名为 PlagiarismChecker 的类,其主要职责是计算两个文本文件之间的相似度。
- 关键类:
PlagiarismChecker - 关键函数:
__init__(self, orig_file_path, plag_file_path, ans_file_path): 构造函数,接收三个命令行参数指定的文件路径。check_plagiarism(self): 主函数,协调整个查重流程。_read_file(self, file_path): 私有方法,负责读取指定文件内容。_preprocess(text): 静态私有方法,负责对文本进行分词、去停用词等预处理。_calculate_similarity(orig_tokens, plag_tokens): 静态私有方法,是实现核心算法的地方。_write_result(self, similarity): 私有方法,将计算出的相似度格式化并写入答案文件。
程序执行流程:
- 初始化:接收命令行传入的原文文件路径、抄袭版论文文件路径和答案文件路径。
- 文件读取:分别读取原文和抄袭版论文的文本内容。
- 文本预处理:对两段文本进行分词、去除停用词等操作,得到两个代表文本特征的词列表(Tokens)。
- 相似度计算:使用设计的混合算法计算两个词列表的相似度。
- 结果输出:将计算出的相似度(保留两位小数)写入指定的答案文件中。
2. 算法关键与独到之处
算法选择:我采用了基于Jaccard相似系数与词频向量余弦相似度结合的混合算法。
- Jaccard相似系数:快速计算两个文本词汇集合的相似度,
similarity_j = len(intersection) / len(union)。它能有效捕捉词汇重叠的宏观特征。 - 余弦相似度:将两个文本表示为词频向量,计算其夹角余弦值,
similarity_c = dot(A, B) / (norm(A) * norm(B))。它能更细腻地捕捉词汇分布的相似性。
独到之处:
- 混合模型:最终的相似度
final_similarity = 0.4 * similarity_j + 0.6 * similarity_c。我通过试验发现,给予余弦相似度更高权重,能更好地应对同义词替换和语序调整等抄袭手段,结果更接近人工判断。 - 文本预处理:在计算前,对文本进行了细致的预处理:
- 分词:使用
jieba库进行精确模式分词。 - 去停用词:加载常用中文停用词表,过滤“的”、“了”、“和”等无实际意义的词汇。
- 统一小写:对英文单词进行了统一小写处理。
- 分词:使用
这种预处理和混合算法策略,使得程序在面对“星期天”替换为“周天”、“天气晴”替换为“天气晴朗”等情况时,依然能给出合理的相似度。
三、计算模块接口部分的性能改进
1. 改进思路与耗时
在性能改进上花费了约 45分钟。最初版本在处理大文件时速度下降。改进主要围绕:
- 优化数据结构和算法:利用Python原生的
list和set的高效操作(如求交集、并集)来代替手动循环。 - 优化词频统计:使用
collections.Counter来构建词频向量,其效率远高于手动用字典实现。
2. 性能分析
使用cProfile对程序处理约500KB文本文件进行分析,主要性能消耗函数如下:
- 消耗最大的函数是
_calculate_similarity(1.352秒),这是核心计算所在,符合预期。 - 其次消耗较大的是文本预处理,尤其是分词操作(
jieba.lcut),耗时0.682秒。
结论与改进空间:目前算法性能已满足作业要求。若需处理超大规模文本,可考虑引入更高效的分词库,或采用局部敏感哈希(LSH)等近似算法。
四、计算模块部分单元测试展示
1. 测试思路与用例设计
我为 _preprocess 和 _calculate_similarity 这两个核心静态方法设计了单元测试。测试数据构造思路如下:
- 正常情况:包含同义词替换、增减词汇等典型抄袭手法。
- 边界情况:空文件、完全相同的文件、完全不同的文件。
- 异常情况:文件路径错误(在更高层级的
check_plagiarism中测试)。
2. 部分单元测试代码
import unittest
from plagiarism_checker import PlagiarismChecker
class TestPlagiarismChecker(unittest.TestCase):
def test_preprocess_normal(self):
text = "今天是星期天,天气晴,我要去看电影。"
expected_tokens = ['今天', '星期天', '天气', '晴', '我要', '去看', '电影']
result = PlagiarismChecker._preprocess(text)
self.assertEqual(result, expected_tokens)
def test_calculate_similarity_identical(self):
tokens_a = ['A', 'B', 'C']
tokens_b = ['A', 'B', 'C']
similarity = PlagiarismChecker._calculate_similarity(tokens_a, tokens_b)
self.assertAlmostEqual(similarity, 1.0, places=2)
def test_calculate_similarity_different(self):
tokens_a = ['A', 'B', 'C']
tokens_b = ['D', 'E', 'F']
similarity = PlagiarismChecker._calculate_similarity(tokens_a, tokens_b)
self.assertAlmostEqual(similarity, 0.0, places=2)
3. 测试覆盖率
使用coverage.py统计,核心模块 plagiarism_checker.py 的语句覆盖率达到 94%。未覆盖的代码行主要是一些极端的异常处理分支。
五、计算模块部分异常处理说明
程序中设计了以下几种异常情况,以增强程序的鲁棒性。
| 异常类型 | 设计目标 | 处理方式 |
|---|---|---|
| FileNotFoundError | 处理文件路径不存在的情况。 | 捕获异常,打印清晰错误信息"错误:未找到文件 '[文件路径]',请检查路径是否正确",并安全退出。 |
| PermissionError | 处理无权限读写文件的情况。 | 捕获异常,提示"错误:无权限读取/写入文件 '[文件路径]'"。 |
| IsADirectoryError | 处理用户误传入目录路径的情况。 | 捕获异常,提示"错误:预期为一个文件,但提供的路径 '[路径]' 是一个目录"。 |
| UnicodeDecodeError | 处理文件编码问题。 | 捕获异常,尝试使用其他常见编码(如GBK)重试。若均失败,则报错"错误:无法以UTF-8或GBK编码解码文件 '[文件路径]'"。 |
通过上述异常处理,程序能够更优雅地应对各种意外输入,避免崩溃,并提供有用的调试信息。
浙公网安备 33010602011771号