需求要求实现一个文本查重,重复率超过70% 就不让用户新增文本。固研究实现基于java的文本查重工具,分享出来方便大家使用~
ansj 分词法介绍
Ansj 是一个开源的 Java 中文分词工具,基于中科院的 ictclas 中文分词算法,采用隐马尔科夫模型(HMM),比其他常用的开源分词工具(如 MMseg4j)的分词准确率更高。作者为孙健(ansjsun),目前实现了中文分词、中文姓名识别、用户自定义词典、关键字提取、自动摘要、关键字标记等功能,适用于对分词效果要求高的各种项目。
虽然 Ansj 分词的基本原理与 ictclas 的一样,但是 Ansj 做了一些工程上的优化,比如:用 DAT 高效地实现检索词典、邻接表实现分词 DAG、支持自定义词典与自定义消歧义规则等。
Ansj 分词器 git地址: https://github.com/NLPchina/ansj_seg
配置文件
在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] | 你的同义词典路径 | 针对不同语料调用不同的分词模型 |
默认的配置文件:
#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 | √ | √ | √ | √ | √ |
计算余弦相似度
余弦相似度是常见的相似度衡量手段,能够用以比对两个向量间的相似水准。如下代码呈现了计算两篇论文之余弦相似度的方式:
余弦相似度是一种常用的计算两个向量相似性的方法。它基于向量的点积和向量的模。
计算余弦相似度的原理如下:
- 向量表示:将需要比较的对象表示为向量。
- 点积运算:计算两个向量的点积,即对应元素相乘后再求和。
- 向量模的计算:分别计算每个向量的模,通常使用欧几里得范数。
- 计算余弦相似度:用两个向量的点积除以它们的模的乘积。
具体公式为:
余弦相似度 = 向量 A 与向量 B 的点积 / (向量 A 的模 × 向量 B 的模)
余弦相似度的取值范围在 -1 到 1 之间:
- 值为 1:表示两个向量完全相同,即具有最大相似度。
- 值为 0:表示两个向量相互垂直,即没有相似性。
- 值为 -1:表示两个向量完全相反,即具有最小相似度。
余弦相似度的优点包括:
- 不受向量大小影响:它比较的是向量的方向,而不是它们的绝对大小。
- 对噪声相对不敏感:在存在一些噪声或误差的情况下仍能给出相对合理的相似度度量。
在实际应用中,余弦相似度常用于:
- 文本相似性度量:比较文本向量的相似度。
- 图像相似性分析:衡量图像特征向量的相似程度。
- 推荐系统:找到用户或物品之间的相似性。
/**
* 计算余弦相似度
* @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的版本
<dependency>
<groupId>org.ansj</groupId>
<artifactId>ansj_seg</artifactId>
<version>5.1.6</version>
</dependency>
util 代码如下
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);
}
}
使用案例
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));
}
执行结果: