java实现论文查重,文本查重方案 采用 ansj 分词法

公司需求要求实现一个文本查重,重复率超过70% 就不让用户新增文本。固研究实现基于java的文本查重工具,分享出来方便大家使用~

ansj 分词法介绍

Ansj 是一个开源的 Java 中文分词工具,基于中科院的 ictclas 中文分词算法,采用隐马尔科夫模型(HMM),比其他常用的开源分词工具(如 MMseg4j)的分词准确率更高。作者为孙健(ansjsun),目前实现了中文分词、中文姓名识别、用户自定义词典、关键字提取、自动摘要、关键字标记等功能,适用于对分词效果要求高的各种项目。 虽然 Ansj 分词的基本原理与 ictclas 的一样,但是 Ansj 做了一些工程上的优化,比如:用 DAT 高效地实现检索词典、邻接表实现分词 DAG、支持自定义词典与自定义消歧义规则等。

Ansj 分词器 git地址: github.com/NLPchina/an...

配置文件

在ansj中配置文件名为library.properties,这是一个不可更改的约定。

字段名 默认值 说明
isNameRecognition true 是否开启人名识别
isNumRecognition true 是否开启数字识别
isQuantifierRecognition true 是否数字和量词合并
isRealName false 是否取得真实的词,默认情况会取得标注化后的
isSkipUserDefine false 是否用户辞典不加载相同的词
dic library/default.dic 自定义词典路径
dic_[key] 你的词典路径 针对不同语料调用不同的自定义词典
ambiguity library/ambiguity.dic 歧义词典路径
ambiguity_[key] library/ambiguity.dic 歧义词典路径
crf null crf 词典路径,不设置为默认
crf_[key] 你的模型路径 针对不同语料调用不同的分词模型
synonyms 默认的同义词典 针对不同语料调用不同的分词模型
synonyms_[key] 你的同义词典路径 针对不同语料调用不同的分词模型

默认的配置文件:

ini 复制代码
#path of userLibrary this is default library
dic=library/default.dic

#redress dic file path
ambiguityLibrary=library/ambiguity.dic

#set real name
isRealName=true

#isNameRecognition default true
isNameRecognition=true

#isNumRecognition default true
isNumRecognition=true

#digital quantifier merge default true
isQuantifierRecognition=true

目前支持的分词策略:

名称 用户自定义词典 数字识别 人名识别 机构名识别 新词发现
BaseAnalysis X X X X X
ToAnalysis X X
DicAnalysis X X
IndexAnalysis X X
NlpAnalysis

计算余弦相似度

余弦相似度是常见的相似度衡量手段,能够用以比对两个向量间的相似水准。如下代码呈现了计算两篇论文之余弦相似度的方式:

余弦相似度是一种常用的计算两个向量相似性的方法。它基于向量的点积和向量的模。

计算余弦相似度的原理如下:

  1. 向量表示:将需要比较的对象表示为向量。
  2. 点积运算:计算两个向量的点积,即对应元素相乘后再求和。
  3. 向量模的计算:分别计算每个向量的模,通常使用欧几里得范数。
  4. 计算余弦相似度:用两个向量的点积除以它们的模的乘积。

具体公式为:

余弦相似度 = 向量 A 与向量 B 的点积 / (向量 A 的模 × 向量 B 的模)

余弦相似度的取值范围在 -1 到 1 之间:

  • 值为 1:表示两个向量完全相同,即具有最大相似度。
  • 值为 0:表示两个向量相互垂直,即没有相似性。
  • 值为 -1:表示两个向量完全相反,即具有最小相似度。

余弦相似度的优点包括:

  1. 不受向量大小影响:它比较的是向量的方向,而不是它们的绝对大小。
  2. 对噪声相对不敏感:在存在一些噪声或误差的情况下仍能给出相对合理的相似度度量。

在实际应用中,余弦相似度常用于:

  1. 文本相似性度量:比较文本向量的相似度。
  2. 图像相似性分析:衡量图像特征向量的相似程度。
  3. 推荐系统:找到用户或物品之间的相似性。
