「猜你喜歡」的背後揭祕--10分鐘教你用Python打造推薦系統

欲直接下載代碼文件,關注咱們的公衆號哦!查看歷史消息便可!python

話說,最近的瓜實在有點多,從我科校友李雨桐怒錘某男、陳羽凡吸毒被捕、蔣勁夫家暴的三連瓜,到不知知網翟博士,再到鄧紫棋解約蜂鳥、王思聰花千芳隔空互懟。算法

而最近的勝利夜店、張紫妍巨瓜案、最強大腦選手做弊醜聞,更是讓吃瓜羣衆直呼忙不過來:瓜來的太快就像龍捲風,扶我起來,我還能吃!網絡

說到底,這實際上是一個信息過載的時代:公衆號天天數十條的推送、朋友圈的曬娃曬旅遊、各類新聞報道撲面而來使人眼花繚亂、應接不暇……app

那麼問題來了,怎麼找到本身的關注點呢?俗話說得好,有問題,找度娘,輸入關鍵詞一回車就完事兒了。dom

然而,懶是人的天性,而有的人(好比小編)則連關鍵詞都懶得搜,但願計算機能自動挖掘咱們的興趣點,併爲咱們推薦感興趣的內容,因此咱們就迫切地須要推薦系統來幫助咱們了。那麼如今咱們就來說講推薦系統吧~ide

目錄

01 什麼是推薦系統

推薦系統相信你們並不陌生,從「我有歌也有熱評」的雲村裏的每日歌曲推薦,淘寶的猜你喜歡,再到外賣APP和視頻網站的推送,推薦系統彷佛成了各類APP的寵兒(請忽略小編的老年人口味)。函數

熱門app的推薦系統學習

簡單來講,推薦系統就是根據用戶的各類數據(歷史行爲數據、社交關係數據、關注點、上下文環境等)在海量數據中判斷用戶感興趣的item並推薦給用戶的系統。測試

「哇這個東西就是我想要的。」優化

「誒,這首歌還真好聽。」

 「emm這部電影還挺對我胃口的」

推薦系統對用戶而言,可以簡化搜尋過程、發現新鮮好玩使人驚喜的東西;而對商家而言,則是可以提供個性化服務,提升用戶的信任度、粘度以及活躍度,從而提升營收。

02 推薦系統的評判標準

一個完整的推薦系統每每是十分複雜的。既然如此,僅僅經過準確率一個標準推薦系統做評測是遠遠不夠的,爲此咱們須要定義多個標準,從多維度評價一個推薦系統的好壞

notation

1.準確度

打分系統(好比說淘寶的評論一到五星打分)而言,一說到評判標準,最早想到的確定就是均方根偏差RMSE平均絕對偏差MAE啦:

RMSE和MAE計算公式

TOP N推薦(生成一個TOP N推薦列表)而言,PrecisionRecall則是咱們的關注點:

Precision和Recall計算公式

說人話版本:Precision就是指系統推薦的東西中用戶感興趣的有多少,Recall就是用戶感興趣的東西中你推薦了多少

舉個栗子:

Precison和Recall算例

2.覆蓋率

表示推薦系統對item長尾的發掘能力

科普知識

馬太效應:強者愈強,弱者愈弱

長尾效應:大多數的需求會集中在頭部(爆款商品),而分佈在尾部的需求是個性化的、零散的、小量的需求(冷門商品)。但這部分差別化的、少許的需求會在需求曲線上面造成一條長長的「尾巴」。若是將全部非流行的市場累加起來就會造成一個比流行市場還大的市場。

長尾效應示意圖

由長尾效應可知,知足用戶個性化的需求十分重要,因此推薦系統要儘量地挖掘出合用戶口味的冷門商品;若是推薦系統嚴重偏向推薦熱門商品,那麼只會形成熱門商品越熱門,冷門商品越冷門,對商家的營收十分不利。

