这个作业属于哪个课程 | 广工计院计科34班软工 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 个人独立完成一次论文查重项目,完成项目后能够了解项目开发工程流程,学会使用PSP表格,完成性能分析以及测试等 |
*代码链接:https://github.com/lhq3122004617/lhq3122004617/tree/main/论文查重 |
一.需求:
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
二、PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 90 |
Estimate | 估计这个任务需要多少时间 | 740 | 850 |
Development | 开发 | 300 | 250 |
Analysis | 需求分析 (包括学习新技术) | 30 | 25 |
Design Spec | 生成设计文档 | 20 | 30 |
Design Review | 设计复审 | 20 | 40 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
Design | 具体设计 | 50 | 60 |
Coding | 具体编码 | 50 | 70 |
Code Review | 代码复审 | 20 | 20 |
Test | 测试(自我测试,修改代码,提交修改) | 50 | 30 |
Reporting | 报告 | 40 | 20 |
Test Repor | 测试报告 | 40 | 50 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 740 | 850 |
二、模块接口的设计和实现过程
代码以模块化的方式设计,共有以下几个关键模块:
1.对命令行的读取
2.文件读取模块:负责读取文本文件的内容。
3.文本预处理模块:对原始文本进行分词和清洗,去除标点符号、换行符和多余的空格,把停用词也去除。
4.建立向量模块:对处理的文本内容进行分割为列表,后建立向量
5.相似度计算模块:使用余弦相似度算法,基于向量进行相似度计算。
6.结果输出模块:将计算结果输出到指定文件。
函数设计说明
1.cos_dist(vec1,vec2):采用余弦相似度进行计算,确保分母不为0,然后将内积除以平方和
2.remove_punctuation(text):去除所有的标点符号,然后将处理完的文本返回
3.remove_stopwords(text,stopwordist):将停用词从文本中摘除,然后再将文本返回
4.stopword_cut(stopwordlist):读取停用词,用于建立停用词列表
5.read_file(file_path):读取文件内容并处理可能的异常情况。确保即使在各种错误(如文件不存在、权限问题)下,也能进行报错,防止程序崩溃。
6.write_to_text_file(content,put_outpath):输出文件内容并处理可能得异常情况,将写入的情况进行反馈
7.creat_vector(file_content,file_content1):根据传入的内容创建向量
8.put_out(file_path1,file_path2,c,put_outpath):输出到指定的地址put_path的文本
函数流程图
主函数流程图
余弦相似度
算法的关键与独到之处
余弦相似度计算:这是算法的核心,通过计算两个词频向量的内积与它们模长的乘积之比来衡量文本之间的相似度。这种方法能够有效地捕捉文本之间的语义相似性,而不仅仅是字面上的相似性。
文本预处理:通过去除标点符号,停用词等预处理步骤,减少了噪声对相似度计算的影响,提高了算法的准确性和鲁棒性。
分词技术:使用jieba库进行中文分词,能够准确地将中文文本切分成有意义的词汇单元,为后续的统计和相似度计算提供了良好的基础。
命令行参数解析:通过sys模块解析命令行参数,使得程序更加灵活和易于使用。用户可以通过命令行指定输入文件和输出文件的路径,而无需修改代码。
四、计算模块接口部分的性能分析
Code Quality Analysis
PyCharm的Inspect Code提供了代码静态审查功能,可以检测出语法错误。在结果检查后将警告等全部清除。
我已经消除了找到原因的警告
Python代码优化工具——line_profile
这里我们使用python命令进行分析
引入第三方包LineProfiler
对代码做如下修改
添加第三方包
进行每个函数的测试
接着打开命令行窗口
分别输入下面两行命令(注意:这里是要根据你的项目文件存放地址来修改)
我们这里是以orig.txt 和 orig_0.8_add.txt为例
运行结果:
cos_dist(vec1,vec2):
remove_punctuation(text):
remove_stopwords(text,stopwordist):
read_file(file_path):
write_to_text_file(content,put_outpath):
write_to_text_file(content,put_outpath):
delet(file_path):
creat_vector(file_content,file_content1):
put_out(file_path1,file_path2,c,put_outpath):
pass_function(file_content,file_path1,put_outpath):
main():
改进方案
由于计算stop_wordslist和建文本列表的时间比较长
此时我尝试去改进该函数
从减少内存使用和可能的重复计算上着手。由于jieba分词已经相对高效,主要的优化点可能在于词频统计的效率和内存使用上。
从collections模块导入Counter类。
这个改进主要利用了Counter的效率和便捷性。Counter可以直接从可迭代对象(如jieba分词的生成器)中统计元素出现的次数,无需手动遍历和更新字典。
但是结果变化不大,故没有保留
单元测试
unittest --- 单元测试框架
unittest是python自带的一个单元测试框架,不仅适用于单元测试,还可用于Web、Appium、接口自动化测试用例的开发与执行;此框架可以组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否执行通过,并生成测试结果。经过单元测试,覆盖率都是100%!
import unittest
from unittest.mock import patch
从论文查重.py中导入要测试的函数
from 论文查重 import read_file, cos_dist , remove_punctuation,main
class TestMain(unittest.TestCase):
# 测试读取文件函数
def test_read_file(self):
# 模拟文件内容
file_path = 'test_file.txt'
with open(file_path, 'w', encoding='utf-8') as f:
f.write("测试读取文件函数")
# 测试文件存在的情况
result = read_file(file_path)
self.assertEqual(result, "测试读取文件函数")
# 测试文件不存在情况
with self.assertRaises(FileNotFoundError):
read_file('non_existent_file.txt')
# 模拟权限错误
with patch("builtins.open", side_effect=PermissionError):
with self.assertRaises(PermissionError):
read_file('permission_error.txt')
# 模拟文件编码错误
with patch("builtins.open", side_effect=UnicodeError):
with self.assertRaises(UnicodeError):
read_file('encoding_error.txt')
# 模拟 I/O 错误
with patch("builtins.open", side_effect=IOError("I/O 错误")):
with self.assertRaises(IOError):
read_file('io_error.txt')
# 测试未知错误
with patch("builtins.open", side_effect=Exception("未知错误")):
with self.assertRaises(Exception):
read_file('unknown_error.txt')
# 测试预处理文本函数
def test_remove_punctuation(self):
text = "测试一下!\n这个,函数。"
expected_result = ['测试', '一下', '这个', '函数']
result = remove_punctuation
print(result)
self.assertEqual(result, expected_result)
# 测试计算余弦相似度函数
def test_cos_dist(self):
# 测试部分相同的文本
text1 = ['调试', '函数', '完成']
text2 = ['测试', '函数', '成功']
expected_similarity = 0.3333
result = cos_dist(text1, text2)
self.assertAlmostEqual(result, expected_similarity, places=4)
# 测试完全不同的文本
text3 = ['不同', '内容']
expected_similarity = 0.0
result = cos_dist(text1, text3)
self.assertAlmostEqual(result, expected_similarity)
# 测试完全相同的文本
text4 = text1
expected_similarity = 1.0
result = cos_dist(text1, text4)
self.assertAlmostEqual(result, expected_similarity)
if name == 'main':
unittest.main()
该测试代码中共有以下几个测试函数:
def test_read_file(self):
def test_remove_punctuation(self):
def test_cos_dist(self):
2. 测试数据构造思路
- test_read_file()函数
目标函数:read_file()
测试目标:验证文件读取的功能,包括各种可能的异常情况。
测试思路:
文件读取成功的情况:测试 read_file 是否能够成功读取存在的文件内容。通过在测试文件 test_file.txt 中写入固定文本 "测试读取文件函数",并验证函数返回的结果是否与预期相符。
文件不存在的情况:验证当文件不存在时,read_file 是否能够抛出 FileNotFoundError。
文件权限错误的情况:通过 unittest.mock.patch 模拟权限错误 (PermissionError),验证函数在文件权限不足时是否抛出 PermissionError。
文件编码错误的情况:通过 unittest.mock.patch 模拟文件编码错误 (UnicodeError),验证函数在遇到编码问题时是否抛出 UnicodeError。
I/O 错误的情况:通过 unittest.mock.patch 模拟 I/O 错误 (IOError),验证函数在遇到 I/O 问题时是否正确抛出 IOError。
未知错误的情况:通过 unittest.mock.patch 模拟其他异常,验证函数在遇到未知错误时是否抛出相应的 Exception。
构造测试数据:
文件路径 'test_file.txt' 用于模拟正常文件读取。
不存在的文件路径 'non_existent_file.txt' 用于模拟文件不存在。
模拟的文件权限错误、编码错误、I/O 错误、以及未知错误通过 patch 方法来构造不同的异常情况。 - test_remove_punctuation()函数
目标函数:remove_punctuation()
测试目标:验证文本预处理功能,包括去除标点符号和将文本切分为单词列表的功能。
测试思路:
常规文本预处理:通过传入包含标点符号和换行符的中文文本 "测试一下!\n这个,函数。", 测试 remove_punctuation 函数能否去除标点和符号,并返回一个正确分词后的列表。
构造测试数据:
文本 "测试一下!\n这个,函数。" 用于测试标点和换行符的去除。
期望结果为分词后的列表 ['测试', '一下', '这个', '函数'],作为 preprocess 的输出。 - test_cos_dist函数
目标函数:cos_dist(text1, text2)
测试目标:验证两个文本之间的余弦相似度计算是否正确。
测试思路:
使用两个文本 ['调试', '函数', '完成'] 和 ['测试', '函数', '成功'],它们有两个词相同,因此余弦相似度应该大于 0。设定预期相似度值为 0.3333,并验证返回值是否与预期接近。
使用一个完全不同的文本 ['不同', '内容'],预期余弦相似度应为 0.0。
使用完全相同的文本 ['测试', '函数', '运行'],预期余弦相似度应为 1.0。
构造测试数据:
测试数据 ['调试', '函数', '成功'] 和 ['测试', '函数', '成功'] 用于测试部分相同文本的相似度。
测试数据 ['不同', '内容'] 用于测试完全不同文本的相似度。
测试数据 ['测试', '函数', '运行'] 用于测试完全相同文本的相似度。
1. FileNotFoundError
设计目标:当指定的文件路径不存在时,程序应该抛出 FileNotFoundError,并给出适当的错误提示,防止程序在处理不存在的文件时继续执行错误操作。
对应的单元测试:
测试文件不存在情况
with self.assertRaises(FileNotFoundError):
read_file('non_existent_file.txt')
错误场景:程序尝试打开一个不存在的文件路径(如 'non_existent_file.txt'),这时应该抛出 FileNotFoundError。
2. PermissionError
设计目标:当文件存在,但由于权限设置导致程序无法读取文件时,程序应该抛出 PermissionError,提示用户检查文件权限,有助于处理系统级别的文件访问权限问题。
对应的单元测试:
模拟权限错误
with patch("builtins.open", side_effect=PermissionError):
with self.assertRaises(PermissionError):
read_file('permission_error.txt')
错误场景:通过 unittest.mock.patch 模拟文件的权限不足,模拟读取一个文件时没有权限的情形(如 'permission_error.txt'),应该抛出 PermissionError。
3. IOError
设计目标:当在读取文件时发生输入输出错误(如硬盘故障或文件系统错误),程序应该抛出 IOError,并提示用户检查硬盘或文件系统。
对应的单元测试:
模拟 I/O 错误
with patch("builtins.open", side_effect=IOError("I/O 错误")):
with self.assertRaises(IOError):
read_file('io_error.txt')
错误场景:通过 unittest.mock.patch 模拟 I/O 错误(如 'io_error.txt'),可以模拟硬件层面的输入输出错误,程序应该抛出 IOError。