scss 复制代码
/**
     * 计算余弦相似度
     * @param vec1 map1
     * @param vec2 map2
     * @return 相似度
     */
    public double calculateCosSimilarity(Map<String, Integer> vec1, Map<String, Integer> vec2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        for (Map.Entry<String, Integer> entry : vec1.entrySet()) {
            String word = entry.getKey();
            int count = entry.getValue();
            dotProduct += count * vec2.getOrDefault(word, 0);
            norm1 += Math.pow(count, 2);
        }
        for (Map.Entry<String, Integer> entry : vec2.entrySet()){
            int count = entry.getValue();
            norm2 += Math.pow(count, 2);
        }
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    /**
     * 计算余弦相似度
     * @param context1 文本1
     * @param context2 文本2
     * @return 相似度
     */
    public double calculateCosSimilarity(String context1, String context2){
        return calculateCosSimilarity(participleNlp(context1).stream().collect(Collectors.groupingBy(o -> o, Collectors.summingInt(o -> 1))),
                participleNlp(context2).stream().collect(Collectors.groupingBy(o -> o, Collectors.summingInt(o -> 1))));
    }

具体实现看这里

mavaen 依赖

采用 ansj 5.1.6的版本

xml 复制代码
        <dependency>
            <groupId>org.ansj</groupId>
            <artifactId>ansj_seg</artifactId>
            <version>5.1.6</version>
        </dependency>

util 代码如下

typescript 复制代码
package cn.ideamake.business.tools.util;

import lombok.experimental.UtilityClass;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.NlpAnalysis;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Barcke
 * @version 1.0
 * @projectName business-tools
 * @className TextPlagiarismCheckUtil
 * @date 2024/4/16 09:40
 * @slogan: 源于生活 高于生活
 * @description: 文本查重工具 分词采用 ansj 分词方法
 **/
@UtilityClass
public class TextPlagiarismCheckUtil {

    // 分词方法 可替换!!!  start 
    
    /**
     * Nlp分词方式
     * @param context 文本信息
     * @return 分词后的list
     */
    public List<String> participleNlp(String context){
        return participleNlpToTerm(context).stream().map(Term::getName).collect(Collectors.toList());
    }

    /**
     * Nlp分词方式
     * @param context 文本信息
     * @return 分词后的Term
     */
    public List<Term> participleNlpToTerm(String context){
        return NlpAnalysis.parse(context).getTerms();
    }

    // 分词方法 可替换!!!  end

    /**
     * 计算余弦相似度
     * @param vec1 map1
     * @param vec2 map2
     * @return 相似度
     */
    public double calculateCosSimilarity(Map<String, Integer> vec1, Map<String, Integer> vec2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        for (Map.Entry<String, Integer> entry : vec1.entrySet()) {
            String word = entry.getKey();
            int count = entry.getValue();
            dotProduct += count * vec2.getOrDefault(word, 0);
            norm1 += Math.pow(count, 2);
        }
        for (Map.Entry<String, Integer> entry : vec2.entrySet()){
            int count = entry.getValue();
            norm2 += Math.pow(count, 2);
        }
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    /**
     * 计算余弦相似度
     * @param context1 文本1
     * @param context2 文本2
     * @return 相似度
     */
    public double calculateCosSimilarity(String context1, String context2){
        return calculateCosSimilarity(participleNlp(context1).stream().collect(Collectors.groupingBy(o -> o, Collectors.summingInt(o -> 1))),
                participleNlp(context2).stream().collect(Collectors.groupingBy(o -> o, Collectors.summingInt(o -> 1))));
    }

    /**
     * 判断文本是否重复
     * @param context1 文本1
     * @param context2 文本2
     * @param threshold 阈值
     * @return 是否重复
     */
    public boolean ifPlagiarism(String context1, String context2, double threshold){
        return calculateCosSimilarity(context1, context2) > threshold;
    }

    /**
     * 判断文本是否重复 默认阈值 0.7
     * @param context1 文本1
     * @param context2 文本2
     * @return 是否重复
     */
    public boolean ifPlagiarism(String context1, String context2){
        return ifPlagiarism(context1, context2, 0.7);
    }

}

使用案例

rust 复制代码
    public static void main(String[] args) {
        String str = "java实现论文查重,文本查重方案 采用 ansj 分词法(barcke) -----" ;

        String str2 = "java实现论文查重,文本查重方案 采用 ansj 分词法(barcke) -----" ;

	    String str3 = "java实现cke) -----" ;

        System.out.println("分词数据(ansj分词法):" + TextPlagiarismCheckUtil.participleNlp(str));

        System.out.println("重复率:" + TextPlagiarismCheckUtil.calculateCosSimilarity(str, str2) + "是否重复(默认阈值0.7):" + TextPlagiarismCheckUtil.ifPlagiarism(str, str2));

        System.out.println("重复率:" + TextPlagiarismCheckUtil.calculateCosSimilarity(str, str3) + "是否重复(默认阈值0.7):" + TextPlagiarismCheckUtil.ifPlagiarism(str, str3));
    }

执行结果:

相关推荐
陈平安Java and C5 小时前
MyBatisPlus
java
秋野酱5 小时前
如何在 Spring Boot 中实现自定义属性
java·数据库·spring boot
安的列斯凯奇6 小时前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
Bunny02126 小时前
SpringMVC笔记
java·redis·笔记
架构文摘JGWZ6 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC6 小时前
Swift语言的网络编程
开发语言·后端·golang
feng_blog66886 小时前
【docker-1】快速入门docker
java·docker·eureka
邓熙榆6 小时前
Haskell语言的正则表达式
开发语言·后端·golang
枫叶落雨2228 小时前
04JavaWeb——Maven-SpringBootWeb入门
java·maven
m0_748232398 小时前
SpringMVC新版本踩坑[已解决]
java