NLP 开源形近字算法补完计划(完结篇)

2021年11月26日 阅读数:3
这篇文章主要向大家介绍NLP 开源形近字算法补完计划(完结篇),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

前言

全部的故事都有开始,也终将结束。java

本文将做为 NLP 汉字类似度的完结篇,为该系列画上一个句号。python

起-NLP 中文形近字类似度计算思路git

承-中文形近字类似度算法实现,为汉字 NLP 尽一点绵薄之力程序员

转-当代中国最贵的汉字是什么?github

不足之处

之因此有本篇,是由于上一次的算法实现存在一些不足。算法

巴别塔

《圣经》中有关于巴别塔建造,最终人们由于语言问题而停工的故事​。编程

巴别塔

创11:6 “看哪!他们成为同样的人民,都是同样的言语,现在既做起这事来,之后他们所要做的事,就没有不成就的了。

创11:7 咱们下去,在那里变乱他们的口音,使他们的言语彼此不通。”

创11:8 因而,耶和华使他们从那里分散在全地上;他们就停工不造那城了。

为了不语言问题,我一开始就实现了一个 exe4j 打包的对比程序,本身跑的很顺畅。maven

小伙伴一跑,运行失败。各类环境配置一顿操做,最后仍是报错。编程语言

因而,我写了一个 python 简易版本,便于作 NLP 研究的小伙伴们学习。ide

https://github.com/houbb/nlp-hanzi-similar/releases/tag/pythn

java 是一种语言,python 是一种语言。

编程语言,让人和机器之间能够沟通,却让人与人之间产生了隔阂。

拆字

当代中国最贵的汉字是什么? 一文中,咱们首次说明了汉字的拆合。

汉字的拆分实现,核心目的之一就是为了完善汉字的类似度比较。

经过对比汉字的拆分部分,而后获取拆字的类似度,提升对比的准确性。

拆字类似度

简单的需求

为了便于小伙伴们理解,咱们用产品经理的思惟和你们介绍一下实现方式。

个人需求比较简单。

你看,【明】能够拆分【日】【月】,【冐】也能够拆分为【日】【月】。对比一下,结果是显然的。

怎么实现我无论,明天上线吧。

小伙伴们,应该已经知道怎么实现了吧?

简单

使用体验

诚如产品所言,这个需求已经实现。

maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>nlp-hanzi-similar</artifactId>
    <version>1.2.0</version>
</dependency>

使用

double rate1 = HanziSimilarHelper.similar('末', '未');

对应的结果为:0.9696969696969697

更多使用细节,参考开源地址:

https://github.com/houbb/nlp-hanzi-similar

写在完结前

涉及的项目

汉字的类似度计算到这里算是告一段落。

主要涉及的资料及项目有:

拼音

拆字

四角编码词库

汉字结构词库

汉字偏旁词库

笔画数词库

固然,还能够结果 opencc4j 进行繁简体的处理,此处再也不延伸。

以后的计划

NLP 的领域还有不少东西须要你们攻克,毕竟中文 NLP 才刚刚开始。

技术还没有成功,同志仍需努力。

听说最近鹅城的某位黄老爷惹得你们怨声载道。

不少小伙伴说,若是有一款软件能够实现【月丷夫马言卂彳山兀攴人言】的沟通功能,那么我确定会用。

所谓说者无意,听者有意。

写一个通信软件,主要是为了巩固下 netty 的学习,其余的都不重要。

没有你,对我很重要

虽然知道就算有,你们确定也不太会改变,可是老马仍是准备试试。

java 实现思路

警告,若是你头发已经所剩无几,或者对实现并不感兴趣。

那么就能够收藏+点赞+评论【不明觉厉】,而后离开了。

下面是枯燥的代码实现环节。

轻松

程序员的思惟

下面是程序员的思惟。

首先要解决几个问题:

(1)汉字的拆分实现

这个直接复用已经实现的汉字拆分实现。

List<String> stringList = ChaiziHelper.chai(charWord.charAt(0));

相同的一个汉字能够有多种拆分方式,简单起见,咱们默认取第一个。

(2)类似的比较

假设咱们对比 A B 两个汉字,能够拆分为以下的子集。

A = {A1, A2, ..., Am}

