第一次个人编程作业:论文查重算法设计与实现报告

作业信息

这个作业属于哪个课程 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): 私有方法,将计算出的相似度格式化并写入答案文件。

程序执行流程​:

  1. 初始化​:接收命令行传入的原文文件路径、抄袭版论文文件路径和答案文件路径。
  2. 文件读取​:分别读取原文和抄袭版论文的文本内容。
  3. 文本预处理​:对两段文本进行分词、去除停用词等操作,得到两个代表文本特征的词列表(Tokens)。
  4. 相似度计算​:使用设计的混合算法计算两个词列表的相似度。
  5. 结果输出​:将计算出的相似度(保留两位小数)写入指定的答案文件中。

2. 算法关键与独到之处

算法选择​:我采用了基于Jaccard相似系数词频向量余弦相似度结合的混合算法。

  1. Jaccard相似系数​:快速计算两个文本词汇集合的相似度,similarity_j = len(intersection) / len(union)。它能有效捕捉词汇重叠的宏观特征。
  2. 余弦相似度​:将两个文本表示为词频向量,计算其夹角余弦值,similarity_c = dot(A, B) / (norm(A) * norm(B))。它能更细腻地捕捉词汇分布的相似性。

独到之处​:

  • 混合模型​:最终的相似度 final_similarity = 0.4 * similarity_j + 0.6 * similarity_c。我通过试验发现,给予余弦相似度更高权重,能更好地应对同义词替换和语序调整等抄袭手段,结果更接近人工判断。
  • 文本预处理​:在计算前,对文本进行了细致的预处理:
    • 分词​:使用jieba库进行精确模式分词。
    • 去停用词​:加载常用中文停用词表,过滤“的”、“了”、“和”等无实际意义的词汇。
    • 统一小写​:对英文单词进行了统一小写处理。

这种预处理和混合算法策略,使得程序在面对“星期天”替换为“周天”、“天气晴”替换为“天气晴朗”等情况时,依然能给出合理的相似度。

三、计算模块接口部分的性能改进

1. 改进思路与耗时

在性能改进上花费了约 ​45分钟。最初版本在处理大文件时速度下降。改进主要围绕:

  1. 优化数据结构和算法​:利用Python原生的listset的高效操作(如求交集、并集)来代替手动循环。
  2. 优化词频统计​:使用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编码解码文件 '[文件路径]'"。

通过上述异常处理,程序能够更优雅地应对各种意外输入,避免崩溃,并提供有用的调试信息。

posted on 2025-09-21 21:37  caoweibbb  阅读(39)  评论(0)    收藏  举报