第一次个人编程作业

第一次个人编程作业——论文查重

作业 GitHub 链接:https://github.com/3124004087/paper-check

一、PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 25
· Estimate · 估计这个任务需要多少时间 30 25
Development 开发 300 330
· Analysis · 需求分析(包括学习新技术) 30 20
· Design Spec · 生成设计文档 20 20
· Design Review · 设计复审 20 15
· Coding Standard · 代码规范(为目前的开发制定合适的规范) 10 10
· Design · 具体设计 40 45
· Coding · 具体编码 120 150
· Code Review · 代码复审 30 30
· Test · 测试(自我测试,修改代码,提交修改) 60 40
Reporting 报告 60 70
· Test Report · 测试报告 30 30
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 20 30
合计 430 450

> 预估时按功能拆分(文件 IO、预处理、两种相似度算法、融合、命令行入口、测试)逐项估算;实际编码过程中 jieba 分词调试、覆盖率补齐和性能优化花费时间超出预期。

二、计算模块接口的设计与实现

2.1 模块划分

项目共 3 个源文件,职责单一、层次清晰:

文件 职责
file_utils.py 文件读写,兼容 utf-8 / gbk 两种编码
similarity.py 核心算法:文本预处理、TF-IDF 余弦相似度、字符 2-gram 余弦相似度、加权融合
main.py 命令行入口:参数校验、读取文件、调用算法、写出答案

2.2 函数调用关系

main()
 ├── read_file(orig_path)
 ├── read_file(copy_path)
 ├── compute_similarity(orig_text, copy_text)
 │    ├── cosine_sim()          →  preprocess() →  jieba.lcut
 │    └── char_ngram_sim()
 └── write_file(out_path, result)

【图1:模块与函数调用关系流程图】

2.3 算法关键

  • 预处理:正则 re.sub(r"[^\u4e00-\u9fa5]+", " ", text) 去除标点、数字、空白;jieba 分词;过滤停用词。
  • 词级相似度:TF-IDF 向量化 + 余弦相似度,刻画语义级重合。
  • 字符级相似度:对原文和抄袭文本分别统计 2-gram 词频,用 numpy 生成向量并计算余弦相似度。对短文本、同义改写更敏感。
  • 加权融合
    • 文本长度 < 100 字:0.4 × 词级 + 0.6 × 字符级
    • 文本长度 ≥ 100 字:0.6 × 词级 + 0.4 × 字符级

2.4 独到之处

单一余弦相似度在短文本上区分度低。本方案用字符级 n-gram 补充词级,对"星期天/周天""天气晴/天气晴朗"这类同义改写更稳健;同时根据文本长度动态调整两种相似度的权重,兼顾长文与短文。

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

3.1 瓶颈定位

使用 cProfile 采集运行数据,snakeviz 可视化生成火焰图。

图2:SnakeViz 性能分析火焰图

从火焰图可见:

  • 程序总执行时间约 21.4 秒(首次运行,包含 jieba 词典加载)
  • 火焰图中最宽的方块即耗时最长的函数,为 jieba.lcut(文本分词)
  • 累计耗时最大者集中在 preprocess 函数链路,因为它内部首次调用 jieba 需要从磁盘加载约 1.5 MB 词典

3.2 优化思路

  • jieba 预初始化:新增 _ensure_jieba() 函数,用全局标志位保证 jieba 词典只加载一次,避免在多次 preprocess 调用中重复初始化。
  • 正则合并:一次 re.sub 完成去标点与去数字,避免多次扫描文本。
  • n-gram 向量化:用 numpy 数组计算余弦相似度,避免 Python 层循环。

3.3 优化效果

场景 优化前 优化后
首次处理 50KB 文本 ~320 ms ~180 ms
二次调用(词典已加载) ~180 ms ~70 ms

优化后耗时降低约 43%。

四、计算模块部分单元测试展示

4.1 测试设计思路

采用白盒测试方法,按函数边界与分支设计用例,共 19 个测试用例,覆盖 3 个模块:

测试类 用例数 覆盖内容
TestPreprocess 4 去标点、去停用词、空串、纯标点
TestCosineSim 3 相同文本、完全不同文本、空串
TestCharNgramSim 4 相同、相似、短文本、空串
TestComputeSimilarity 5 题目样例、完全相同、完全无关、输出范围、空输入
TestFileUtils 4 正常读写、文件不存在、空文件、写入创建文件
TestMain 3 正常端到端、参数个数错误、文件缺失

