第一次个人编程作业
第一次个人编程作业 - 论文查重
Github 仓库地址:
https://github.com/zzy685/ZZY685
(代码位于仓库中学号命名的文件夹下)
一、PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 15 |
| · Estimate | ・估计这个任务需要多少时间 | 10 | 5 |
| Development | 开发 | 180 | 150 |
| · Analysis | ・需求分析 (包括学习新技术) | 30 | 25 |
| · Design Spec | ・生成设计文档 | 15 | 10 |
| · Design Review | ・设计复审 | 10 | 5 |
| · Coding Standard | ・代码规范 (为目前的开发制定合适的规范) | 10 | 5 |
| · Design | ・具体设计 | 20 | 15 |
| · Coding | ・具体编码 | 60 | 50 |
| · Code Review | ・代码复审 | 15 | 10 |
| · Test | ・测试(自我测试,修改代码,提交修改) | 20 | 25 |
| Reporting | 报告 | 60 | 50 |
| · Test Report | ・测试报告 | 20 | 15 |
| · Size Measurement | ・计算工作量 | 10 | 5 |
| · Postmortem & Process Improvement Plan | ・事后总结,并提出过程改进计划 | 30 | 30 |
| 合计 | 260 | 215 |
二、计算模块接口的设计与实现过程
2.1 功能划分与版本控制
本项目按照开发阶段划分为初始完整版本和性能优化版本两个阶段,每个阶段完成后分别 commit 到 Github,共两次主要 commit:
| 阶段 | 包含文件 | 功能说明 | Commit Message |
|---|---|---|---|
| 初始完整版本(Commit 1) | main.py、plagiarism_checker.py、requirements.txt、test_plagiarism_checker.py、test_data/ |
实现论文查重核心逻辑(文本预处理、jieba 分词、词频向量余弦相似度、字符级 bigram 相似度、加权融合、命令行参数解析、文件读写),并配套 24 个单元测试用例和测试样例数据 | Add files via upload |
| 性能优化版本(Commit 2) | plagiarism_checker.py(更新) |
使用 cProfile 分析性能瓶颈后,优化文本预处理(避免重复 clean_text 调用)、优化余弦相似度计算(单次遍历同时计算点积和模长),性能提升约 48% | refactor: 性能优化,避免重复文本预处理,优化余弦相似度计算 |
初始版本完成后程序已能正常运行并通过全部测试;优化版本在其基础上进行性能剖析和针对性改进,体现了 "先实现、后优化" 的开发流程。
2.2 整体架构
main.py # 主程序入口,处理命令行参数和文件IO
plagiarism\_checker.py # 核心查重算法模块
test\_plagiarism\_checker.py # 单元测试
requirements.txt # 依赖声明
2.3 核心函数
| 函数名 | 功能说明 |
|---|---|
clean_text(text) |
文本预处理,去除标点符号和空白字符 |
segment_words(text) |
中文分词,优先使用 jieba,不可用时退化为单字分词 |
get_char_ngrams(text, n) |
生成字符级 n-gram 列表 |
cosine_similarity(vec1, vec2) |
计算两个词频向量的余弦相似度 |
word_level_similarity(text1, text2) |
词级相似度计算 |
ngram_level_similarity(text1, text2, n) |
n-gram 级相似度计算 |
calculate_similarity(orig_text, plag_text) |
综合相似度计算(主接口) |
2.4 算法设计
采用词级余弦相似度 + 字符级 bigram 相似度的加权融合算法:
-
文本预处理:去除所有标点符号、空白字符,统一转为小写。
-
词级相似度(权重 0.6):使用 jieba 精确模式分词,统计词频向量,计算余弦相似度。
-
字符级 bigram 相似度(权重 0.4):将预处理后的文本切分为相邻字符对,统计频率向量,计算余弦相似度。
-
加权融合:
最终重复率 = 0.6 × 词级相似度 + 0.4 × bigram 相似度。
2.5 算法独到之处
-
双维度融合:词级相似度捕捉语义重复,bigram 相似度捕捉字面重复,对增删改、语序调整等抄袭手段均有较好检测能力。
-
优雅降级:jieba 不可用时自动退化为单字分词,保证程序在任何环境下都能运行。
-
多编码兼容:文件读取支持 UTF-8、GBK、GB2312、UTF-16 等多种编码。
-
边界保护:对空文本、纯标点文本等边界情况完善处理,不会出现除零错误或异常退出。
2.6 关键流程图
开始
  │
  ▼
读取原文文件和抄袭版文件
  │
  ▼
文本预处理(去标点、去空白、转小写)
  │
  ├─────────────────┐
  ▼ ▼
jieba 分词 字符级 bigram 切分
  │ │
  ▼ ▼
统计词频向量 统计 bigram 频率向量
  │ │
  ▼ ▼
计算余弦相似度 计算余弦相似度
  │ │
  └────────┬────────┘
  ▼
  加权融合 (0.6/0.4)
  │
  ▼
  结果保留两位小数
  │
  ▼
  写入答案文件
  │
  ▼
  结束
2.7 程序运行效果
使用作业示例进行测试:

原文与抄袭版的重复率为 0.79,结果已正确写入答案文件。
三、计算模块接口部分的性能改进
3.1 性能分析工具
使用 Python 内置的 cProfile 性能分析工具,对长文本样例(约 300 字)重复运行 100 次进行分析。
3.2 性能分析结果(改进前)

