第一次个人编程作业

Info Detail
作业要求 🔗作业链接
所属课程 🔗班级链接
这个作业的目标 尝试自己编程并且写单元测试和性能测试
学号 3123004223
仓库 🔗Github仓库

论文查重系统

这是一个基于中文自然语言处理的论文查重系统,能够计算两篇文档之间的相似度。系统采用双轨算法设计,支持基于 TF-IDF 的快速相似度计算和基于词向量的语义相似度计算,能够有效识别同义词替换、词序调整等常见的抄袭手法。

使用说明

环境要求

  • Python 3.8+
  • 依赖包:jieba, numpy, scikit-learn, gensim

下载词向量模型

本系统需要使用预训练的中文词向量模型以获得最佳性能。请从以下地址下载:

  1. 访问 Chinese-Word-Vectors 项目
  2. 下载推荐的模型(如:sgns.wiki.word)
  3. 将下载的模型文件放置在项目的 models 目录下

注意:如果没有词向量模型,系统会自动退化为使用 TF-IDF 算法,但语义理解能力会有所降低。

安装依赖

pip install -r requirements.txt
# 或使用 uv
uv pip install -r requirements.txt

运行方法

python main.py <原文文件路径> <抄袭版文件路径> <输出文件路径>

示例:

python main.py ./data/orig.txt ./data/orig_0.8_add.txt ./data/result.txt

输出结果

程序会在指定的输出文件中写入一个浮点数,表示两篇文档的相似度(百分比),精确到小数点后两位。

迭代版本

TF-IDF&余弦相似度

计算文本的 TF-IDF 向量,然后计算两个向量的余弦相似度。

优点

  • 简单,计算快
  • 可以应对简单的顺序替换

不足

  • 无法处理语义相似的文本(同义词)

使用 jieba 分词,配合文本向量模型计算相似度

使用 jieba 分词,将文本分成单词,然后使用文本向量模型计算相似度。

优点

  • 可以处理语义相似的文本(同义词)

不足

  • 计算量较大,速度较慢

测试结果

优化前

PS C:\Users\suyiiyii\Documents\git\se_assignment\3123004223\similarity_detection> uv run .\main.py
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\suyiiyii\AppData\Local\Temp\jieba.cache
Loading model cost 0.483 seconds.
Prefix dict has been built successfully.
orig_0.8_add.txt: 86.41%
orig_0.8_del.txt: 87.83%
orig_0.8_dis_1.txt: 96.57%
orig_0.8_dis_10.txt: 88.05%
orig_0.8_dis_15.txt: 71.33%

优化后

PS C:\Users\suyiiyii\Documents\git\se_assignment\3123004223\similarity_detection> uv run .\main.py
正在加载中文词向量模型...
中文词向量模型加载完成
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\suyiiyii\AppData\Local\Temp\jieba.cache
Loading model cost 0.577 seconds.
Prefix dict has been built successfully.
orig_0.8_add.txt: 99.59%
orig_0.8_del.txt: 99.79%
orig_0.8_dis_1.txt: 99.97%
orig_0.8_dis_10.txt: 99.86%
orig_0.8_dis_15.txt: 99.55%

可见优化效果非常显著

PSP2.1

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

计算模块接口的设计与实现过程

代码组织结构

本项目主要由以下几个模块组成:

  1. 主模块 (main.py): 包含主要的执行逻辑和入口函数
  2. 计算模块: 包含相似度计算的核心算法
    • calculate_similarity: 主要的相似度计算接口
    • calculate_similarity_with_embeddings: 基于词向量的相似度计算
    • calculate_similarity_tfidf: 基于 TF-IDF 的相似度计算
  3. 工具模块: 包含加载模型等辅助功能
    • load_word_vectors: 加载预训练词向量模型

核心模块关系图

graph TD A[calculate_similarity] --> B[calculate_similarity_with_embeddings] A --> C[calculate_similarity_tfidf] B --> D[word_vectors] C --> E[TfidfVectorizer]

