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));
    }

执行结果:

相关推荐
我命由我123451 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
武子康3 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
舒一笑4 小时前
我的开源项目-PandaCoder迎来史诗级大更新啦
后端·程序员·intellij idea
@昵称不存在5 小时前
Flask input 和datalist结合
后端·python·flask
zhuyasen5 小时前
Go 分布式任务和定时任务太难?sasynq 让异步任务从未如此简单
后端·go
东林牧之6 小时前
Django+celery异步:拿来即用,可移植性高
后端·python·django
YuTaoShao6 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw6 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨6 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
AntBlack6 小时前
从小不学好 ,影刀 + ddddocr 实现图片验证码认证自动化
后端·python·计算机视觉