软工第二次作业
软工第二次作业
这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里作业要求 | 作业要求 |
这个作业的目标 | 完成论文查重项目 |
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 |
计算模块接口的设计与实现过程
代码组织
我们的项目包括以下几个主要部分:
-
参数解析部分:使用
argparse
库解析命令行参数,指定原文文件路径、抄袭版论文文件路径和输出答案文件路径。 -
预处理部分:
preprocess_text
函数对输入的文本进行预处理,去除标点符号、数字和特殊字符,并将文本分割成词列表。 -
余弦相似度计算部分:
calculate_cosine_similarity
函数计算两个文档之间的余弦相似度。它使用词频字典和词向量表示文档,并计算它们之间的余弦相似度。 -
主函数部分:
main
函数是程序的主要逻辑部分。它调用其他函数来读取文件内容、进行预处理和计算相似度,并将结果写入输出文件。 -
文件读写部分:使用
open
函数打开原文文件、抄袭版论文文件和输出文件,并使用文件操作读取和写入文件内容。
需要注意的是,上述代码片段并没有包含完整的项目
关键函数流程图
preprocess_text函数
calculate_cosine_similarity函数
算法关键点
- 文本预处理:使用正则表达式移除标点符号,保留中文字符、字母和数字。
- 中文分词:采用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()
运行截图
我们设计了几种异常处理情况,包括:
- 文件不存在异常
设计目标:防止因文件路径错误导致程序崩溃
处理方式:捕获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)
- 空文件异常
设计目标:处理空文件输入,避免后续计算错误
处理方式:检查文件内容是否为空,直接返回相似度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)
- 内存溢出异常
设计目标:处理极大文件导致的内存不足问题
处理方式:使用高频词限制向量维度,防止内存溢出
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)