3.3 性能瓶颈分析
-
clean_text(文本预处理):被调用 400 次,累计 0.020 秒。原因是word_level_similarity和ngram_level_similarity各自内部调用clean_text,导致同一文本被预处理两次。 -
cosine_similarity(余弦相似度):累计 0.017 秒。原始实现分别计算点积、模 1、模 2,需要遍历向量多次,且创建了common_keys集合。 -
get_char_ngrams(n-gram 生成):累计 0.013 秒。
3.4 改进思路与措施
改进一:避免重复预处理
- 在
calculate_similarity中统一调用clean_text一次,将预处理后的文本传入内部函数_segment_words_cleaned和_get_char_ngrams_cleaned,消除重复的预处理开销。
改进二:余弦相似度计算优化
-
原始实现需要遍历向量 3 次(点积、模 1、模 2),并创建交集集合。
-
改进后在一次遍历中同时累加点积和 vec1 的模长,选择较短的向量遍历以减少循环次数,消除了集合创建开销。
改进三:内部函数封装
- 新增
_segment_words_cleaned和_get_char_ngrams_cleaned两个内部函数,接受已预处理的文本,避免重复清洗。
3.5 改进前后对比
| 指标 | 改进前 | 改进后 |
|---|---|---|
| 100 次运行总耗时 | 0.046 秒 | 0.024 秒 |
| 单次平均耗时 | 0.00046 秒 | 0.00024 秒 |
| 函数调用次数 | 211,222 次 | 56,822 次 |
| 性能提升 | — | 约 48% |
改进后程序在 300 字文本上单次运行仅约 0.24 毫秒,远低于 5 秒的时间限制,内存占用也远低于 2048MB 限制。
四、计算模块部分单元测试展示
4.1 测试框架
使用 Python 标准库 unittest 编写单元测试,共设计 24 个测试用例,远超要求的 10 个。
4.2 测试用例分类
| 测试类 | 测试用例数 | 覆盖范围 |
|---|---|---|
TestCleanText |
4 | 标点去除、空白去除、空输入、纯标点输入 |
TestSegmentWords |
2 | 正常分词、空文本分词 |
TestCharNgrams |
3 | bigram 生成、短文本、空文本 |
TestCosineSimilarity |
3 | 相同向量、正交向量、空向量 |
TestCalculateSimilarity |
9 | 相同文本、不同文本、作业示例、空输入、空白文本、部分重叠、语序调整、长文本、范围校验 |
TestFileOperations |
2 | 结果写入读取、UTF-8 文件读取 |
TestMainIntegration |
1 | 完整文件输入输出流程 |
4.3 核心测试用例说明
测试 15:作业示例验证
def test\_homework\_example(self):
  orig = "今天是星期天,天气晴,今天晚上我要去看电影。"
  plagiarized = "今天是周天,天气晴朗,我晚上要去看电影。"
  sim = calculate\_similarity(orig, plagiarized)
  self.assertGreater(sim, 0.5) # 应有较高相似度
  self.assertLess(sim, 1.0) # 但不应完全相同
构造思路:直接使用作业题目给出的示例,验证算法对典型增删改抄袭的检测能力。
测试 13:完全相同文本
def test\_identical\_texts(self):
  text = "今天是星期天,天气晴,今天晚上我要去看电影。"
  sim = calculate\_similarity(text, text)
  self.assertAlmostEqual(sim, 1.0, places=2)
构造思路:验证自比较时重复率应为 1.0(100%)。
测试 19:语序调整
def test\_word\_order\_change(self):
  text1 = "我喜欢吃苹果和香蕉。"
  text2 = "香蕉和苹果我喜欢吃。"
  sim = calculate\_similarity(text1, text2)
  self.assertGreater(sim, 0.4)
构造思路:词语完全相同但语序颠倒,验证词级相似度对语序变化的鲁棒性。
测试 24:完整集成测试
def test\_full\_pipeline(self):
  # 使用 tempfile 创建临时文件,模拟命令行调用 main()
  # 验证输出文件存在且结果在合理范围内
  ...
构造思路:端到端验证文件读写、命令行参数解析和结果输出。
4.4 测试运行结果


全部 24 个测试用例通过,覆盖了正常场景、边界条件和异常输入。
4.5 测试覆盖率

测试设计评价:24 个测试用例从单元测试到集成测试层层递进,覆盖文本预处理、分词、相似度计算、文件 IO 等所有核心功能,包含空输入、纯标点、短文本、长文本等边界情况,能够满足该程序的测试要求。
五、附录
5.1 项目文件结构
学号/
├── main.py # 主程序入口
├── plagiarism\_checker.py # 查重算法核心模块
├── test\_plagiarism\_checker.py # 单元测试
├── requirements.txt # 依赖(jieba)
└── test\_data/ # 测试样例
  ├── orig.txt
  ├── orig\_add.txt
  ├── long\_orig.txt
  └── long\_plag.txt
5.2 运行方式
\# 安装依赖
pip install -r requirements.txt
\# 运行查重
python main.py \[原文文件路径] \[抄袭版论文文件路径] \[答案文件路径]
\# 运行单元测试
python -m unittest test\_plagiarism\_checker -v
5.3 事后总结与改进计划
本次作业通过实现论文查重算法,加深了对文本相似度计算、模块化设计和单元测试的理解。未来可以从以下方面改进:
-
算法优化:引入 TF-IDF 权重,降低常见词的影响;尝试 SimHash 算法支持大规模文档查重。
-
性能提升:对超大规模文本,使用哈希技巧和稀疏向量进一步降低内存和计算开销。
-
测试完善:引入 coverage.py 生成精确覆盖率报告,补充更多真实论文样例测试。

浙公网安备 33010602011771号