第一次个人项目:简易论文查重Java

2021/9/18 17:34:56

本文主要是介绍第一次个人项目:简易论文查重Java,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

这个作业属于哪个课程 <班级链接>
这个作业要求在哪里 <作业要求的链接>
这个作业的目标 个人项目作业: 简单论文查重算法

页面导航

  1. GitHub链接
  2. 原理介绍
  3. 基本配置
  4. 算法实现
  5. 程序运行
  6. 模块接口的性能展示
  7. 模块部分单元测试展示
    1. 测试覆盖率
    2. 测试文件异常
      1. 空文件
      2. 文件路径异常
  8. PSP表格

GitHub链接

https://github.com/yzqctf/3119005483/tree/main/Final commit

原理介绍

相比于在NLP领域更为专业的查重算法,其考虑的方面更为细致更接近人们日常生活中对抄袭的认值;本篇所实现的查重算法是相对简单的,主要是对两个方法的实现:

  1. 基于simHash的海明距离 + Jaccard Similarity;
  2. 基于余弦相似度。

因为在大文本的度量中,相比于simHash,余弦相似度方案的性能表现更差;而在小文本的度量中,余弦相似度的准确率更好,因此针对不同应用场景应该采用不同的方案。

simHash + Jaccard Similarity

simHash

传统的Hash算法只负责将原始内容尽量均匀随机地映射为一个签名值,原理上仅相当于伪随机数产生算法。即便是两个原始内容只相差一个字节,所产生的签名也很可能差别很大,所以传统的Hash是无法在签名的维度上来衡量原内容的相似度。而SimHash本身属于一种局部敏感hash,其主要思想是降维,将高维的特征向量转化成一个f位的指纹(fingerprint),通过算出两个指纹的海明距离(hamming distince)来确定两篇文章的相似度,海明距离越小,相似度越低(根据 Detecting Near-Duplicates for Web Crawling 论文中所说),一般海明距离为3就代表两篇文章相同。
simhash也有其局限性,在处理小于500字的短文本时,simhash的表现并不是很好,所以在使用simhash前一定要注意这个细节。