因此這就要求推薦系統的覆蓋率要高(推薦系統對全部用戶推薦的item佔item總數的比例):

覆蓋率還有另外一種計算方式:信息熵

其中,p(i)=第i件item被推薦次數/全部item總被推薦次數

科普時間:

「信息是用來消除隨機不肯定性的東西。」由高中化學知道,熵是用來衡量一個系統的混亂程度的,熵越大,混亂程度越高。而一樣的,信息熵越大,對一件事情的不肯定性就越大

32支球隊打世界盃,只有一隊勝利,可是咱們不知道關於比賽、關於球隊的任何信息,也就是說每支球隊獲勝的機率爲1/32。若是咱們想要知道哪支隊伍勝利,咱們只能無任何根據地瞎猜,那麼最要猜幾回呢?經過折半查找法咱們能夠發現,咱們頂多五次就能找到勝利的球隊。因此說,這個問題的最大信息熵就是5bit(信息熵在p(i)所有相等時最大)。

若是這時候,咱們知道了更多的信息,好比說是哪國球隊(德國隊和中國隊你選哪一個?)、球隊的既往勝率,那麼這時候,各個球隊獲勝的機率就發生了變更,而此時信息熵也獲得了下降。

回到咱們的推薦系統中來,信息熵越大,代表item之間的p(i)越接近,也就是說每一個item被推薦的次數越接近, 即覆蓋率越大

3.多樣性

表示推薦列表中物品之間的不類似性

Q:爲何須要多樣性?

A:試想,一個喜歡裙子的用戶,若是你給她推薦的十件商品都是裙子,那她可能也只會買一條最合心意的裙子,倒不如把一部分的推薦名額給其餘種類的商品;另外,一位用戶買了一臺計算機,你還給他推薦另外的計算機嗎?從商家的角度看,推薦鼠標、鍵盤等是最好的選擇。

因此咱們的推薦列表須要儘量地拓寬種類,增長用戶的購買慾望。

Diversity計算公式

4.還有其餘的評判標準

新穎度:新穎的推薦是指給用戶推薦那些他們之前沒有據說過的物品。

驚喜度:推薦結果和用戶的歷史興趣不類似,但卻讓用戶以爲滿意。(而新穎性僅僅取決於用戶是否據說過這個推薦結果。)

信任度:推薦系統給你推薦的依據是什麼(「你的朋友也喜歡這首歌」比起「喜歡那首歌的人也喜歡這首歌」更能讓用戶信任)

03 算法(具體實現請看第四部分)

1. 協同過濾(collaborative filtering)

回想一下,當你遇到劇荒、歌荒時,會怎麼作?

「誒,小陳,最近有啥好看的電視劇,給我安利安利唄。」

相信大多數人首先想到的都是找一個趣味相投的朋友,問一下他們有啥好推薦的。

協同過濾也就是基於這樣的思想。而協同過濾分爲兩種:user-based(基於用戶,找到最類似的user)和item-based(基於商品,找到最類似的item)。

那麼,協同過濾須要解決的核心問題是:

如何找到最類似的user/item?

所以咱們須要衡量類似性的指標:

衡量類似性指標的公式

其實Pearson類似度是考慮了user/item之間的差別而來。

Q:爲何要減去mean?

A:好比,user1打分十分苛刻,3分已是至關好的商品;而user2是一位佛系用戶,不管商品有多差,打分時3分起步。那麼咱們能夠說user1的3分和user2的5分是等價的。而Pearson類似度就是經過減去mean來進行相對地歸一化。

user-based/item-based算法流程:

1. 計算目標user/item和其他user/item的類似度

2. 選擇與目標user/item有正類似度的user/item

3. 加權打分

舉個栗子(使用Pearson類似度和user-based):

咱們有這樣一個打分表,想要預測USER2對ITEM3的打分

Pearson類似度算例

2. 隱因子模型(Latent Factors Model)

