一:
推薦系統任務:聯絡使用者和資訊,一方面協助使用者發現對自己有價值的資訊,另一方面讓資訊能夠展現在對它感興趣的使用者面前,從而實現資訊消費者和資訊生產中的雙贏。
長尾理論:傳統80/20(%80銷售額來自於20%熱門商品)原則在互連網加入下受到挑戰。長尾商品銷售額是個不容小覷的數字,也許會超過熱門商品帶來的銷售額。熱門商品代表絕大多數使用者需求,而長尾商品代表一小部分使用者個人化需求。因此要發掘長尾以提高銷售,就必須充分研究使用者興趣。
社會化推薦:通過社交關係來獲得推薦。
基於內容的推薦:例如通過演員擷取電影。
基於協同過濾:通過熱門排行榜。
個人化推薦成功兩個條件:①存在資訊過載②使用者大部分時候沒有特別明確的需求。
推薦系統評測:什麼是好的推薦系統。一個推薦系統一般有三個參與方:使用者,物品提供者,提供推薦系統的網址。首先推薦系統要滿足使用者需求,給使用者推薦他們感興趣的物品;其次推薦系統要讓各個物品能夠被推薦給感興趣的使用者,而不是只推薦幾個熱門的物品;推薦系統本身能夠收集高品質的使用者反饋,不斷完善推薦品質。因此評測一個推薦系統,需要同時考慮三方利益,一個好的推薦系統能夠令三方共贏。
推薦系統實驗方法:
1離線方法:從實際系統日誌中提取資料,劃分訓練集測試集訓練模型。
優點:不需要有對實際系統控制權,不需要使用者參與,速度快,可測試大量演算法
缺點:無法計算商業上關心指標。離線實驗指標和商業指標存在差距。
2:使用者調查:即直接詢問使用者。優點:可用擷取很多體現使用者主觀感受指標,缺點招募使用者代價較大很難組織大規模測試使用者,因此測試結果統計意義不大。
3:線上實驗:推薦系統上線做AB測試,將它和舊的演算法進行比較。(使用者分組,不同組採用不同演算法).
優點:公平擷取不同演算法實際線上時效能指標包括商業上關注指標。
缺點:周期長,必須進行長期的實驗才能得到比較靠譜的結果。
評測標準:
1.使用者滿意度
使用者調查或者線上實驗獲得。
2.預測準確度
在離線資料集,劃分訓練集和測試集,通過在訓練集上建立使用者行為和興趣模型,預測使用者在測試集上行為,並計算預測行為和測試集上實際行為重合度作為預測準確度。
①:評分預測:一般通過均方根誤差(RMSE)和平均絕對誤差(MAE)計算
RMSE:
推薦系統->userCF演算法_推薦系統">
MAE:
推薦系統->userCF演算法_推薦系統">
import mathdef RMSE(records): return math.sqrt(sum([(rui-pui)*(rui-pui) for u,i,rui,pui in records]))/float(len(records))def MAE(records): return sum([math.fabs(rui-pui) for u,i,rui,pui in records])/float(len(records))
②:TopN推薦:網站提供建議服務時,一般是給使用者一個個人化的推薦列表,這個推薦叫做TopN推薦。預測準確度通過準確率和召回率度量。 令R(u)是根據使用者在訓練集上的行為給使用者做出推薦列表,而T(u)是使用者在測試集上的行為列表。 召回率定義:
推薦系統->userCF演算法_推薦系統">
準確率定義為:
推薦系統->userCF演算法_推薦系統">
T(u)是實際的行為列表,R(u)是預測的行為列表。
def PrecisionRecall(test,N): hit=0 n_recall=0 n_precision=0 ''' test.items():user,items測試集中對應使用者和對該使用者的實際行為列表 rank是該使用者預測的行為列表。 ''' for user,items in test.items(): rank=Recommend(user,N) hit+=len(rank&items) n_recall+=len(items) n_precision+=N return [hit/(1.0*n_recall),hit/(1.0*n_precision)]
3:覆蓋率
描述一個推薦系統對物品長尾的發掘能力;定義為推薦系統能推薦出來的物品佔總物品集合的比例。
推薦系統->userCF演算法_推薦系統">
但上面定義過於粗略。覆蓋率為100%的推薦系統可以有無數的物品流行度分布。為了更好的發掘長尾能力,需要統計推薦列表中不同物品出現次數的分布。因此可以通過研究物品在推薦列表中出現次數分布描述發掘長尾的能力。有兩個指標可以用來定義覆蓋率。①:資訊熵:
推薦系統->userCF演算法_推薦系統">
這裡p(i)表示物品i流行度除以所有物品流行度之和。
②:基尼係數(Gini Index):
推薦系統->userCF演算法_推薦系統">
這裡ij表示按照物品流行度p()從小到大排序的物品列表中第j個物品。
def Gini_index(p): j=1 n=len(p) G=0 for item,weight in sorted(p.items(),key=itemgetter(1)): G+=(2*j-n-1)*weight return G/float(n-1)
馬太效應:強者更強,弱者更弱。判斷推薦系統是否有馬太效應:如果G1是從初始使用者行為中計算出的物品流行度的基尼係數,G2是從推薦列表中計算出的物品流行度的基尼係數,如果G2>G1,就說明推薦演算法具有馬太效應。
4:多樣性
使用者興趣具有多樣性,推薦列表比較多樣,則覆蓋了使用者絕大多數興趣點,那麼就會增加使用者找到感興趣物品的機率。
多樣性和相似性是對應的。假設
定義了物品i和物品j之間的相似性, 那麼使用者u的推薦列表的多樣性定義如下:
推薦系統->userCF演算法_推薦系統">
註:R(u)是使用者u的推薦列表
推薦系統整體多樣性可以定義為所有使用者推薦列表多樣性的平均值:
推薦系統->userCF演算法_推薦系統">
5:新穎性
新穎的推薦是指給使用者推薦他們以前沒有聽說過的物品。,評測新穎性最簡單的方法是利用推薦結果的平均流行度,因為越是不熱門的物品越有可能令使用者感到新穎。
6:驚喜度
如果推薦結果和使用者的曆史興趣不相似,但卻讓使用者覺得滿意,那麼說推薦結果的驚喜度比較高。而推薦的新穎度僅僅取決於使用者是否聽說過這個推薦結果。
二:
使用者行為資料:
最簡單的存在形式是日誌,這些日誌記錄了使用者各種行為。
顯式反饋行為:使用者明確表示對物品的喜好行為。
隱式反饋行為:指那些不能明確反應使用者喜好的行為。相比顯式反饋行為,隱式反饋行為資料量更大。
很多時候我們並不使用統一結構表示所有行為,而是針對不同的行為給出不同表示。
①:無上下文資訊的隱性反饋資料集:每一條記錄僅僅包含物品ID和使用者ID
②:無上下文資訊的顯性反饋資料集:每一條記錄包含物品ID和使用者ID和使用者對物品評價。
③:有上下文資訊的隱形反饋資料集:……….使用者對物品產生行為的時間戳記。
④:有上下文資訊的顯性反饋資料集:
使用者行為分析:
長尾分布:將一文本中的詞按照它們在文本中的出現(或使用)次數由高至低排列,以r表示序號(又稱等級),g(r)表示序號為r的詞在文本中的出現次數,則r的某一冪次r(β)和g(r)的乘積漸近為一常數,即g(r)*r(β)≈c。即每個單詞出現的頻率和他在排序的序號的常數次冪成反比。
使用者行為資料也蘊含這種規律:物品流行度高的物品在總的物品數裡只佔少數;活躍度很高的使用者只佔少數。
使用者活躍度和物品流行度關係:使用者越活躍,月傾向於瀏覽冷門的物品。
協同過濾演算法:僅僅基於使用者行為資料設計的推薦演算法。
基於使用者的協同過濾演算法(userCF):給使用者推薦和他興趣相似的其他使用者喜歡的物品。
基於物品的協同過濾演算法(itemCF):給使用者推薦和他之前喜歡的物品相似的物品。
userCF演算法主要包括兩個部分:
①:找到和目標使用者相似的使用者集合
②:找到這個集合中使用者喜歡的,且目標使用者沒有聽說過的物品推薦個目標使用者。
首先計算兩兩使用者相似性。協同過濾演算法主要利用行為的相似性計算興趣的相似性。給定兩個使用者u,v,令N(u)表示使用者u曾經做出的有過正反饋的物品集合,N(v)表示使用者v曾經做出的有過正反饋的物品集合。
可通過餘弦相似性計算:
推薦系統->userCF演算法_推薦系統">
上面使用者興趣相似性計算過於粗略,比如兩個使用者同樣買過熱門物品並不代表他們興趣相似,換句話說只有買過相同的冷門物品才能表示兩個使用者興趣相似。故有改進版本的計算相似性:
推薦系統->userCF演算法_推薦系統">
其中N(u)表示與使用者u產生過行為的物品列表;N(i)表示與物品i產生過行為的使用者列表。
可用看出該公式通過
推薦系統->userCF演算法_推薦系統">
懲罰了使用者u和使用者v共同興趣列表中熱門物品對他們相似性的影響。
那在實際計算時應該如何計算相似性呢。
首先建立物品到使用者的倒排表,一個物品可能有多個使用者與其產生過行為,對於每個物品都儲存對該物品產生行為使用者的列表。假設使用者u和使用者v同時屬於倒排表中K個物品對應的使用者列表,那麼C[u][v]=k,從而可用掃描倒排表中每個物品對應的使用者列表,依次算出C[u][v],最終得到所有使用者之間不為0的C[u][v]。
得到使用者之間興趣相似性以後,userCF演算法會給使用者推薦和他興趣最相似的k個使用者喜歡的物品,如下公式計算使用者u對物品i的感興趣程度:
推薦系統->userCF演算法_推薦系統">
其中S(u,K)表示和使用者u最為相似的k個使用者,N(i)表示和物品i產生行為的使用者列表。Wuv表示使用者u和使用者v的相似性。rvi表示使用者v對物品i的興趣,因為使用的是單一行為的隱反饋,故所有rvi=1.
實戰:UserCF演算法 實現代碼:
資料來源
#coding:utf-8import randomimport mathfrom numpy import *import csvimport datetimeNumOfUsers=1000def GetData(datafile='u.data'): ''' 把datafile檔案中資料讀出來,返回data對象 :param datafile: 資料來源檔案名稱 :return: 一個列表,每一個元素是一個元組(userId,movieId) ''' data=[] try: file=open(datafile) except: print ("No such file name"+datafile) for line in file: line=line.split('\t') try: data.append((int(line[0]),int(line[1]))) except: pass file.close() return datadef SplitData(data,M,k,seed): ''' 劃分訓練集和測試集 :param data:傳入的資料 :param M:測試集佔比 :param k:一個任意的數字,用來隨機篩選測試集和訓練集 :param seed:隨機數種子,在seed一樣的情況下,其產生的隨機數不變 :return:train:訓練集 test:測試集,都是字典,key是使用者id,value是電影id集合 ''' test=dict() train=dict() random.seed(seed) # 在M次實驗裡面我們需要相同的隨機數種子,這樣產生的隨機序列是相同的 for user,item in data: if random.randint(0,M)!=k: # 相等的機率是1/M,所以M決定了測試集在所有資料中的比例 # 選用不同的k就會選定不同的訓練集和測試集 if user not in test.keys(): test[user]=set() test[user].add(item) else: if user not in train.keys(): train[user]=set() train[user].add(item) return train,testdef Recall(train,test,N,k): ''' :param train: 訓練集 :param test: 測試集 :param N: TopN推薦中N數目 :param k: :return:返回召回率 ''' hit=0# 預測準確的數目 totla=0# 所有行為總數 W,relatedusers=ImprovedCosineSimilarity(train) for user in train.keys(): tu=test[user] rank=GetRecommendation(user,train,N,k,W,relatedusers) for item in rank: if item in tu: hit+=1 totla+=len(tu) return hit/(totla*1.0)def Precision(train,test,N,k): ''' :param train: :param test: :param N: :param k: :return: ''' hit=0 total=0 W, relatedusers = ImprovedCosineSimilarity(train) for user in train.keys(): tu = test[user] rank = GetRecommendation(user, train, N, k, W, relatedusers) for item in rank: if item in tu: hit += 1 total += N return hit / (total * 1.0)def Coverage(train,test,N,k): ''' 計算覆蓋率 :param train:訓練集 字典user->items :param test: 測試機 字典 user->items :param N: topN推薦中N :param k: :return:覆蓋率 ''' recommend_items=set() all_items=set() W,relatedusers=ImprovedCosineSimilarity(train) for user in train.keys(): for item in train[user]: all_items.add(item) rank=GetRecommendation(user,train,N,k,W,relatedusers) for item in rank: recommend_items.add(item) return len(recommend_items)/(len(all_items)*1.0)def Popularity(train,test,N,k): ''' 計算平均流行度 :param train:訓練集 字典user->items :param test: 測試機 字典 user->items :param N: topN推薦中N :param k: :return:覆蓋率 ''' item_popularity=dict() W,relatedusers=ImprovedCosineSimilarity(train) for user,items in train.items(): for item in items: if item not in item_popularity: item_popularity[item]=0 item_popularity[item]+=1 ret=0 n=0 for user in train.keys(): rank= GetRecommendation(user, train, N, k, W, relatedusers) for item in rank: if item!=0: ret+=math.log(1+item_popularity[item]) n+=1 ret/=n*1.0 return retdef CosineSimilarty(train): ''' 計算訓練集中每兩個使用者的餘弦相似性 這個函數沒有實際價值,複雜度相當高,而且容易Out Of Memory,即在訓練集大的時候容易產生記憶體不足的錯誤 但是這個函數比較容易看出公式的原型,可以藉此理解公式運用 :param train: 訓練集,字典user->items :return: 返回相似性矩陣 ''' W=dict() print (len(train.keys())) for u in train.keys(): for v in train.keys(): if u==v: continue W[(u,v)]=len(train[u]&train[v]) W[(u,v)]/=math.sqrt(len(train[u])*len(train[v])*1.0) W[(v,u)]=W[(u,v)] return Wdef ImprovedCosineSimilarity(train): ''' 計算使用者相似性 :param train: :return: 返回使用者相似性矩陣W,W[u][v]表示u,v的相似性 :return: 返回相關使用者user_relatedusers字典,key為使用者id,value為和而使用者有共同電影的使用者集合。 ''' #建立電影->使用者倒排表 item_user=dict() for u,items in train.items(): for i in items: if i not in item_user: item_user[i]=set() item_user[i].add(u) #C[u][v] 表示使用者u和使用者v之間共同喜歡的電影 C=zeros([NumOfUsers,NumOfUsers],dtype=float16) #N[u]表示u評價的電影數目 N=zeros([NumOfUsers],dtype=int32) # user_relatedusers[u]表示u的相關使用者(共同電影不為零的使用者) user_relatedusers=dict() # 對於每個電影,把它對應的使用者組合C[u][v]加一 for item,users in item_user.items(): for u in users: N[u]+=1 for v in users: if u==v: continue if u not in user_relatedusers: user_relatedusers[u]=set() user_relatedusers[u].add(v) C[u][v]+=(1/math.log(1+len(users))) #使用者相似性矩陣 W=zeros([NumOfUsers,NumOfUsers],dtype=float16) for u in range(1,NumOfUsers): if u in user_relatedusers: for v in user_relatedusers[u]: W[u][v]=C[u][v]/sqrt(N[u]*N[v]) return W,user_relatedusersdef Recommend(user,train,W,relatedusers,k,N): ''' 通過相似性矩陣W得到和user相似的rank字典 :param user:使用者id :param train: 訓練集 :param W: 相似性矩陣 :param relatedusers: :param k: 決定了從相似使用者中取出多少進行計算 :param N: :return: rank字典,包含了所有興趣程度不為0的電影,按照從大到小排序 ''' rank=dict() for i in range(1,1700): rank[i]=0# i表示user可能喜歡的電影id,初始興趣程度為0 k_users=dict() try: for v in relatedusers[user]: k_users[v]=W[user][v] except KeyError: print ("User "+str(user)+" doesn't have any related users in train set") k_users=sorted(k_users.items(),key=lambda x:x[1],reverse=True) k_users=k_users[0:k]#取前k個使用者 for i in range(1700): for v,wuv in k_users: if i in train[v] and i not in train[user]:#取出被user相似使用者v產生行為的電影,同時user沒有和這部電影產生行為 rank[i]+=wuv*1 return sorted(rank.items(),key=lambda d:d[1],reverse=True)def GetRecommendation(user,train,N,k,W,relatedusers): ''' 獲得N個推薦 :param user: 使用者 :param train: 訓練集 :param W: 相似性矩陣 :param N: 推薦數目N :param k: 決定了從相似使用者中取出多少個進行計算 :return: recommend字典,key是movie id,value是興趣程度 ''' rank=Recommend(user,train,W,relatedusers,k,N) recommend=dict() for i in range(N): recommend[rank[i][0]]=rank[i][1] return recommenddef evaluate(train,test,N,k): ##計算一系列評測標準 recommends=dict() W,relatedusers=ImprovedCosineSimilarity(train) for user in test: recommends[user]=GetRecommendation(user,train,N,k,W,relatedusers) recall=Recall(train,test,N,k) precision=Precision(train,test,N,k) coverage=Coverage(train,test,N,k) popularity=Popularity(train,test,N,k) return recall,precision,coverage,popularitydef test1(): data=GetData() train,test=SplitData(data,2,1,1) del data user=int(input("Input the user id \n")) print("The train set contains the movies of the user: ") print(train[user]) N=int(input("Input the number of recommendations\n")) k=int(input("Input the number of related users\n")) starttime=datetime.datetime.now() W,relatedusers=ImprovedCosineSimilarity(train) endtime=datetime.datetime.now() print("it takes ",(endtime-starttime).seconds," seconds to get W") starttime=datetime.datetime.now() recommend=GetRecommendation(user,train,N,k,W,relatedusers) endtime=datetime.datetime.now() print("it takes ",(endtime-starttime).seconds," seconds to get recommend for one user") W,relatedusers=ImprovedCosineSimilarity(train) recommend=GetRecommendation(user,train,N,k,W,relatedusers) print(recommend) for item in recommend: print(item), if(item in test[user]): print(" True") else: print(" False")def test2(): N=int(input("Input the number of recommendations: \n")) k=int(input("Input the number of related users: \n")) data = GetData() train, test = SplitData(data, 2, 1, 1) del data recall,precision,coverage,popularity=evaluate(train,test,N,k) print("Recall: ",recall) print("Precision: ",precision) print("Coverage: ",coverage) print("Popularity: ",popularity)if __name__=='__main__': test2()