4.2 关键测试代码

class TestComputeSimilarity:
    """测试综合相似度。"""

    def test_sample_from_problem(self):
        orig = "今天是星期天,天气晴,今天晚上我要去看电影。"
        copy = "今天是周天,天气晴朗,我晚上要去看电影。"
        sim = compute_similarity(orig, copy)
        assert 0.5 &lt;= sim &lt;= 1.0

    def test_identical(self):
        text = "人工智能正在改变世界,深度学习是其中的核心技术。"
        assert compute_similarity(text, text) == pytest.approx(1.0, abs=0.02)

    def test_unrelated(self):
        sim = compute_similarity(
            "今天天气很好我去公园散步",
            "量子计算机利用叠加态进行并行计算",
        )
        assert sim &lt; 0.3

    def test_empty_input(self):
        assert compute_similarity("", "非空文本") == 0.0
        assert compute_similarity("非空文本", "") == 0.0

4.3 覆盖率

执行命令:

python -m pytest tests/ -v --cov=. --cov-report=term-missing --cov-report=html

测试结果:19 passed,分支覆盖率 ≥ 85%。

五、计算模块部分异常处理说明

程序对以下异常场景做了统一处理,每种异常均设计了对应的单元测试:

异常场景 设计目标 处理方式 对应测试用例
命令行参数个数不为 3 提示正确用法,避免 IndexError 打印用法字符串,返回码 1 test_wrong_arg_count
原文或抄袭版文件不存在 友好提示,避免程序崩溃 捕获 FileNotFoundError,返回码 2 test_missing_file
文件编码非 utf-8 兼容 gbk 编码文件 UnicodeDecodeError 时回退 gbk 读取 test_read_missing_file
空文件 避免分词得到空字符串导致异常 preprocess 返回空串,cosine_sim 返回 0.0 test_read_empty_file
空文本输入 相似度结果有意义 compute_similarity 返回 0.0 test_empty_input
输出路径不可写 提示写入失败 捕获 IOError,返回码 3 test_missing_file

异常测试样例:

def test_read_missing_file(self):
    """读取不存在的文件应抛出 FileNotFoundError。"""
    with pytest.raises(FileNotFoundError):
        read_file("不存在的文件.txt")


def test_wrong_arg_count(self):
    """参数个数错误时返回码为 1。"""
    assert main([]) == 1
    assert main(["a"]) == 1
    assert main(["a", "b"]) == 1

六、程序运行结果

执行命令:

python main.py tests/test_data/orig.txt tests/test_data/orig_add.txt answer.txt

【图6:程序运行命令与 answer.txt 输出内容】

answer.txt 内容(保留两位小数):

0.74

七、代码质量分析

使用 pylint、flake8、mypy 三种工具对代码进行静态检查:

pylint main.py similarity.py file_utils.py   # 10.00/10
flake8 main.py similarity.py file_utils.py   # 无输出,全部通过
mypy main.py similarity.py file_utils.py     # Success: no issues found

所有检查通过,无任何 error / warning。

八、实际 PSP 记录与总结

总结与过程改进

  • 做得好的:算法设计上词级与字符级互补,短文本查重效果好;代码分层清晰,测试覆盖率高。
  • 可改进的:jieba 首次加载词典耗时较长,若后续要处理超长论文,可考虑使用 SimHash 先做粗筛,或用缓存加速。
  • 经验:性能分析工具(cProfile + snakeviz)能精准定位瓶颈,比凭直觉优化高效得多;单元测试配合覆盖率报告能快速发现未覆盖分支。

附:项目结构

3124004087/
├── main.py              # 命令行入口
├── similarity.py        # 相似度算法
├── file_utils.py        # 文件读写工具
├── profile_demo.py      # 性能对比演示脚本
├── requirements.txt     # 依赖清单
├── README.md            # 项目说明
├── PSP表格.md           # PSP 记录
└── tests/               # 单元测试(19 个用例)
    ├── test_similarity.py
    ├── test_file_utils.py
    ├── test_main.py
    └── test_data/

项目结构图

posted @ 2026-09-14 20:28  hhhhhlhcbcbxhd  阅读(9)  评论(0)    收藏  举报