推薦系統學習的歷程(一)
2017下半年就開始沉澱機器學習的知識。剛剛開始的時候接觸到這個知識因為覺得很有意思,另一方面大家都在學習這也是大勢所趨。接觸到之後,我把線性代數深入的理解了一遍,這也就為我後來對推薦系統裡面的矩陣理解奠定了基礎。十分感謝youtube上的3B1B(3Blue1Brown,聽說這個小哥的瞳孔3/4是藍色的1/4是棕色的因此得名)對我的啟發,同時他還激發了我對數學的興趣,不僅僅是應用數學還有原理數學。
開始的路是十分艱難的,本來我還有兩個隊友一起學習,但是他們都放棄了。他們對這個知識不感興趣,也許是被這個難度勸退了。的確,對於我這種二本院校的普通學生來說,沒有人指導的情況下是很難學習的。現在隊友走了,我連交流的可能性也沒有了,我的路變得越來越艱難。不過這都是半年前的事情了。我也是太忙了,連記錄學習歷程的時間都沒有,最近才找到時間總結一下。
剛開始我查了一些機器學習方面的研究進展和知識框架,我選擇了一個最為基礎的演算法-協同過濾演算法。協同過濾演算法就是用來做推薦系統的,這是我當時的想法,那我乾脆就做一個商品的推薦系統吧。雖說打著系統的名號,只不過是把核心演算法用python寫好然後找來資料來實驗而已。
這個時候問題就來了,我一個大二的學生,大一學個c語言,在ACM實驗室刷水題,哪裡會python。我又得自學python,倒是c語言的基礎讓我學習python只花了半個月不到吧。只是會了其中的語法,並沒有深究。隨後就是最最煩惱的論文閱讀。在老師的催促下,我找到了網上一些協同過濾演算法的python程式碼,這個時候我才發現,蒐集情報和知識的能力也是一項很重要的技能。如果說ACM教會了我怎麼看部落格,這次的學習就教會了我怎麼去獲取資訊。包括所有研究協同過濾演算法的人都會有的movielens的資料集,我也去官網下載到。
推薦系統的原始碼實驗成功了,當時的心情無法形容,自己種下的種子好像在一夜之間長出了瓜果。
#coding:UTF-8 from math import sqrt def loadData(): trainSet = {} testSet = {} movieUser = {} u2u = {} TrainFile = 'ml-100k/u1.base' #指定訓練集 TestFile = 'ml-100k/u1.test' #指定測試集 #載入訓練集 for line in open(TrainFile): (userId, itemId, rating, timestamp) = line.strip().split('\t') #strip()用於去除字串前後的指定字串,預設為空格 trainSet.setdefault(userId,{}) trainSet[userId].setdefault(itemId,float(rating)) movieUser.setdefault(itemId,[]) movieUser[itemId].append(userId.strip()) #載入測試集 for line in open(TestFile): (userId, itemId, rating, timestamp) = line.strip().split('\t') testSet.setdefault(userId,{}) testSet[userId].setdefault(itemId,float(rating)) #生成使用者使用者共有電影矩陣 for m in movieUser.keys(): #物品的ID m for u in movieUser[m]: #使用者的集合 u u2u.setdefault(u,{}) #已使用者為鍵,建立字典 for n in movieUser[m]: # 使用者的集合 if u!=n: u2u[u].setdefault(n,[]) u2u[u][n].append(m) return trainSet,testSet,u2u #計算一個使用者的平均評分 def getAverageRating(user): average = (sum(trainSet[user].values())*1.0) / len(trainSet[user].keys()) return average #計算使用者的總分 def getSumRating(user): sumrate=(sum(trainSet[user].values())*1.0) return sumrate #計算使用者相似度 def getUserSim(u2u,trainSet): userSim = {} # 計算使用者的使用者相似度 for u in u2u.keys(): #對每個使用者u userSim.setdefault(u,{}) #將使用者u加入userSim中設為key,該使用者對應一個字典 average_u_rate = getAverageRating(u) #獲取使用者u對電影的平均評分 for n in u2u[u].keys(): #對與使用者u相關的每個使用者n userSim[u].setdefault(n,0) #將使用者n加入使用者u的字典中 average_n_rate = getAverageRating(n) #獲取使用者n對電影的平均評分 R_un=0 #共有電影數量與使用者最大數量 sum_un_m=0 sum_u_m=0 #u使用者對共有電影評分的總和 sum_n_m=0 #n是使用者對共有電影評分的總和 part1 = 0 #皮爾遜相關係數的分子部分 part2 = 0 #皮爾遜相關係數的分母的一部分 part3 = 0 #皮爾遜相關係數的分母的一部分 for m in u2u[u][n]: #對使用者u和使用者n的共有的每個電影 part1 += (trainSet[u][m]-average_u_rate)*(trainSet[n][m]-average_n_rate)*1.0 part2 += pow(trainSet[u][m]-average_u_rate, 2)*1.0 #pow返回x的y次方 part3 += pow(trainSet[n][m]-average_n_rate, 2)*1.0 sum_u_m += trainSet[u][m]*1.0 sum_n_m += trainSet[n][m]*1.0 R_un=R_un+1 sum_un_m=sum_un_m+abs(trainSet[u][m]-trainSet[n][m])*1.0 d_un=sum_un_m/R_un R_un=R_un/max(len(trainSet[u].keys()),len(trainSet[n].keys()))*1.0 part2 = sqrt(part2) part3 = sqrt(part3) sumrate_u=getSumRating(u) sumrate_n=getSumRating(n) sumrate_u=sqrt(sumrate_u) sumrate_n=sqrt(sumrate_n) sum_u_m=sqrt(sum_u_m) sum_n_m=sqrt(sum_n_m) if part2 == 0 or part3 == 0: #若分母為0,相似度為0 userSim[u][n] = 0 else: userSim[u][n] = part1 / (part2 * part3) userSim[u][n]=userSim[u][n]*sum_u_m*sum_n_m userSim[u][n]=userSim[u][n]/sumrate_u/sumrate_n userSim[u][n]=userSim[u][n]*d_un*R_un return userSim #尋找使用者最近鄰並生成推薦結果 def getRecommendations(N,trainSet,userSim): pred = {} for user in trainSet.keys(): #對每個使用者 pred.setdefault(user,{}) #生成預測空列表 interacted_items = trainSet[user].keys() #獲取該使用者評過分的電影 average_u_rate = getAverageRating(user) #獲取該使用者的評分平均分 userSimSum = 0 simUser = sorted(userSim[user].items(),key = lambda x : x[1],reverse = True)[0:N] #reverse True為大到小 False相反 for n, sim in simUser: average_n_rate = getAverageRating(n) userSimSum += sim #對該使用者近鄰使用者相似度求和 for m, nrating in trainSet[n].items(): if m in interacted_items: continue else: pred[user].setdefault(m,0) pred[user][m] += (sim * (nrating - average_n_rate)) for m in pred[user].keys(): #計算預測評分 pred[user][m] = average_u_rate + (pred[user][m]*1.0) / userSimSum return pred #計算預測分析準確度 def getMAE(testSet,pred): MAE = 0 rSum = 0 setSum = 0 for user in pred.keys(): #對每一個使用者 for movie, rating in pred[user].items(): #對該使用者預測的每一個電影 if user in testSet.keys() and movie in testSet[user].keys() : #如果使用者為該電影評過分 setSum = setSum + 1 #預測準確數量+1 rSum = rSum + abs(testSet[user][movie]-rating) #累計預測評分誤差 MAE = rSum / setSum return MAE def getRecall(testSet,pred): setSum=0 Sum_test=0 for user in testSet.keys(): for movie in testSet[user].keys(): Sum_test = Sum_test + 1 if movie in pred[user].keys(): setSum = setSum + 1 recall = setSum * 1.0 / Sum_test * 1.0 return recall def getPrec(testSet,pred): setSum=0 Sum_test=0 for user in pred.keys(): for movie in pred[user].keys(): Sum_test = Sum_test + 1 if user in testSet.keys() and movie in testSet[user].keys(): setSum = setSum + 1 prec = setSum * 1.0 / Sum_test * 1.0 return prec def getCouver(pred): #獲得覆蓋率Couverage Sum=[] for user in pred.keys(): for movie in pred[user].keys(): if movie not in Sum: Sum.append(movie) Sum_num=len(Sum) all_s=1682 couver=Sum_num*1.0/all_s*1.0 return couver if __name__ == '__main__': print(u'正在載入資料...') trainSet,testSet,u2u = loadData() print(u'正在計算使用者間相似度...') userSim = getUserSim(u2u,trainSet) print(u'正在尋找最近鄰...') for N in (5,10,20,30,40,50,60,70,80,90,100): #對不同的近鄰數 pred = getRecommendations(N,trainSet,userSim) #獲得推薦 mae = getMAE(testSet,pred) #計算MAE recall=getRecall(testSet,pred) prec=getPrec(testSet,pred) couver=getCouver(pred) print (u'鄰居數為:N= %d 時 預測評分準確度為:'%(N)) print ('MAE=%f Recall=%f Precision=%f Couverage=%f'%(mae,recall,prec,couver)) input('按任意鍵繼續...')
這一串程式碼從此把我拉進了這個大坑之中。對著段程式碼研究了幾天,終於對這個演算法有的膚淺的認識,同時進行了一些修改,因為原來的程式碼有很多問題。這也讓我duipython的認識有了進一步的認識。