软工作业2:个人项目——论文查重
这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 个人项目——论文查重 |
这个作业的目标 | 1.实现论文查重的程序制作 2.进行PSP表格的绘制与使用 3.性能的分析与测试 4.学习使用单元测试 |
一、项目地址
二、项目需求
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
三、PSP表格
PSP2.1 | Personal Software Process Stages | 预计耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 210 | 220 |
Analysis | 需求分析 (包括学习新技术) | 500 | 480 |
Design Spec | 生成设计文档 | 45 | 40 |
Design Review | 设计复审 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
Design | 具体设计 | 150 | 160 |
Coding | 具体编码 | 150 | 180 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 200 |
Reporting | 报告 | 20 | 20 |
Test Repor | 测试报告 | 30 | 30 |
Size Measurement | 计算工作量 | 20 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 50 |
合计 | 1485 | 1610 |
四、模块接口的设计与实现过程
4.1 算法流程图
4.2 SimHash算法 + 海明距离
1、分词
把需要判断文本分词形成这个文章的特征单词。最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。比如:“美国‘51区’雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是代表单词在整个句子里重要程度,数字越大越重要。
2、hash
通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。
3、加权
通过第2步骤的hash生成结果,需要按照单词的权重形成加权数字串,比如“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”;“51区”的hash值为“101011”,通过加权计算为 “ 5 -5 5 -5 5 5”。
4、合并
把上面各个单词算出来的序列值累加,变成只有一个序列串。比如 “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”, 把每一位进行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5” ==> “9 -9 1 -1 1 9”。这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。
5、降维
把第4步骤算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,形成我们最终的simhash签名。 如果每一位大于0 记为 1,小于 0 记为 0。最后算出结果为:“1 0 1 0 1 1”。
参考博文:simhash算法及原理简介 SimHash算法原理
4.3 TF-IDF与余弦相似性
进行分词后计算词频,写出词频向量。两篇文章的文章相似度转变为计算两个词频向量的夹角,夹角越小即代表文章相似度越高,夹角越大即代表文章相似度越低。在二维空间中计算两向量间的夹角大小可利用余弦定理,从而扩展到n维空间中也成立。
(1)使用TF-IDF算法,找出两篇文章的关键词;
(2)每篇文章各取出若干个关键词(比如20个),合并成一个集合,计算每篇文章对于这个集合中的词的词频(为了避免文章长度的差异,可以使用相对词频);
(3)生成两篇文章各自的词频向量;
(4)计算两个向量的余弦相似度,值越大就表示越相似。
五、代码实现
(1)获取指定路径的文件内容
点击查看代码
# 获取指定路径的文件内容
def get_file_contents(path):
str = ''
f = open(path, 'r', encoding='UTF-8')
line = f.readline()
while line:
str = str + line
line = f.readline()
f.close()
return str
(2)导入jieba库,对文本进行分词
点击查看代码
def filter(str):
# 将读取到的文件内容先进行jieba分词,
str = jieba.lcut(str)
result = []
# 把标点符号、转义符号等特殊符号过滤掉
for tags in str:
if (re.match(u"[a-zA-Z0-9\u4e00-\u9fa5]", tags)):
result.append(tags)
else:
pass
return result
(3)导入gensim库,对文本进行词典文档集数据处理以及余弦相似度计算
点击查看代码
# 传入过滤之后的数据,通过调用gensim.similarities.Similarity计算余弦相似度
def calc_similarity(text1, text2):
texts = [text1, text2]
# 形成词典
dictionary = gensim.corpora.Dictionary(texts)
# 形成向量
corpus = [dictionary.doc2bow(text) for text in texts]
# print(corpus)
similarity = gensim.similarities.Similarity('-Similarity-index', corpus, num_features=len(dictionary))
test_corpus_1 = dictionary.doc2bow(text1)
cosine_sim = similarity[test_corpus_1][1]
return cosine_sim
def main(path1,path2):
save_path = 'D:\\Python\\homework\\result.txt' # 输出结果绝对路径
str1 = get_file_contents(path1)
str2 = get_file_contents(path2)
text1 = filter(str1)
text2 = filter(str2)
similarity = calc_similarity(text1, text2)
# print(text1)
print("文章相似度: %.4f" % similarity)
# 将相似度结果写入指定文件
f = open(save_path, 'w', encoding="utf-8")
f.write("文章相似度: %.4f" % similarity)
f.close()
六、性能测试及改进
profile和line_profiler两个模块都是性能分析工具。有时候需要找到代码中运行速度较慢处或瓶颈,可以通过这两模块实现,而不再使用time计时。
line_profiler模块可以记录每行代码的运行时间和耗时百分比。
参考博文:Python性能分析工具Line_profiler 使用line_profiler对python代码性能进行评估优化
性能测试代码如下:
点击查看代码
# 性能测试
p = LineProfiler()
p.add_function(get_file_contents)
p.add_function(filter)
p.add_function(calc_similarity)
p_wrap = p(main)
p_wrap(path1,path2)
p.print_stats() # 控制台打印相关信息
p.dump_stats('saveName.lprof') # 当前项目根目录下保存文件
运行结果如下:
七、单元测试
unittest 单元测试框架是受到 JUnit 的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化,配置共享和关机代码测试。支持将测试样例聚合到测试集中,并将测试与报告框架独立。
为了实现这些,unittest 通过面向对象的方式支持了一些重要的概念。
参考博文:unittest —— 单元测试框架
运行结果如下:
八、异常处理
程序对文件路径进行判断,如果文件路径不存在,则退出程序。引入os.path.exists()方法用于检验文件是否存在。os.path模块主要用于文件的属性获取,exists是“存在”的意思,所以顾名思义,os.path.exists()就是判断括号里的文件是否存在的意思,括号内的可以是文件路径。
异常处理代码如下:
点击查看代码
if __name__ == '__main__':
path1 = "D:\\Python\\homework\\orig.txt" # 论文原文的文件的绝对路径(作业要求)
path2 = "D:\\Python\\homework\\orig_0.8_add.txt" # 抄袭版论文的文件的绝对路径
path1 = input("输入论文原文的文件的绝对路径:")
path2 = input("输入抄袭版论文的文件的绝对路径:")
if not os.path.exists(path1):
print("论文原文文件不存在!")
exit()
if not os.path.exists(path2):
print("抄袭版论文文件不存在!")
exit()
main(path1,path2)