软件工程(个人项目)
软件工程 | 计科21级4班 |
---|---|
作业要求 | 作业2要求连接 |
作业目标 | 实现论文查重功能,输出查重率,进行单元测试和Github提交 |
PSP表格
PSP2.1 | ersonal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 86 | 147 |
· Analysis | · 需求分析 (包括学习新技术) | 10 | 30 |
· Design Spec | · 生成设计文档 | 4 | 6 |
· Design Review | · 设计复审 | 3 | 3 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 3 | 3 |
· Design | · 具体设计 | 15 | 20 |
· Coding | · 具体编码 | 30 | 60 |
· Code Review | · 代码复审 | 5 | 5 |
· Test | · 测试(自我测试,修改代码,提交修改) | 16 | 20 |
Reporting | 报告 | 10 | 12 |
· Test Repor | · 测试报告 | 4 | 6 |
· Size Measurement | · 计算工作量 | 3 | 3 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 3 | 3 |
· 合计 | 106 | 169 |
需求分析
作业要求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
- 原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
- 抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
注意:答案文件中输出的答案为浮点型,精确到小数点后两位
思路分析
项目要求能实现论文查重功能,对两个文件分析比对,求出重复率,输出重复率并保存到文件中
实现方法:
- 可以基于字符串匹配算法比对两篇文章,使用动态规划算法实现,实现较为复杂
- 使用simHash算法 + 海明距离实现,将高维度特征向量降维,计算海明距离
- 采用余弦相似度算法,将文章分词后向量化,计算词频向量的夹角余弦值
本次作业项目采用的是第3种方法,通过求余弦相似度来计算文章重复率,实现论文查重
流程分析
- 使用文件I/O流加载两篇目标文章,读取文章内容
- 对文章内容进行分词,获得一个个基本分词,便于后续向量化降低维度
- 采用TFIDF算法来计算两篇文章内容的分词向量,映射在低维空间
- 计算文本向量的夹角余弦值,得到余弦相似度,并保存在指定文件
采用python3.10环境,使用jieba库分词,scikit-learn库实现向量化
设计实现
根据流程分析,将每步流程抽象成一个函数,实现每个关键功能,一共5个功能函数,位于同一个文件paperChecked中,分别是
- 处理文章内容函数file_handle
- 内容分词函数segment
- 向量化函数vector_transform
- 计算余弦相似度函数calculate_cos_similarity
- 保存结果到文件函数save_file
再设计一个main文件,包含get_args函数,用于获取命令行的参数,还用paper_check函数用于顺序调用paperChecked中的功能函数,组合实现文章查重功能,main函数为入口函数
性能分析
由图分析可知,右下边main.py入口文件paper_check函数执行时间为621ms,且主要花费时间在对文章进行分词,调用jieba库中lcut函数,由于对文章进行分词的过程由文章长度和构成决定,所以在性能上无法进一步优化
单元测试
为对程序功能进行较为全面的评估,采用单元测试的方式进行过功能检验,测试程序代码的健壮性,进行以下一共10个单元测试,从不同角度,不同函数进行测试分析
-
test_read_empty_file:测试读取空文件
# 测试读取空文件 def test_read_empty_file(self): orig_path = "" orig_add_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig_0.8_add.txt" paperChecked.file_handle(orig_path, orig_add_path)
-
test_read_notExist_file:测试读取不存在的文件
# 测试读取不存在的文件 def test_read_notExist_file(self): orig_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig_add.txt" orig_add_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig_0.8_add.txt" paperChecked.file_handle(orig_path, orig_add_path)
-
test_filter_dot:测试读取的文件能否过滤标点符号
# 测试读取的文件能否过滤标点符号 def test_filter_dot(self): orig_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig.txt" orig_add_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig_0.8_add.txt" orig_file, orig_add_file = paperChecked.file_handle(orig_path, orig_add_path) print(orig_file, orig_add_file)
-
test_segment:测试文件内容分词效果
# 测试文件内容分词效果 def test_segment(self): orig_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig.txt" orig_add_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/orig_0.8_add.txt" orig_file, orig_add_file = paperChecked.file_handle(orig_path, orig_add_path) orig_string, orig_add_string = paperChecked.segment(orig_file, orig_add_file) print(orig_string, orig_add_string)
-
test_vectorize:测试分词向量化效果
# 测试分词向量化效果 def test_vectorize(self): orig_string = '今天 天气 很好 晴朗 舒适' orig_add_string = '今日 天气 非常好 晴 愉快' vector1, vector2 = paperChecked.vector_transform(orig_string, orig_add_string) print(vector1, vector2)
-
test_calculate_cos_similarity:测试余弦相似度计算
# 测试余弦相似度计算 def test_calculate_cos_similarity(self): vector1 = np.array([1, 2, 3, 4, 5, 6]) vector2 = np.array([1, 2, 3, 4, 5, 6]) similarity = paperChecked.calculate_cos_similarity(vector1, vector2) print(similarity)
-
test_save_file:测试输出结果到指定文件中
# 测试输出结果到指定文件中 def test_save_file(self): answer_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/answer02.txt" result = 0.86748569623 similarity = paperChecked.save_file(answer_path, result) print(similarity)
-
test_save_not_exist_file:测试输出结果到不存在文件中
# 测试输出结果到不存在文件中 def test_save_not_exist_file(self): answer_path = "" result = 0.86748569623 similarity = paperChecked.save_file(answer_path, result) print(similarity)
-
test_result_not_float:测试输出的结果不是浮点型
# 测试输出的结果不是浮点型 def test_result_not_float(self): answer_path = "C:/Users/L/Desktop/3121005134/3121005134/resource/answer03.txt" answer = 2 result = paperChecked.save_file(answer_path, answer) print(result)
-
test_paper_check:测试文章查重
# 测试文章查重 def test_paper_check(self): main.paper_check()test_result_not_float
测试结果
由图可知,10个测试通过,一共耗时825ms,文章查重测试耗时为614ms,分词耗时180ms
测试覆盖率
异常处理
- 文件读取异常 FileNotFoundError
在单元测试test_read_empty_file,test_read_notExist_file,test_save_not_exist_file都有体现
# 处理文件函数
def file_handle(path1, path2):
try:
# 读取文件内容
with open(path1, 'r', encoding='utf-8') as orig_file, open(path2, 'r', encoding='utf-8') as orig_add_file:
orig_file = orig_file.read()
orig_add_file = orig_add_file.read()
except FileNotFoundError:
print("请输入正确的文件路径")
return FileNotFoundError
# 过滤文件内容的标点符号
dot_str = "\n\r 、,。;:?‘’“”''""!《》,.;:?!<>"
for char in dot_str:
orig_file = orig_file.replace(char, '')
orig_add_file = orig_add_file.replace(char, '')
return orig_file, orig_add_file
- 结果值非浮点类型异常 ValueError
在单元测试test_result_not_float体现
# 保存结果到文件中,保留两位小数
def save_file(answer_path, answer):
try:
if isinstance(answer, float):
with open(answer_path, 'w') as answer_file:
print('%.2f' % answer, file=answer_file)
return round(answer, 2)
else:
raise ValueError
except ValueError:
print("结果必须是浮点型")
return ValueError
except FileNotFoundError:
print("保存结果的文件不存在")
return FileNotFoundError