1. 程式人生 > >推薦系統學習的歷程(一)

推薦系統學習的歷程(一)

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的認識有了進一步的認識。