最近工作上需要處理文本相似度的問題,一共5萬多個文檔森渐;
第一步做入,是先是要進(jìn)行顆粒度較粗的,發(fā)現(xiàn)基本相似的文檔同衣,進(jìn)行基本的 “聚類”竟块;
第二步,針對相似的文檔耐齐,然后進(jìn)行詳細(xì)的比較浪秘;
經(jīng)過調(diào)研,發(fā)現(xiàn)google的simhash是在顆粒度較粗的方面埠况,進(jìn)行文本相似度比較的較好的方案耸携;
一. 何為simhash
關(guān)于什么是simhash,網(wǎng)上學(xué)院派的介紹還是很多的辕翰,核心思想就是夺衍,對文本進(jìn)行分詞,并統(tǒng)計(jì)詞頻(相當(dāng)于權(quán)重)喜命,然后沟沙,進(jìn)行對每個詞進(jìn)行hash操作,并將結(jié)果按二進(jìn)制位乘以權(quán)重壁榕;然后將所有結(jié)果矛紫,按位相加,統(tǒng)計(jì)出文本的“指紋”护桦。
此處以一個例子進(jìn)行介紹更為形象:
- 假設(shè)含衔,需要進(jìn)行simhash的文本為:
“我很想要打游戲,但是女朋友會生氣!”
贪染; - 首先缓呛,就是對文本進(jìn)行分詞操作,使用結(jié)巴分詞的全分詞模式杭隙,結(jié)果為:
['我', '很', '想要', '打游戲', '游戲', '但是', '女朋友', '朋友', '會生', '生氣']
哟绊; - 然后,進(jìn)行詞頻統(tǒng)計(jì)痰憎,得到:
{'會生': 1, '但是': 1, '女朋友': 1, '很': 1, '想要': 1, '我': 1, '打游戲': 1, '朋友': 1, '游戲': 1, '生氣': 1}
; - 然后票髓,對以上每個詞進(jìn)行 HASH操作,比如md5铣耘,比如洽沟,
'會生'
的md5是f3ab426bf0c05aa49cd0903c31adcb38
,然后蜗细,以二進(jìn)制位的方式裆操,進(jìn)行處理,如果 bit == 1炉媒,則 為 1x1踪区;如果,bit == 0吊骤,則 為-1x1缎岗; - 計(jì)算完,每一個詞之后白粉,將所有的結(jié)果按位相加传泊,并生成唯一的HASH,結(jié)果為:
1459185014329402212
鸭巴;
二. python上的實(shí)現(xiàn)
首先或渤,python是有現(xiàn)成的simhash的包的,包名奕扣,就是這個名字薪鹦;
直接執(zhí)行pip install simhash
即可;
剛開始看惯豆,這是針對英文的池磁,所以,想去搜搜有沒有中文方面現(xiàn)成的楷兽,找了找沒有地熄,于是就去看看simhash的源碼,看看對中文的支持如何芯杀;
結(jié)果:simhash“表面”上對中文的支持不好端考,是因?yàn)樗闹形姆衷~是完全一個個字的分解雅潭;
但是,這完全不影響對simhash包的使用却特,simhash支持分詞完的列表作為輸入數(shù)據(jù)扶供,所以,完全可以使用jieba分詞之后裂明,在使用simhash進(jìn)行計(jì)算椿浓;
PS:可以看看simhash的實(shí)現(xiàn)代碼,很簡潔
三. 例子
例子就很簡單了:
import jieba
from simhash import Simhash
words1 = jieba.lcut('我很想要打游戲闽晦,但是女朋友會生氣扳碍!', cut_all=True)
words2 = jieba.lcut('我很想要打游戲,但是女朋友非常生氣仙蛉!', cut_all=True)
print(Simhash(words1).distance(Simhash(words2)))
#輸出:6笋敞,因?yàn)槎涛谋臼褂胹imhash的話,文字稍微有些改動荠瘪,還是挺明顯的液样,大家可以用長文本嘗試
四. simhash的核心源碼
# 說明:self.f 為simhash的長度;
# self.value 為當(dāng)前實(shí)例的simhash值巧还;
# self.hashfunc 為計(jì)算hash的函數(shù),默認(rèn)是md5坊秸;
# 計(jì)算文本的hash值
def build_by_features(self, features):
"""
`features` might be a list of unweighted tokens (a weight of 1
will be assumed), a list of (token, weight) tuples or
a token -> weight dict.
"""
v = [0] * self.f
masks = [1 << i for i in range(self.f)] #生成從1位到f位的mashs值麸祷,用于每個位的匹配操作
if isinstance(features, dict):
features = features.items()
# h是計(jì)算的hash值, w是權(quán)重(詞頻)
for f in features:
if isinstance(f, basestring):
h = self.hashfunc(f.encode('utf-8'))
w = 1
else:
assert isinstance(f, collections.Iterable)
h = self.hashfunc(f[0].encode('utf-8'))
w = f[1]
for i in range(self.f):
v[i] += w if h & masks[i] else -w # 位操作褒搔,位值為1阶牍,則為w,位值為0星瘾,則為-w走孽;
ans = 0
for i in range(self.f):
if v[i] > 0:
ans |= masks[i] # 合并所有計(jì)算結(jié)果
self.value = ans
# 計(jì)算兩個hash值得距離
def distance(self, another):
assert self.f == another.f
x = (self.value ^ another.value) & ((1 << self.f) - 1)
ans = 0
while x:
ans += 1
x &= x - 1
return ans