最近,因為導(dǎo)師項目需要,花了幾天時間學(xué)習(xí)了項亮的《推薦系統(tǒng)實踐》,并用python實現(xiàn)了書上的Item Collaborative Filtering即基于物品的協(xié)同過濾算法,發(fā)現(xiàn)很多博客在算法的代碼的實現(xiàn)上說得很籠統(tǒng),而且項亮的書中關(guān)于協(xié)同過濾的代碼實現(xiàn)寫得又很零碎,故寫此文總結(jié)。
什么是協(xié)同過濾
協(xié)同過濾是推薦系統(tǒng)中最經(jīng)典和常用的算法,其核心思想就是它的名字:利用所有用戶的歷史行為數(shù)據(jù),用戶通過不斷地和網(wǎng)站互動,使推薦列表能夠不斷過濾掉用戶不感興趣的物品,從而越來越滿足需求。
協(xié)同過濾分為兩大類:
- 基于用戶的協(xié)同過濾(UserCF):當(dāng)要給目標(biāo)用戶進(jìn)行推薦時,先找出與他相似的其他用戶,再從那些用戶喜歡的項目中找出目標(biāo)用戶沒有看過的項目推薦給他。
- 基于項目的協(xié)同過濾(ItemCF):先找出目標(biāo)用戶歷史觀看列表里與之相似的其他項目,再對這些相似項目進(jìn)行排序并生成最后的推薦列表。
評分預(yù)測還是TopN推薦?
評分預(yù)測和TopN推薦是推薦系統(tǒng)兩種衡量預(yù)測準(zhǔn)確度的指標(biāo),大多數(shù)的推薦研究都是基于評分預(yù)測來展開的,主要是因為從事這方面早期研究的組織GroupLens主要就是針對電影評分進(jìn)行的,而且Netflix等推薦算法的大賽也主要是在解決評分預(yù)測問題,然而,評分高就是好的推薦嗎?當(dāng)然不是,有時候用戶給出好的評分并不代表他最想看它或者購買它。
部分代碼實現(xiàn)
- 記載數(shù)據(jù)集并劃分訓(xùn)練集與測試集
def get_dataset(self, filename, pivot=0.75):
trainSet_len = 0
testSet_len = 0
for line in self.load_file(filename):
user, movie, rating, timestamp = line.split(',')
if(random.random() < pivot):
self.trainSet.setdefault(user, {})
self.trainSet[user][movie] = rating
trainSet_len += 1
else:
self.testSet.setdefault(user, {})
self.testSet[user][movie] = rating
testSet_len += 1
print('劃分訓(xùn)練集與測試集成功!')
print('訓(xùn)練集長 = %s' % trainSet_len)
print('測試集長 = %s' % testSet_len)
這里的pivot(樞軸)因子將數(shù)據(jù)集以3:1分成了訓(xùn)練集與數(shù)據(jù)集。
- 計算電影之間的相似度并生成相似矩陣
def calc_movie_sim(self):
#建立movies_popular字典
for user, movies in self.trainSet.items():
for movie in movies:
#若該movie沒在movies_popular字典中,則把其插入字典并賦值為0,否則+1,最終的movie_popular字典鍵為電影名,值為所有用戶總的觀看數(shù)
if movie not in self.movie_popular:
self.movie_popular[movie] = 0
else:
self.movie_popular[movie] += 1
self.movie_count = len(self.movie_popular)
print("訓(xùn)練集中電影總數(shù) = %d" % self.movie_count)
for user, movies in self.trainSet.items():
for m1 in movies:
for m2 in movies:
if m1 == m2:
continue
#下面三步的作用是:分別將每個用戶看過的每一部電影與其他所有電影的聯(lián)系值置1,若之后又有用戶同時看了兩部電影, 則+1
self.movie_sim_matrix.setdefault(m1, {})
self.movie_sim_matrix[m1].setdefault(m2, 0)
self.movie_sim_matrix[m1][m2] += 1
print("建立電影的相似矩陣成功!")
# print("矩陣進(jìn)行相似計算前movieId=1的一行為:")
# print(self.movie_sim_matrix['1'])
# 計算電影之間的相似性
for m1, related_movies in self.movie_sim_matrix.items():
for m2, count in related_movies.items():
# 注意0向量的處理,即某電影的用戶數(shù)為0
if self.movie_popular[m1] == 0 or self.movie_popular[m2] == 0:
self.movie_sim_matrix[m1][m2] = 0
else:
self.movie_sim_matrix[m1][m2] = count / math.sqrt(self.movie_popular[m1] * self.movie_popular[m2])
print('計算電影的相似矩陣成功!')
# print("電影相似矩陣中movieId=736的一行:")
# print(self.movie_sim_matrix['736'])
- 生成推薦列表
def recommend(self, user):
K = int(self.n_sim_movie)
N = int(self.n_rec_movie)
rank = {}
watched_movies = self.trainSet[user]
for movie, rating in watched_movies.items():
#對目標(biāo)用戶每一部看過的電影,從相似電影矩陣中取與這部電影關(guān)聯(lián)值最大的前K部電影,若這K部電影用戶之前沒有看過,則把它加入rank字典中,其鍵為movieid名,其值(即推薦度)為w(相似電影矩陣的值)與rating(用戶給出的每部電影的評分)的乘積
for related_movie, w in sorted(self.movie_sim_matrix[movie].items(), key=itemgetter(1), reverse=True)[:K]:
if related_movie in watched_movies:
continue
rank.setdefault(related_movie, 0)
#計算推薦度
rank[related_movie] += w * float(rating)
return sorted(rank.items(), key=itemgetter(1), reverse=True)[:N]
4.評估算法
def evaluate(self):
N = int(self.n_rec_movie)
reuserN = input("請輸入?yún)⑴c評估的用戶數(shù)量:(總用戶數(shù)為457)")
reuserN = int(reuserN)
# 準(zhǔn)確率和召回率
hit = 0
rec_count = 0
test_count = 0
# 覆蓋率
all_rec_movies = set()
for user,m in list(self.trainSet.items())[:reuserN]:
test_moives = self.testSet.get(user, {})
rec_movies = self.recommend(user)
print("用戶 %s 的電影推薦列表為:" % user)
self.precommend(rec_movies)
#注意,這里的w與上面recommend的w不要一樣,上面的w是指計算出的相似電影矩陣的權(quán)值,而這里是這推薦字典rank對應(yīng)的推薦度
for movie, w in rec_movies:
if movie in test_moives:
hit += 1
all_rec_movies.add(movie)
rec_count += N
test_count += len(test_moives)
precision = hit / (1.0 * rec_count)
recall = hit / (1.0 * test_count)
coverage = len(all_rec_movies) / (1.0 * self.movie_count)
print('準(zhǔn)確率=%.4f\t召回率=%.4f\t覆蓋率=%.4f' % (precision, recall, coverage))