前言
所有的故事都有開始,也終將結(jié)束。
本文將作為 NLP 漢字相似度的完結(jié)篇,為該系列畫上一個句號。
承-中文形近字相似度算法實現(xiàn),為漢字 NLP 盡一點綿薄之力
不足之處
之所以有本篇,是因為上一次的算法實現(xiàn)存在一些不足。
巴別塔
《圣經(jīng)》中有關(guān)于巴別塔建造,最終人們因為語言問題而停工的故事?。
創(chuàng)11:6 “看哪!他們成為一樣的人民,都是一樣的言語,如今既作起這事來,以后他們所要作的事,就沒有不成就的了。創(chuàng)11:7 我們下去,在那里變亂他們的口音,使他們的言語彼此不通。”創(chuàng)11:8 于是,耶和華使他們從那里分散在全地上;他們就停工不造那城了。
為了避免語言問題,我一開始就實現(xiàn)了一個 exe4j 打包的對比程序,自己跑的很順暢。
小伙伴一跑,運(yùn)行失敗。各種環(huán)境配置一頓操作,最后還是報錯。
于是,我寫了一個 python 簡易版本,便于做 NLP 研究的小伙伴們學(xué)習(xí)。
https://github.com/houbb/nlp-hanzi-similar/releases/tag/pythn
java 是一種語言,python 是一種語言。
編程語言,讓人和機(jī)器之間可以溝通,卻讓人與人之間產(chǎn)生了隔閡。
拆字
在 當(dāng)代中國最貴的漢字是什么? 一文中,我們首次說明了漢字的拆合。
漢字的拆分實現(xiàn),核心目的之一就是為了完善漢字的相似度比較。
通過對比漢字的拆分部分,然后獲取拆字的相似度,提高對比的準(zhǔn)確性。
拆字相似度
簡單的需求
為了便于小伙伴們理解,我們用產(chǎn)品經(jīng)理的思維和大家介紹一下實現(xiàn)方式。
我的需求比較簡單。你看,【明】可以拆分【日】【月】,【冐】也可以拆分為【日】【月】。對比一下,結(jié)果是顯然的。怎么實現(xiàn)我不管,明天上線吧。
小伙伴們,應(yīng)該已經(jīng)知道怎么實現(xiàn)了吧?
使用體驗
誠如產(chǎn)品所言,這個需求已經(jīng)實現(xiàn)。
maven 引入
com.github.houbb nlp-hanzi-similar 1.2.0
使用
double rate1 = HanziSimilarHelper.similar(末, 未);
對應(yīng)的結(jié)果為:0.9696969696969697
更多使用細(xì)節(jié),參考開源地址:
寫在完結(jié)前
涉及的項目
漢字的相似度計算到這里算是告一段落。
主要涉及的資料及項目有:
當(dāng)然,還可以結(jié)果 opencc4j 進(jìn)行繁簡體的處理,此處不再延伸。
之后的計劃
NLP 的領(lǐng)域還有很多東西需要大家攻克,畢竟中文 NLP 才剛剛開始。
技術(shù)尚未成功,同志仍需努力。
據(jù)說最近鵝城的某位黃老爺惹得大家怨聲載道。
很多小伙伴說,如果有一款軟件可以實現(xiàn)【月丷夫馬言卂彳山兀攴人言】的溝通功能,那么我肯定會用。
所謂說者無心,聽者有意。
寫一個通訊軟件,主要是為了鞏固下 netty 的學(xué)習(xí),其他的都不重要。
雖然知道就算有,大家肯定也不太會改變,但是老馬還是準(zhǔn)備試試。
java 實現(xiàn)思路
警告,如果你頭發(fā)已經(jīng)所剩無幾,或者對實現(xiàn)并不感興趣。
那么就可以收藏+點贊+評論【不明覺厲】,然后離開了。
下面是枯燥的代碼實現(xiàn)環(huán)節(jié)。
程序員的思維
下面是程序員的思維。
首先要解決幾個問題:
(1)漢字的拆分實現(xiàn)
這個直接復(fù)用已經(jīng)實現(xiàn)的漢字拆分實現(xiàn)。
List stringList = ChaiziHelper.chai(charWord.charAt(0));
相同的一個漢字可以有多種拆分方式,簡單起見,我們默認(rèn)取第一個。
(2)相似的比較
假設(shè)我們對比 A B 兩個漢字,可以拆分為如下的子集。
A = {A1, A2, ..., Am}
B = {B1, B2, ..., Bm}
/** * 獲取拆分后對應(yīng)的拆分字符 * @param charWord 字符 * @return 結(jié)果 */private char[] getSplitChars(String charWord) { List stringList = ChaiziHelper.chai(charWord.charAt(0)); // 這里應(yīng)該選擇哪一個是有講究的。此處為了簡單,默認(rèn)選擇第一個。 String string = stringList.get(0); return string.toCharArray();}
拆分后的子集對比有多種實現(xiàn)方式,簡單起見,我們直接遍歷元素,判斷另一個子集是否存在。
當(dāng)然,遍歷的時候要以拆分?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)重
比如 一
月
兩個漢字都是子集,但是因為筆畫數(shù)不同,權(quán)重也不同。
我們用一個子集的筆畫數(shù)占整體漢字的筆畫數(shù)計算權(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)筆畫數(shù)
獲取筆畫數(shù)的方式,我們可以直接復(fù)用以前的方法。
如果沒有匹配的,默認(rèn)筆畫數(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 完整實現(xiàn)
我們把所有的碎片拼接起來,就得到一個完整的實現(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(); } /** * 獲取拆分后對應(yīng)的拆分字符 * @param charWord 字符 * @return 結(jié)果 */ private char[] getSplitChars(String charWord) { List stringList = ChaiziHelper.chai(charWord.charAt(0)); // 這里應(yīng)該選擇哪一個是有講究的。此處為了簡單,默認(rèn)選擇第一個。 String string = stringList.get(0); return string.toCharArray(); } /** * 獲取筆畫數(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)一步豐富了相似度的實現(xiàn)。
當(dāng)然,實現(xiàn)本身依然有很多值得提升的地方,比如拆分后的選擇,是否可以遞歸拆分等,這個還是留給后人研究吧。
我是老馬,期待與你的下次重逢。