NLP之相似语句识别

本文涉及的产品
NLP 自学习平台,3个模型定制额度 1个月
NLP自然语言处理_基础版,每接口每天50万次
NLP自然语言处理_高级版,每接口累计50万次
简介: NLP之相似语句识别

使用simhash计算文本相似度



Java RESTful API — HanLP Documentation


demo


14f1669b0eed4967822bed7dd9f15dc5_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

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.HashMap;
import java.util.List;
import java.util.Map;
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", " "};
        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;
    }
    public double getSemblance(MySimHash s2) {
        double i = (double) this.hammingDistance(s2);
        return 1 - i / this.hashbits;
    }
    public static void main(String[] args) {
        String s1 = "张继群,23岁,临沂大学。";
        String s2 = "我叫张继群,今年23岁,在临沂大学上学。";
        String s3 = "我在临沂大学上学是张继群,今年23岁。";
        String s4 = "11111111111111。";
        MySimHash hash1 = new MySimHash(s1, 64);
        MySimHash hash2 = new MySimHash(s2, 64);
        System.out.println("======================================");
        System.out.println(hash1.hammingDistance(hash2));
        System.out.println(hash1.getSemblance(hash2));
        System.out.println("======================================");
    }
}


汉明距离计算



主要的6个步骤为:分词、hash、加权、合并、降维、计算汉明距离,前5个步骤本质上是simhash算法的流程,思路很简单易懂。


分词


分词工具有很多,例如Ansj、JieBa、HanLP等等,本文使用的 是HanLP。

为了减小无关词的影响(降噪),可以将标点等过滤掉,并移除停用词,只留下有意义的词语。


hash(64位)


使用MurmurHash3.hash64(byte[] data)来得到词语的hash值。需要注意的是它返回的是Long类型,如果转换为二进制表示后不足64位,需要在补齐64位(高位补0)


加权


词频是衡量一个词语在句子中的常用方法之一,除此之外还有TF-IDF、词语的情感色彩值等等,本文使用词频作为词语的权重。


合并


对每个词语计算到的加权后的hash值按位求和


降维


对求和后的数据进行降维,即对于每一位,大于0的位变为1,小于0的位变为0,得到一个二进制数(或者字符串),即为最终的simhash值。


计算汉明距离


对以上步骤得到的两个simhash值计算其汉明距离,即统计两个64位的二进制数中对应位不同的个数(异或后1的个数),最终得到汉明距离。一般根据经验值,汉明距离小于等于3的即可认为相似。


汉明距离为一个整数,似乎不能很直观的反应两个文本的相似度(0 ~ 1),所以这里通过实验的方法找了条类似正态分布的函数来将汉明距离转化为一个0~1之间的数来表示相似度,更直观一些,方程如下:


630f19357deb4260860e1cfd71827ed5_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

在IDEA里gradle配置和使用



搜索对应的jar名称进行加入


建议:转用maven的项目使用pom.xml文件



d83d39cecc7c4a94bc2bc56474feb1c8_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

在IDEA里gradle配置和使用,一般别使用同步gradle



在gradle 配置中dependencies 是不管用的,sync 的话之前添加的依赖都没有了


85e0248d4aeb474e9f520722e183870a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

Gradle和maven都是构建工具



.gradle和pom.xml 是对应的配置文件



相关文章
|
机器学习/深度学习 自然语言处理
利用深度学习技术改进自然语言处理中的命名实体识别
命名实体识别(Named Entity Recognition, NER)在自然语言处理领域扮演着重要角色,但传统方法在处理复杂语境和多样化实体时存在局限性。本文将探讨如何利用深度学习技术,特别是基于预训练模型的方法,来改进命名实体识别,提高其在现实场景中的性能和适用性。
|
自然语言处理 数据可视化 API
ESRE 系列(二):如何部署自然语言处理 (NLP):命名实体识别 (NER) 示例
本文采用示例讲解的方式,介绍使用一个命名实体识别 (NER) NLP 模型来定位和提取非结构化文本字段中预定义类别的实体。我们将通过一个公开可用的模型向您展示如何完成以下几种操作:部署模型到 Elasticsearch 中,利用 new _infer API 查找文本中的命名实体,以及在 Ingest 管道中使用 NER 模型,在文档被采集到 Elasticsearch 中时提取实体。
18431 12
ESRE 系列(二):如何部署自然语言处理 (NLP):命名实体识别 (NER) 示例
|
机器学习/深度学习 人工智能 自然语言处理
深度学习应用篇-自然语言处理-命名实体识别[9]:BiLSTM+CRF实现命名实体识别、实体、关系、属性抽取实战项目合集(含智能标注)
深度学习应用篇-自然语言处理-命名实体识别[9]:BiLSTM+CRF实现命名实体识别、实体、关系、属性抽取实战项目合集(含智能标注)
深度学习应用篇-自然语言处理-命名实体识别[9]:BiLSTM+CRF实现命名实体识别、实体、关系、属性抽取实战项目合集(含智能标注)
|
存储 自然语言处理 UED
[NLP比赛推荐]商品标题实体识别
[NLP比赛推荐]商品标题实体识别
644 0
[NLP比赛推荐]商品标题实体识别
|
自然语言处理 Java API
阿里云自然语言处理--命名实体识别(中文高级版)Quick Start
自然语言处理(Natural Language Processing,简称NLP),是为各类企业及开发者提供的用于文本分析及挖掘的核心工具,旨在帮助用户高效的处理文本,已经广泛应用在电商、文娱、司法、公安、金融、医疗、电力等行业客户的多项业务中,取得了良好的效果。命名实体识别服务可以帮助您快速识别文本中的实体,针对电商领域,识别品牌、产品、型号等,同时也包括一些通用领域实体如人名、地名、机构名、时间日期等。进而挖掘各实体间的关系,是进行深度文本挖掘,知识库构建等常用自然语言处理领域里的必备工具。本文将使用Java Common SDK演示命名实体识别服务的快速调用以供参考。
1037 0
阿里云自然语言处理--命名实体识别(中文高级版)Quick Start
|
自然语言处理 算法
NLP(5) | 命名实体识别
NLP(5) | 命名实体识别
518 0
NLP(5) | 命名实体识别
|
机器学习/深度学习 自然语言处理 算法
NLP之ASR:基于python和机器学习算法带你玩转的语音实时识别技术
NLP之ASR:基于python和机器学习算法带你玩转的语音实时识别技术
NLP之ASR:基于python和机器学习算法带你玩转的语音实时识别技术
|
自然语言处理
自然语言处理工具HanLP-基于层叠HMM地名识别
本篇接上一篇内容《HanLP-基于HMM-Viterbi的人名识别原理介绍》介绍一下层叠隐马的原理。首先说一下上一篇介绍的人名识别效果对比: 只有Jieba识别出的人名准确率极低,基本为地名或复杂地名组成部分或复杂机构名组成部分。
1375 0
|
自然语言处理 算法
Pyhanlp自然语言处理中的新词识别
本“新词发现”模块基于信息熵和互信息两种算法,可以在无语料的情况下提取一段长文本中的词语,并支持过滤掉系统中已存在的“旧词”,得到新词列表。
1715 0
|
自然语言处理 Python Ubuntu
自然语言处理工具python调用hanlp中文实体识别
Hanlp作为一款重要的中文分词工具,在GitHub的用户量已经非常之高,应该可以看得出来大家对于hanlp这款分词工具还是很认可的。本篇继续分享一篇关于hanlp的使用实例即Python调用hanlp进行中文实体识别。
2012 0