#!/usr/bin/env python
# coding: utf-8
# # 推荐系统
#
# - 音乐数据处理
#
# - 基于商品相似性的推荐
#
# - 基于SVD矩阵分解的推荐
# In[1]:
import os
os.getcwd()
# ## 数据读取
# In[2]:
import pandas as pd
import numpy as np
import time
import sqlite3
data_home = './'
# 在数据中只需要用户,歌曲,播放量
# In[4]:
get_ipython().run_cell_magic('time', '', "triplet_dataset = pd.read_csv(filepath_or_buffer=data_home+'train_triplets.txt', \n sep='\\t', header=None, \n names=['user','song','play_count'])")
# In[5]:
get_ipython().run_cell_magic('time', '', 'triplet_dataset.shape')
# In[6]:
get_ipython().run_cell_magic('time', '', 'triplet_dataset.info()')
# In[7]:
get_ipython().run_cell_magic('time', '', 'triplet_dataset.head(n=10)')
# In[8]:
get_ipython().run_cell_magic('time', '', "triplet_dataset['play_count'] = triplet_dataset['play_count'].astype('int32')")
# In[9]:
triplet_dataset.info()
# In[10]:
triplet_dataset.head(n=10)
# In[11]:
get_ipython().run_cell_magic('time', '', 'triplet_dataset.count()')
# ## 对每一个用户,分别统计他的播放总量
# In[12]:
get_ipython().run_cell_magic('time', '', "output_dict = {}\n# b80344d063b5ccb3212f76538f3d9e43d87dca9e \tSOBXHDL12A81C204C0 \t1\nwith open(data_home+'train_triplets.txt') as f:\n for line_number, line in enumerate(f):\n user = line.split('\\t')[0] # b80344d063b5ccb3212f76538f3d9e43d87dca9e\n play_count = int(line.split('\\t')[2]) # 1\n if user in output_dict: # 如果这个用在字典中 \n play_count +=output_dict[user] # 将play_count播放量累加\n output_dict.update({user:play_count}) # 更新该用户的累计播放量\n output_dict.update({user:play_count}) # 给该用户初始赋值\noutput_list = [{'user':k,'play_count':v} for k,v in output_dict.items()] # 将字典转换成一个列表\nplay_count_df = pd.DataFrame(output_list) # 将列表转换成dataframe\nplay_count_df = play_count_df.sort_values(by = 'play_count', ascending = False)\nplay_count_df.head(10)")
# In[16]:
play_count_df.count()
# In[13]:
play_count_df.head(10)
# In[14]:
get_ipython().run_cell_magic('time', '', "play_count_df.to_csv(path_or_buf='user_playcount_df.csv', index = False)")
# ## 对于每一首歌,分别统计它的播放总量
# In[15]:
get_ipython().run_cell_magic('time', '', "output_dict = {}\nwith open(data_home+'train_triplets.txt') as f:\n for line_number, line in enumerate(f):\n song = line.split('\\t')[1]\n play_count = int(line.split('\\t')[2])\n if song in output_dict:\n play_count +=output_dict[song]\n output_dict.update({song:play_count})\n output_dict.update({song:play_count})\noutput_list = [{'song':k,'play_count':v} for k,v in output_dict.items()]\nsong_count_df = pd.DataFrame(output_list)\nsong_count_df = song_count_df.sort_values(by = 'play_count', ascending = False)")
# In[18]:
song_count_df.head(10)
# song_count_df.tail(10)
# In[19]:
song_count_df.count()
# In[20]:
get_ipython().run_cell_magic('time', '', "song_count_df.to_csv(path_or_buf='song_playcount_df.csv', index = False)")
# ## 看看目前的排行情况
# In[21]:
play_count_df = pd.read_csv(filepath_or_buffer='user_playcount_df.csv')
play_count_df.head(n =10)
# In[22]:
song_count_df = pd.read_csv(filepath_or_buffer='song_playcount_df.csv')
song_count_df.head(10)
# ## 取其中一部分数(按大小排好序的了,这些应该是比较重要的数据),作为我们的实验数据
# In[23]:
get_ipython().run_cell_magic('time', '', 'total_play_count = sum(song_count_df.play_count) # 统计所有歌曲的播放总量\nprint ((float(play_count_df.head(n=100000).play_count.sum())/total_play_count)*100) # 播放量前10w的用户播放总量/所有歌曲播放总量\nplay_count_subset = play_count_df.head(n=100000) # 将前10w的用户播放量保存成一个新的df')
# In[24]:
get_ipython().run_cell_magic('time', '', '(float(song_count_df.head(n=30000).play_count.sum())/total_play_count)*100 # 前3w首最热门的歌曲的播放总量/所有歌曲的播放总量')
# In[25]:
song_count_subset = song_count_df.head(n=30000)
# 取10W个用户,3W首歌
# In[26]:
user_subset = list(play_count_subset.user)
song_subset = list(song_count_subset.song)
# 过滤掉其他用户数据
# In[ ]:
get_ipython().run_cell_magic('time', '', "triplet_dataset = pd.read_csv(filepath_or_buffer=data_home+'train_triplets.txt',sep='\\t', \n header=None, names=['user','song','play_count'])\ntriplet_dataset_sub = triplet_dataset[triplet_dataset.user.isin(user_subset) ]\ndel(triplet_dataset)\ntriplet_dataset_sub_song = triplet_dataset_sub[triplet_dataset_sub.song.isin(song_subset)]\ndel(triplet_dataset_sub)")
# In[18]:
triplet_dataset_sub_song.to_csv(path_or_buf=data_home+'triplet_dataset_sub_song.csv', index=False)
# 我们的数据量
# In[19]:
triplet_dataset_sub_song.shape
# In[20]:
triplet_dataset_sub_song.head(n=10)
# ## 加入音乐详细信息
# .db文件需要稍微处理下 转换成csv
# In[21]:
conn = sqlite3.connect(data_home+'track_metadata.db')
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
cur.fetchall()
# In[22]:
track_metadata_df = pd.read_sql(con=conn, sql='select * from songs')
track_metadata_df_sub = track_metadata_df[track_metadata_df.song_id.isin(song_subset)]
# In[23]:
track_metadata_df_sub.to_csv(path_or_buf=data_home+'track_metadata_df_sub.csv', index=False)
# In[24]:
track_metadata_df_sub.shape
# ## 我们现有的数据
# In[25]:
# 用户 音乐 播放量 10w用户 3w歌曲
triplet_dataset_sub_song = pd.read_csv(filepath_or_buffer=data_home+'triplet_dataset_sub_song.csv',encoding = "ISO-8859-1")
# 3w歌曲的信息
track_metadata_df_sub = pd.read_csv(filepath_or_buffer=data_home+'track_metadata_df_sub.csv',encoding = "ISO-8859-1")
# In[26]:
triplet_dataset_sub_song.head()
# In[27]:
# 音乐元数据的信息
track_metadata_df_sub.head()
# ## 清洗数据集
# 去除掉无用的和重复的
# In[28]:
del(track_metadata_df_sub['track_id'])
del(track_metadata_df_sub['artist_mbid']) # 删除字段
track_metadata_df_sub = track_metadata_df_sub.drop_duplicates(['song_id']) # 去重
# merge合并 triplet_dataset_sub_song用户id音乐id播放量 track_metadata_df_sub音乐id+各种信息
triplet_dataset_sub_song_merged = pd.merge(triplet_dataset_sub_song, track_metadata_df_sub, how='left', left_on='song', right_on='song_id')
triplet_dataset_sub_song_merged.rename(columns={'play_count':'listen_count'},inplace=True)
# In[29]:
del(triplet_dataset_sub_song_merged['song_id'])
del(triplet_dataset_sub_song_merged['artist_id'])
del(triplet_dataset_sub_song_merged['duration'])
del(triplet_dataset_sub_song_merged['artist_familiarity'])
del(triplet_dataset_sub_song_merged['artist_hotttnesss'])
del(triplet_dataset_sub_song_merged['track_7digitalid'])
del(triplet_dataset_sub_song_merged['shs_perf'])
del(triplet_dataset_sub_song_merged['shs_work'])
# 搞定数据
# In[31]:
triplet_dataset_sub_song_merged.head(n=10)
# ## 瞅瞅音乐数据集的情况
# ### 最流行的歌曲
# In[32]:
# 返回歌曲名 以及歌曲的播放量
popular_songs = triplet_dataset_sub_song_merged[['title','listen_count']].groupby('title').sum().reset_index()
# 按照播放量排序 倒序 取前20
popular_songs_top_20 = popular_songs.sort_values('listen_count', ascending=False).head(n=20)
import matplotlib.pyplot as plt; plt.rcdefaults()
import numpy as np
import matplotlib.pyplot as plt
objects = (list(popular_songs_top_20['title'])) # 20首歌的名字
y_pos = np.arange(len(objects)) # 0-19
performance = list(popular_songs_top_20['listen_count']) # 20首歌的播放量
plt.bar(y_pos, performance, align='center', alpha=0.5)
plt.xticks(y_pos, objects, rotation='vertical')
plt.ylabel('Item count')
plt.title('Most popular songs')
plt.show()
# ### 最受欢迎的releases
# In[33]:
popular_release = triplet_dataset_sub_song_merged[['release','listen_count']].groupby('release').sum().reset_index()
popular_release_top_20 = popular_release.sort_values('listen_count', ascending=False).head(n=20)
objects = (list(popular_release_top_20['release']))
y_pos = np.arange(len(objects))
performance = list(popular_release_top_20['listen_count'])
plt.bar(y_pos, performance, align='center', alpha=0.5)
plt.xticks(y_pos, objects, rotation='vertical')
plt.ylabel('Item count')
plt.title('Most popular Release')
plt.show()
# ## 最受欢迎的歌手
# In[34]:
popular_artist = triplet_dataset_sub_song_merged[['artist_name','listen_count']].groupby('artist_name').sum().reset_index()
popular_artist_top_20 = popular_artist.sort_values('listen_count', ascending=False).head(n=20)
objects = (list(popular_artist_top_20['artist_name']))
y_pos = np.arange(len(objects))
performance = list(popular_artist_top_20['listen_count'])
plt.bar(y_pos, performance, align='center', alpha=0.5)
plt.xticks(y_pos, objects, rotation='vertical')
plt.ylabel('Item count')
plt.title('Most popular Artists')
plt.show()
# ## 用户播放量的分布
# In[35]:
user_song_count_distribution = triplet_dataset_sub_song_merged[['user','title']].groupby('user').count().reset_index().sort_values(
by='title',ascending = False)
user_song_count_distribution.title.describe()
# In[36]:
x = user_song_count_distribution.title
n, bins, patches = plt.hist(x, 50, facecolor='green', alpha=0.75)
plt.xlabel('Play Counts')
plt.ylabel('Num of Users')
plt.title(r'$\mathrm{Histogram\ of\ User\ Play\ Count\ Distribution}\ $')
plt.grid(True)
plt.show()
# # 推荐系统
# In[38]:
import Recommenders as Recommenders
from sklearn.model_selection import train_test_split
# ## 简单暴力,排行榜单推荐
# In[39]:
triplet_dataset_sub_song_merged_set = triplet_dataset_sub_song_merged
train_data, test_data = train_test_split(triplet_dataset_sub_song_merged_set, test_size = 0.40, random_state=0)
# In[40]:
train_data.head()
# In[41]:
def create_popularity_recommendation(train_data, user_id, item_id):
#Get a count of user_ids for each unique song as recommendation score
# #根据指定的特征来统计其播放情况,可以选择歌曲名,专辑名,歌手名
train_data_grouped = train_data.groupby([item_id]).agg({user_id: 'count'}).reset_index() # 按照歌曲名称分组,统计每组的数量
# #为了直观展示,我们用得分来表示其结果
train_data_grouped.rename(columns = {user_id: 'score'},inplace=True) # 将每个歌曲的用户量重命名为歌曲的得分
#Sort the songs based upon recommendation score
# #排行榜单需要排序
train_data_sort = train_data_grouped.sort_values(['score', item_id], ascending = [0,1])
#Generate a recommendation rank based upon score
#加入一项排行等级,表示其推荐的优先级
train_data_sort['Rank'] = train_data_sort['score'].rank(ascending=0, method='first')
#Get the top 10 recommendations
popularity_recommendations = train_data_sort.head(20)
return popularity_recommendations
# In[42]:
recommendations = create_popularity_recommendation(triplet_dataset_sub_song_merged,'user','title')
# In[43]:
recommendations
# ## 基于歌曲相似度的推荐
# 选择一小部分歌曲来实验
# In[44]:
song_count_subset = song_count_df.head(n=5000) # 获取全部歌曲的前5000条
user_subset = list(play_count_subset.user) # 获取10w用户
song_subset = list(song_count_subset.song) # 获取5000歌曲id
# triplet_dataset_sub_song_merged为用户 歌曲 播放量 + 歌曲信息
triplet_dataset_sub_song_merged_sub = triplet_dataset_sub_song_merged[triplet_dataset_sub_song_merged.song.isin(song_subset)]
# In[45]:
triplet_dataset_sub_song_merged_sub.head()
# In[46]:
train_data, test_data = train_test_split(triplet_dataset_sub_song_merged_sub, test_size = 0.30, random_state=0)
is_model = Recommenders.item_similarity_recommender_py()
is_model.create(train_data, 'user', 'title')
user_id = list(train_data.user)[7]
user_items = is_model.get_user_items(user_id)
# In[47]:
#Recommend songs for the user using personalized model
is_model.recommend(user_id)
# ## 基于矩阵分解(SVD)的推荐
# <img src="1.png" style="width:550px;height:280px;float:left">
# <img src="5.png" style="width:350px;height:280px;float:left">
# 对矩阵进行SVD分解,将得到USV
# <img src="2.png" style="width:500px;height:380px;float:left"><img src="3.png" style="width:400px;height:200px;float:left">
# 重新计算 U*S*V的结果得到A2 来比较下A2和A的差异,看起来差异是有的,但是并不大,所以我们可以近似来代替
# <img src="4.png" style="width:330px;height:220px;float:left">
# <img src="5.png" style="width:330px;height:220px;float:left">
# <img src="6.png" style="width:650px;height:480px;float:left">
# <img src="7.png" style="width:650px;height:480px;float:left">
# 先计算歌曲被当前用户播放量 / 用户播放总量 当做分值
# In[48]:
triplet_dataset_sub_song_merged_sum_df = triplet_dataset_sub_song_merged[['user','listen_count']].groupby('user').sum().reset_index()
triplet_dataset_sub_song_merged_sum_df.rename(columns={'listen_count':'total_listen_count'},inplace=True)
triplet_dataset_sub_song_merged = pd.merge(triplet_dataset_sub_song_merged,triplet_dataset_sub_song_merged_sum_df)
triplet_dataset_sub_song_merged.head()
# In[49]:
triplet_dataset_sub_song_merged['fractional_play_count'] = triplet_dataset_sub_song_merged['listen_count']/triplet_dataset_sub_song_merged['total_listen_count']
# 大概是这样
# In[50]:
triplet_dataset_sub_song_merged[triplet_dataset_sub_song_merged.user =='d6589314c0a9bcbca4fee0c93b14bc402363afea'][['user','song','listen_count','fractional_play_count']].head()
# In[51]:
from scipy.sparse import coo_matrix
small_set = triplet_dataset_sub_song_merged
user_codes = small_set.user.drop_duplicates().reset_index()
song_codes = small_set.song.drop_duplicates().reset_index()
user_codes.rename(columns={'index':'user_index'}, inplace=True)
song_codes.rename(columns={'index':'song_index'}, inplace=True)
song_codes['so_index_value'] = list(song_codes.index)
user_codes['us_index_value'] = list(user_codes.index)
small_set = pd.merge(small_set,song_codes,how='left')
small_set = pd.merge(small_set,user_codes,how='left')
mat_candidate = small_set[['us_index_value','so_index_value','fractional_play_count']]
data_array = mat_candidate.fractional_play_count.values
row_array = mat_candidate.us_index_value.values
col_array = mat_candidate.so_index_value.values
data_sparse = coo_matrix((data_array, (row_array, col_array)),dtype=float)
# In[52]:
data_sparse
# In[53]:
user_codes[user_codes.user =='2a2f776cbac6df64d6cb505e7e834e01684673b6']
# In[54]:
import math as mt
from scipy.sparse.linalg import * #used for matrix multiplication
from scipy.sparse.linalg import svds
from scipy.sparse import csc_matrix
# In[55]:
def compute_svd(urm, K):
U, s, Vt = svds(urm, K)
dim = (len(s), len(s))
S = np.zeros(dim, dtype=np.float32)
for i in range(0, len(s)):
S[i,i] = mt.sqrt(s[i])
U = csc_matrix(U, dtype=np.float32)
S = csc_matrix(S, dtype=np.float32)
Vt = csc_matrix(Vt, dtype=np.float32)
return U, S, Vt
def compute_estimated_matrix(urm, U, S, Vt, uTest, K, test):
rightTerm = S*Vt
max_recommendation = 250
estimatedRatings = np.zeros(shape=(MAX_UID, MAX_PID), dtype=np.float16)
recomendRatings = np.zeros(shape=(MAX_UID,max_recommendation ), dtype=np.float16)
for userTest in uTest:
prod = U[userTest, :]*rightTerm
estimatedRatings[userTest, :] = prod.todense()
recomendRatings[userTest, :] = (-estimatedRatings[userTest, :]).argsort()[:max_recommendation]
return recomendRatings
# In[56]:
K=50
urm = data_sparse
MAX_PID = urm.shape[1]
MAX_UID = urm.shape[0]
U, S, Vt = compute_svd(urm, K)
# In[57]:
uTest = [4,5,6,7,8,873,23]
uTest_recommended_items = compute_estimated_matrix(urm, U, S, Vt, uTest, K, True)
# In[58]:
for user in uTest:
print("Recommendation for user with user id {}". format(user))
rank_value = 1
for i in uTest_recommended_items[user,0:10]:
song_details = small_set[small_set.so_index_value == i].drop_duplicates('so_index_value')[['title','artist_name']]
print("The number {} recommended song is {} BY {}".format(rank_value, list(song_details['title'])[0],list(song_details['artist_name'])[0]))
rank_value+=1
# In[59]:
uTest = [27513]
#Get estimated rating for test user
print("Predictied ratings:")
uTest_recommended_items = compute_estimated_matrix(urm, U, S, Vt, uTest, K, True)
# In[60]:
for user in uTest:
print("Recommendation for user with user id {}". format(user))
rank_value = 1
for i in uTest_recommended_items[user,0:10]:
song_details = small_set[small_set.so_index_value == i].drop_duplicates('so_index_value')[['title','artist_name']]
print("The number {} recommended song is {} BY {}".format(rank_value, list(song_details['title'])[0],list(song_details['artist_name'])[0]))
rank_value+=1