首页/文章列表/文章详情

文本相似度 HanPL汉语言处理

编程知识2102024-08-05评论

@


前言

请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i


提示:以下是本篇文章正文内容,下面案例可供参考

需求

当我们需要求两个或两个以上的字符串相似度百分比时,可以使用HanLP汉语言处理包来帮助我们,求两个文本的相似度百分比、海明距离、

简介

HanLP(Han Language Processing)在汉语义处理方面具备强大的功能,由中国科学院计算技术研究所自然语言处理与社会人文计算研究中心开发。它提供了包括分词、词性标注、命名实体识别、依存句法分析、情感分析、文本分类等多种自然语言处理任务的功能。

主要功能:

  • 分词:HanLP提供了多种分词模型,包括基于规则的模型、基于神经网络的模型等,能够准确地进行中文分词。
  • 词性标注:在分词的基础上,HanLP还能对词语进行词性标注,帮助理解词语在句子中的作用。
  • 命名实体识别:HanLP能够识别文本中的命名实体,如人名、地名、组织机构名等,这对于信息抽取等任务非常重要。
  • 依存句法分析:通过对句子中各个词语之间的依存关系建模,HanLP能够分析句子结构,提取句子的语义信息。
  • 情感分析:HanLP支持对文本进行情感分析,判断文本所表达的情感倾向,如正面、负面或中性。
  • 文本分类:HanLP还提供了文本分类的功能,可以将文本按照预设的分类体系进行分类。

友情链接:HanLP项目主页HanLP下载地址详细介绍

实操开始

注意:本文以Java开发语言为案例

1. 添加pom.xml依赖

 <!--junit依赖--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!--hanlp语言处理依赖--> <dependency> <groupId>com.hankcs</groupId> <artifactId>hanlp</artifactId> <version>portable-1.7.6</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.3</version> </dependency>

2. 文本相似度工具类