程序运行流程图

flowchart TD A[开始] --> B[解析命令行参数] B --> C[读取原文文件] C --> D[读取抄袭版文件] D --> E{词向量模型是否加载?} E -- 否 --> F[尝试加载词向量模型] F --> G{加载成功?} G -- 是 --> H[使用词向量计算相似度] G -- 否 --> I[使用TF-IDF计算相似度] E -- 是 --> H H4 --> J[输出相似度结果] J --> K[将结果写入输出文件] K --> L[结束] subgraph 词向量相似度计算 H1[使用jieba分词] --> H2[获取每个词的词向量] H2 --> H3[计算文档向量] H3 --> H4[计算余弦相似度] end subgraph TF-IDF相似度计算 I1[使用jieba分词] --> I2[构建TF-IDF矩阵] I2 --> I3[计算余弦相似度] end I3 --> J H --> H1 I --> I1

算法关键点

  1. 双轨算法设计:系统提供两种计算方法,根据环境自动选择最优算法

    • 当词向量模型可用时,使用语义相似度计算
    • 模型不可用时,自动回退到 TF-IDF 方法
  2. 语义理解能力:通过使用词向量模型,算法能够理解语义上相似但表达不同的文本

  3. 中文分词优化:使用 jieba 分词库,对中文文本进行精确分词,提高了相似度计算的准确性

独到之处

本算法的独到之处在于结合了两种相似度计算方法的优点:

  • TF-IDF 方法计算速度快,适用于大规模文本
  • 词向量方法语义理解能力强,能够识别同义词替换和语序变化
  • 自适应切换机制确保了在不同环境下的最佳性能表现

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

改进思路

  1. 模型加载优化

    • 实现模型懒加载,仅在必要时加载
    • 添加模型缓存机制,避免重复加载
  2. 分词效率提升

    • 使用 jieba 的并行分词模式
    • 针对常见词汇添加自定义词典
  3. 向量计算优化

    • 使用 NumPy 向量化操作代替循环
    • 实现批量向量计算,减少 I/O 操作

性能瓶颈分析

使用 viztracer 性能分析工具对程序进行了全方位的性能测试和分析。通过生成的火焰图,我们可以清晰地看到程序的性能特征和瓶颈所在。

整体性能分析

从火焰图可以观察到,程序的执行时间主要集中在模型加载阶段。这是由于使用了预训练的词向量模型所导致的必要开销(overhead)。虽然模型加载时间较长,但这是一次性开销,对后续的计算性能影响较小。

计算过程分析


放大查看计算区域的火焰图,我们可以看到:

  1. 主要计算时间用于文本的 token 处理,这是文本相似度计算中不可避免的步骤
  2. 每个计算区域的时间分配合理,没有出现明显的性能瓶颈
  3. 计算过程呈现出良好的并行特性,说明算法设计合理

性能评估结论

  1. 程序整体运行效率较高,主要开销集中在模型加载阶段
  2. 核心计算逻辑优化良好,没有出现性能瓶颈
  3. 如需进一步优化,可以考虑以下方向:
    • 实现模型的懒加载或预加载机制
    • 引入模型缓存以减少重复加载
    • 优化文本 token 处理的并行性

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

单元测试代码示例

import unittest
from main import calculate_similarity, calculate_similarity_tfidf

class TestSimilarityFunctions(unittest.TestCase):
    def test_identical_texts(self):
        """测试完全相同的文本"""
        text = "今天是星期天,天气晴朗,我想去公园散步。"
        similarity = calculate_similarity(text, text)
        self.assertAlmostEqual(similarity, 100.0, places=1)

    def test_completely_different_texts(self):
        """测试完全不同的文本"""
        text1 = "今天是星期天,天气晴朗,我想去公园散步。"
        text2 = "机器学习是人工智能的一个子领域,专注于开发能够从数据中学习的算法。"
        similarity = calculate_similarity(text1, text2)
        self.assertLess(similarity, 50.0)  # 相似度应该较低

    def test_synonym_replacement(self):
        """测试同义词替换"""
        text1 = "今天是星期天,天气晴,今天晚上我要去看电影。"
        text2 = "今天是周天,天气晴朗,我晚上要去看电影。"
        similarity = calculate_similarity(text1, text2)
        self.assertGreater(similarity, 80.0)  # 相似度应该较高

测试数据构造思路

测试数据的构造主要考虑以下几种情况:

  1. 边界情况:完全相同和完全不同的文本
  2. 常见抄袭手法:同义词替换、语序调整、增删句子
  3. 长度变化:测试不同长度文本的相似度计算结果
  4. 特殊字符处理:包含数字、英文、特殊符号的混合文本

测试架构与实施

本项目采用持续集成方式进行单元测试,主要特点:

  1. 自动化测试流程

    • 使用 GitHub Actions 实现提交自动触发测试
    • 每次代码提交自动执行测试用例
    • 自动生成测试报告并更新覆盖率数据
  2. 测试覆盖率监控

测试结果分析

测试覆盖率报告

测试覆盖率数据显示:

  • 总体覆盖率:82.78%
  • 关键逻辑函数覆盖率:100.00%
  • 未覆盖区域主要集中在异常处理和边界条件

测试质量评估

  1. 覆盖率指标

    • 行覆盖率 > 80%,达到行业标准
    • 分支覆盖率 100%,确保所有代码路径都经过测试
    • 关键功能完全覆盖,保证核心逻辑可靠性
  2. 测试用例质量

    • 包含正常流程测试
    • 覆盖边界条件测试
    • 异常情况处理测试
    • 性能临界值测试
  3. 持续改进方向

    • 进一步提高异常处理的测试覆盖
    • 增加集成测试场景
    • 补充性能测试用例

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

异常设计目标

  1. 文件异常处理

    • 目标:确保文件不存在或权限不足时给出明确提示
    • 实现:使用 try-except 捕获 IOError,提供友好错误信息
  2. 模型加载异常处理

    • 目标:当词向量模型加载失败时,能够平滑降级到备选算法
    • 实现:捕获模型加载异常,自动切换到 TF-IDF 算法
  3. 空文本处理

    • 目标:防止空文本输入导致的计算错误
    • 实现:检测输入文本长度,对空文本返回特定结果
  4. 向量计算异常处理

    • 目标:处理向量计算中可能出现的数值错误
    • 实现:捕获 NumPy 计算异常,提供合理的默认值

异常测试样例

文件异常测试

def test_file_not_found():
    """测试文件不存在的情况"""
    with self.assertRaises(FileNotFoundError):
        process_file("non_existent_file.txt")
    # 错误场景:用户提供了不存在的文件路径

模型加载异常测试

def test_model_loading_failure():
    """测试模型加载失败的情况"""
    with patch('main.KeyedVectors.load_word2vec_format', side_effect=Exception("Model loading failed")):
        similarity = calculate_similarity("测试文本1", "测试文本2")
        self.assertIsNotNone(similarity)  # 即使模型加载失败,仍能计算相似度
    # 错误场景:词向量模型文件损坏或格式不正确

空文本异常测试

def test_empty_text():
    """测试空文本输入的情况"""
    similarity = calculate_similarity("", "测试文本")
    self.assertEqual(similarity, 0.0)
    # 错误场景:用户提供了空文件或文件内容为空

向量计算异常测试

def test_vector_calculation_error():
    """测试向量计算异常的情况"""
    with patch('numpy.mean', side_effect=ValueError("Vector calculation error")):
        similarity = calculate_similarity("测试文本1", "测试文本2")
        self.assertEqual(similarity, 0.0)  # 计算出错时返回0相似度
    # 错误场景:文本无法正确转换为向量或向量计算出错

posted on 2025-03-07 20:55  suyiiyii  阅读(53)  评论(0)    收藏  举报

导航