软工第一次个人编程作业
软工第一次个人编程作业
| 软件工程 | 21计科34班 |
|---|---|
| 作业要求 | 第一次个人编程作业 |
| 作业目标 | 按软件设计开发流程设计实现论文查重程序 |
1 作业GitHub地址
https://github.com/JWven/3120009066
2 Personal Software Process
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 60 | 120 |
| · Estimate | · 估计这个任务需要多少时间 | 60 | 120 |
| Development | 开发 | 600 | 475 |
| · Analysis | · 需求分析 (包括学习新技术) | 240 | 300 |
| · Design Spec | · 生成设计文档 | 60 | 30 |
| · Design Review | · 设计复审 | 10 | 10 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | · 具体设计 | 60 | 30 |
| · Coding | · 具体编码 | 120 | 60 |
| · Code Review | · 代码复审 | 10 | 10 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 90 | 25 |
| Reporting | 报告 | 90 | 90 |
| · Test Report | · 测试报告 | 30 | 40 |
| · Size Measurement | · 计算工作量 | 30 | 20 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
| · 合计 | 750 | 685 |
3 需求分析
3.1 功能需求
3.1.1 输入需求
程序应满足以下输入需求:
-
从命令行参数接收论文原文的文件的绝对路径。
-
从命令行参数接收抄袭版论文的文件的绝对路径。
-
从命令行参数接收输出的输出文件的绝对路径。
3.1.2 输出需求
程序应满足以下输出需求:
- 原文与抄袭版论文的相似度保存到输出文件,以浮点型表示,并精确到小数点后两位。
3.1.3 查重算法需求
程序需要实现以下查重算法:
-
通过比较原文和抄袭版论文的文本内容,计算相似度。
-
使用算法判断原文和抄袭版论文之间的重复率。
-
确保算法能够处理文本中的增删改等操作,以捕捉文本的相似性。
3.2 非功能需求
3.2.1 文件格式需求
-
原文文件和抄袭版论文文件应采用UTF-8编码。
-
文件格式可以是纯文本文件,或者包含格式化信息的文本文件。
3.2.2 用户友好性
-
用户应能够轻松使用程序,通过命令行参数提供所需文件路径。
-
错误消息应具有明确的信息,以帮助用户解决问题。
3.3 使用示例
-
提供一份样例原文文件(例如:orig.txt)和多个抄袭版论文文件(例如:orig_add.txt)。
-
用户可以通过以下方式使用程序:
python check_similarity.py orig.txt orig_add.txt output.txt
其中,orig.txt是原文文件的路径,orig_add.txt是抄袭版论文文件的路径,output.txt是输出答案的文件路径。
3.4 注意事项
输出文件中的相似度应以浮点型表示,精确到小数点后两位。
4 程序设计与实现
实现思路:
- 先将待处理的数据(中文文章)进行分词,得到一个存储若干个词汇的列表
- 接着计算并记录出列表中词汇对应出现的次数,将这些次数列出来即可得到一个向量
- 将两个数据对应的向量代入夹角余弦定理
- 计算出的值意义为两向量的偏移度,这里也即对应两个数据的相似度
除了余弦定理求相似度,还可以使用欧氏距离、海明距离等
4.1 总体设计
-
导入必要的库:
-
jieba: 用于中文分词。 -
gensim: 用于处理文本数据和计算余弦相似度。 -
re: 用于正则表达式操作。
-
-
相关模块:
-
get_file_contents(path): 用于获取指定路径的文件内容,读取文件并返回其内容。 -
filter(str): 用于对文本进行处理,先使用jieba库进行分词,然后过滤掉标点符号、转义符号等特殊符号,返回处理后的文本。 -
calc_similarity(text1, text2): 用于计算两个文本之间的余弦相似度。它首先将文本转换为词袋(bag of words),然后使用gensim的Similarity计算余弦相似度。
-
-
总体执行流程:
从程序入口main()开始执行以下操作:
(1) 定义了三个文件路径:
path1(原文的文件路径)、path2(抄袭版论文的文件路径)、save_path(输出结果的文件路径)。(2) 使用
get_file_contents函数读取文件内容,并将结果存储在str1和str2中。(3)使用
filter函数对文本进行处理,将处理后的文本存储在text1和text2中。(4)调用
calc_similarity函数计算文本的余弦相似度,将结果存储在similarity变量中。(5)打印文本相似度的结果,并将结果写入指定的输出文件中。
4.2 相关库
4.2.1 jieba.cut
功能:用于对中文句子进行分词
官方文档:结巴中文分词
Jieba提供四种分词模式:
- 精确模式:试图将句子最精确地切开,适合文本分析;
- 全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
- 搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
- paddle模式:利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词。同时支持词性标注。PaddlePaddle官网
这里只需用到默认最简单的“精确模式”
4.2.2 re.match
re.match 尝试从字符串的起始位置匹配一个模式,匹配成功 re.match 方法返回一个匹配的对象,否则返回 None。
re.match(pattern, string, flags=0)
函数参数说明
| pattern | 匹配的正则表达式 |
|---|---|
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
4.2.3 gensim
gensim是一个python的自然语言处理库,能够将文档根据TF-IDF, LDA, LSI 等模型转化成向量模式,以便进行进一步的处理。
官方文档:GENSIM
(1)gensim.dictionary.doc2bow
Doc2Bow是gensim中封装的一个方法,主要用于实现Bow模型。
Bag-of-words model (BoW model) 最早出现在自然语言处理(Natural Language Processing)和信息检索(Information Retrieval)领域.。该模型忽略掉文本的语法和语序等要素,将其仅仅看作是若干个词汇的集合,文档中每个单词的出现都是独立的。
(2)gensim.similarities.Similarity
用于计算余弦相似度
Compute similarities across a collection of documents in the Vector Space Model.
4.3 模块设计
4.3.1 文件读取模块
get_file_contents(path) 函数的实现思路如下:
- 接收一个参数
path,表示要读取的文件的路径。 - 打开指定路径的文件,使用
'r'模式以只读方式打开文件,并指定'UTF-8'编码来处理中文字符。这里使用open函数。 - 创建一个空字符串变量
str,用于存储文件内容。 - 使用
f.readline()逐行读取文件内容,将每一行的内容追加到字符串变量str中。 - 继续循环读取文件的下一行,直到文件的末尾。在每次循环迭代中,将读取的内容追加到
str中。 - 关闭文件,释放文件资源。
- 返回存储在
str中的整个文件内容。
这个函数的核心思路是打开文件,逐行读取文件内容并将其存储在一个字符串变量中,最后返回整个文件的内容。
4.3.2 文本处理模块
filter(str) 函数的实现思路如下:
- 接收一个参数
str,表示要处理的文本字符串。 - 使用
jieba库的jieba.lcut(str)函数对文本进行中文分词,将文本分割成一个个词语,得到一个词语列表。 - 创建一个空列表
result,用于存储处理后的结果。 - 遍历词语列表中的每个词语
tags。 - 对每个词语进行判断,使用正则表达式
re.match(u"[a-zA-Z0-9\u4e00-\u9fa5]", tags)来检查词语是否包含中文字符、英文字母或数字。这个正则表达式匹配包括中文、英文大小写字母和数字的字符,排除了标点符号等特殊符号。 - 如果词语符合条件,将它添加到
result列表中;如果不符合条件(包含特殊符号),则跳过不处理。 - 完成对所有词语的处理后,返回存储在
result中的处理后的文本列表。
具体而言,这个函数的实现思路是通过中文分词工具 jieba 将文本分割成词语,然后使用正则表达式过滤掉其中不包含中文字符、英文大小写字母和数字的词语,最终返回处理后的文本词语列表,其中已经去除了标点符号等特殊符号。这个步骤是为了将文本处理成一个可以用于后续文本分析的干净数据集。
4.3.3 相似度计算模块
calc_similarity(text1, text2) 函数的实现思路如下:
- 接收两个参数
text1和text2,分别表示要比较相似度的两篇文本,它们都是经过预处理的文本,以词语列表的形式表示。 - 首先,将
text1和text2合并成一个包含两篇文本的列表texts。 - 使用
gensim.corpora.Dictionary(texts)创建一个词典对象,将文本转换为数字表示的文档-词袋格式。词典将文本中的每个词语映射到一个唯一的整数 ID。 - 创建一个语料库
corpus,将text1和text2分别转换为文档-词袋格式的语料库。这一步将词语转换为它们在词典中的整数 ID 的表示。 - 使用
gensim.similarities.Similarity初始化一个余弦相似度计算对象,需要传入以下参数:- 输出文件的路径(通常为空字符串,表示不保存计算结果到文件中)。
- 语料库
corpus,其中包含两篇文本的文档-词袋表示。 - 词典的长度,即词典中不同词语的数量。
- 将第一篇文本
text1转换为文档-词袋格式,然后使用余弦相似度计算对象来计算它与第二篇文本text2之间的余弦相似度。 - 返回余弦相似度值,这个值范围在0到1之间,表示两篇文本之间的相似性。值越接近1表示两篇文本越相似,越接近0表示越不相似。
4.3.4 程序入口
- 获得文件路径,包括原文、抄袭版论文和输出结果的文件路径。
- 读取和预处理这些文件的内容。
- 计算文章相似度。
- 打印相似度结果和将结果写入输出文件。
5 性能分析
5.1 时间花销
使用PyCharm提供的性能分析工具Profile,可以得到以下耗费时间的几个主要函数排名

