论文查重项目

这个作业的GitHub地址 https://github.com/kakadomi/kakadomi
这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477
这个作业的目标 <完成 PSP 表格、开发查重代码、测试及性能分析>

一. PSP表格

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



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

1. 模块接口设计

通过5 个核心函数实现模块化拆分,函数间通过 “参数传递” 形成依赖关系,属于典型的 “管道式” 流程设计。

  • 各个函数作用

    • read_fild : 读取文本文件,处理文件读取异常
    • preprocess_text : 中文分词、过滤空白符,标准化文本格式
    • calculate_similarity : 核心计算:TF-IDF 向量转换 + 余弦相似度计算
    • write_result : 将相似度结果写入result.txt,处理写入异常
    • main : 流程控制:调用其他函数,完成“输入-计算-输出”过程
  • 程序简单流程图

31


2. 算法关键:精准衡量文本相似的核心逻辑

算法通过“特征提取 + 量化计算”,避开传统“逐字比对”的缺陷,重点解决“中文文本如何准确比相似”的问题:

  • TF-IDF向量转换: 通过“词频(TF) × 逆文档频率(IDF)” 给词语加权,自动压低“的、是”等无意义高频词的权重,突出“专业术语、特殊表述”等核心词,避免垃圾词干扰查重结果。
  • 余弦相似度: 将文本转化为向量后,通过计算向量夹角的余弦值衡量相似性。即使两篇文本长度不同(如抄袭文是原文节选),只要核心词分布一致,就能精准识别抄袭关系,比“字符匹配率”更贴合实际需求。

3. 独到设计:适配场景的代码优势

代码围绕 “中文查重” 的需求做了轻量化优化,兼顾易用性和稳定性:

  • 轻量模块化架构:不用类和复杂结构,仅通过 5 个函数实现 “输入→预处理→计算→输出” 全流程,代码量少、逻辑清晰,容易维护和调试,适合小规模查重场景。
  • 精细化异常处理:针对 “文件读取 / 写入” 等高频出错环节,捕获 “文件未找到” 等具体异常,同时用通用异常兜底。既避免程序崩溃,又能明确告知错误原因,方便定位问题。
  • 适配中文:用 jieba 做中文分词(比 Python 内置方法更精准,能正确拆分专业词),同时过滤空格、换行等空白字符,避免因排版差异(如原文多换行、抄袭文无换行)导致的相似度误判。



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

1. 记录改进计算模块性能上所花费的时间

阶段 花费时间(分钟) 主要内容
初始版本 80 完成基本功能,核心算法
第一次性能分析 40 使用Snakeviz性能分析工具,找到主要耗时部分
进行优化 60 主要对文本预处理、相似度计算、批量处理与内存部分进行优化
第二次性能分析 20 验证优化效果,与第一次进行对比
总花费时间 200

2. 第一次性能分析结果

基于Snakeviz性能分析工具,找到主要耗时部分

6

性能瓶颈总结
函数 执行时间 占比 严重程度 问题分析
core_task 0.753s 100% 严重 整体流程耗时集中在核心逻辑
calculate_similarity 0..753s 100% 严重 相似度计算几乎消耗core_task全部时间
preprocess_test 0.743s 98.7% 文本预处理是相似度计算的主要耗时点
jieba.cut 0.735s 97.6% 结巴分词的cut方法以及DAG构建、分词策略是文本预处理的性能瓶颈

3.改进优化

分词优化:

  • @lru_cache 缓存重复文本的分词结果,避免了相同文本多次调用 jieba.cut
  • 预编译正则 PUNCT_RE 加速了文本清洗,减少了无效分词的开销
import re  
from functools import lru_cache  

# 预编译更严格的标点/无效字符正则(覆盖更多干扰符号,减少分词负担)
# 匹配非中文、非字母、非数字、非空格的字符
PUNCT_RE = re.compile(r'[^\w\s\u4e00-\u9fa5]', re.UNICODE)  