simHash算法分为5个步骤: 1. 分词;2. hash;3. 加劝;4. 合并;5. 降维

  1. 分词:现在有很多可供使用的包来进行文本的分词,本篇所使用的分词器是IKAnalysis,需要安装 IKAnalyzer2012_u6.jar包,具体信息可看这篇[博客] (http://lxw1234.com/archives/2015/07/422.htm#10006-weixin-1-52626-6b3bffd01fdde4900130bc5a2751b6d1) 以及其相关介绍

  2. hash:通过hash函数计算各个特征向量(这里为划分好的词)的hash值,hash值为二进制数01组成的n-bit签名。

  3. 加权:权重:就是词频;把第2步生成的hash值从左至右与权重进行运算;如果该bit的数值为1,则将权重赋给该位;如果该bit的数值为0,则将权重的负值赋给该位。example:"我",hash = 101011,weight(词频) = 5;则加权后的结果为:5 -5 5 -5 5 5;

  4. 合并:经过上述的三个步骤,我们可以得到全部词(word)的加权hash值,此时需要将全部的加权后的hash值进行累加;

  5. 降维:将第四步计算出来的序列串变为01串;具体规则:如果该位的数值>0,则置为1;反之则置为0.

  6. 海明距离(Hamming Distance):在信息编码中,两个合法代码对应位上编码不同的位数称为码距,又称海明距离;

Jaccard Similarity

给定两个文本或两句话,把两句话中出现的单词取交集和并集,交集和并集的大小之商即为Jaccard Similarity:
J(A, B) = |A∩B| / |A∪B|

A = [0, 1, 2, 5, 6, 8, 9]
B = [0, 2, 3, 4, 5, 7, 9]

|A∩B| = [0, 2, 5, 9] = 4;
|A∪B| = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 10;
所以 J(A, B) = 4 / 10 = 0.4

基于余弦相似度

余弦相似性通过测量两个向量的夹角的余弦值来度量它们之间的相似性。0度角的余弦值是1,而其他任何角度的余弦值都不大于1;并且其最小值是-1。从而两个向量之间的角度的余弦值确定两个向量是否大致指向相同的方向。两个向量有相同的指向时,余弦相似度的值为1;两个向量夹角为90°时,余弦相似度的值为0;两个向量指向完全相反的方向时,余弦相似度的值为-1。这结果是与向量的长度无关的,仅仅与向量的指向方向相关。余弦相似度通常用于正空间,因此给出的值为-1到1之间

具体流程:
(1)找出两篇文章的关键词;

(2)每篇文章各取出若干个关键词,合并成一个集合,计算每篇文章对于这个集合中的词的词频

(3)生成两篇文章各自的词频向量;

(4)计算两个向量的余弦相似度,值越大就表示越相似。

基本配置

简单查重算法的实现并不是很困难,真正的难点反而在于如何处理在导入jar包后的“程序包不存在”,打包为jar包时“程序包不存在”(即便他运行是没有问题的),命令行运行时的“找不到主类”诸如此类的配置问题。就我本次经历而言,在这上面所花费的时间近乎80%。所以这里也是对经历过的问题进行记录,方便自己和别人遇到时进行方案选择。(本篇基于maven开发)

  1. 导入jar包后“程序包不存在”:需要在自己的项目里添加libraries,并将jar包导入该文件夹下,然后rebuild整个project即可;具体可参考此博客
  2. 封装jar包时“程序包不存在”:一般人问题可能出在这常见;小白:注意看自己的META-INF内是否存在自己所需要的包的路径,如果没有,就将这个META-INF包给删除,重新进入File->project structure->Artifacts->jar->from modules中进行设置,记得添加启动类即可。经过上述两种措施,一般而言是可以完成基本问题的。至于网上一般建议的mvn idea:idea 还是需要看具体情况。

算法实现

工程结构图

其中main是程序的入口,也即启动类;CalculateSimilar类是实现余弦相似度的类;
SimHash是将文本转换为simhash值,HammingDistance计算了文本间的海明距离并完成了Jaccard Similarity的计算。

关键代码

public static double getSimilarity(String simHash1, String simHash2){
        //海明距离
        int dis = getHammingDistance(simHash1,simHash2);
        //System.out.println(dis);
        //一般来说,海明距离小于3即可认为文本之间相似度高,
        //使用Jaccard进行相似度计算, 因为dis就是simhash之间不同,也即差集的大小,所以
        //交集
        int intersection = simHash1.length()-dis;
        //并集
        int union = dis+simHash1.length();
        double sim = 0.01*(100*intersection/union);

        return sim;
    }
    int sumA = 0;
    int sumB = 0;
    int sumAB = 0;
    for (int i=0;i < words1.size(); i++){
        int a = FA.get(i);
        int b = FB.get(i);
        sumAB += a*b;
        sumA += a*a;
        sumB += b*b;
    }
    double  A = Math.sqrt(sumA);
    double  B = Math.sqrt(sumB);
    BigDecimal AB = BigDecimal.valueOf(A).multiply(BigDecimal.valueOf(B));

程序运行

运行结果

模块接口的性能展示

内存消耗

从上图可以知道,此程序运行时内存消耗最多的是IKAnalysis的分词操作,其占 (13454+5664)/39947 = 47.86%

CPUf负载

堆内存消耗情况

模块部分单元测试展示

由于测试单元过多,这里只展示其中比较重要的几个单元测试:1. 空文件;2. 大文件与小文件测试;3. 路径输入错误;4. 参数异常错误;5. 同一文件

    //测试代码是否能正常运行,小文本:29K
    public void main() throws IOException {
        String f1 = "D:\\学习资料\\softwareProject\\orig.txt";
        String f2 = "D:\\学习资料\\softwareProject\\orig_0.8_add.txt";
        String f3 = "D:\\学习资料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
        main.getAnalysisResult(f1,f2,f3);
    }
//相对较大文本165K
    public void testLargeTxt() throws IOException{
        String f1 = "D:\\学习资料\\softwareProject\\测试文本\\orig_0.8_dis_15.txt";
        String f2 = "D:\\学习资料\\softwareProject\\测试文本\\orig.txt";
        String f3 = "D:\\学习资料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
        main.getAnalysisResult(f1,f2,f3);
    }
 //测试文本为空的情况
    public void testNullTxt() throws IOException{
        String f1 = "D:\\学习资料\\softwareProject\\测试文本\\orig.txt";
        String f2 = "D:\\学习资料\\softwareProject\\测试文本\\null.txt";
        String f3 = "D:\\学习资料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
        main.getAnalysisResult(f1,f2,f3);
    }
//同一文件
    public void testTheSame() throws IOException{
        String f1 = "D:\\学习资料\\softwareProject\\测试文本\\orig.txt";
        String f2 = "D:\\学习资料\\softwareProject\\测试文本\\orig.txt";
        String f3 = "D:\\学习资料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
        main.getAnalysisResult(f1,f2,f3);
    }

//路径有误
    public void testTheRoute() throws IOException{
        String f1 = "D:\\学习资料\\softwareProject\\测试文本\\orig.txt";
        String f2 = "D:\\softwareProject\\测试文本\\orig.txt";
        String f3 = "D:\\学习资料\\softwareProject\\WorkWithConsineVector\\out\\output.txt";
        main.getAnalysisResult(f1,f2,f3);
    }
//输入参数有误
if (args.length>3){
            System.out.println("一次只能比较两个文本哦");
            System.exit(1);
        }else if (args.length<=0){
            System.out.println("请输入待比较文件");
            System.exit(1);
        }

代码覆盖率


启动类main的method之所以没有达到100%,是因为我用了两个方式产生了分支,因此只有66%的覆盖率。

使用codacy对代码进行分析

codacy分析才是重中之重,虽然这个只能免费体验七天,不过真的很强大。

可以看出来总共29个问题,潜在的隐患还是非常多的;

不出意料的是复杂度0%,还真是简易查重算法[苦涩];代码复制17%,可能一些比较公共的部分写法比较格式化,17%应该算是重复度比较低的吧?也不清楚其判断方式是什么。

进入安全界面

问题很多意味着改进的方式也很多,每一点细看的话将会学到很多东西,还需要继续加强自己的代码编写能力。

PSP表

*PSP2.1* *Personal Software Process Stages* *预估耗时(分钟)* *实际耗时(分钟)*
Planning 计划 20 17
· Estimate · 估计这个任务需要多少时间 120 70
Development 开发 240 240
· Analysis · 需求分析 (包括学习新技术) 300 200
· Design Spec · 生成设计文档 30 15
· Design Review · 设计复审 30 60
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 60 20
· Design · 具体设计 30 30
· Coding · 具体编码 180 120
· Code Review · 代码复审 120 240
· Test · 测试(自我测试,修改代码,提交修改) 60 120
Reporting 报告 60 60
· Test Repor · 测试报告 60 40
· Size Measurement · 计算工作量 30 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 120
· 合计 1370 1382

附注:对于PSP表其实不是很了解,比如说开发是指什么,跟下面的项目属于什么关系,这个不是很清楚,所以在填的时候就是凭着感觉写的,还有关于修改代码以及提交修改这点,在本次实验过程中执行的并不是很好,修改过的点不记得上传,所以看似是只提交了一次,但是实际上在本地修改了几遍,这点也不好。本次实验的过程中最主要的认知是:项目开发过程中一些辅助的手段和项目的规范性一定要掌握,比如导包、配置依赖,git命令上传等等一些不涉及到具体编码但是跟具体开发息息相关的操作。磨刀不误砍柴工嘛。



这篇关于第一次个人项目:简易论文查重Java的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程