关注到filter函数:由于cut和lcut暂时找不到可提到的其他方法(jieba库已经算很强大了),暂时没办法进行改进,因此考虑对正则表达式匹配改进。
这里是先用lcut处理后再进行匹配过滤,这样做显得过于臃肿,可以考虑先匹配过滤之后再用lcut来处理
改进代码:
def filter(string):
pattern = re.compile(u"[^a-zA-Z0-9\u4e00-\u9fa5]")
string = pattern.sub("", string)
result = jieba.lcut(string)
return result
再次运行Profile:

可见时间花销有所减少
5.2 代码覆盖率
代码覆盖率100%,满足要求:

6 单元测试
这里用了python的unittest单元测试框架,详见官网介绍
main函数:
def main_test():
path1 = input("输入论文原文的文件的绝对路径:")
path2 = input("输入抄袭版论文的文件的绝对路径:")
str1 = get_file_contents(path1)
str2 = get_file_contents(path2)
text1 = filter(str1)
text2 = filter(str2)
similarity = calc_similarity(text1, text2) #生成的similarity变量类型为<class 'numpy.float32'>
result=round(similarity.item(),2) #借助similarity.item()转化为<class 'float'>,然后再取小数点后两位
return results
if __name__ == '__main__':
main_test()
为了使预期值更好确定,这里考虑只取返回的相似度值的前两位,借助round(float,2)即可处理,由于生成的similarity类型为<class 'numpy.float32'>,因此应当先转化为<class 'float'>,查找对应解决方法:通过xxx.item()即可转化。
再新建单元测试文件unit_test.py:
import unittest
from main import main_test
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertEqual(main_test(),0.99) #首先假设预测的是前面第一组运行的测试数据
if __name__ == '__main__':
unittest.main()
运行测试文件:

可以发现预测值为0.99正确:

7 异常处理说明
在读取指定文件路径时,如果文件路径不存在,程序将会出现异常,因此可以在读取指定文件内容之前先判断文件是否存在,若不存在则做出响应并且结束程序。
这里引入os.path.exists()方法用于检验文件是否存在:
def main_test():
path1 = input("输入论文原文的文件的绝对路径:")
path2 = input("输入抄袭版论文的文件的绝对路径:")
if not os.path.exists(path1) :
print("论文原文文件不存在!")
exit()
if not os.path.exists(path2):
print("抄袭版论文文件不存在!")
exit()

浙公网安备 33010602011771号