第一次个人编程作业
👉 这里先附上Github Repo地址和作业地址 👈
需求
题目:论文查重
描述如下:
设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。
原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:
- 从命令行参数给出:论文原文的文件的绝对路径。
- 从命令行参数给出:抄袭版论文的文件的绝对路径。
- 从命令行参数给出:输出的答案文件的绝对路径。
解决方案
1. 计算模块接口的设计与实现过程
代码的总设计结构如下:

-
读取文本
先处理从命令行读取到的参数,即文本路径。读取文件时注意使用
utf-8编码。 -
文本预处理
首先,生成停用词表
get_stopwords(),这里引用了goto456的中文停用词表。注意一下,在测试须知里提到不允许读写其他文件!!于是只能手动生成停用词表😶(运行process.py,从输出的文件中获得相应列表)。接着,先对文本分段,再用
jieba分词。这里使用了搜索引擎模式jieba.lcut_for_search,因为考虑到无法针对测试集自定义词典,而论文中的专有名词较多,精确模式在这种情况下分词效果可能不够好。最后,从得到的分词列表中过滤掉出现在停用词表中的词。分段、分词、过滤均封装在函数
segment()中。以上函数均在
process.py中定义。 -
相似度模型
我设计的模型是Jaccard index + Cosine similarity。之所以选择这个组合,是因为一开始我联想到在CV目标检测中有个IoU(其实就是Jaccard index),用来衡量锚框(anchor box)与真实边界框(ground-truth bounding box)之间的相似度。而查重也是度量两篇文章之间的相似度,它们之间的相似度与使用到相同词的个数正相关。Jaccard index计算公式:
\[J(\mathcal{A},\mathcal{B}) = \frac{\left|\mathcal{A} \cap \mathcal{B}\right|}{\left| \mathcal{A} \cup \mathcal{B}\right|}. \]但是只用Jaccard index显然不够,我们还能进行更精细的度量。Jaccard index只衡量了两个文本之间用词的重叠度,而忽略了词语在文本中出现的频率——词频TF。设想,若一个文本通篇讲半导体芯片,另一个文本只是科普性文章,顺带提到了半导体芯片,那么它们的Jaccard index有可能较大。因此,我对两个文本分别计算了共同出现词的词频:
\[词频(TF)=\frac{某个词在文章中的出现次数}{文章的总词数}, \]并依此写出词频向量,利用Cosine similarity衡量它们的相似度:
\[\cos<\vec a, \vec b>=\frac{\vec a \cdot \vec b}{\| \vec a \| \| \vec b \|}. \]尝试画图。。。
![]()
最后,定义相似度:
\[Sim = J(\mathcal{A},\mathcal{B}) \times \cos<\vec a, \vec b>. \]注:相似度计算封装在函数
get_sim()中。
这里用词集合代替词空间或许会更准确。毕竟语言太复杂,我在此没有对其定义基。
-
输出
和读取文本一样,注意使用
utf-8编码。
2. 计算模块接口部分的性能改进
使用的性能分析工具为gprof2dot。
依次执行如下命令:
python -m cProfile -o profile.pstats main.py ..\sample\sim_0.8\orig.txt ..\sample\sim_0.8\orig_0.8_mix.txt ..\output\mix.txt
python -m gprof2dot -f pstats profile.pstats -o profile.dot
dot -Tpng -o ..\blog.assets\profile_result.png profile.dot
得到

简化版则执行:
python -m gprof2dot -f pstats profile.pstats -o profile_simple.dot -n20 -s
dot -Tpng -o ..\blog.assets\profile_result_simple.png profile_simple.dot
得到

这里可以看到调用jieba进行分词的部分耗时最多,具体如下:
python -c "import pstats; p=pstats.Stats('profile.pstats'); p.strip_dirs(); p.sort_stats('cumulative').print_stats(0.005)"

