從零開始寫一個(gè)推薦系統(tǒng)第一篇,誰和你相似

我們在電商網(wǎng)站上購買一件商品后,網(wǎng)站總是會提醒我們,購買這件商品的用戶還購買了哪些商品,網(wǎng)站會猜出你可能會購買的商品。網(wǎng)易云音樂有一個(gè)每日推薦功能,會根據(jù)你的個(gè)人喜好為你推薦一批歌曲。這些推薦并不是隨機(jī)的,而是經(jīng)過一系列復(fù)雜的計(jì)算生成的。
網(wǎng)站背后的算法是非常復(fù)雜的,但可以總結(jié)為一個(gè)簡單的目標(biāo):找到和你相似的人,把這個(gè)人喜歡的東西推薦給你。
那么誰和你相似呢?這又是一個(gè)復(fù)雜的問題,為了找到和你相似的人,必須將人的行為進(jìn)行量化,所謂量化,就是用數(shù)學(xué)來描述人的行為。假設(shè)有4部電影,分別是霸王別姬,雷神,大話西游,戰(zhàn)狼。你,小明,小紅三個(gè)人對這4部電影的評分如下

想要找到和你觀影品味相似的人,需要對你們的行為進(jìn)行量化,所得到的的是一份對電影評分的明細(xì)表,這里記錄了你們?nèi)齻€(gè)人對這4部電影的評分。
接下來,我們需要一種方法,通過這些評分來計(jì)算出誰和你最相似。不要想當(dāng)然的通過人為觀察數(shù)據(jù)來判斷,這樣做是沒有說服力的。
我們有兩種算法可以拿來使用:
歐幾里得距離
皮爾遜相關(guān)度
我們從一個(gè)簡單的例子出發(fā)來講解歐幾里得距離是怎么一回事。你,小明,小紅三個(gè)人是同班同學(xué),這次考試,你們的語文成績分別是90, 95, 88分,誰和你最相似呢?
小明和的分?jǐn)?shù)相差了5分,而小紅和你的分?jǐn)?shù)相差了2分,小紅和你最相似。但考試不只考語文,還有數(shù)學(xué),分?jǐn)?shù)情況如下

加上數(shù)學(xué)分?jǐn)?shù)后,誰和你最相似呢?這里不能用總分來描述學(xué)習(xí)能力的相似,學(xué)習(xí)能力是由多個(gè)科目綜合作用的結(jié)果,有可能你和某個(gè)人的總分是一樣的,但是你數(shù)學(xué)0分,語文100,而他數(shù)學(xué)100,語文0分,盡管總分一樣,但你們的學(xué)習(xí)能力明顯相差巨大。我們可以在直角坐標(biāo)系里把你們?nèi)齻€(gè)人的分?jǐn)?shù)表示出來,語文做x軸,數(shù)學(xué)做y軸。我用plotly 在jupyter里做了一張圖
import plotlyimport plotly.offline as pypy.init_notebook_mode(connected=False)import plotly.graph_objs as godataset = {'語文': [90, 95, 88],'數(shù)學(xué)': [95, 94, 90],}data_g = []data = go.Scatter(x=dataset['語文'],y=dataset['數(shù)學(xué)'],text = ['你(90, 95)', '小明(95, 94)', '小紅(88, 90)'],mode='markers+text',textposition="top right",name='考試成績')data_g.append(data)layout = go.Layout(title="考試成績",xaxis={'title': '語文'}, yaxis={'title': '數(shù)學(xué)'})fig = go.Figure(data=data_g, layout=layout)fig.show()
生成的成績圖如下

到底是紅色線段更短還是青色的線段更短呢?距離越近,說明兩個(gè)人學(xué)習(xí)能力越相似。這個(gè)問題已經(jīng)被轉(zhuǎn)換成計(jì)算平面內(nèi)兩個(gè)點(diǎn)的距離,公式如下

用python計(jì)算你和小紅的距離
import mathdistance = math.sqrt(pow(90 - 88, 2) + pow(95 - 90, 2))print(distance) # 5.385164807134504
計(jì)算你和小明的距離
import mathdistance = math.sqrt(pow(90 - 95, 2) + pow(95 - 94, 2))print(distance) # 5.0990195135927845
顯然,你和小明的距離小于小紅,小明的學(xué)習(xí)能力和你更為相似。
除了語文,數(shù)學(xué),還會有英語

