欲直接下載代碼文件,關注我們的公眾號哦!查看歷史消息即可!
話說,最近的瓜實在有點多,從我科校友李雨桐怒錘某男、陳羽凡吸毒被捕、蔣勁夫家暴的三連瓜,到不知知網翟博士,再到鄧紫棋解約蜂鳥、王思聰花千芳隔空互懟。
而最近的勝利夜店、張紫妍巨瓜案、最強大腦選手作弊丑聞,更是讓吃瓜群眾直呼忙不過來:瓜來的太快就像龍卷風,扶我起來,我還能吃!
說到底,這其實是一個信息過載的時代:公眾號每天數十條的推送、朋友圈的曬娃曬旅游、各種新聞報道撲面而來令人眼花繚亂、目不暇接……
那么問題來了,怎么找到自己的關注點呢?俗話說得好,有問題,找度娘,輸入關鍵詞一回車就完事兒了。
然而,懶是人的天性,而有的人(比如小編)則連關鍵詞都懶得搜,希望計算機能自動挖掘我們的興趣點,並為我們推薦感興趣的內容,所以我們就迫切地需要推薦系統來幫助我們了。那么現在我們就來講講推薦系統吧~
目錄
01 什么是推薦系統
推薦系統相信大家並不陌生,從“我有歌也有熱評”的雲村里的每日歌曲推薦,淘寶的猜你喜歡,再到外賣APP和視頻網站的推送,推薦系統似乎成了各種APP的寵兒(請忽略小編的老年人口味)。
熱門app的推薦系統
簡單來說,推薦系統就是根據用戶的各種數據(歷史行為數據、社交關系數據、關注點、上下文環境等)在海量數據中判斷用戶感興趣的item並推薦給用戶的系統。
“哇這個東西就是我想要的。”
“誒,這首歌還真好聽。”
“emm這部電影還挺對我胃口的”
推薦系統對用戶而言,能夠簡化搜尋過程、發現新鮮好玩令人驚喜的東西;而對商家而言,則是能夠提供個性化服務,提高用戶的信任度、粘度以及活躍度,從而提高營收。
02 推薦系統的評判標准
一個完整的推薦系統往往是十分復雜的。既然如此,僅僅通過准確率一個標准推薦系統作評測是遠遠不夠的,為此我們需要定義多個標准,從多維度評價一個推薦系統的好壞。
notation
1.准確度
對打分系統(比如說淘寶的評論一到五星打分)而言,一說到評判標准,最先想到的肯定就是均方根誤差RMSE和平均絕對誤差MAE啦:
RMSE和MAE計算公式
對TOP N推薦(生成一個TOP N推薦列表)而言,Precision和Recall則是我們的關注點:
Precision和Recall計算公式
說人話版本:Precision就是指系統推薦的東西中用戶感興趣的有多少,Recall就是用戶感興趣的東西中你推薦了多少
舉個栗子:
2.覆蓋率
表示推薦系統對item長尾的發掘能力
科普知識
馬太效應:強者愈強,弱者愈弱
長尾效應:大多數的需求會集中在頭部(爆款商品),而分布在尾部的需求是個性化的、零散的、小量的需求(冷門商品)。但這部分差異化的、少量的需求會在需求曲線上面形成一條長長的“尾巴”。如果將所有非流行的市場累加起來就會形成一個比流行市場還大的市場。
由長尾效應可知,滿足用戶個性化的需求十分重要,所以推薦系統要盡可能地挖掘出合用戶口味的冷門商品;如果推薦系統嚴重偏向推薦熱門商品,那么只會造成熱門商品越熱門,冷門商品越冷門,對商家的營收十分不利。
所以這就要求推薦系統的覆蓋率要高(推薦系統對所有用戶推薦的item占item總數的比例):
覆蓋率還有另一種計算方式:信息熵
其中,p(i)=第i件item被推薦次數/所有item總被推薦次數
科普時間:
“信息是用來消除隨機不確定性的東西。”由高中化學知道,熵是用來衡量一個系統的混亂程度的,熵越大,混亂程度越高。而同樣的,信息熵越大,對一件事情的不確定性就越大。
32支球隊打世界杯,只有一隊勝利,但是我們不知道關於比賽、關於球隊的任何信息,也就是說每支球隊獲勝的概率為1/32。如果我們想要知道哪支隊伍勝利,我們只能無任何根據地瞎猜,那么最要猜幾次呢?通過折半查找法我們可以發現,我們頂多五次就能找到勝利的球隊。所以說,這個問題的最大信息熵就是5bit(信息熵在p(i)全部相等時最大)。
如果這時候,我們知道了更多的信息,比如說是哪國球隊(德國隊和中國隊你選哪個?)、球隊的既往勝率,那么這時候,各個球隊獲勝的概率就發生了變動,而此時信息熵也得到了降低。
回到我們的推薦系統中來,信息熵越大,表明item之間的p(i)越接近,也就是說每個item被推薦的次數越接近, 即覆蓋率越大。
3.多樣性
表示推薦列表中物品之間的不相似性
Q:為什么需要多樣性?
A:試想,一個喜歡裙子的用戶,如果你給她推薦的十件商品都是裙子,那她可能也只會買一條最合心意的裙子,倒不如把一部分的推薦名額給其他種類的商品;另外,一位用戶買了一台計算機,你還給他推薦另外的計算機嗎?從商家的角度看,推薦鼠標、鍵盤等是最好的選擇。
所以我們的推薦列表需要盡可能地拓寬種類,增加用戶的購買欲望。
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的打分
2. 隱因子模型(Latent Factors Model)
我們有一個user對item的打分矩陣,但是有一些位置是空着的(我們候選推薦的item),所以我們要做的,就是把這些空位一網打盡,一次性填滿。
隱因子模型的基本思想就是:
打分矩陣R只有兩個維度:user和item,那么能否引入影響用戶打分的隱藏因素(即隱因子,不一定是人可以理解的,如同神經網絡一樣的黑箱)在user和item之間搭一座橋(分解為多個矩陣,使得這些矩陣的乘積近似於R)
舉個例子:
隱因子模型舉例
仔細觀察,在非0的部位上R和R’十分接近,而在0的部位上,R’得到了填充,而這可以作為我們推薦的依據。
Q:如何分解矩陣?
A:說到矩陣分解,首先想到的就是SVD了(Python AI 教學|SVD(Singular Value Decomposition)算法及應用)。
然而,SVD的時間復雜度為O(n3),在這里小編推薦另一種實現:梯度下降
算法流程:
隱因子模型的梯度下降實現算法流程
算法改進:
前面提到了用戶之間評分標准的差異性(某些用戶比較嚴格),而我們通過引入正則化項和偏差項(user bias和item bias)來進行優化。由於篇幅原因,本文不作具體解釋,感興趣的朋友麻煩自行百度啦~
3. 算法優缺點比較:
冷啟動問題:
對於冷啟動問題,一般分為三類:
用戶冷啟動:如何對新用戶做個性化推薦。
物品冷啟動:如何將新加進來的物品推薦給對它感興趣的用戶。
系統冷啟動:新開發的網站如何設計個性化推薦系統。
在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()