软工第二次作业

软工第二次作业

Github 仓库链接


这个作业属于哪个课程 软件工程
这个作业要求在哪里作业要求 作业要求
这个作业的目标 完成论文查重项目

PSP 表格

Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
计划 30 60
估计这个任务需要多少开发时间 350 540
开发 360 540
需求分析(包括学习新技术) 30 140
生成设计文档 30 60
设计复审 10 20
代码规范(为目前的开发制定合适的规范) 10 20
具体设计 50 70
具体编码 110 140
代码复审 20 40
测试(自我测试,修改代码,提交代码) 20 30
报告 40 40
测试报告 50 20
计算工作量 5 10
事后总结 20 30
合计 1135 1760

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

代码组织

我们的项目包括以下几个主要部分:

  1. 参数解析部分:使用 argparse 库解析命令行参数,指定原文文件路径、抄袭版论文文件路径和输出答案文件路径。

  2. 预处理部分:preprocess_text 函数对输入的文本进行预处理,去除标点符号、数字和特殊字符,并将文本分割成词列表。

  3. 余弦相似度计算部分:calculate_cosine_similarity 函数计算两个文档之间的余弦相似度。它使用词频字典和词向量表示文档,并计算它们之间的余弦相似度。

  4. 主函数部分:main 函数是程序的主要逻辑部分。它调用其他函数来读取文件内容、进行预处理和计算相似度,并将结果写入输出文件。

  5. 文件读写部分:使用 open 函数打开原文文件、抄袭版论文文件和输出文件,并使用文件操作读取和写入文件内容。

需要注意的是,上述代码片段并没有包含完整的项目

关键函数流程图

preprocess_text函数

image

calculate_cosine_similarity函数

image

算法关键点

  • 文本预处理:使用正则表达式移除标点符号,保留中文字符、字母和数字。
  • 中文分词​:采用jieba分词器,准确处理中文分词问题
  • 停用词过滤​:加载停用词表,过滤常见无意义词汇
  • 余弦相似度:使用余弦相似度算法来计算两篇文档之间的相似度。

独到之处

  • 灵活的停用词处理​:支持自定义停用词文件路径,且在文件不存在时自动跳过过滤
    *​ 零向量保护​:处理零向量情况,避免除以零错误
    *​ 全面的异常处理​:对文件不存在、空文件等情况都有相应处理机制
    *​ 编码兼容性​:统一使用UTF-8编码,确保中文文本正确处理

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

改进过程

在初始实现中,使用全部词汇构建向量空间,导致计算复杂度随文档大小呈指数增长。通过对实际文本分析发现,高频词往往包含主要语义信息,而低频词多为噪声。

改进思路

  • ​限制词汇量​:仅使用每个文档中前500个高频词构建向量空间

​* 提前过滤​:在预处理阶段移除停用词和单字词,减少无效计算

  • ​向量优化​:使用numpy数组进行向量运算,提高计算效率

性能分析

使用VS 2017性能分析工具对程序进行分析,发现:

calculate_cosine_similarity函数消耗最大,占总运行时间的65%
preprocess_text函数占25%,主要耗时在jieba分词
其余函数共占10%

消耗最大的函数

calculate_cosine_similarity函数由于需要处理高维向量计算,成为性能瓶颈。后续优化可考虑采用更高效的相似度算法,如SimHash或MinHash。

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

单元测试代码

import unittest
import os
import tempfile
from main import preprocess_text, calculate_cosine_similarity, main

