1. 程式人生 > >協同過濾演算法python實現簡單入門詳細註釋

協同過濾演算法python實現簡單入門詳細註釋

基於Python2.7   資料集為MovieLens 100k資料集

# -*-coding=utf-8 -*-
import sys
import math
from texttable import Texttable

#演算法流程:
#1、建立電影-使用者的倒排表,表示電影被那些使用者看過
#2、從目標使用者看過的每一步電影開始遍歷
#3、尋找和目標使用者有共同看過電影交集的所有使用者作為初始鄰居
#4、對初始鄰居里邊的每一個鄰居,計算和目標使用者的餘弦夾角相似度
#5、建立相似度到使用者的倒排表,相似度從高到低排列
#6、根據相似度找出最近k鄰居
#7、從最近k鄰看過的所有的電影開始遍歷
#  這裡的推薦思路是:對於最近k鄰居看過的所有電影中的某一電影m
#  如果m僅僅被一個鄰居看過,那麼目標使用者對此電影的的興趣度就是目標使用者和這個鄰居的相似度
#  如果m被多個鄰居看過,那麼目標使用者對此電影的相似度為目標使用者與這些鄰居相似度之和
#8、建立目標使用者興趣度-電影id的倒排表
#9、根據興趣度由大到小進行推薦 

#計算餘弦距離
#listUser2Score[2]=[(1,5),(4,2)].... 表示使用者2對電影1的評分是5,對電影4的評分是2
#dist = getCosDist(listUser2Score[userId], listUser2Score[neighbor])
def getCosDist(user1, user2):
     sum_x = 0.0
     sum_y = 0.0
     sum_xy = 0.0
     for key1 in user1:
          for key2 in user2:
# key1[0]表示電影id,key1[1]表示對電影的評分
# 如果是兩個使用者共同評價的一部電影
               if key1[0] == key2[0]:
                    sum_x += key1[1] * key1[1]
                    sum_y += key2[1] * key2[1]
                    sum_xy += key1[1] * key2[1]
     if sum_xy == 0.0:
          return 0
     demo = math.sqrt(sum_x * sum_y)
     return sum_xy / demo

#讀取檔案,讀取以行為單位,每一行是列表裡的一個元素
def readFile(filename):
     contents = []
     f = open(filename, "r")
     contents = f.readlines()
     f.close()
     return contents

#資料格式化為二維陣列
def getRatingInfo(ratings):
     rates = []
     for line in ratings:
          rate = line.split("\t")
#  u.data
#  user id | item id | rating | timestamp.
          rates.append([int(rate[0]), int(rate[1]), int(rate[2])])
     return rates

#生成使用者評分資料結構
def getUserScoreDataStructure(rates):
    
#listUser2Score[2]=[(1,5),(4,2)].... 表示使用者2對電影1的評分是5,對電影4的評分是2
     listUser2Score = {}
#dictItem2Users{},key=item id,value=user id list
     dictItem2Users = {}
     for k in rates:
#  u.data
#  user id | item id | rating | timestamp.
          user_rank = (k[1], k[2])
          if k[0] in listUser2Score:
               listUser2Score[k[0]].append(user_rank)
          else:
               listUser2Score[k[0]] = [user_rank]

          if k[1] in dictItem2Users:
               dictItem2Users[k[1]].append(k[0])
          else:
               dictItem2Users[k[1]] = [k[0]]
     return listUser2Score, dictItem2Users

#計算與目標使用者
def getNearestNeighbor(userId, listUser2Score, dictItem2Users):
     neighbors = []
#listUser2Score[2]=[(1,5),(4,2)].... 表示使用者2對電影1的評分是5,對電影4的評分是2
# 對於目標使用者userId的每一個評價過的專案item
     for item in listUser2Score[userId]:
#dictItem2Users{},key=item id,value=user id list
#item[0]表示電影id,item[1]表示電影評分
#dictItem2Users[item[0]]=dictItem2Users[電影id]=value=評價過這個電影的使用者列表
#從評價過這個電影的使用者列表裡,計算目標使用者和這個列表裡邊所有使用者的相似度
          for neighbor in dictItem2Users[item[0]]:
