余弦計算相似度度量
相似度度量(Similarity),即計算個體間的相似程度,相似度度量的值越小,說明個體間相似度越小,相似度的值越大說明個體差異越大。
對於多個不同的文本或者短文本對話消息要來計算他們之間的相似度如何,一個好的做法就是將這些文本中詞語,映射到向量空間,形成文本中文字和向量數據的映射關系,通過計算幾個或者多個不同的向量的差異的大小,來計算文本的相似度。下面介紹一個詳細成熟的向量空間余弦相似度方法計算相似度
向量空間余弦相似度(Cosine Similarity)
余弦相似度用向量空間中兩個向量夾角的余弦值作為衡量兩個個體間差異的大小。余弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,這就叫"余弦相似性"。
上圖兩個向量a,b的夾角很小可以說a向量和b向量有很高的的相似性,極端情況下,a和b向量完全重合。如下圖:
如上圖二:可以認為a和b向量是相等的,也即a,b向量代表的文本是完全相似的,或者說是相等的。如果a和b向量夾角較大,或者反方向。如下圖
如上圖三: 兩個向量a,b的夾角很大可以說a向量和b向量有很底的的相似性,或者說a和b向量代表的文本基本不相似。那么是否可以用兩個向量的夾角大小的函數值來計算個體的相似度呢?
向量空間余弦相似度理論就是基於上述來計算個體相似度的一種方法。下面做詳細的推理過程分析。
想到余弦公式,最基本計算方法就是初中的最簡單的計算公式,計算夾角
圖(4)
的余弦定值公式為:
但是這個是只適用於直角三角形的,而在非直角三角形中,余弦定理的公式是
圖(5)
三角形中邊a和b的夾角 的余弦計算公式為:
在向量表示的三角形中,假設a向量是(x1, y1),b向量是(x2, y2),那么可以將余弦定理改寫成下面的形式:
向量a和向量b的夾角 的余弦計算如下
擴展,如果向量a和b不是二維而是n維,上述余弦的計算法仍然正確。假定a和b是兩個n維向量,a是 ,b是 ,則a與b的夾角 的余弦等於:
余弦值越接近1,就表明夾角越接近0度,也就是兩個向量越相似,夾角等於0,即兩個向量相等,這就叫"余弦相似性"。
【下面舉一個例子,來說明余弦計算文本相似度】
舉一個例子來說明,用上述理論計算文本的相似性。為了簡單起見,先從句子着手。
句子A:這只皮靴號碼大了。那只號碼合適
句子B:這只皮靴號碼不小,那只更合適
怎樣計算上面兩句話的相似程度?
基本思路是:如果這兩句話的用詞越相似,它們的內容就應該越相似。因此,可以從詞頻入手,計算它們的相似程度。
第一步,分詞。
句子A:這只/皮靴/號碼/大了。那只/號碼/合適。
句子B:這只/皮靴/號碼/不/小,那只/更/合適。
第二步,列出所有的詞。
這只,皮靴,號碼,大了。那只,合適,不,小,很
第三步,計算詞頻。
句子A:這只1,皮靴1,號碼2,大了1。那只1,合適1,不0,小0,更0
句子B:這只1,皮靴1,號碼1,大了0。那只1,合適1,不1,小1,更1
第四步,寫出詞頻向量。
句子A:(1,1,2,1,1,1,0,0,0)
句子B:(1,1,1,0,1,1,1,1,1)
到這里,問題就變成了如何計算這兩個向量的相似程度。我們可以把它們想象成空間中的兩條線段,都是從原點([0, 0, ...])出發,指向不同的方向。兩條線段之間形成一個夾角,如果夾角為0度,意味着方向相同、線段重合,這是表示兩個向量代表的文本完全相等;如果夾角為90度,意味着形成直角,方向完全不相似;如果夾角為180度,意味着方向正好相反。因此,我們可以通過夾角的大小,來判斷向量的相似程度。夾角越小,就代表越相似。
使用上面的公式(4)
計算兩個句子向量
句子A:(1,1,2,1,1,1,0,0,0)
和句子B:(1,1,1,0,1,1,1,1,1)的向量余弦值來確定兩個句子的相似度。
計算過程如下:
計算結果中夾角的余弦值為0.81非常接近於1,所以,上面的句子A和句子B是基本相似的
由此,我們就得到了文本相似度計算的處理流程是:
-
找出兩篇文章的關鍵詞;
-
每篇文章各取出若干個關鍵詞,合並成一個集合,計算每篇文章對於這個集合中的詞的詞頻
-
生成兩篇文章各自的詞頻向量;
-
計算兩個向量的余弦相似度,值越大就表示越相似。
python實現

def cosin_distance(vector1, vector2): """ K(X, Y) = <X, Y> / (||X||*||Y||) :param vector1: :param vector2: :return: """ dot_product = 0.0 normA = 0.0 normB = 0.0 for a, b in zip(vector1, vector2): dot_product += a * b normA += a ** 2 normB += b ** 2 if normA == 0.0 or normB == 0.0: return None else: return dot_product / ((normA * normB) ** 0.5)

