sklearn的快速使用之九(推薦演算法)
"""
==============
構建電影推薦系統
==============
http://blog.csdn.net/u013185349/article/details/61192218
"""
print(__doc__)
# 電影打分資料
data = {'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5,
'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5,
'The Night Listener': 3.0},
'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5,
'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0,
'You, Me and Dupree': 3.5},
'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
'Superman Returns': 3.5, 'The Night Listener': 4.0},
'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
'The Night Listener': 4.5, 'Superman Returns': 4.0,
'You, Me and Dupree': 2.5},
'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
'You, Me and Dupree': 2.0},
'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
'Toby': {'Snakes on a Plane': 4.5, 'You, Me and Dupree': 1.0, 'Superman Returns': 4.0}
}
#
# 因為計算以電影物品為主,所以先將上面data[user][movie]資料轉換成newdata[movie][user]格式,
# 也即二維矩陣進行行列對換
#
def transformdata(data):
'''
物品之間的相似度 與 使用者之間的相似度 求解 一樣。故只需要將使用者換成物品即可
'''
newdata = {}
for person in data:
for movie in data[person]:
# 初始化
newdata.setdefault(movie, {})
# 物品與使用者對調
newdata[movie][person] = data[person][movie] # 字典可以直接寫[key],就表示插入key值了。非常簡便
return newdata
print("1. 電影打分:")
print(transformdata(data))
print("")
from math import sqrt
def sim_distance(data, person1, person2):
'''歐氏距離求相似度,距離越大,越相似'''
commonmovies = [movie for movie in data[person1] if movie in data[person2]]
if len(commonmovies) == 0: return 0
# 平方和
sumSq = sum([pow(data[person1][movie] - data[person2][movie], 2) for movie in commonmovies])
# 使最終結果是,越相似,距離越大。所以將上面距離取倒數即可
sim = 1 / (1 + sqrt(sumSq))
return sim
def sim_pearson(data, person1, person2):
'''
計算上面格式的資料 裡的 兩個使用者 相似度.
基於使用者過濾思路:找出兩個使用者看過的相同電影的評分,從而進行按pearson公式求值。那些非公共電影不列入求相似度值範圍。
基於物品過濾思路:找過兩部電影相同的觀影人給出的評分,從而按pearson公式求值
返回:評分的相似度,[-1,1]範圍,0最不相關,1,-1為正負相關,等於1時,表示兩個使用者完全一致評分
這裡的data格式很重要,這裡計算相似度是嚴格按照上面data格式所算。
此字典套字典格式,跟部落格計算單詞個數 儲存格式一樣
'''
# 計算pearson係數,先要收集兩個使用者公共電影名單
# commonmovies = [ movie for movie in data[person1] if movie in data[person2]] 分解步驟為如下:
commonmovies = [] # 改成列表呢
for movie in data[person1]: # data[person1]是字典,預設第一個元素 in (字典)是指 key.所以這句話是指 對data[person1]字典裡遍歷每一個key=movie
if movie in data[person2]: # data[person2]也是字典,表示該字典有key是movie.
commonmovies.append(movie) # commonmovie是 兩個使用者的公共電影名的列表
# 看過的公共電影個數
n = float(len(commonmovies))
if n == 0:
return 0
'''下面正是計算pearson係數公式 '''
# 分佈對兩個使用者的公共電影movie分數總和
sum1 = sum([data[person1][movie] for movie in commonmovies])
sum2 = sum([data[person2][movie] for movie in commonmovies])
# 計算乘積之和
sum12 = sum([data[person1][movie] * data[person2][movie] for movie in commonmovies])
# 計算平方和
sum1Sq = sum([pow(data[person1][movie], 2) for movie in commonmovies])
sum2Sq = sum([pow(data[person2][movie], 2) for movie in commonmovies])
# 計算分子
num = sum12 - sum1 * sum2 / n
# 分母
den = sqrt((sum1Sq - pow(sum1, 2) / n) * (sum2Sq - pow(sum2, 2) / n))
if den == 0: return 0
return num / den
def topmatches(data, givenperson, returnernum=5, simscore=sim_pearson):
'''
使用者匹配推薦:給定一個使用者,返回對他口味最匹配的其他使用者
物品匹配: 給定一個物品,返回相近物品
輸入引數:對person進行預設推薦num=5個使用者(基於使用者過濾),或是返回5部電影物品(基於物品過濾),相似度計算用pearson計算
'''
# 建立最終結果列表
usersscores = [(simscore(data, givenperson, other), other) for other in data if other != givenperson]
# 對列表排序
usersscores.sort(key=None, reverse=True)
return usersscores[0:returnernum]
moviedata = transformdata(data)
print("2. 找出跟“超人迴歸”這電影相關的電影:")
print(topmatches(moviedata, 'Superman Returns'))
print("")
def calSimilarItems(data, num=10):
# 以物品為中心,對偏好矩陣轉置
moviedata = transformdata(data)
ItemAllMatches = {}
for movie in moviedata:
ItemAllMatches.setdefault(movie, [])
# 對每個電影 都求它的匹配電影集,求電影之間的距離用pearson距離
ItemAllMatches[movie] = topmatches(moviedata, movie, num, simscore=sim_pearson)
return ItemAllMatches
print("3. 列出所有電影之間的相關性:")
print(calSimilarItems(data))
print("")
"""
推薦使用者沒看過的電影
某一部未看過電影分數= sum(該部未看過的電影與每一部已看電影之間相似度*已看電影的評分)/sum(未看電影與每一部已看電影之間相似度)
例如:未看電影A,已看電影B,C:
則,電影A分數 = [sim(A,B)*rating(B) +sim(A,C)*rating(C)] / [ sim(A,B) + sim(A,C)]
"""
def getrecommendations(data, targetperson, moviesAllsimilarity):
'''
輸入movieAllSimilarity就是上面calsimilarItems已經計算好的所有物品之間的相似度資料集:
'''
# 獲得所有物品之間的相似資料集
scoresum = {}
simsum = {}
# 遍歷所有看過的電影
for watchedmovie in data[targetperson]:
rating = data[targetperson][watchedmovie]
# 遍歷與當前電影相近的電影
for (similarity, newmovie) in moviesAllsimilarity[watchedmovie]: # 取一對元組
# 已經對當前物品評價過,則忽略
if newmovie in data[targetperson]: continue
scoresum.setdefault(newmovie, 0)
simsum.setdefault(newmovie, 0)
# 全部相似度求和
simsum[newmovie] += similarity
# 評價值與相似度加權之和
scoresum[newmovie] += rating * similarity
rankings = [(score / simsum[newmovie], newmovie) for newmovie, score in scoresum.items()]
rankings.sort(key=None, reverse=True)
return rankings
itemsAllsim = calSimilarItems(data) # 這個值會事先計算好
print('4. 基於物品過濾,為使用者Toby推薦的電影是:')
print(getrecommendations(data, 'Toby', itemsAllsim))
print()
print("5. 為使用者Toby推薦品味相當的使用者:")
print(topmatches(data, 'Toby', 3))
print()
"""
推薦未看過的電影:
未看過電影分數=sum(被推薦使用者與其他使用者之間相似度*使用者對該電影評分)/sum(被推薦使用者與其他使用者之間相似度)
"""
def recommendItems(data, givenperson, num=5, simscore=sim_pearson):
'''
物品推薦:給定一個使用者person,預設返回num=5物品
要兩個for,對使用者,物品 都進行 遍歷
'''
# 所有變數儘量用字典,凡是列表能表示的字典都能表示,那何不用字典
itemsimsum = {}
# 存給定使用者沒看過的電影的其他使用者評分加權
itemsum = {}
# 遍歷每個使用者,然後遍歷該使用者每個電影
for otheruser in data:
# 不要和自己比較
if otheruser == givenperson: continue
# 忽略相似度=0或小於0情況
sim = simscore(data, givenperson, otheruser)
if sim <= 0: continue
for itemmovie in data[otheruser]:
# 只對使用者沒看過的電影進行推薦,參考了其他使用者的評價值(協同物品過濾是參考了歷史物品相似度值)
if itemmovie not in data[givenperson]:
# 一定要初始化字典:初始化itemsum與itemsimsum
itemsum.setdefault(itemmovie, 0)
itemsimsum.setdefault(itemmovie, 0)
# 使用者相似度*評價值
itemsum[itemmovie] += sim * data[otheruser][itemmovie]
itemsimsum[itemmovie] += sim
# 最終結果列表,列表包含一元組(item,分數)
rankings = [(itemsum[itemmovie] / itemsimsum[itemmovie], itemmovie) for itemmovie in itemsum]
# 結果排序
rankings.sort(key=None, reverse=True);
return rankings
# 呼叫此方法如下:
print("6. 為使用者Toby推薦未看過的電影:")
print(recommendItems(data, 'Toby', 3))
print()