#如果這個鄰居不是目標使用者並且這個鄰居還沒有被加入鄰居集就加進來
               if neighbor != userId and neighbor not in neighbors:
                    neighbors.append(neighbor)
     neighbors_dist = []
#裡邊儲存的是[相似度,鄰居id]
     for neighbor in neighbors:
#listUser2Score[2]=[(1,5),(4,2)].... 表示使用者2對電影1的評分是5,對電影4的評分是2
          dist = getCosDist(listUser2Score[userId], listUser2Score[neighbor])
          neighbors_dist.append([dist, neighbor])
#  按照相似度倒排,相似度從道到低
     neighbors_dist.sort(reverse = True)
     return neighbors_dist

#使用UserFC進行推薦,輸入:檔名,使用者ID,鄰居數量
def recommendByUserFC(filename, userId, k = 5):
#讀取檔案
     contents = readFile(filename)
#檔案格式資料轉化為二維陣列
     rates = getRatingInfo(contents)
#格式化成字典資料
     listUser2Score, dictItem2Users = getUserScoreDataStructure(rates)
#找鄰居
#找出最相似的前五個鄰居
     neighborsTopK = getNearestNeighbor(userId, listUser2Score, dictItem2Users)[:5]
#neighborsTopK儲存了相似度和鄰居id的倒排表
#所以neighbor[1]表示鄰居id,neighbor[0]表示相似度
#這裡的推薦思路是:對於最近k鄰居看過的所有電影中的某一電影m
#如果m僅僅被一個鄰居看過,那麼目標使用者對此電影的的興趣度就是目標使用者和這個鄰居的相似度
#如果m被多個鄰居看過,那麼目標使用者對此電影的相似度為目標使用者與這些鄰居相似度之和
#建立推薦字典
     recommand_dict = {}
     for neighbor in neighborsTopK:
          neighbor_user_id = neighbor[1]
#找出這個鄰居看過的所有電影資訊 
          movies = listUser2Score[neighbor_user_id]
          for movie in movies:
               if movie[0] not in recommand_dict:
                    recommand_dict[movie[0]] = neighbor[0]
               else:
                    recommand_dict[movie[0]] += neighbor[0]
#建立推薦列表
     recommand_list = []
     for key in recommand_dict:
#建立目標使用者興趣度-電影id的倒排表    
          recommand_list.append([recommand_dict[key], key])
     recommand_list.sort(reverse = True)
#listUser2Score列表裡邊的元素是電影id到電影評分的鍵值對
#所以這裡的k[0]表示電影id,k[1]表示電影評分
     user_movies = [k[0] for k in listUser2Score[userId]]
#recommand_list儲存的是目標使用者興趣度到電影id的倒排表
#所以這裡的的k[1]表示的是電影id,k[0]表示的是興趣度
     return [k[1] for k in recommand_list], user_movies, dictItem2Users, neighbors

#獲取電影的列表
def getMovieList(filename):
     contents = readFile(filename)
     movies_info = {}
     for movie in contents:
          single_info = movie.split("|")
          movies_info[int(single_info[0])] = single_info[1:]
     return movies_info

#從這裡開始執行    
if __name__ == '__main__':
     reload(sys)
     sys.setdefaultencoding('utf-8')
#獲取所有電影的列表,所有電影id到電影名字的鍵值對
     dictMovieId2Info = getMovieList("u.item")
     listRecommendMovieId, user_movie, items_movie, neighbors = recommendByUserFC("u.data", 50, 80)
     neighbors_id=[ i[1] for i in neighbors]
     table = Texttable()
     table.set_deco(Texttable.HEADER)
     table.set_cols_dtype(['t', 't', 't'])
     table.set_cols_align(["l", "l", "l"])
     rows=[]
     rows.append([u"movie name",u"release time", u"from userid"])
#列印推薦列表的前20項資料,listRecommendMovieId裡邊儲存的僅僅是id
     for movie_id in listRecommendMovieId[:20]:
          from_user=[]
          for user_id in items_movie[movie_id]:
               if user_id in neighbors_id:
                    from_user.append(user_id)
#dictMovieId2Info[movie_id][0]表示電影名 dictMovieId2Info[movie_id][1]表示時間
          rows.append([dictMovieId2Info[movie_id][0],dictMovieId2Info[movie_id][1],from_user])
     table.add_rows(rows)
     print table.draw()