import numpy as np from sklearn.metrics.pairwise import cosine_similarity user_tag_matric = np.matrix(np.array([vect1, vect2])) user_similarity = cosine_similarity(user_tag_matric) print(user_similarity)

import functools import math import re import time text1 = "This game is one of the very best. games ive played. the ;pictures? " \ "cant descripe the real graphics in the game." text2 = "this game have/ is3 one of the very best. games ive played. the ;pictures? " \ "cant descriPe now the real graphics in the game." text3 = "So in the picture i saw a nice size detailed metal puzzle. Eager to try since I enjoy 3d wood puzzles, i ordered it. Well to my disappointment I got in the mail a small square about 4 inches around. And to add more disappointment when I built it it was smaller than the palm of my hand. For the price it should of been much much larger. Don't be fooled. It's only worth $5.00.Update 4/15/2013I have bought and completed 13 of these MODELS from A.C. Moore for $5.99 a piece, so i stand by my comment that thiss one is overpriced. It was still fun to build just like all the others from the maker of this brand.Just be warned, They are small." text4 = "I love it when an author can bring you into their made up world and make you feel like a friend, confidant, or family. Having a special child of my own I could relate to the teacher and her madcap class. I've also spent time in similar classrooms and enjoyed the uniqueness of each and every child. Her story drew me into their world and had me laughing so hard my family thought I had lost my mind, so I shared the passage so they could laugh with me. Read this book if you enjoy a book with strong women, you won't regret it." def timeit(func): @functools.wraps(func) def wrap(*args, **kwargs): start = time.time() res = func(*args, **kwargs) print('運行時間為: {0:.4f}' .format(time.time() - start)) return res return wrap def preprocess(text): """ 文本預處理,可根據具體情況書寫邏輯 :param text: :return: """ return text.split() @timeit def compute_cosine(words1, words2): """ 計算兩段文本的余弦相似度 :param text_a: :param text_b: :return: """ # 1. 統計詞頻 words1_dict = {} words2_dict = {} for word in words1: # word = word.strip(",.?!;") word = re.sub('[^a-zA-Z]', '', word) word = word.lower() # print(word) if word != '' and word in words1_dict: num = words1_dict[word] words1_dict[word] = num + 1 elif word != '': words1_dict[word] = 1 else: continue for word in words2: # word = word.strip(",.?!;") word = re.sub('[^a-zA-Z]', '', word) word = word.lower() if word != '' and word in words2_dict: num = words2_dict[word] words2_dict[word] = num + 1 elif word != '': words2_dict[word] = 1 else: continue # nltk統計詞頻 # 方式一 # words1_dict2 = FreqDist(words1) # 方式二 # from collections import Counter # Counter(text1.split()) # print(words1_dict) # print(words2_dict) # 2. 按照頻率排序 dic1 = sorted(words1_dict.items(), key=lambda x: x[1], reverse=True) dic2 = sorted(words2_dict.items(), key=lambda x: x[1], reverse=True) # print(dic1) # print(dic2) # 3. 得到詞向量 words_key = [] list(map(lambda x: words_key.append(x[0]), dic1)) list(map(lambda x: words_key.append(x[0]), filter(lambda x: x[0] not in words_key, dic2))) # print(words_key) vect1 = [] vect2 = [] for word in words_key: if word in words1_dict: vect1.append(words1_dict[word]) else: vect1.append(0) if word in words2_dict: vect2.append(words2_dict[word]) else: vect2.append(0) # print(vect1) # print(vect2) # 4. 計算余弦相似度 sum = 0 sq1 = 0 sq2 = 0 for i in range(len(vect1)): sum += vect1[i] * vect2[i] sq1 += pow(vect1[i], 2) sq2 += pow(vect2[i], 2) try: result = round(float(sum) / (math.sqrt(sq1) * math.sqrt(sq2)), 2) except ZeroDivisionError: result = 0.0 # skleran實現 import numpy as np # from sklearn.metrics.pairwise import cosine_similarity # user_tag_matric = np.matrix(np.array([vect1, vect2])) # user_similarity = cosine_similarity(user_tag_matric) # print(user_similarity) return result def cosin_distance(vector1, vector2): """ K(X, Y) = <X, Y> / (||X||*||Y||) :param vector1: :param vector2: :return: """ dot_product = 0.0 normA = 0.0 normB = 0.0 for a, b in zip(vector1, vector2): dot_product += a * b normA += a ** 2 normB += b ** 2 if normA == 0.0 or normB == 0.0: return None else: return dot_product / ((normA * normB) ** 0.5) if __name__ == '__main__': text1 = preprocess(text1) text2 = preprocess(text2) print(compute_cosine(text1, text2))