1.前言
近期热播的《长安十二时辰》中大案牍术推荐出的不良帅、网易云音乐的每日歌曲推荐,这些生活中看似神乎其技的方法,用一个数公式便可简单实现。
2.欧几里得空间
2.1 简介
大名鼎鼎的欧几里得在他大名鼎鼎的著作《几何原本》中:
通过几何(即欧氏几何,从小学到高中,我们学习的就是欧氏几何)来描述、研究这个世界:
在几何世界中,世界上的所有东西都可以用几何来表示,比如可以把世界上每个人表示为一串特征向量,这个特征向量记录了这个人的性格,外貌,经历,职业,年龄…等等数据,然后根据这些数据,只需一个公式便可找出与你相似的人。是不是很熟悉——大案牍术
假设AB是俩个人,a1,a2,b1,b2分别是这俩个人的年龄与性格,那么在直角坐标系可如此表示,θ的角度越小说明你们俩个人的相似度越高(至于这个公式怎么计算出来的,老弟,回去看课本吧)
那么当维度不在二维,扩展到三维,四维,一百维呢?这个公式仍然是有效的
中国有句古话,“有缘千里来相会,无缘对面手难牵”:
这句话有两种不一样的距离:
- 千里、对面:直线距离,也叫欧氏距离,也就是刚才说的长度
- 有缘、无缘:余弦距离,也就是刚才说的角度
2.2 欧式距离
比如,求a与b的欧氏距离:
我们手机信号会随着与基站距离的增大而衰减,这里的距离就是指的欧氏距离:
在多维空间中,要知道两个点的距离的远近,就可以用欧式距离。
2.3 余弦距离
“有缘”、“无缘”,翻译成大白话,就是关系是否紧密。描述关系紧密的就是余弦距离。
从几何上看,两个向量夹角几乎为0时,非常相似;
当然夹角为0的时候,两者关系最为紧密,此时余弦值为1。而相互垂直时,余弦值为0,相关性最差,或称相互独立
3.应用
正因为余弦距离解释了事物的相关性,所以有非常多的应用。比如在数据挖掘中,很多时候通过比较用户数据的余弦距离,以判断用户的相似性。
下面是某书评网站,用户对一些书籍进行了相应的评分:
那么第一个用户信息可以用向量来表示。
第二个的用户信息可以用向量来表示。
则它们的相似性就可以用余弦距离来表示:
带入数据,结果保留到小数点后两位:
以此类推,我们就可以做出如下表格,表明各个用户的相似性:
但是这有一个问题,比如第一个用户喜好假如是:
也就是说他对所有的书评分都为1。
而第二个用户的喜好是:
也就是说他对所有的书评分都为5。
按照我们的直觉,这两个人喜好是很不相同,但是:
余弦距离居然表明两者的喜好是相同的。
我们来改进一下:
- 5分,表示很喜欢,实际值为2
- 4分,表示喜欢,实际值为1
- 3分,表示中性态度,实际值为0
- 2分,表示讨厌,实际值为-1
- 1分,表示很讨厌,实际值为-2
- 不打分,默认实际值为0
因此,第一个用户喜好的实际值为:
而第二个用户喜好的实际值为:
结果:
-1表示两人的爱恨是相反的。
-1,也就是相反的爱恨不代表不相关,我们可以这么来看,比如我们知道第一个用户和第二个用户的余弦距离为-1,那么第一个用户喜欢的就不要推荐给第二个用户,第一个用户讨厌的可以推荐给第二个用户,所以实际两人是相关的,而且还非常相关。——网易云音乐推荐
有句话怎么说来着:
爱的相反词,不是恨,是冷漠。
4.代码
纸上得来终觉浅,下面我们简单实现一个特征向量的余弦,并思考一下特征向量该如何抓取:
url理解为上面的每一个人,url上的每一个字符都是这个人的特征向量中的一位数——万物皆几何
#coding=utf-8
'''
维度
'''
dim = 256
'''
将字符串转化为特征向量后,计算字符串之间的余弦相似度
'''
def turn_num(str):
'''
转化为特征向量
:param str: str
:return: str的特征向量
'''
str = list("".join([j for j in str]))
for i in range(0, len(str)):
str[i] = ord(str[i]) #ASCII
for i in range(len(str), dim):#url不够dim位数补0特征向量
str.append(0)
return str
def cos(vector1,vector2):
'''
余弦相似度计算
:param vector1: url1
:param vector2: url2
: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) #余弦距离
def compare(str,strOther):
str = turn_num(str)
strOther = turn_num(strOther)
relate = cos(str, strOther)
return relate
if __name__ == '__main__':
str = 'http://www.baidu.com/detail.html?id=35047'
strOther = 'http://www.baidu.com/detail.html?id=312312&aa=11'
# str = '你好特普朗'
# strOther = '你好特普朗翻翻'
print(compare(str,strOther))
对于特征向量的抓取是比较麻烦的,会涉及到 特征工程:特征获取、特征处理、特征监控…
而上面的代码简单实现了特征向量的处理与使用,但是本质是不变的。