咱們有一個user對item的打分矩陣,可是有一些位置是空着的(咱們候選推薦的item),因此咱們要作的,就是把這些空位一網打盡,一次性填滿。

隱因子模型的基本思想就是:

打分矩陣R只有兩個維度:user和item,那麼可否引入影響用戶打分的隱藏因素(即隱因子,不必定是人能夠理解的,如同神經網絡同樣的黑箱)在user和item之間搭一座橋(分解爲多個矩陣,使得這些矩陣的乘積近似於R

舉個例子:

image

隱因子模型舉例

仔細觀察,在非0的部位上R和R’十分接近,而在0的部位上,R’獲得了填充,而這能夠做爲咱們推薦的依據。

Q:如何分解矩陣?

A:說到矩陣分解,首先想到的就是SVD了(Python AI 教學|SVD(Singular Value Decomposition)算法及應用)。

然而,SVD的時間複雜度爲O(n3),在這裏小編推薦另外一種實現:梯度降低

算法流程:

image

隱因子模型的梯度降低實現算法流程

算法改進:

前面提到了用戶之間評分標準的差別性(某些用戶比較嚴格),而咱們經過引入正則化項誤差項(user bias和item bias)來進行優化。因爲篇幅緣由,本文不做具體解釋,感興趣的朋友麻煩自行百度啦~

改進後的損失函數

3. 算法優缺點比較:

冷啓動問題:

對於冷啓動問題,通常分爲三類:

用戶冷啓動:如何對新用戶作個性化推薦。

物品冷啓動:如何將新加進來的物品推薦給對它感興趣的用戶。

系統冷啓動:新開發的網站如何設計個性化推薦系統。

協同過濾與隱因子模型比較

UserCF和ItemCF比較

在user-based和item-based之間,通常使用item-based,由於item-based穩定性高(user的打分標準、喜愛等飄忽不定,有很大的不肯定性),並且user太多時計算量大。

04 手把手打造一個推薦系統

都說女人心海底針,還在爲選什麼電影才能打動妹子煩惱嗎?還在擔憂沒法彰顯本身的品味嗎?相信下面的電影推薦系統代碼必定可以回答你關於如何選擇電影的疑惑。(捂臉,逃)

1. 數據源

小編經過問卷調查獲取了朋友圈對15部電影的評分(1表明第一個選項即沒看過,2~6表示1~5星)

訓練數據一覽

2.代碼

注:python有許多方便計算的函數,如norm()計算向量的模和corrcoef()計算pearson類似度,不過爲了廣大朋友們記憶深入,小編這裏本身來實現這些計算~

1# -*- coding: utf-8 -*-
  2
  3"""
  4Created on Thu Mar 21 19:29:54 2019
  5
  6@author: o
  7"""
  8import pandas as pd
  9import numpy as np
 10from math import sqrt
 11import copy
 12
 13def load_data(path):
 14    df = pd.read_excel(path)
 15    #去掉不須要的列
 16    df = df[df.columns[5:]]
 17    #1表示沒看過,2~6表示1~5星
 18    df.replace([1,2,3,4,5,6],[0,1,2,3,4,5],inplace = True)
 19    columns = df.columns
 20    df = np.array(df)
 21    #測試過程當中發現有nan存在,原來是由於有人惡做劇,全填了沒看過,致使分母爲0
 22    #此處要刪除全爲0的行
 23    delete = []
 24    for i in range(df.shape[0]):
 25        all_0 = (df[i] == [0]*15)
 26        flag = False
 27        for k in range(15):
 28            if all_0[k] == False:
 29                flag = True
 30                break
 31        if flag == False:
 32            delete.append(i)
 33            print(i)
 34    df = np.delete(df,delete,0)
 35    return df,columns
 36
 37
 38#定義幾種衡量類似度的標準
 39#餘弦類似度
 40def cos(score,your_score):
 41    cos = []
 42    len1 = 0
 43    for i in range(15):
 44        len1 += pow(your_score[i],2)
 45    len1 = sqrt(len1)
 46    for i in range(score.shape[0]):
 47        len2 = 0
 48        for k in range(15):
 49            len2 += pow(score[i][k],2)
 50        len2 = sqrt(len2)
 51        cos.append(np.dot(your_score,score[i])/(len1*len2))                
 52    return cos
 53
 54#歐氏距離
 55def euclidean(score,your_score):
 56    euclidean = []
 57    for i in range(score.shape[0]):
 58        dist = 0
 59        for k in range(score.shape[1]):
 60            dist += pow((score[i][k]-your_score[k]),2)
 61        dist = sqrt(dist)
 62        euclidean.append(dist)
 63    return euclidean
 64
 65
 66#pearson類似度
 67#Python有內置函數corrcoef()能夠直接計算,不過這裏仍是手寫鞏固一下吧~
 68def pearson(score,your_score):
 69    pearson = []
 70    n = score.shape[1]
 71    sum_y = 0
 72    count = 0
 73    #計算目標用戶打分的均值
 74    for i in range(n):
 75        if your_score[i]!=0:
 76            count += 1
 77            sum_y += your_score[i]
 78    mean_y = sum_y/count
 79    print('\n')
 80    print('你的平均打分爲:',mean_y)
 81    print('\n')
 82    #計算目標用戶打分向量的長度
 83    len1 = 0
 84    for i in range(n):
 85        if your_score[i]!=0:
 86            your_score[i] -= mean_y
 87        len1 += pow(your_score[i],2)
 88    len1 = sqrt(len1)
 89    #print(len1,mean_y,your_score)
 90
 91    for i in range(score.shape[0]):
 92        #計算其餘用戶打分的均值
 93       # print(i,score[i])
 94        count = 0
 95        sum_x = 0
 96        for k in range(n):
 97            if score[i][k]!=0:
 98                count += 1
 99                sum_x += score[i][k]
100        mean_x = sum_x/count
101        #計算其餘用戶打分向量的長度
102        len2 = 0
103        for k in range(n):
104            if score[i][k]!=0:
105                score[i][k] -= mean_x
106            len2 += pow(score[i][k],2)
107        len2 = sqrt(len2)
108        #print(len2,mean_x,score[i],'\n','\n')
109        #分母不可爲零,否則會產生nan
110        if len2 == 0:
111            pearson.append(0)
112        else:
113            pearson.append(np.dot(your_score,score[i])/len1/len2)
114    return pearson,mean_y      
115
116
117#找到類似度最高的用戶
118def find_nearest(sim):
119    index = [i for i in range(len(sim))]
120    #index和sim的元組列表
121    sorted_value = list(zip(index,sim))
122    #降序排序
123    sorted_value = sorted(sorted_value,key = lambda x : x[1],reverse = True)
124    return sorted_value
125
126
127#user-based collaborative_filtering
128def collaborative_filtering(score,your_score,movies):
129    #目標用戶對15部電影有無看過的bool列表
130    seen1 = np.array([bool(i) for i in your_score])
131    #使用pearson過程當中會改變score矩陣的值,須要用deepcopy複製一份
132    score1 = copy.deepcopy(score)
133    #幾種類似度的衡量
134    #sim = cos(score,your_score)
135    #sim = euclidean(score,your_score)
136    sim, mean_target= pearson(score1,your_score)
137    #找到最類似的用戶
138    sorted_value = find_nearest(sim)
139
140    #找到類似度>0的用戶數量
141    count = 0
142    for i in range(score.shape[0]):
143        if sorted_value[i][1]<=0:
144            break
145        else:
146            count += 1
147    #取根值,去掉正類似度中偏低的user
148    count = int(sqrt(count))
149
150    #加權打分 進行推薦
151    print('使用user-based協同過濾進行加權預測打分:')
152    for i in range(score.shape[1]):
153        #若是目標用戶沒看過
154        if not seen1[i]:
155            #初始化分子分母
156            numerator = denominator = 0
157            for k in range(count):
158                index = sorted_value[k][0]
159                if score[index][i] != 0:
160                    numerator += score[index][i]*sorted_value[k][1]
161                    denominator += sorted_value[k][1]
162            if not denominator:
163                print(movies[i],'無相關度高的人看過,沒法預測得分')
164            elif numerator/denominator > mean_target:
165                print(movies[i],':',numerator/denominator,'推薦觀看')
166            else:
167                print(movies[i],":",numerator/denominator,'不推薦觀看')
168    return None
169
170
171#梯度降低+隱因子模型
172def latent_factors(score,your_score,movies):
173    #目標用戶的打分向量整合進打分矩陣
174    score1 = np.vstack([your_score,score])
175    #打分矩陣的維度
176    n,m = score1.shape[0],score1.shape[1]
177    #隱因子數量設爲K
178    K = 15
179    #最大迭代次數
180    max_iteration = 1000
181    #學習速率和正則化因子
182    alpha = 0.01
183    beta = 0.01
184    #LossFunction改變值小於threshold就結束
185    threshold = 0.7
186    #迭代次數
187    count = 0
188    #初始化分解後的矩陣P、Q
189    p = np.random.random([n,K])
190    q = np.random.random([m,K])
191    #全體用戶對15部電影有無看過的bool矩陣
192    bool_matrix = [[bool(k) for k in i] for i in score1]
193
194    while True:
195        count += 1
196        #更新P Q矩陣
197        for i in range(n):
198            for j in range(m):
199                if bool_matrix[i][j]:
200                    eij = score1[i][j] - np.dot(p[i],q[j])
201                    for k in range(K):
202                        #同時更新pik和qjk
203                        diff=[0,0]
204                        diff[0] = p[i][k] + alpha*(2*eij*q[j][k]-beta*p[i][k])
205                        diff[1] = q[j][k] + alpha*(2*eij*p[i][k]-beta*q[j][k])
206                        p[i][k] = diff[0]
207                        q[j][k] = diff[1]
208        #計算偏差
209        error = 0
210        for i in range(n):
211            for j in range(m):
212                if bool_matrix[i][j]:                    
213                    error += pow((score1[i][j]-np.dot(p[i],q[j])),2)
214                    for k in range(K):
215                        error += beta/2*(pow(p[i][k],2)+pow(q[j][k],2))
216        RMSE = sqrt(error/n)
217        print(count,'root_mean_square_error:',RMSE)
218        if RMSE<threshold or count>max_iteration:
219            break    
220
221    #打印目標用戶沒看過的電影
222    seen1 = np.array([bool(i) for i in your_score])
223    print('你沒看過的影片有:')
224    for i in range(m):
225        if not seen1[i]:
226            print(movies[i])
227    print('\n')
228
229    #輸出梯度降低預測分數
230    predict = np.dot(p[0],q.T)
231    print('使用梯度降低+隱因子模型進行預測打分:')
232    for i in range(m):
233        if not seen1[i]:
234            print(movies[i],':',predict[i]) 
235    return None
236
237
238def recommend():
239    #請自行修改路徑
240    path = r'C:\Users\o\Desktop\請給15部電影打分吧.xls'
241    #原始打分矩陣,電影名稱列表
242    score, movies = load_data(path)
243    #必須轉換成Float類型,否則會出現分母爲0的狀況
244    score = score.astype(float)
245    your_score = []
246    #構造目標用戶打分向量
247    print('請依次輸入你對15部電影的打分(0表示沒看過,1~5表示1~5星,以空格分隔):')
248    print(movies)
249    str = input()
250    your_score = np.array([int(i) for i in str.split(' ')]).astype(float)
251    #進行預測
252    latent_factors(score,your_score,movies)
253    collaborative_filtering(score,your_score,movies)
254    return None
255
256
257if __name__=='__main__':
258    recommend()
相關文章
相關標籤/搜索