import com.hankcs.hanlp.seg.common.Term;import com.hankcs.hanlp.tokenizer.StandardTokenizer;import org.apache.commons.lang3.StringUtils;import org.jsoup.Jsoup;import org.jsoup.safety.Whitelist;import java.math.BigInteger;import java.util.*;public class MySimHash { private String tokens; //字符串 private BigInteger strSimHash;//字符产的hash值 private int hashbits = 64; // 分词后的hash数; public MySimHash(String tokens) { this.tokens = tokens; this.strSimHash = this.simHash(); } private MySimHash(String tokens, int hashbits) { this.tokens = tokens; this.hashbits = hashbits; this.strSimHash = this.simHash(); } /** * 清除html标签 * * @param content * @return */ private String cleanResume(String content) { // 若输入为HTML,下面会过滤掉所有的HTML的tag content = Jsoup.clean(content, Whitelist.none()); content = StringUtils.lowerCase(content); String[] strings = {"","\n","\r","\t","\\r","\\n","\\t","&nbsp;"}; for (String s : strings) { content = content.replaceAll(s,""); } return content; } /** * 这个是对整个字符串进行hash计算 * * @return */ private BigInteger simHash() { tokens = cleanResume(tokens); // cleanResume 删除一些特殊字符 int[] v = new int[this.hashbits]; List<Term> termList = StandardTokenizer.segment(this.tokens); // 对字符串进行分词 //对分词的一些特殊处理 : 比如: 根据词性添加权重 , 过滤掉标点符号 , 过滤超频词汇等; Map<String, Integer> weightOfNature = new HashMap<String, Integer>(); // 词性的权重 weightOfNature.put("n", 2); //给名词的权重是2; Map<String, String> stopNatures = new HashMap<String, String>();//停用的词性 如一些标点符号之类的; stopNatures.put("w",""); // int overCount = 5; //设定超频词汇的界限 ; Map<String, Integer> wordCount = new HashMap<String, Integer>(); for (Term term : termList) { String word = term.word; //分词字符串 String nature = term.nature.toString(); // 分词属性; // 过滤超频词 if (wordCount.containsKey(word)) { int count = wordCount.get(word); if (count > overCount) { continue; } wordCount.put(word, count + 1); } else { wordCount.put(word, 1); } // 过滤停用词性 if (stopNatures.containsKey(nature)) { continue; } // 2、将每一个分词hash为一组固定长度的数列.比如 64bit 的一个整数. BigInteger t = this.hash(word); for (int i = 0; i < this.hashbits; i++) { BigInteger bitmask = new BigInteger("1").shiftLeft(i); // 3、建立一个长度为64的整数数组(假设要生成64位的数字指纹,也可以是其它数字), // 对每一个分词hash后的数列进行判断,如果是1000...1,那么数组的第一位和末尾一位加1, // 中间的62位减一,也就是说,逢1加1,逢0减1.一直到把所有的分词hash数列全部判断完毕. int weight = 1; //添加权重 if (weightOfNature.containsKey(nature)) { weight = weightOfNature.get(nature); } if (t.and(bitmask).signum() != 0) { // 这里是计算整个文档的所有特征的向量和 v[i] += weight; } else { v[i] -= weight; } } } BigInteger fingerprint = new BigInteger("0"); for (int i = 0; i < this.hashbits; i++) { if (v[i] >= 0) { fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i)); } } return fingerprint; } /** * 对单个的分词进行hash计算; * * @param source * @return */ private BigInteger hash(String source) { if (source == null || source.length() == 0) { return new BigInteger("0"); } else { /** * 当sourece 的长度过短,会导致hash算法失效,因此需要对过短的词补偿 */ while (source.length() < 3) { source = source + source.charAt(0); } char[] sourceArray = source.toCharArray(); BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7); BigInteger m = new BigInteger("1000003"); BigInteger mask = new BigInteger("2").pow(this.hashbits).subtract(new BigInteger("1")); for (char item : sourceArray) { BigInteger temp = BigInteger.valueOf((long) item); x = x.multiply(m).xor(temp).and(mask); } x = x.xor(new BigInteger(String.valueOf(source.length()))); if (x.equals(new BigInteger("-1"))) { x = new BigInteger("-2"); } return x; } } /** * 计算海明距离,海明距离越小说明越相似; * * @param other * @return */ private int hammingDistance(MySimHash other) { BigInteger m = new BigInteger("1").shiftLeft(this.hashbits).subtract( new BigInteger("1")); BigInteger x = this.strSimHash.xor(other.strSimHash).and(m); int tot = 0; while (x.signum() != 0) { tot += 1; x = x.and(x.subtract(new BigInteger("1"))); } return tot; } /** * . * 求百分比 * * @param s2 * @return */ public double getSemblance(MySimHash s2) { double i = (double) this.hammingDistance(s2); return 1 - i / this.hashbits; }}

详细说明:

 /*--------------------------------------分词说明-------------------------------------- 分词,把需要判断文本分词形成这个文章的特征单词。 最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。 只要相似的字符串只有个别的位数是有差别变化。那这样我们可以推断两个相似的文本, 比如:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”, 括号里是代表单词在 整个句子里重要程度,数字越大越重要。 2、hash,通过hash算法把每个词变成hash值, 比如“美国”通过hash算法计算为 100101, “51区”通过hash算法计算为 101011。 这样我们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。 3、加权,通过 2步骤的hash生成结果, 需要按照单词的权重形成加权数字串, 比如“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”;“51区”的hash值为“101011”,通过加权计算为 “ 5 -5 5 -5 5 5”。 4、合并, 把上面各个单词算出来的序列值累加,变成只有一个序列串。 比如 “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”, 把每一位进行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5” ==》 “9 -9 1 -1 1 9”。 这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。 5、降维,把4步算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,形成我们最终的simhash签名。 如果每一位大于0 记为 1,小于0 是统优先公司";  

3. 案例验证

 public static void main(String[] args) { String text ="杏南一区"; List<String> itemList = new LinkedList<String>(); itemList.add("杏南一区"); itemList.add("杏南二区"); itemList.add("杏南三区"); itemList.add("杏南四区"); itemList.add("杏南五区"); itemList.add("杏南六区"); itemList.add("杏南七区"); itemList.add("杏南八区"); itemList.add("杏南九区"); itemList.add("杏南十区"); System.out.println("======================================"); long startTime = System.currentTimeMillis(); MySimHash hash1 = new MySimHash(text, 64); List<Double> list = new LinkedList<Double>(); for (String str : itemList) { MySimHash hash2 = new MySimHash(str, 64); //海明距离越小说明越相似 System.out.println("海明距离:" + hash1.hammingDistance(hash2) +"###" +"文本相似度:" + hash2.getSemblance(hash1)); list.add(hash2.getSemblance(hash1)); } long endTime = System.currentTimeMillis(); Double max = Collections.max(list); int index = list.indexOf(max);//获取集合下标 System.out.println("======================================"); System.out.println("耗时:" + (endTime - startTime)); System.out.println("相似度集合内容:" + list.toString()); System.out.println("集合中最大值:" + max +"###" +"集合下标:" + index); System.out.println("对比内容:" + text +"###" +"相似度最高:" + itemList.get(index)); }

4. 验证结果

测试

总结

我是南国以南i记录点滴每天成长一点点,学习是永无止境的!转载请附原文链接!!!

参考链接

博客园

这个人很懒...

用户评论 (0)

发表评论

captcha