SegmentFault 上不少做者都喜欢写一系列博客,可是并无很好的归类,就作了一个标题类似度匹配出一系列的文章,相关原理都是 Google 的,这里稍微纪录一下本身从中学到的东西。php
以前也没作过天然语言处理相关的东西,因此开始一头雾水,查了一下文本类似度匹配,发现解决方案还不少,就开始尝试了。第一次使用了一个叫 LD 的算法:python
LD算法(Levenshtein Distance)又成为编辑距离算法(Edit Distance)。他是以字符串A经过插入字符、删除字符、替换字符变成另外一个字符串B,那么操做的过程的次数表示两个字符串的差别。算法
参照网上的代码弄出来以后发现匹配效果不佳,另外运行效率真也很差,最后仍是放弃了;数据结构
另外就是这里的匹配出结果以后发现一个问题就是全部的输出都是两两相配的,并无归类起来,而后就在这里卡了很久。app
贴代码:机器学习
import time from numpy import * def strcmp(s, t): if len(s) > len(t): s, t = t, s n = len(s) m = len(t) if not m : return n if not n : return m v0 = [ i for i in range(0, m+1) ] v1 = [ 0 ] * (m+1) cost = 0 for i in range(1, n+1): v1[0] = i for j in range(1, m+1): if s[i-1] == t[j-1]: cost = 0 else: cost = 1 a = v0[j] + 1 b = v1[j-1] + 1 c = v0[j-1] + cost v1[j] = min(a, b, c) v0 = v1[:] return v1[m]
又去查了一下,学习到了机器学习总的聚类和分类的概念,以前尝试过 KNN,虽然按照教程搞出来的,可是不是很理解什么是监督学习和非监督学习,此次算是完全搞懂了,因此无论看起来多难的东西,只要去尝试作,必定会有收获的。函数
这里有了解了 k-means 聚类算法,是一种非监督学习算法,其实原理至关简单:学习
随机抽取 k 个中心簇。this
计算每一个元素与上面中心簇的距离,取最小的归为一类。.net
再次计算上面新产生的一类的中心簇,更新起坐标。
经过这样一个循环,就能够吧一些数据作出分类了。
觉得上面的 LD 算法只是拿字来作向量,因此多是匹配效果不佳的缘由,因此选择使用词来作向量,第一步就是要分词了,原本打算用别人的分词库的,可是想一想以为这么点功能不值得,就直接用最简单的不基于语料的分词,直接以两个字符为单位进行分词,尝试了一下发现效果还蛮不错的。贴代码:
preg_match_all("/.{2}/u", $string, $splited_str);
嗯,你没看错,就只有一行,PHP 的字符串函数仍是真多的,参考的示例时用python实现的,相比之下分词函数就没这么简洁了:
def splitContents(content,k=5): content_split=[] for i in range(len(content)-k): content_split.append(content[i:i+k]) return content_split
这么看来 PHP 还真是世界上最好的语言,虽然我这开始写分词的时候喷了它一下,主要是由于开始用的str_split函数,分词以后发现中文全是乱码,这已经不记得是第多少次使用php字符串函数以后中文乱码的问题了,可能主要是由于前期php不支持unicode的缘由吧,不过如今的mb_前缀的一系列函数貌似不会出乱码了,因此果断放弃原来的那套字符串函数吧。
两两分词以后就要统计一下词频,下面分别是php的代码和python的代码,感觉下:
php
$hash_content = array_count_values($array);
python
def hashContentsList(content_list): hash_content={} for i in content_list: if i in hash_content: hash_content[i]=hash_content[i]+1 else: hash_content[i]=1 return hash_content
原理参考:文本类似度计算-JaccardSimilarity和哈希签名函数
这里就直接上php代码了,不在赘述原理:
Update: 这里的 calcIntersection
函数返回值实际上是两个集合交集的词频之和,calcUnionSet
函数同理。
// 主函数 function calcEachSimilar($hash_contents) { $similar_list = array(); $all = (float)count($hash_contents); $pos = 0.0; foreach ($hash_contents as $key1 => $value1) { $pos = $pos + 1; foreach ($hash_contents as $key2 => $value2) { if($value1[1] != $value2[1] and $key1 > $key2){ $intersection = $this -> calcIntersection($value1[0], $value2[0]); #计算交集 $union_set = $this -> calcUnionSet($value1[0], $value2[0], $intersection); #计算并集 $similar = $this -> calcSimilarity($intersection, $union_set); array_push($similar_list, array($similar, $value1[1], $value2[1])); } } } rsort($similar_list); return $similar_list; } // 计算交集 function calcIntersection($hash_a, $hash_b){ $intersection = 0; if(count($hash_a) <= count($hash_b)){ $hash_min = $hash_a; $hash_max = $hash_b; } else { $hash_min = $hash_b; $hash_max = $hash_a; } foreach ($hash_min as $key => $value) { if(array_key_exists($key, $hash_max)){ if($value <= $hash_max[$key]){ $intersection = $intersection + $value; } else { $intersection = $intersection + $hash_max[$key]; } } } return $intersection; } // 计算并集 function calcUnionSet($hash_a, $hash_b, $intersection) { $union_set = 0; foreach ($hash_a as $key => $value) { $union_set = $union_set + $value; } foreach ($hash_b as $key => $value) { $union_set = $union_set + $value; } return $union_set - $intersection; } // 计算类似度 function calcSimilarity($intersection, $union_set){ if($union_set > 0){ return (float)$intersection/(float)$union_set; } else { return 0.0; } }
固然这样匹配出来的结果是两两一块儿的,并无把类似的分类,因此要分类的话还须要进行下一步,不过我php实现时已经知道了分类中的其中一个元素,因此只须要拿他去和其余的匹配就行了,就没有用php写,却是用pyhton实现了一个版本:
其实原理很简单,就是最简单的数据结构:并查集:
class unionfind: def __init__(self, groups): self.groups=groups self.items=[] for g in groups: self.items+=list(g) self.items=set(self.items) self.parent={} self.rootdict={} #记住每一个root下节点的数量 for item in self.items: self.rootdict[item]=1 self.parent[item]=item def union(self, r1, r2): rr1=self.findroot(r1) rr2=self.findroot(r2) cr1=self.rootdict[rr1] cr2=self.rootdict[rr2] if cr1>=cr2: #将节点数量较小的树归并给节点数更大的树 self.parent[rr2]=rr1 self.rootdict.pop(rr2) self.rootdict[rr1]=cr1+cr2 else: self.parent[rr1]=rr2 self.rootdict.pop(rr1) self.rootdict[rr2]=cr1+cr2 def findroot(self, r): if r in self.rootdict.keys(): return r else: return self.findroot(self.parent[r]) def createtree(self): for g in self.groups: if len(g)< 2: continue else: for i in range(0, len(g)-1): if self.findroot(g[i]) != self.findroot(g[i+1]): #若是处于同一个集合的节点有不一样的根节点,归并之 self.union(g[i], g[i+1]) def printree(self): rs={} for item in self.items: root=self.findroot(item) rs.setdefault(root,[]) rs[root]+=[item] for key in rs.keys(): # print rs[key], for t in rs[key]: print t print "============\n" ##### 调用 u=unionfind(class_contents) u.createtree() u.printree()
代码也是参考网上的,具体哪里的懒得去找了,做者要是看到了须要我加参考连接的联系我好了。
总之,历来没正经学过算法的人只能可耻的到网上找解决方案了,嗯,以后会努力学习算法了,但愿不要被喷。