如果要改进的话,看看源码?开发者最近的commit都和PaddlePaddle深度学习相关,可神经网络的速度难以改进,除非换一个模型(扯远了这里没用到paddle模式)。还有一个思路是从Python的特性入手,应用一些trick。自以为如果trick会降低可读性,那我可能不会采用。“Readability counts.”
这里挖个坑,回头看看Decorators怎么用。。。
3. 计算模块部分单元测试展示
测试分为三个部分,具体如下。
-
对样例的测试
![]()
-
对个人数据的测试
![]()
-
对异常的测试
这部分看下面。
-
最后是测试执行文件
![]()
其中目录结构如下:
![]()
使用coverage得到的测试覆盖率:
coverage run main_test.py coverage html -i -d report![]()
实际上,测试覆盖率应该为\(100\%\)。代码没有被测试到是因为它们在
main函数中,没有单独执行文件的话是不会运行的。test_*.py:![]()
process.py:这是预处理,用来手动生成停用词表,不用进行测试。![]()
![]()
main.py:用os.system()传参导致这部分没有测试到。但是从下面的异常处理中可知,这段代码可执行。![]()
糟心的是样例测试输出结果:
![]()
个人数据的结果就不放了,文档选得不太好,没多大意义。倒是这个样例!我有点方!!和各位大佬相比低了很多!!!刚拿到测试样例我还觉得这没有现实意义,有的文章一看就知道是合成的,句子就不是个句子。这样的测试数据不考察NLP中的语义关联问题,换句话说,上下文对于模型没有任何意义。但是,
orig_0.8_add.txt和orig_0.8_del.txt的测试结果也太低了!只改动了\(20\%\),结果却远低于\(80\%\)。这要是有标准答案不得被🔨但是仔细想想,这个结果也是可以接受的。
jieba是根据词频来分词的,文章经过增减,分出来的词变化可能很大。例如:原:国庆\放假\我\想\出去\玩
增:国家\庆典\放假\我\想\出去\玩(相似度:0.62)
删:国\假\我\想\出去\玩(相似度:0.50)
相似度也很低!这个例子同时也说明了上下文的重要性。
![]()
4. 计算模块部分异常处理说明
-
命令行参数输入格式错误
在
main.py中使用assert:![]()
对应的单元测试样例:
![]()
-
命令行参数给出的文件路径不存在
-
输入文件路径
在
main.py中使用assert:![]()
对应的单元测试样例:
![]()
-
输出文件路径
在
main.py中使用try...except...:![]()
对应的单元测试样例:
![]()
-
-
测试
main.py能否正常执行![]()
-
两篇完全不相同的文章相似度应接近0.00
![]()
-
两篇完全相同的文章相似度应为1.00
![]()
-
有一个文档是空白的,相似度应为0.00
![]()
-
两个文档都是空白的,无法计算Jaccard index,相似度为1.00
![]()
PSP2.1
| Personal Software Process Stages | Estimate (min) | Actual (min) |
|---|---|---|
| Planning | 60 | 30 |
| · Estimate | 60 | 30 |
| Development | 960 | 2360 |
| · Analysis | 180 | 1440 |
| · Design Spec | 90 | 60 |
| · Design Review | 30 | 30 |
| · Coding Standard | 30 | 20 |
| · Design | 180 | 150 |
| · Coding | 90 | 300 |
| · Code Review | 60 | 120 |
| · Test | 300 | 240 |
| Reporting | 210 | 240 |
| · Test Report | 90 | 180 |
| · Size Measurement | 60 | 30 |
| · Postmortem & Process Improvement Plan | 60 | 30 |
| TOTAL | 1230 | 2630 |
写在后面
- 一开始看到题目是“论文查重”的OS:这是要训练一个神经网络模型???找数据集好麻烦😕。深入思考后发觉不对,测试就只输入两个文件,还没有给测试文章的主题范围,数据集难以建立。后来想用gensim里的模型,得到的结果却不是很理想,要么很接近\(1\),要么很接近\(0\)(到现在还没整明白为啥😶)。Deadline马上要到了,最后只好自己手写一个😔。这次走了很多弯路,主要是因为对需求的分析不够到位。接触了很多新技术,学习时间比预估的长很多。
- 谈谈我是怎么填PSP表的吧。这个模板已经很详细了,如果我自己设计可能就只有加粗的大框架。整体来看时间分配为鸡蛋型,开发最多,接下来依次为报告、计划。开发就不说了,注意为设计文档、复审和测试留时间。计划的话就尽量快,一开始不可能面面俱到。先动手,之后逢山开路遇水搭桥。至于报告,本着开源精神就认真一点啦,希望各位多提意见多交流~
- 收获:
- 遗憾:没有和其他模型进行对比分析
- TODO:学习Decorator
























浙公网安备 33010602011771号