使用Python的Pandas庫實現基於使用者的協同過濾推薦演算法
阿新 • • 發佈:2018-12-31
本文在下文的程式碼基礎上修改而來:
環境 | 版本 |
---|---|
Python | 3.5.5 |
Pandas | 0.22.0 |
import pandas as pd
df = None
def dataSet2Matrix(filename):
"""
匯入訓練資料
:param filename: 資料檔案路徑
"""
table_name = ['userId', 'movieId', 'rating', 'timestamp']
# 按照','分割讀取csv檔案
ratings = pd.read_table(filename, sep=',' , header=0, names=table_name)
global df
# 轉換成User-Item矩陣
df = ratings.pivot(index='userId', columns='movieId', values='rating')
你可以使用MovieLens提供的資料集,不過,為了便於闡述,這裡使用一個很小的測試資料集。
匯入test.csv這個測試資料,看dataSet2Matrix函式是否執行成功。你可以在這裡下載這個測試資料。
資料的格式為:使用者ID,電影ID,評分(5分制),時間戳
dataSet2Matrix('test.csv' )
df
userId/movieId | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
1 | 3.5 | 2.0 | NaN | 4.5 | 5.0 | 1.5 | 2.5 | 2.0 |
2 | 2.0 | 3.5 | 4.0 | NaN | 2.0 | 3.5 | NaN | 3.0 |
3 | 5.0 | 1.0 | 1.0 | 3.0 | 5.0 | 1.0 | NaN | NaN |
4 | 3.0 | 4.0 | 4.5 | NaN | 3.0 | 4.5 | 4.0 | 2.0 |
5 | NaN | 4.0 | 1.0 | 4.0 | NaN | NaN | 4.0 | 1.0 |
6 | NaN | 4.5 | 4.0 | 5.0 | 5.0 | 4.5 | 4.0 | 4.0 |
7 | 5.0 | 2.0 | NaN | 3.0 | 5.0 | 4.0 | 5.0 | NaN |
8 | 3.0 | NaN | NaN | 5.0 | 4.0 | 2.5 | 3.0 | 4.0 |
可以看到,成功將資料集轉化成了UI矩陣。
之後,我們需要構建共同評分矩陣。程式碼如下:
# 構建共同的評分向量
def build_xy(user_id1, user_id2):
bool_array = df.loc[user_id1].notnull() & df.loc[user_id2].notnull()
return df.loc[user_id1, bool_array], df.loc[user_id2, bool_array]
我們測試下userId分別為1和2的兩個使用者的共同評分矩陣:
print(build_xy(1,2))
(movieId
1 3.5
2 2.0
5 5.0
6 1.5
8 2.0
Name: 1, dtype: float64, movieId
1 2.0
2 3.5
5 2.0
6 3.5
8 3.0
Name: 2, dtype: float64)
對比UI矩陣,1和2的共同評分向量是正確的,即使用者1和使用者2都曾經對電影1、2、5、6、8做出過評價。
# 歐幾里德距離
def euclidean(user_id1, user_id2):
x, y = build_xy(user_id1, user_id2)
try:
value = sum((x - y)**2)**0.5
except ZeroDivisionError:
value = 0
return value
# 餘弦相似度
def cosine(user_id1, user_id2):
x, y = build_xy(user_id1, user_id2)
# 分母
denominator = (sum(x*x)*sum(y*y))**0.5
try:
value = sum(x*y)/denominator
except ZeroDivisionError:
value = 0
return value
# 皮爾遜相關係數
def pearson(user_id1, user_id2):
x, y = build_xy(user_id1, user_id2)
mean1, mean2 = x.mean(), y.mean()
# 分母
denominator = (sum((x-mean1)**2)*sum((y-mean2)**2))**0.5
try:
value = sum((x - mean1) * (y - mean2)) / denominator
except ZeroDivisionError:
value = 0
return value
我們來看一下使用者1和使用者2的皮爾遜相關係數
print(pearson(1,2))
-0.9040534990682686
metric_funcs = {
'euclidean': euclidean,
'pearson': pearson,
'cosine': cosine
}
# 計算最近的鄰居
def computeNearestNeighbor(user_id, metric='pearson', k=3):
"""
metric: 度量函式
k: 返回k個鄰居
返回:pd.Series,其中index是鄰居名稱,values是距離
"""
if metric in ['manhattan', 'euclidean']:
return df.drop(user_id).index.to_series().apply(metric_funcs[metric], args=(user_id,)).nsmallest(k)
elif metric in ['pearson', 'cosine']:
return df.drop(user_id).index.to_series().apply(metric_funcs[metric], args=(user_id,)).nlargest(k)
我們使用皮爾遜相似度計算一下與使用者3興趣最相近的3個使用者:
print(computeNearestNeighbor(3))
userId
1 0.819782
6 0.801784
7 0.766965
Name: userId, dtype: float64
# 向給定使用者推薦(返回:pd.Series)
def recommend(user_id):
# 找到距離最近的使用者id
nearest_user_id = computeNearestNeighbor(user_id, metric='cosine').index[0]
print('最近鄰使用者id:', nearest_user_id)
# 找出鄰居評價過、但自己未曾評價的樂隊(或商品)
# 結果:index是商品名稱,values是評分
return df.loc[nearest_user_id, df.loc[user_id].isnull() & df.loc[nearest_user_id].notnull()].sort_values()
嘗試對使用者3做出推薦:
recommend(3)
最近鄰使用者id: 1
movieId
8 2.0
7 2.5
Name: 1, dtype: float64