現(xiàn)在有3個(gè)科目,3個(gè)維度,已經(jīng)不能在二維平面上把這3個(gè)人的數(shù)據(jù)畫出來了,想要畫出來,需要x,y,z三個(gè)軸,但再增加一個(gè)科目呢?不管有多少科目,多個(gè)維度,計(jì)算他們之間距離的方法是不變的
import math# 你和小明的距離distance1 = math.sqrt(pow(90 - 95, 2) + pow(95 - 94, 2) + pow(98 - 94, 2))print(distance1) # 6.48074069840786# 你和小紅的距離distance1 = math.sqrt(pow(90 - 88, 2) + pow(95 - 90, 2) + pow(98 - 97, 2))print(distance1) # 5.477225575051661
加上英語,小紅的學(xué)習(xí)能力與你更相似。我們在表示相似程度的時(shí)候,并不是直接使用歐幾里得距離,而是將其與1進(jìn)行比較,相似度在0和1之間,距離1越近,表示越相似,因此評價(jià)相似程度時(shí),要使用公式:
1/(1+distance)用代碼來表示
def sim_distance(lst1, lst2):
sum_value = 0
for x1, x2 in zip(lst1, lst2):
sum_value += pow(x1 - x2, 2)
return 1/(1 + math.sqrt(sum_value))皮爾遜相關(guān)系數(shù),很難像歐幾里得距離那樣形象的解釋,它的算法也更加復(fù)雜,涉及到了矩陣,哎,線性代數(shù)的內(nèi)容啊,當(dāng)年考試就不及格。。。。。。
既然難以解釋,就不解釋了,直接上數(shù)據(jù)

計(jì)算你和小明的歐幾里得距離是6.48074069840786, 和小剛的歐幾里得距離是8.660254037844387, 似乎小明的學(xué)習(xí)能力和你更為接近,但是,如果仔細(xì)觀察數(shù)據(jù)就能發(fā)現(xiàn),小剛的學(xué)習(xí)能力其實(shí)和你更接近,理由如下:
分?jǐn)?shù)上看,你的英語分 > 數(shù)學(xué)分 > 語文分, 小剛也呈現(xiàn)出相同的規(guī)律
小剛各科的分?jǐn)?shù)都比你少5分,你們的數(shù)學(xué)都比語文多5分,英語都比數(shù)學(xué)多3分
反觀小明,他語文成績比你好,數(shù)學(xué)和英語分?jǐn)?shù)相同,而你的英語比數(shù)學(xué)好,他的各科分?jǐn)?shù) 語文分 > 數(shù)學(xué)分 = 英語分
以上分析,反應(yīng)出的是兩組數(shù)據(jù)之間的相關(guān)性,你和小剛的成績呈現(xiàn)出比較明顯的正相關(guān)性,假設(shè)化學(xué)你考了96分,根據(jù)規(guī)律大體可以猜測小剛的化學(xué)分?jǐn)?shù)是91分,還是差了5分。小剛的成績與你的成績就沒有呈現(xiàn)出比較明顯的正相關(guān)性,讓你猜測小明的化學(xué)分?jǐn)?shù),你能像猜小剛的化學(xué)分?jǐn)?shù)那樣肯定么?
皮爾遜相關(guān)系數(shù)可以衡量兩個(gè)數(shù)據(jù)集合是否在一條線上面,計(jì)算出來的結(jié)果在-1到1之間,負(fù)數(shù)表示負(fù)相關(guān),正數(shù)表示正相關(guān),0表示不相關(guān),數(shù)值越大,相關(guān)性越強(qiáng)。
計(jì)算你們3個(gè)人之間的皮爾遜相關(guān)系數(shù)
import math
import numpy as np
lst1 = [90, 95, 98]
lst2 = [95, 94, 94]
lst3 = [85, 90, 93]
def cal_pccs(x, y):
'''
皮爾遜相關(guān)系數(shù)
'''
count = len(x)
sum_xy = np.sum(np.sum(x*y))
sum_x = np.sum(np.sum(x))
sum_y = np.sum(np.sum(y))
sum_x2 = np.sum(np.sum(x*x))
sum_y2 = np.sum(np.sum(y*y))
pcc = (count*sum_xy-sum_x*sum_y)/np.sqrt((count*sum_x2-sum_x*sum_x)*(count*sum_y2-sum_y*sum_y))
return pcc
def sim_distance(lst1, lst2):
sum_value = 0
for x1, x2 in zip(lst1, lst2):
sum_value += pow(x1 - x2, 2)
return 1/(1 + math.sqrt(sum_value))
print(cal_pccs(np.array(lst1), np.array(lst3))) # 1.0
print(sim_distance(lst1, lst3)) # 0.10351694645735657
print(cal_pccs(np.array(lst1), np.array(lst2))) # -0.9285714285714286
print(sim_distance(lst1, lst2)) # 0.13367660240019172以皮爾遜相關(guān)系數(shù)做為評價(jià)標(biāo)準(zhǔn),你和小剛的學(xué)習(xí)能力更為接近。
現(xiàn)在,我們已經(jīng)掌握了兩種度量相似度的算法,下一篇文章,我們將使用一份電影評分?jǐn)?shù)據(jù)進(jìn)行實(shí)戰(zhàn)訓(xùn)練,指定一個(gè)參與評分的用戶小A,計(jì)算得出和他相似性最高的5個(gè)人,并根據(jù)這5個(gè)人對其他電影的評分為小A推薦幾部電影。
