今天看到一篇關(guān)于判斷兩句話相似的例子和推薦電影的例子,都用到了cosa這種余弦函數(shù)。
本篇非原創(chuàng)是整理:
原貼
一、基礎(chǔ)知識
聽起來很高深,其實說起來原理簡單,先看個高中的知識:

求圖中a和b線夾角的cos值,這個值怎么求那,如果在b的邊上做個垂直線x,那么這個角cos計算公式如下:cosA = y/a
計算下:

還是同事推導(dǎo)的,我竟然忘記了,汗顏。
得出一個余弦計算公式:

在坐標(biāo)系中的計算公式如下:
在直角坐標(biāo)系中,向量表示的三角形的余弦函數(shù)是怎么樣的呢?下圖中向量a用坐標(biāo)(x1,y1)表示,向量b用坐標(biāo)(x2,y2)表示。

向量a和向量b在直角坐標(biāo)中的長度為

,向量a和向量b之間的距離我們用向量c表示,就是上圖中的黃色直線,那么向量c在直角坐標(biāo)系中的長度為

,將a,b,c帶入三角函數(shù)的公式中得到如下的公式:

這是2維空間中余弦函數(shù)的公式,那么多維空間余弦函數(shù)的公式就是:

二、余弦公式有什么用
空間中兩個點的距離可以通過余弦來表示,如果余弦值越小,那么角度越大,兩個點表示的相似度越低,越接近于1,則越接近。
假設(shè)有3個物品,item1,item2和item3,用向量表示分別為:
item1[1,1,0,0,1],
item2[0,0,1,2,1],
item3[0,0,1,2,0],
即五維空間中的3個點。用歐式距離公式計算item1、itme2之間的距離,以及item2和item3之間的距離,分別是:
item1-item2=

item2-item3=

用余弦函數(shù)計算item1和item2夾角間的余弦值為:

用余弦函數(shù)計算item2和item3夾角間的余弦值為:

由此可得出item1和item2相似度小,兩個之間的距離大(距離為7),item2和itme3相似度大,兩者之間的距離小(距離為1)。
余弦相似度算法:一個向量空間中兩個向量夾角間的余弦值作為衡量兩個個體之間差異的大小,余弦值接近1,夾角趨于0,表明兩個向量越相似,余弦值接近于0,夾角趨于90度,表明兩個向量越不相似。
三、計算兩個句子的相似度
余弦相似度量:計算個體間的相似度。
相似度越小,距離越大。相似度越大,距離越小。
余弦相似度算法:一個向量空間中兩個向量夾角間的余弦值作為衡量兩個個體之間差異的大小,余弦值接近1,夾角趨于0,表明兩個向量越相似,余弦值接近于0,夾角趨于90度,表明兩個向量越不相似。
下面我們介紹使用余弦相似度計算兩段文本的相似度。思路:1、分詞;2、列出所有詞;3、分詞編碼;4、詞頻向量化;5、套用余弦函數(shù)計量兩個句子的相似度。
句子A:這只皮靴號碼大了。那只號碼合適。
句子B:這只皮靴號碼不小,那只更合適。
3.1 分詞
使用結(jié)巴分詞對上面兩個句子分詞后,分別得到兩個列表:
listA=[‘這‘, ‘只‘, ‘皮靴‘, ‘號碼‘, ‘大‘, ‘了‘, ‘那‘, ‘只‘, ‘號碼‘, ‘合適‘]
listB=[‘這‘, ‘只‘, ‘皮靴‘, ‘號碼‘, ‘不小‘, ‘那‘, ‘只‘, ‘更合‘, ‘合適‘]
3.2、列出所有詞。
將listA和listB放在一個set中,得到:
set={'不小', '了', '合適', '那', '只', '皮靴', '更合', '號碼', '這', '大'}
將上述set轉(zhuǎn)換為dict,key為set中的詞,value為set中詞出現(xiàn)的位置,即‘這’:1這樣的形式。
dict1={'不小': 0, '了': 1, '合適': 2, '那': 3, '只': 4, '皮靴': 5, '更合': 6, '號碼': 7, '這': 8, '大': 9},可以看出“不小”這個詞在set中排第1,下標(biāo)為0。
3.3 將listA和listB進(jìn)行編碼。
將每個字轉(zhuǎn)換為出現(xiàn)在set中的位置,轉(zhuǎn)換后為:
listAcode=[8, 4, 5, 7, 9, 1, 3, 4, 7, 2]
listBcode=[8, 4, 5, 7, 0, 3, 4, 6, 2]
我們來分析listAcode,結(jié)合dict1,可以看到8對應(yīng)的字是“這”,4對應(yīng)的字是“只”,9對應(yīng)的字是“大”,就是句子A和句子B轉(zhuǎn)換為用數(shù)字來表示。
其實這個在后面的例子中沒有用。
3.4 對listA code和listB code進(jìn)行oneHot編碼。
就是計算每個分詞出現(xiàn)的次數(shù)。oneHot編號后得到的結(jié)果如下:
listAcodeOneHot = [0, 1, 1, 1, 2, 1, 0, 2, 1, 1]
listBcodeOneHot = [1, 0, 1, 1, 2, 1, 1, 1, 1, 0]
注意這個list的總長度為兩句話所有詞語的總長度。
下圖總結(jié)了句子從分詞,列出所有詞,對分詞進(jìn)行編碼,計算詞頻的過程:

