前言

所有的故事都有開(kāi)始,也終將結(jié)束。

本文將作為 NLP 漢字相似度的完結(jié)篇,為該系列畫(huà)上一個(gè)句號(hào)。

起-NLP 中文形近字相似度計(jì)算思路

承-中文形近字相似度算法實(shí)現(xiàn),為漢字 NLP 盡一點(diǎn)綿薄之力

轉(zhuǎn)-當(dāng)代中國(guó)最貴的漢字是什么?

不足之處

之所以有本篇,是因?yàn)樯弦淮蔚乃惴▽?shí)現(xiàn)存在一些不足。

巴別塔

《圣經(jīng)》中有關(guān)于巴別塔建造,最終人們因?yàn)檎Z(yǔ)言問(wèn)題而停工的故事?。

創(chuàng)11:6 “看哪!他們成為一樣的人民,都是一樣的言語(yǔ),如今既作起這事來(lái),以后他們所要作的事,就沒(méi)有不成就的了。創(chuàng)11:7 我們下去,在那里變亂他們的口音,使他們的言語(yǔ)彼此不通。”創(chuàng)11:8 于是,耶和華使他們從那里分散在全地上;他們就停工不造那城了。

為了避免語(yǔ)言問(wèn)題,我一開(kāi)始就實(shí)現(xiàn)了一個(gè) exe4j 打包的對(duì)比程序,自己跑的很順暢。

小伙伴一跑,運(yùn)行失敗。各種環(huán)境配置一頓操作,最后還是報(bào)錯(cuò)。

于是,我寫了一個(gè) python 簡(jiǎn)易版本,便于做 NLP 研究的小伙伴們學(xué)習(xí)。

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

java 是一種語(yǔ)言,python 是一種語(yǔ)言。

編程語(yǔ)言,讓人和機(jī)器之間可以溝通,卻讓人與人之間產(chǎn)生了隔閡。

拆字

當(dāng)代中國(guó)最貴的漢字是什么? 一文中,我們首次說(shuō)明了漢字的拆合。

漢字的拆分實(shí)現(xiàn),核心目的之一就是為了完善漢字的相似度比較。

通過(guò)對(duì)比漢字的拆分部分,然后獲取拆字的相似度,提高對(duì)比的準(zhǔn)確性。

拆字相似度

簡(jiǎn)單的需求

為了便于小伙伴們理解,我們用產(chǎn)品經(jīng)理的思維和大家介紹一下實(shí)現(xiàn)方式。

我的需求比較簡(jiǎn)單。你看,【明】可以拆分【日】【月】,【冐】也可以拆分為【日】【月】。對(duì)比一下,結(jié)果是顯然的。怎么實(shí)現(xiàn)我不管,明天上線吧。

小伙伴們,應(yīng)該已經(jīng)知道怎么實(shí)現(xiàn)了吧?

使用體驗(yàn)

誠(chéng)如產(chǎn)品所言,這個(gè)需求已經(jīng)實(shí)現(xiàn)。

maven 引入

    com.github.houbb    nlp-hanzi-similar    1.2.0

使用

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

對(duì)應(yīng)的結(jié)果為:0.9696969696969697

更多使用細(xì)節(jié),參考開(kāi)源地址:

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

寫在完結(jié)前

涉及的項(xiàng)目

漢字的相似度計(jì)算到這里算是告一段落。

主要涉及的資料及項(xiàng)目有:

拼音

拆字

四角編碼詞庫(kù)

漢字結(jié)構(gòu)詞庫(kù)

漢字偏旁詞庫(kù)

筆畫(huà)數(shù)詞庫(kù)

當(dāng)然,還可以結(jié)果 opencc4j 進(jìn)行繁簡(jiǎn)體的處理,此處不再延伸。

之后的計(jì)劃

NLP 的領(lǐng)域還有很多東西需要大家攻克,畢竟中文 NLP 才剛剛開(kāi)始。

技術(shù)尚未成功,同志仍需努力。

據(jù)說(shuō)最近鵝城的某位黃老爺惹得大家怨聲載道。

很多小伙伴說(shuō),如果有一款軟件可以實(shí)現(xiàn)【月丷夫馬言卂彳山兀攴人言】的溝通功能,那么我肯定會(huì)用。