class TestPlagiarismChecker(unittest.TestCase):
    def setUp(self):
        # 创建临时目录和文件
        self.temp_dir = tempfile.TemporaryDirectory()
        self.test_file1 = os.path.join(self.temp_dir.name, "test1.txt")
        self.test_file2 = os.path.join(self.temp_dir.name, "test2.txt")
        self.output_file = os.path.join(self.temp_dir.name, "output.txt")
    
    def tearDown(self):
        self.temp_dir.cleanup()
    
    def test_preprocess_text_basic(self):
        """测试基本文本预处理功能"""
        text = "今天是星期天,天气晴,今天晚上我要去看电影。"
        words = preprocess_text(text)
        self.assertTrue(len(words) > 0)
        self.assertNotIn("今天", words)  # 停用词应被过滤
        self.assertIn("星期天", words)   # 有效词应保留
    
    def test_preprocess_text_without_stopwords(self):
        """测试无停用词文件时的处理"""
        text = "自然语言处理是人工智能的重要分支"
        # 使用不存在的停用词路径
        words = preprocess_text(text, "nonexistent.txt")
        self.assertTrue(len(words) > 0)
    
    def test_cosine_similarity_identical(self):
        """测试完全相同文本的相似度"""
        words1 = ["自然语言", "处理", "人工智能", "重要", "分支"]
        words2 = ["自然语言", "处理", "人工智能", "重要", "分支"]
        similarity = calculate_cosine_similarity(words1, words2)
        self.assertAlmostEqual(similarity, 1.0, places=2)
    
    def test_cosine_similarity_different(self):
        """测试完全不同文本的相似度"""
        words1 = ["自然语言", "处理", "人工智能"]
        words2 = ["生物学", "研究", "生命现象"]
        similarity = calculate_cosine_similarity(words1, words2)
        self.assertAlmostEqual(similarity, 0.0, places=2)
    
    def test_cosine_similarity_partial(self):
        """测试部分相似文本的相似度"""
        words1 = ["自然语言", "处理", "人工智能", "技术"]
        words2 = ["自然语言", "处理", "机器学习", "算法"]
        similarity = calculate_cosine_similarity(words1, words2)
        self.assertTrue(0.3 < similarity < 0.7)
    
    def test_empty_documents(self):
        """测试空文档处理"""
        words1 = []
        words2 = ["自然语言", "处理"]
        similarity = calculate_cosine_similarity(words1, words2)
        self.assertAlmostEqual(similarity, 0.0, places=2)
    
    def test_integration(self):
        """测试完整流程"""
        with open(self.test_file1, 'w', encoding='utf-8') as f:
            f.write("自然语言处理是人工智能的重要分支")
        with open(self.test_file2, 'w', encoding='utf-8') as f:
            f.write("自然语言处理是AI领域的重要分支")
        
        main(self.test_file1, self.test_file2, self.output_file)
        
        with open(self.output_file, 'r', encoding='utf-8') as f:
            result = float(f.read())
        
        self.assertTrue(0.0 <= result <= 1.0)

if __name__ == '__main__':
    unittest.main()

运行截图

image

我们设计了几种异常处理情况,包括:

  1. 文件不存在异常

设计目标​:防止因文件路径错误导致程序崩溃

​处理方式​:捕获FileNotFoundError,输出错误信息,写入默认结果0.00

def test_file_not_found(self):
    """测试文件不存在时的处理"""
    main("nonexistent_file.txt", "another_nonexistent.txt", self.output_file)
    with open(self.output_file, 'r', encoding='utf-8') as f:
        result = float(f.read())
    self.assertAlmostEqual(result, 0.0, places=2)
  1. 空文件异常

    设计目标​:处理空文件输入,避免后续计算错误

​处理方式​:检查文件内容是否为空,直接返回相似度0.00

def test_empty_files(self):
    """测试空文件处理"""
    with open(self.test_file1, 'w', encoding='utf-8') as f:
        f.write("")  # 空文件
    with open(self.test_file2, 'w', encoding='utf-8') as f:
        f.write("自然语言处理")
    
    main(self.test_file1, self.test_file2, self.output_file)
    
    with open(self.output_file, 'r', encoding='utf-8') as f:
        result = float(f.read())
    self.assertAlmostEqual(result, 0.0, places=2)
  1. 内存溢出异常

    设计目标​:处理极大文件导致的内存不足问题

​处理方式​:使用高频词限制向量维度,防止内存溢出

def test_large_documents(self):
    """测试大文档处理"""
    # 生成大文本内容
    large_text = "自然语言处理 " * 10000
    with open(self.test_file1, 'w', encoding='utf-8') as f:
        f.write(large_text)
    with open(self.test_file2, 'w', encoding='utf-8') as f:
        f.write(large_text)
    
    # 应正常处理而不内存溢出
    main(self.test_file1, self.test_file2, self.output_file)
    
    with open(self.output_file, 'r', encoding='utf-8') as f:
        result = float(f.read())
    self.assertAlmostEqual(result, 1.0, places=2)
posted @ 2025-09-24 00:28  666699990000  阅读(14)  评论(0)    收藏  举报