B = {B1, B2, ..., Bm}

/**
 * 获取拆分后对应的拆分字符
 * @param charWord 字符
 * @return 结果
 */
private char[] getSplitChars(String charWord) {
    List<String> stringList = ChaiziHelper.chai(charWord.charAt(0));

    // 这里应该选择哪个是有讲究的。此处为了简单,默认选择第一个。
    String string = stringList.get(0);
    return string.toCharArray();
}

拆分后的子集对比有多种实现方式,简单起见,咱们直接遍历元素,判断另外一个子集是否存在。

固然,遍历的时候要以拆分数量较少的的为基准。

int minLen = Math.min(charsOne.length, charsTwo.length);

// 比较
double totalScore = 0.0;
for(int i = 0; i <  minLen; i++) {
    char iChar = charsOne[i];
    String textChar = iChar+"";
    if(ArrayPrimitiveUtil.contains(charsTwo, iChar)) {
        //累加分数
    }
}

(3)拆分子集的权重

好比 两个汉字都是子集,可是由于笔画数不一样,权重也不一样。

咱们用一个子集的笔画数占总体汉字的笔画数计算权重。

 int textNumber = getNumber(textChar, similarContext);

double scoreOne = textNumber*1.0 / numberOne * 1.0;
double scoreTwo = textNumber*1.0 / numberTwo * 1.0;

totalScore += (scoreOne + scoreTwo) / 2.0;

ps: 这里的除以 2,是为了归一化。保证最后的结果在 0-1 之间。

(4)笔画数

获取笔画数的方式,咱们能够直接复用之前的方法。

若是没有匹配的,默认笔画数为 1。

private int getNumber(String text, IHanziSimilarContext similarContext) {
    Map<String, Integer> map = similarContext.bihuashuData().dataMap();
    Integer number = map.get(text);
    if(number == null) {
        return 1;
    }
    return number;
}

java 完整实现

咱们把全部的碎片拼接起来,就获得一个完整的实现。

/**
 * 拆字
 *
 * @author 老马啸西风
 * @since 1.0.0
 */
public class ChaiziSimilar implements IHanziSimilar {

    @Override
    public double similar(IHanziSimilarContext similarContext) {
        String hanziOne = similarContext.charOne();
        String hanziTwo = similarContext.charTwo();

        int numberOne = getNumber(hanziOne, similarContext);
        int numberTwo = getNumber(hanziTwo, similarContext);

        // 拆分
        char[] charsOne = getSplitChars(hanziOne);
        char[] charsTwo = getSplitChars(hanziTwo);

        int minLen = Math.min(charsOne.length, charsTwo.length);

        // 比较
        double totalScore = 0.0;
        for(int i = 0; i <  minLen; i++) {
            char iChar = charsOne[i];
            String textChar = iChar+"";
            if(ArrayPrimitiveUtil.contains(charsTwo, iChar)) {
                int textNumber = getNumber(textChar, similarContext);

                double scoreOne = textNumber*1.0 / numberOne * 1.0;
                double scoreTwo = textNumber*1.0 / numberTwo * 1.0;

                totalScore += (scoreOne + scoreTwo) / 2.0;
            }
        }

        return totalScore * similarContext.chaiziRate();
    }

    /**
     * 获取拆分后对应的拆分字符
     * @param charWord 字符
     * @return 结果
     */
    private char[] getSplitChars(String charWord) {
        List<String> stringList = ChaiziHelper.chai(charWord.charAt(0));

        // 这里应该选择哪个是有讲究的。此处为了简单,默认选择第一个。
        String string = stringList.get(0);

        return string.toCharArray();
    }

    /**
     * 获取笔画数
     * @param text 文本
     * @param similarContext 上下文
     * @return 结果
     */
    private int getNumber(String text, IHanziSimilarContext similarContext) {
        Map<String, Integer> map = similarContext.bihuashuData().dataMap();

        Integer number = map.get(text);
        if(number == null) {
            return 1;
        }

        return number;
    }

}

小结

本文引入了汉字拆字,进一步丰富了类似度的实现。

固然,实现自己依然有不少值得提高的地方,好比拆分后的选择,是否能够递归拆分等,这个仍是留给后人研究吧。

我是老马,期待与你的下次重逢。