5、得出兩個句子的詞頻向量之后,就變成了計算兩個向量之間夾角的余弦值,值越大相似度越高。
listAcodeOneHot = [0, 1, 1, 1, 2, 1, 0, 2, 1, 1]
listBcodeOneHot = [1, 0, 1, 1, 2, 1, 1, 1, 1, 0]

看下代碼:
#! /usr/bin/python
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
#pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jieba
'''
清華:https://pypi.tuna.tsinghua.edu.cn/simple
阿里云:http://mirrors.aliyun.com/pypi/simple/
中國科技大學(xué) https://pypi.mirrors.ustc.edu.cn/simple/
華中理工大學(xué):http://pypi.hustunique.com/
山東理工大學(xué):http://pypi.sdutlinux.org/
豆瓣:http://pypi.douban.com/simple/
'''
import jieba
import math
s1 = '這只皮靴號碼大了。那只號碼合適'
s1_cut = [i for i in jieba.cut(s1, cut_all=True) if i != '']
s2 = '這只皮靴號碼不小,那只更合適'
s2_cut = [i for i in jieba.cut(s2, cut_all=True) if i != '']
print(s1_cut)
print(s2_cut)
word_set = set(s1_cut).union(set(s2_cut))
for a in word_set:
print a
print(word_set)
word_dict = dict()
i = 0
for word in word_set:
word_dict[word] = i
i += 1
print(word_dict)
'''
s1_cut_code = [word_dict[word] for word in s1_cut]
print(s1_cut_code)
'''
s1_cut_code = [0]*len(word_dict)
print(s1_cut_code)
for word in s1_cut:
s1_cut_code[word_dict[word]]+=1
print(s1_cut_code)
'''
s2_cut_code = [word_dict[word] for word in s2_cut]
print(s2_cut_code)
'''
s2_cut_code = [0]*len(word_dict)
for word in s2_cut:
s2_cut_code[word_dict[word]]+=1
print(s2_cut_code)
# 計算余弦相似度
sum = 0
sq1 = 0
sq2 = 0
for i in range(len(s1_cut_code)):
sum += s1_cut_code[i] * s2_cut_code[i]
sq1 += pow(s1_cut_code[i], 2)
sq2 += pow(s2_cut_code[i], 2)
try:
result = round(float(sum) / (math.sqrt(sq1) * math.sqrt(sq2)), 2)
except ZeroDivisionError:
result = 0.0
print(result)
輸出:
[u'\u8fd9', u'\u53ea', u'\u76ae\u9774', u'\u53f7\u7801', u'\u5927', u'\u4e86', u'\u90a3', u'\u53ea', u'\u53f7\u7801', u'\u5408\u9002']
[u'\u8fd9', u'\u53ea', u'\u76ae\u9774', u'\u53f7\u7801', u'\u4e0d\u5c0f', u'\u90a3', u'\u53ea', u'\u66f4\u5408', u'\u5408\u9002']
那
大
號碼
了
不小
只
這
更合
合適
皮靴
set([u'\u90a3', u'\u5927', u'\u53f7\u7801', u'\u4e86', u'\u4e0d\u5c0f', u'\u53ea', u'\u8fd9', u'\u66f4\u5408', u'\u5408\u9002', u'\u76ae\u9774'])
{u'\u90a3': 0, u'\u5927': 1, u'\u53f7\u7801': 2, u'\u4e86': 3, u'\u4e0d\u5c0f': 4, u'\u53ea': 5, u'\u5408\u9002': 8, u'\u66f4\u5408': 7, u'\u8fd9': 6, u'\u76ae\u9774': 9}
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 2, 1, 0, 2, 1, 0, 1, 1]
[1, 0, 1, 0, 1, 2, 1, 1, 1, 1]
0.81