用Python求字符串相似度匹配實戰(zhàn)
?????????最近遇到這樣一個實際問題,有一份紙質(zhì)清單A(打印體文字,但是有磨損),同時有一份電子表格B(與A非同一版本),且A和B反映的內(nèi)容本質(zhì)上是一致的,只是因為一些因素,(1)導(dǎo)致同一個事物所呈現(xiàn)的文字不一樣,比如“電腦”和計算機大概率是指同一個東西。(2)在同一個清單中會有重復(fù)出現(xiàn)的值,這就導(dǎo)致用Excel的Vlookup不好使。要求是對二者進行匹配,對他們做一個檢驗,找出二者能匹配的數(shù)量。
????????為了清楚表達,我模擬還原了一下這個實際情況。
????? ?一、實際問題模擬
? ? ? ? A是一份電子水果清單,為此我專門搜了水果大全......

????????同時為了模擬實際情況,我手抄了一份,以此作為磨損的數(shù)據(jù)。
??????手抄樣式

? ? ? ?

??????? ????????????QQ文字提?。?br>

? ? ? ? ? ? ? ? ? ? ?取出純漢字(好吧,我有點感嘆這個識別準(zhǔn)確率?。?/p>
????????????

? ? ? ? 最后問題轉(zhuǎn)化成了兩個電子版清單之間的匹配,更進一步說也就是兩個數(shù)組的匹配,落實到單個水果上,就是兩個水果漢字的比較,比如“葡萄”和“葡藺”這兩個字符串的比較和匹配。如下:

二、尋找算法
? ? ? ? 最近看了些關(guān)于數(shù)據(jù)分析的書,在講KNN最鄰近算法時候,有一個關(guān)鍵的概念--相似度的度量方法,比如有歐式距離(“直線距離”),曼哈頓距離(兩點在坐標(biāo)系上的相對距離之和),余弦相似度(兩點夾角余弦值),杰卡德相似系數(shù)(“交集”元素數(shù)量占“并集”元素數(shù)量的比例)。
? ? ? ? 本次就是“借鑒”杰卡德相似系數(shù)來解決問題,這個系數(shù)可以舉個例子來說明,比如兩個人在某寶上購物,甲買了10件,乙買了15件,而他們購買的相同商品有7件,那么他們的購物的相似度用杰卡德相似系數(shù)描述就是:甲乙都有購買的數(shù)量(交集-共同部分)為7件,甲乙購買的種類不一樣的數(shù)量之和為(10+15-7=)18件(并集-不重復(fù)合計數(shù)量),所以他們的相似度為7/18。根據(jù)實際情況可知,系數(shù)越大,相似度越大。
? ? ? ? 所以回到這次的問題,在A和B數(shù)據(jù)里面,完全可以將“葡萄”和“葡藺”這兩個字符串之間的相似度,用杰卡德相似系數(shù)來衡量,那么“葡萄”分解為["葡","萄"],掃描文字“葡藺”分解為["葡","藺"]。那么他們的共同部分為“葡”,為1個字符,不重復(fù)合計數(shù)量為3個字符["葡","萄","藺"]。結(jié)果杰卡德相似系數(shù)為1/3,也是他們的相似度。
? ? ? ? 但是我總感覺,純在此問題中搬這個概念,讓兩個字符的相似度“偏小”了,因為我總覺得他們“有1/2的相似度”,也就是一字之差。其實我自己的算法,并不是看了這個杰卡德相似系數(shù)而來,而是我感覺自己的方法要靠一個什么東西的話,那和這個系數(shù)最相近。
? ? ? ? 我使用的辦法基本和上面一樣,只是我的分母,取得是字符串較長的字符串長度(也就是字符的數(shù)量),因此上面的相似度,我計算的結(jié)果是1/2,對于匹配來說指導(dǎo)意義更大。
三、實現(xiàn)“一對多”的模糊匹配
? ? ? ??有了上面的算法基礎(chǔ),我想在A中實現(xiàn)某一個水果比如“哈密瓜”匹配到具有一定相似度的B中水果,按實際情況,因為B中有兩個“哈密瓜”,因此可以出現(xiàn)以下的匹配結(jié)果。

? ? ? ?但其實如果說只要有相似度就可以匹配的話,結(jié)果遠遠不止這兩個,因為西瓜和甜瓜,或者說只要有個瓜字的,都會匹配進來,因為他們有1/2或者1/3相似度。所以在匹配之前,應(yīng)該設(shè)定一個可接受的相似度,來進行過濾,比如最低要求50%的相似度。那匹配進來的“奇怪結(jié)果”就要少很多,或者叫做更加精確。又或者要求100%,那就是精確匹配,容不得一點雜質(zhì)。
????????具體實現(xiàn),算法核心就是相似度這個概念定義,在數(shù)據(jù)量不大的情況,直接暴力循環(huán),讓程序首先在A中選一個水果,然后逐個與B中水果計算相似度,超過指定相似度(比如50%)就可以入選,否則就丟棄,然后選擇A中的下一個,重復(fù)進行,直到A中水果都匹配完。
四、執(zhí)行結(jié)果
????????寫到這里,沒有寫python代碼的實現(xiàn)過程,這次主要想寫這個問題解決的一個思路,其實代碼也就是以上思路的一個“翻譯”而已,本次直接呈現(xiàn)結(jié)果如下,代碼以附件的方式放在了最后。
可接受相似度為100%時:(精確匹配,沒有模糊的結(jié)果出現(xiàn))

可接受相似度為50%時(此時“葡萄就可以模糊匹配出來”,出現(xiàn)小部分“噪聲”-甜瓜匹配結(jié)果中包含西瓜):

可接受相似度為30%時:(“噪聲”開始較多的出現(xiàn))

????????因此,從上面的結(jié)果可以看出,可接受的相似度放太大或者放太小,都不利于結(jié)果的最佳呈現(xiàn),比如此處,50%這個可接受相似度較100,30應(yīng)該是最佳的。當(dāng)然不同的問題,不同的數(shù)據(jù)集,類似的數(shù)據(jù)分析的參數(shù)調(diào)整不一樣,根據(jù)實際檢驗的結(jié)果,做出最佳的調(diào)整。
五、總結(jié)
? ? ? ??寫到這里,我看了下字數(shù)超過2000了,不曉得有人看到這里不。
????????其實,本次主要是借這個實際例子,說一些關(guān)于自己對數(shù)據(jù)分析的認識:
??????? 1、具體的問題,對應(yīng)著具體的算法;
??????? 2、實際問題的轉(zhuǎn)化,比如兩個事物的相似,最終轉(zhuǎn)化成數(shù)學(xué)上簡單的距離,數(shù)值,比例來進行衡量,當(dāng)然一般的模型過程都相對比較雜;
??????? 3、根據(jù)實際問題,調(diào)整相應(yīng)的參數(shù),讓結(jié)果最“契合”;
??????? 4、此次過程只是數(shù)據(jù)分析中的一部分,沒有做相應(yīng)的挖掘,只是數(shù)據(jù)分析、挖掘?qū)崿F(xiàn)算法的一些實現(xiàn)方式、思路。
????????最后,這個辦法可能不是解決這個問題的最好方式,如果有更好的想法,也誠懇希望介紹指導(dǎo)。
附:Python代碼實現(xiàn)
class Match(object):
? ?def __init__(self,stringA,stringB):
? ? ? ?if len(stringA) < len(stringB):
? ? ? ? ? ?self.min = stringA
? ? ? ? ? ?self.max = stringB
? ? ? ?else:
? ? ? ? ? ?self.max = stringA
? ? ? ? ? ?self.min = stringB
? ?def approximate_match(self):
? ? ? ?re = []
? ? ? ?desc = []
? ? ? ?for i in range(len(self.min)):
? ? ? ? ? ?for j in range(len(self.max)):
? ? ? ? ? ? ? ?r = self.min[i].find(self.max[j]) # 核心函數(shù)為find
? ? ? ? ? ? ? ?if r != -1:
? ? ? ? ? ? ? ? ? ?re.append(r)
? ? ? ? ? ? ? ? ? ?desc.append(self.max[j])
? ? ? ?match_ratio = len(re)/len(self.max)
? ? ? ?match_ratio = str(round(match_ratio*100,2))
? ? ? ?return match_ratio
? ?def exact_match(self):
? ? ? ?if self.a == self.b:
? ? ? ? ? ?return str(100)
? ? ? ?else:
? ? ? ? ? ?return str(0)
class Match_list(object):
? ?def __init__(self,item,data_list):
? ? ? ?self.item = item
? ? ? ?self.data_list = data_list
? ?def approximate_match(self,flag):
? ? ? ?item = self.item
? ? ? ?data_list = self.data_list
? ? ? ?temp = 0
? ? ? ?word = []
? ? ? ?print(item)
? ? ? ?for item2 in data_list:
? ? ? ? ? ?m = Match(str(item), str(item2))
? ? ? ? ? ?r = m.approximate_match()
? ? ? ? ? ?r = float(r)
? ? ? ? ? ?if r >= float(temp) and r != 0 and r >= flag:
? ? ? ? ? ? ? ?temp = r
? ? ? ? ? ? ? ?word.append(item2)
? ? ? ?print(item, word, temp)