Zki

论文查重算法

论文查重算法——个人项目作业

学号: 3124004212
姓名: 李智杰

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15702
GitHub 项目地址: https://github.com/Zikie222/Zikie222/tree/main/3124004212

Github仓库截图:
281af5507f0cfb61fabda4c0f1dce0d9


一、项目需求

本次个人项目要求设计并实现一个论文查重程序。

程序接收三个命令行参数:

python main.py <原文文件绝对路径> <抄袭版论文文件绝对路径> <答案文件绝对路径>

其中:

  • 第一个参数为原文文件;
  • 第二个参数为经过增、删、改后的论文文件;
  • 第三个参数为查重结果输出文件。

程序读取两个文本文件,计算两篇文本的相似度,并将最终结果以浮点数形式写入答案文件,结果保留两位小数。

例如:

0.80

答案文件中只保存查重结果,不添加"相似度""重复率"等其他文字,以保证符合自动评测要求。


二、PSP 表格

在正式开发前,我按照 PSP(Personal Software Process)对整个项目的开发过程进行了时间规划。

PSP2.1 Personal Software Process Stages 预计耗时(分钟) 实际耗时(分钟)
Planning 计划 30 35
Estimate 估计任务需要多少时间 20 25
Development 开发 820 1150
Analysis 需求分析 70 85
Design Spec 生成设计文档 90 100
Design Review 设计复审 40 45
Coding Standard 代码规范 20 30
Design 具体设计 130 160
Coding 具体编码 220 290
Code Review 代码复审 70 90
Test 测试、修改代码、提交修改 180 350
Reporting 报告 100 150
Test Report 测试报告 50 75
Size Measurement 计算工作量 20 25
Postmortem & Process Improvement Plan 事后总结并提出改进计划 30 50
合计 970 1360

本次项目中,实际耗时与最初预计存在一定差异。特别是在 Visual Studio Python 性能分析环境配置、性能测试以及优化过程中花费的时间比原先预想更多。通过 PSP 表格可以更加直观地发现时间估计与实际开发之间的差距。


三、程序设计与实现

3.1 项目结构

最终项目的主要结构如下:

3124004212/
│
├── main.py
├── plagiarism_checker.py
├── requirements.txt
├── requirements-dev.txt
├── run_tests.bat
├── run_coverage.bat
│
└── tests/
    └── test_plagiarism_checker.py

各文件职责如下:

文件 主要作用
main.py 程序入口,接收三个命令行参数
plagiarism_checker.py 文本预处理、相似度计算及文件读写
requirements.txt Python 项目运行依赖说明
tests/test_plagiarism_checker.py 单元测试
run_tests.bat 一键运行单元测试
run_coverage.bat 一键运行测试并生成分支覆盖率报告

程序将"命令行处理"和"论文查重算法"分开,使代码结构更加清晰,也方便对核心算法进行单独测试。

3.2 程序执行流程

整体处理流程如下:

开始
  ↓
读取命令行参数
  ↓
检查参数数量
  ↓
读取原文和抄袭版文本
  ↓
文本规范化处理
  ↓
生成文本特征
  ↓
计算文本相似度
  ↓
得到最终重复率
  ↓
保留两位小数
  ↓
写入答案文件
  ↓
结束

main.py 主要负责参数检查和程序入口控制,核心的查重逻辑由 plagiarism_checker.py 完成。

这样设计以后,程序入口只负责调度,而文本处理、文件处理和相似度计算都由独立函数负责,提高了代码的可读性和可测试性。

3.3 文本预处理

原始论文中可能存在空格、换行、标点以及全角字符等差异。如果直接进行字符串比较,这些无关差异会影响最终相似度。

因此,在正式计算前首先对文本进行规范化处理,主要包括:

去除无意义空白字符
        ↓
处理标点等干扰字符
        ↓
统一全角、半角字符
        ↓
得到规范化文本

例如:

今天是星期天,天气晴。

和:

今天是星期天 天气晴

虽然标点形式不同,但其正文表达基本相同,因此预处理后应尽可能降低标点对查重结果的影响。

3.4 N-gram 文本特征

仅仅比较单个字符不能很好地反映上下文关系,因此程序进一步利用 N-gram 提取相邻字符特征。

例如文本:

今天天气很好

当使用二元字符特征时,可以得到类似:

今天
天天
天气
气很
很好

然后统计这些特征出现的频率,将文本转换为可以进行数学计算的特征向量。

这种方法不依赖额外的中文分词库,程序部署更加简单,同时对于论文经过一定程度的增、删、改之后仍然能够保持一定的识别能力。

3.5 余弦相似度

得到两个文本的特征向量后,使用余弦相似度衡量两个向量的接近程度。

其基本公式为:

[
similarity(A,B)=\frac{A \cdot B}{|A||B|}
]

余弦相似度越接近 1,说明两个文本越相似;越接近 0,说明差异越大。

最终程序将计算结果处理为 0~1 范围内的相似度,并保留两位小数写入答案文件。


四、程序接口设计

程序中主要函数按照功能可以划分为以下几类。

文本处理

normalize_text()
_ngram_counter()

负责文本规范化及 N-gram 特征统计。

相似度计算

_cosine_similarity()
calculate_similarity()

负责计算特征向量之间的相似度,并生成最终论文相似度。

文件处理

read_text_file()
write_result()
check_files()

分别负责读取论文、写入结果以及完成一次完整的文件查重流程。

程序入口

main.py

负责接收:

原文路径
抄袭版路径
答案文件路径

并调用查重模块完成计算。

这种模块化设计降低了函数之间的耦合,同时方便单元测试针对不同功能分别进行验证。


五、性能分析与优化

5.1 性能分析

在完成第一版程序以后,我使用 Visual Studio 的 Python 性能分析工具对程序进行了性能检测。

为了使性能分析工具能够采集到足够的数据,我使用较大的测试文本运行程序,并观察各个函数的运行时间。

优化前性能分析截图:
43ea6e7f2ac318397e829b1fcdbce29a

从优化前的检测分析报告可以看到,总执行时间为 17.367 秒,热路径主要集中在 main.mainplagiarism_checker.check_filesplagiarism_checker.calculate_similarityplagiarism_checker.normalize_text / plagiarism_checker.ngram_counter 这一调用链上,其中 normalize_text 占用了约 68.89% 的非独占时间,是明显的性能热点。

从"执行单个工作最多的函数"列表来看,<genexpr> 生成器表达式、_collections._count_elementsstr.joinstr.isalnum 等底层字符串操作的独占耗时占比很高,说明文本规范化和 N-gram 统计过程中存在较多可以精简的重复字符串处理。

因此,后续优化主要围绕这些频繁执行的文本处理操作展开。

5.2 优化思路

根据性能分析结果,我重新检查了查重算法中的数据处理过程,主要目标是减少不必要的重复计算和文本处理开销。

优化时遵循以下原则:

  • 尽可能减少重复的字符串处理;
  • 避免产生没有必要的中间数据;
  • 对文本只进行必要的遍历;
  • 保持原有查重结果和程序接口不变;
  • 在优化性能的同时保证代码仍然具有良好的可读性。

完成修改以后,再使用相同的测试环境和测试数据进行性能分析。

优化后性能分析截图:
e2e1762d721005122809f9dd01c5dc64

优化后的检测分析报告显示,总执行时间下降为 13.178 秒。调用链依然是 main.maincheck_filescalculate_similarity,但 normalize_text 的非独占时间占比从 68.89% 降至 60.12%,说明该函数内部的字符串处理开销得到了有效削减;同时独占时间最高的函数变为 <listcomp>_count_elementsstr.isalnum 等,unicodedata.normalize 也进入了前五,反映出优化后统一全角/半角字符的处理更加集中和高效。

5.3 优化结果

两次实际性能分析的总体耗时如下:

版本 总耗时
优化前 17.367 s
优化后 13.178 s

性能提升比例约为:24.1%

因此,优化后程序的总体运行时间下降约 24.1%

这说明此次针对程序热点进行的优化产生了较明显的效果,同时程序的输入输出格式以及查重功能保持不变。


六、单元测试

为了验证程序在不同情况下是否能够正确工作,我使用 Python 自带的 unittest 框架设计了 15 个单元测试。

测试不仅针对正常输入,还覆盖了边界情况、异常输入以及完整的文件输入输出流程。

本次主要测试内容如下:

测试 测试目的
test_01_normalize_removes_spaces_and_punctuation 验证空格和标点规范化
test_02_normalize_unifies_full_width_characters 验证全角字符转换
test_03_ngram_counter_counts_bigrams 验证二元 N-gram 统计
test_04_ngram_counter_rejects_invalid_n 验证非法 N 值处理
test_05_identical_text_returns_one 验证完全相同文本
test_06_empty_text_returns_zero 验证空文本
test_07_completely_different_text_is_low 验证完全不同文本
test_08_assignment_example_is_similar 验证题目示例
test_09_added_content_stays_similar 验证增加部分内容后的文本
test_10_cosine_similarity_empty_vector 验证空向量处理
test_11_write_result_has_two_decimal_places 验证答案保留两位小数
test_12_missing_file_raises_error 验证文件不存在异常
test_13_check_files_end_to_end 验证完整文件查重流程
test_14_main_rejects_wrong_argument_count 验证命令行参数数量错误
test_15_main_creates_answer_file 验证最终答案文件能够生成

运行测试:

python -m unittest discover -s tests -v

实际运行结果为:

Ran 15 tests
OK

说明 15 个测试全部通过。

单元测试全部通过截图:
9f5603d320b858aca1dc7e1ac0d03cf9


七、测试覆盖率

为了进一步检查单元测试是否充分覆盖程序中的不同执行路径,我使用 coverage 对程序进行了分支覆盖率分析。

执行方式:

python -m coverage run --branch -m unittest discover -s tests -v
python -m coverage report -m
python -m coverage html

实际测试结果如下:

文件 覆盖率
main.py 77%
plagiarism_checker.py 86%
tests/test_plagiarism_checker.py 95%
总体 89%

本次测试共统计:

  • 164 条语句
  • 13 条未覆盖语句
  • 32 个分支
  • 总体覆盖率 89%

从结果可以看出,核心算法 plagiarism_checker.py 的覆盖率达到 86%,整个项目总体覆盖率达到 89%。

未覆盖的部分主要属于部分异常路径或较少出现的边界分支。对于本次作业规模而言,目前测试已经能够覆盖主要算法、正常输入、错误输入以及完整文件输入输出流程。

测试覆盖率报告截图:
cc124ae3a3b0a4c8f8a6dc4d4a69dbae

八、异常处理

论文查重程序需要处理文件、参数以及文本数据,因此程序中设计了相应的异常处理机制。

8.1 命令行参数数量错误

程序规定必须输入三个参数:

  • 原文
  • 抄袭版论文
  • 答案文件

如果参数数量不正确,程序会提示正确的使用方法,例如:

用法:python main.py <原文文件绝对路径> <抄袭版论文文件绝对路径> <答案文件绝对路径>

对应单元测试:test_14_main_rejects_wrong_argument_count

8.2 输入文件不存在

如果用户输入的原文路径或抄袭版路径不存在,程序不能继续读取文件。

因此程序会捕获文件读取异常,并给出错误信息,而不是直接异常退出。

对应单元测试:test_12_missing_file_raises_error

8.3 非法 N-gram 参数

生成 N-gram 特征时,参数必须满足算法要求。

如果传入非法参数,程序会主动拒绝该参数,以防产生无效结果。

对应单元测试:test_04_ngram_counter_rejects_invalid_n

8.4 空文本与空向量

论文文件可能为空,因此程序专门对空文本以及空特征向量进行了处理,避免出现除零等问题。

对应测试:test_06_empty_text_returns_zerotest_10_cosine_similarity_empty_vector

通过这些异常和边界处理,程序能够在输入不符合预期时给出合理结果或提示,提高程序的健壮性。


九、总结

本次论文查重项目从需求分析、算法设计、编码实现,到性能分析优化、单元测试与覆盖率检验,完整走了一遍个人软件开发流程。通过 Visual Studio 性能分析工具定位到 normalize_text 等文本处理函数是主要性能热点后,针对性地精简了字符串处理逻辑,使程序总运行时间从 17.367 秒下降到 13.178 秒,提升约 24.1%,同时保持了原有的查重准确性和接口不变。15 个单元测试全部通过,总体分支覆盖率达到 89%,覆盖了正常、边界、异常等多种输入情况,为程序的正确性和健壮性提供了较好的保障。

posted on 2026-09-15 17:16  Zki  阅读(8)  评论(0)    收藏  举报

导航