所謂說(shuō)者無(wú)心,聽(tīng)者有意。

寫一個(gè)通訊軟件,主要是為了鞏固下 netty 的學(xué)習(xí),其他的都不重要。

雖然知道就算有,大家肯定也不太會(huì)改變,但是老馬還是準(zhǔn)備試試。

java 實(shí)現(xiàn)思路

警告,如果你頭發(fā)已經(jīng)所剩無(wú)幾,或者對(duì)實(shí)現(xiàn)并不感興趣。

那么就可以收藏+點(diǎn)贊+評(píng)論【不明覺(jué)厲】,然后離開(kāi)了。

下面是枯燥的代碼實(shí)現(xiàn)環(huán)節(jié)。

程序員的思維

下面是程序員的思維。

首先要解決幾個(gè)問(wèn)題:

(1)漢字的拆分實(shí)現(xiàn)

這個(gè)直接復(fù)用已經(jīng)實(shí)現(xiàn)的漢字拆分實(shí)現(xiàn)。

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

相同的一個(gè)漢字可以有多種拆分方式,簡(jiǎn)單起見(jiàn),我們默認(rèn)取第一個(gè)。

(2)相似的比較

假設(shè)我們對(duì)比 A B 兩個(gè)漢字,可以拆分為如下的子集。

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

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

/** * 獲取拆分后對(duì)應(yīng)的拆分字符 * @param charWord 字符 * @return 結(jié)果 */private char[] getSplitChars(String charWord) {    List stringList = ChaiziHelper.chai(charWord.charAt(0));    // 這里應(yīng)該選擇哪一個(gè)是有講究的。此處為了簡(jiǎn)單,默認(rèn)選擇第一個(gè)。    String string = stringList.get(0);    return string.toCharArray();}

拆分后的子集對(duì)比有多種實(shí)現(xiàn)方式,簡(jiǎn)單起見(jiàn),我們直接遍歷元素,判斷另一個(gè)子集是否存在。

當(dāng)然,遍歷的時(shí)候要以拆分?jǐn)?shù)量較少的的為基準(zhǔn)。

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)) {        //累加分?jǐn)?shù)    }}

(3)拆分子集的權(quán)重

比如 兩個(gè)漢字都是子集,但是因?yàn)楣P畫(huà)數(shù)不同,權(quán)重也不同。

我們用一個(gè)子集的筆畫(huà)數(shù)占整體漢字的筆畫(huà)數(shù)計(jì)算權(quán)重。

 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,是為了歸一化。保證最后的結(jié)果在 0-1 之間。

(4)筆畫(huà)數(shù)

獲取筆畫(huà)數(shù)的方式,我們可以直接復(fù)用以前的方法。

如果沒(méi)有匹配的,默認(rèn)筆畫(huà)數(shù)為 1。

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

java 完整實(shí)現(xiàn)

我們把所有的碎片拼接起來(lái),就得到一個(gè)完整的實(shí)現(xiàn)。

/** * 拆字 * * @author 老馬嘯西風(fēng) * @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();    }    /**     * 獲取拆分后對(duì)應(yīng)的拆分字符     * @param charWord 字符     * @return 結(jié)果     */    private char[] getSplitChars(String charWord) {        List stringList = ChaiziHelper.chai(charWord.charAt(0));        // 這里應(yīng)該選擇哪一個(gè)是有講究的。此處為了簡(jiǎn)單,默認(rèn)選擇第一個(gè)。        String string = stringList.get(0);        return string.toCharArray();    }    /**     * 獲取筆畫(huà)數(shù)     * @param text 文本     * @param similarContext 上下文     * @return 結(jié)果     */    private int getNumber(String text, IHanziSimilarContext similarContext) {        Map map = similarContext.bihuashuData().dataMap();        Integer number = map.get(text);        if(number == null) {            return 1;        }        return number;    }}

小結(jié)

本文引入了漢字拆字,進(jìn)一步豐富了相似度的實(shí)現(xiàn)。

當(dāng)然,實(shí)現(xiàn)本身依然有很多值得提升的地方,比如拆分后的選擇,是否可以遞歸拆分等,這個(gè)還是留給后人研究吧。

我是老馬,期待與你的下次重逢。