# 缓存重复文本的分词结果(增大缓存容量,适配更多重复文本场景)
@lru_cache(maxsize=1024)  # 缓存容量提升至1024
def preprocess_text(text):
    """优化后:更高效的文本清洗→分词→过滤,减少重复计算和中间开销"""
    # 步骤1:清洗文本(移除更多无效字符,让分词只处理有效内容)
    text = PUNCT_RE.sub('', text).strip()  
    if not text:  # 空文本直接返回,避免后续无效操作
        return ''

    # 步骤2:结巴分词(禁用HMM模式,减少动态规划开销,适合非新词主导的文本)
    # cut_all=False:精准模式;HMM=False:禁用隐马尔可夫模型(加速分词)
    words = jieba.cut(text, cut_all=False, HMM=False)  

    # 步骤3:过滤空白字符并拼接(减少内存临时结构)
    return ' '.join(word for word in words if word.strip())

相似度计算优化:
全局复用 TfidfVectorizer 避免重复初始化,限制最大特征数减少矩阵计算量,速度提升约 15%。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

# 全局复用向量器(避免重复初始化的开销)
tfidf_vectorizer = TfidfVectorizer(
    token_pattern=r"(?u)\b\w+\b",  # 匹配分词后的词语
    min_df=1,  # 最小词频(减少稀疏矩阵计算量)
    max_features=10000  # 限制最大特征数(加速转换)
)

def calculate_similarity(original_text: str, plagiarized_text: str) -> float:
    """优化后的相似度计算:复用向量器+减少中间变量"""
    # 1. 预处理(复用优化后的分词函数)
    orig_words = preprocess_text(original_text)
    plag_words = preprocess_text(plagiarized_text)
    
    # 2. 转换为字符串(适配TfidfVectorizer输入格式)
    orig_str = ' '.join(orig_words) if orig_words else ''
    plag_str = ' '.join(plag_words) if plag_words else ''
    
    # 3. 批量转换+计算相似度(减少函数调用次数)
    tfidf_matrix = tfidf_vectorizer.fit_transform([orig_str, plag_str])
    
    # 4. 直接计算余弦相似度(避免额外的矩阵切片开销)
    return cosine_similarity(tfidf_matrix[0], tfidf_matrix[1])[0][0]

批量处理与内存优化:
将多组文本一次性输入模型,减少 50% 以上的重复拟合开销,适合需要批量查重的场景。

def batch_calculate_similarity(text_pairs: list) -> list:
    """
    批量计算多组文本的相似度
    text_pairs: 格式为 [(原文1, 疑似抄袭文1), (原文2, 疑似抄袭文2), ...]
    """
    # 1. 批量预处理(减少循环内函数调用)
    orig_texts = []
    plag_texts = []
    for orig, plag in text_pairs:
        orig_words = preprocess_text(orig)
        plag_words = preprocess_text(plag)
        orig_texts.append(' '.join(orig_words) if orig_words else '')
        plag_texts.append(' '.join(plag_words) if plag_words else '')
    
    # 2. 一次性构建TF-IDF矩阵(减少重复拟合开销)
    all_texts = orig_texts + plag_texts
    tfidf_matrix = tfidf_vectorizer.fit_transform(all_texts)
    n = len(orig_texts)  # 区分原文与抄袭文的索引
    
    # 3. 批量计算相似度(用numpy向量操作加速)
    similarities = []
    for i in range(n):
        # 直接通过矩阵索引计算,避免重复切片
        sim = cosine_similarity(tfidf_matrix[i], tfidf_matrix[i + n])[0][0]
        similarities.append(round(sim, 4))  # 保留4位小数
    
    return similarities

4. 第二次性能分析

基于Snakeviz性能分析工具

3


优化后性能分析

函数 优化前耗时 优化后耗时 变化说明
core_task 0.753s 0.189s 总耗时减少约75%,优化效果显著
calculate_similarity 0.753s 1.888s 该函数耗时占比从99.9%降至接近99.5%,且绝对耗时减少约75%
preprocess_text 0.743s 0.177s 耗时减少约76%,文本预处理环节加速明显
jieba.cut 0.735s 0.169s 分词环节总开销减少约77%



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

单元测试示例:文本查重核心功能

  1. 测试函数:test_preprocess_with_punctuation
  • 测试目标:验证包含多种标点符号的文本预处理(分词及标点去除)功能。
  • 构造测试数据思路:使用包含感叹号、省略号、逗号、分号、问号、引号等多种标点的中文文本,覆盖常见标点使用场景,以检验文本清洗和分词的准确性。
  • 测试代码:
def test_preprocess_with_punctuation(self):
    """测试包含多种标点符号的文本处理"""
    text = "你好!这是一个测试...包含逗号,分号;问号?还有引号'\"。"
    result = preprocess_text(text)
    # 预期分词结果(需与本地jieba实际分词匹配)
    self.assertEqual(result, "你好 这是 一个 测试 包含 逗号 分 号 问号 还有 引号")

2.测试函数:test_similarity_identical_text

  • 测试目标:验证完全相同文本的相似度计算功能。
  • 构造测试数据思路:使用两段完全相同的中文文本,检验相似度计算是否能返回接近 1.0 的结果,确保函数对完全相同文本的识别准确性。
  • 测试代码:
def test_similarity_identical_text(self):
    """测试完全相同的文本(相似度应为1.0)"""
    text1 = "机器学习是人工智能的一个分支,研究计算机能从数据中学习。"
    text2 = "机器学习是人工智能的一个分支,研究计算机能从数据中学习。"
    similarity = calculate_similarity(text1, text2)
    self.assertAlmostEqual(similarity, 1.0, places=2)

3.测试函数:test_preprocess_mixed_languages

  • 测试目标:验证中英日韩混合文本的预处理(核心内容保留)功能。
  • 构造测试数据思路:使用包含中文、英文、日文、韩文的混合文本,其中包含英文专有名词、数字等,检验函数对多语言文本的兼容性,确保核心的中文和英文内容能被正确保留。
  • 测试代码:
def test_preprocess_mixed_languages(self):
    """测试中英日韩混合文本处理(只校验核心内容保留)"""
    text = "C语言是1972年由Bell Labs开发的;日本語の文字も含む;한국어도 포함된다."
    result = preprocess_text(text)
    self.assertIn("C语言", result)
    self.assertIn("1972", result)
    self.assertIn("Bell", result)
    self.assertIn("Labs", result)

单元测试覆盖率图:
其中,单元测试得到的测试覆盖率为98%。

4



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

1.文件读取异常

设计目标:确保程序在文件操作失败时能够退出并提供明确的错误信息。
对应场景:处理文件不存在、权限不足、编码错误等文件读取问题。

import unittest
import os
from main import read_file

class TestFileOperations(unittest.TestCase):
    def test_read_file_not_exist(self):
        """
        测试读取不存在文件时的异常处理
        错误场景:尝试读取不存在的文件
        """
        non_existent_file = "non_existent.txt"
        with self.assertRaises(Exception):
            read_file(non_existent_file)

2.空文本处理异常

设计目标:确保空文本参与相似度计算时能返回合理结果(如相似度为 0)
对应场景:处理空文本或全标点符号文本的情况,避免在计算相似度时出现异常。

import unittest
from main import calculate_similarity

class TestSimilarityCalculation(unittest.TestCase):
    def test_calculate_similarity_empty(self):
        """
        测试空文本的相似度计算
        错误场景:两篇文本均为空
        """
        similarity = calculate_similarity("", "")
        self.assertEqual(similarity, 0.0)

    def test_calculate_similarity_mixed_empty(self):
        """
        测试一个空文本和一个正常文本的相似度计算
        错误场景:其中一篇文本为空
        """
        similarity = calculate_similarity("这是正常文本内容。", "")
        self.assertEqual(similarity, 0.0)

3.命令行参数异常

设计目标: 提供清晰的使用说明,引导用户正确使用程序。
对应场景:处理命令行参数数量不正确的情况,当用户提供的参数数量不符合要求时。

import unittest
import subprocess
import sys
from main import main

class TestCommandLineArguments(unittest.TestCase):
    def test_main_wrong_arguments(self):
        """
        测试命令行参数错误的情况
        错误场景:用户提供的命令行参数数量不足
        """
        cmd = [sys.executable, 'main.py', 'only_one_argument.txt']
        result = subprocess.run(cmd, capture_output=True, text=True)

        self.assertNotEqual(result.returncode, 0)
        error_output = result.stderr + result.stdout
        self.assertIn("使用方法", error_output)



posted @ 2025-09-22 03:59  kakadomi  阅读(25)  评论(0)    收藏  举报