Python实现音乐推荐系统

发布时间:2023年12月26日
# 推荐系统
# 首先对音乐数据集进行数据清洗和特征提取,基于矩阵分解方式来进行音乐推荐。
#
# 音乐数据处理
# 读取音乐数据集,并统计其各项指标,选择有价值的信息当做我们的特征
#
# 基于商品相似性的推荐
# 选择相似度计算方法,通过相似度来计算推荐结果
#
# 基于SVD矩阵分解的推荐
# 使用矩阵分解方法,快速高效得到推荐结果

import pandas as pd
import numpy as np
import time
import sqlite3

data_home='./'

# 数据读取
# 在数据中只需要用户,歌曲,播放量
triplet_dataset = pd.read_csv(filepath_or_buffer=data_home+'train_triplets.txt',
                              sep='\t', header=None,
                              names=['user','song','play_count'])

# print(triplet_dataset.shape)
# print(triplet_dataset.info())

# 对每一个用户,分别统计他的播放总量
# 数据中有用户的编号,歌曲编号,已经用户对该歌曲播放的次数。
# 有了基础数据之后,我们还可以统计出关于用户与歌曲的各项指标,
# 例如对每一个用户,分别统计他的播放总量

output_dict={}
with open(data_home+'train_triplets.txt') as f:
    for line_number,line in enumerate(f):
#         找到当前用户
        user=line.split('\t')[0]
    #     得到其播放量数据
        play_count=int(line.split('\t')[2])
    #     如果字典中已经有该用户信息,在其基础上增加当前播放量
        if user in output_dict:
            play_count+=output_dict[user]
            output_dict.update({user:play_count})
        output_dict.update({user:play_count})
# 统计 用户-总播放量
output_list=[{'user':k,'play_count':v} for k,v in output_dict.items()]
# 切换成DF格式
play_count_df=pd.DataFrame(output_list)
# 排序
play_count_df=play_count_df.sort_values(by='play_count',ascending=False)

# 构建一个字典结构来统计不同用户分别播放的总数,
# 这需要我们把数据集遍历一遍。
# 当我们的数据集比较庞大的时候,每一步操作都可能花费较长时间,
# 后续操作中如果稍有不慎可能还得重头再来一遍,这就得不偿失了,
# 最好还是把中间结果保存下来,既然我们已经把结果转换成df格式,
# 直接使用to_csv()函数就可以完成保存的操作。
play_count_df.to_csv(path_or_buf='user_playcount_df.csv',index=False)
# 对应每一首哥,分别统计他的播放量
# 统计
output_dict={}
with open(data_home+'train_triplets.txt') as f:
    for line_number,line in enumerate(f):
#         找到当前歌曲
        song=line.split('\t')[1]
#          找到当前播放次数
        play_count=int(line.split('\t')[2])
#         统计每首歌曲播放的次数
        if song in output_dict:
            play_count += output_dict[song]
            output_dict.update({song:play_count})
        output_dict.update({song:play_count})
output_list = [{'song':k,'play_count':v} for k,v in output_dict.items()]
# 转换成df格式
song_count_df=pd.DataFrame(output_list)
song_count_df=song_count_df.sort_values(by='play_count',ascending=False)
song_count_df.to_csv(path_or_buf='song_playcount_df.csv',index=False)

# 看看目前的排行情况
play_count_df=pd.read_csv(filepath_or_buffer='user_playcount_df.csv')
# print(play_count_df.head(n=10))

song_count_df = pd.read_csv(filepath_or_buffer='song_playcount_df.csv')
# print(song_count_df.head(10))

## 取其中一部分数(按大小排好序的了,这些应该是比较重要的数据),作为我们的实验数据
#10W名用户的播放量占总体的比例
total_play_count = sum(song_count_df.play_count)
# print ((float(play_count_df.head(n=100000).play_count.sum())/total_play_count)*100)
play_count_subset = play_count_df.head(n=100000)
(float(song_count_df.head(n=30000).play_count.sum())/total_play_count)*100
(float(song_count_df.head(n=30000).play_count.sum())/total_play_count)*100
song_count_subset = song_count_df.head(n=30000)
# 前3W首歌的播放量占到了总体的78.39%
# 现在已经有了这10W名忠实用户和3W首经典歌曲,
# 接下来我们就要对原始数据集进行过滤清洗

# 取10w用户,3w首歌
user_subset = list(play_count_subset.user)
song_subset = list(song_count_subset.song)

# 读取原始数据集
triplet_dataset=pd.read_csv(filepath_or_buffer=data_home+'train_triplets.txt',sep='\t',
                            header=None,names=['user','song','play_count'])
# 只保留有这十万名用户的数据,其余过滤掉
triplet_dataset_sub=triplet_dataset[triplet_dataset.user.isin(user_subset)]
del(triplet_dataset)
# 只保留有这三万首歌的数据,其余也删掉
triplet_dataset_sub_song=triplet_dataset_sub[triplet_dataset_sub.song.isin(song_subset)]
del(triplet_dataset_sub)
triplet_dataset_sub_song.to_csv(path_or_buf=data_home+'triplet_dataset_sub_song.csv', index=False)
# print(triplet_dataset_sub_song.shape)
# 数据样本个数此时只有原来的1/4不到,
# 但是我们过滤掉的样本都是稀疏数据不利于建模,
# 所以当拿到了数据之后对数据进行清洗和预处理工作还是非常有必要的,
# 不单单提升计算的速度,还会影响最终的结果。
# print(triplet_dataset_sub_song.head(n=10))

# 加入音乐详细信息
conn = sqlite3.connect(data_home+'track_metadata.db')
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
cur.fetchall()

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)]
track_metadata_df_sub.to_csv(path_or_buf=data_home+'track_metadata_df_sub.csv', index=False)
# print(track_metadata_df_sub.shape)

# 我们现有的数据
triplet_dataset_sub_song = pd.read_csv(filepath_or_buffer=data_home+'triplet_dataset_sub_song.csv',encoding = "ISO-8859-1")
track_metadata_df_sub = pd.read_csv(filepath_or_buffer=data_home+'track_metadata_df_sub.csv',encoding = "ISO-8859-1")
# print(triplet_dataset_sub_song.head())
# print(track_metadata_df_sub.head())

# 清洗数据集
# 去除无用的和重复的
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'])
# 将这份音乐信息数据和我们之前的播放数据整合在一起
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)

# 去除不需要的指标
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'])

# print(triplet_dataset_sub_song_merged.head(n=10))

# 展示最流行的歌曲
import matplotlib.pyplot as plt;

plt.rcdefaults()
import numpy as np
import matplotlib.pyplot as plt

# 按歌曲名字来统计其播放量的总数
popular_songs = triplet_dataset_sub_song_merged[['title', 'listen_count']].groupby('title').sum().reset_index()
# 对结果进行排序
popular_songs_top_20 = popular_songs.sort_values('listen_count', ascending=False).head(n=20)

# 转换成list格式方便画图
objects = (list(popular_songs_top_20['title']))
# 设置位置
y_pos = np.arange(len(objects))
# 对应结果值
performance = list(popular_songs_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 songs')
#
# plt.show()

# 最受欢迎的releases
# 按专辑名字来统计播放总量
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()

# 最受欢迎的歌手
# 按歌手来统计其播放总量
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()

# 用户播放过歌曲量的分布
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()
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()

# 开始构建推荐系统
import Recommenders as Recommenders
from sklearn.model_selection import train_test_split
# 简单暴力,排行榜单推荐,对新用户来说解决冷启动问题
# 最简单的推荐方式就是排行榜单了,这里我们创建了一个函数,
# 需要我们传入的是原始数据,用户列名,待统计的指标
# (例如按歌曲名字,歌手名字,专辑名字。选择统计哪项指标得到的排行榜单)
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)
# print(train_data.head())


def create_popularity_recommendation(train_data, user_id, item_id):
    # 根据指定的特征来统计其播放情况,可以选择歌曲名,专辑名,歌手名
    train_data_grouped = train_data.groupby([item_id]).agg({user_id: 'count'}).reset_index()
    # 为了直观展示,我们用得分来表示其结果
    train_data_grouped.rename(columns={user_id: 'score'}, inplace=True)

    # 排行榜单需要排序
    train_data_sort = train_data_grouped.sort_values(['score', item_id], ascending=[0, 1])

    # 加入一项排行等级,表示其推荐的优先级
    train_data_sort['Rank'] = train_data_sort['score'].rank(ascending=0, method='first')

    # 返回指定个数的推荐结果
    popularity_recommendations = train_data_sort.head(20)
    return popularity_recommendations

recommendations = create_popularity_recommendation(triplet_dataset_sub_song_merged,'user','title')
# 得到统计结果
# print(recommendations)

# 返回了一份前20的歌曲排行榜单,其中的得分这里只是进行了简单的播放计算,
# 在设计的时候也可以综合考虑更多的指标,比如综合计算歌曲发布年份,歌手的流行程度等。
#基于歌曲相似度的推荐
song_count_subset = song_count_df.head(n=5000)
user_subset = list(play_count_subset.user)
song_subset = list(song_count_subset.song)
triplet_dataset_sub_song_merged_sub = triplet_dataset_sub_song_merged[triplet_dataset_sub_song_merged.song.isin(song_subset)]
# print(triplet_dataset_sub_song_merged_sub.head())

# 计算相似度得到推荐结果
import Recommenders as Recommenders
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)

#执行推荐
# print(is_model.recommend(user_id))

# 在SVD中我们所需的数据是用户对商品的打分,
# 但是我们现在的数据集中只有用户播放歌曲的情况并没有实际的打分值,
# 所以我们还得自己来定义一下用户对每个歌曲的评分值。如果一个用户喜欢某个歌曲,
# 那应该经常播放这个歌曲,相反如果不喜欢某个歌曲,那播放次数肯定就比较少了。
# 用户对歌曲的打分值,定义为:用户播放该歌曲数量/该用户播放总量。
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)
# print(triplet_dataset_sub_song_merged.head())

triplet_dataset_sub_song_merged['fractional_play_count'] = triplet_dataset_sub_song_merged['listen_count']/triplet_dataset_sub_song_merged['total_listen_count']
triplet_dataset_sub_song_merged[triplet_dataset_sub_song_merged.user =='d6589314c0a9bcbca4fee0c93b14bc402363afea'][['user','song','listen_count','fractional_play_count']].head()

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)
# print(data_sparse)

# 上面代码先根据用户进行分组,计算每个用户的总的播放总量,
# 然后用每首歌的播放总量相处,得到每首歌的分值,
# 最后一列特征fractional_play_count就是用户对每首歌曲的评分值。
# 有了评分值之后就可以来构建矩阵了,这里有一些小问题需要处理一下,
# 原始数据中无论是用户ID还是歌曲ID都是很长一串,这表达起来不太方便,
# 需要重新对其制作索引。
user_codes[user_codes.user =='2a2f776cbac6df64d6cb505e7e834e01684673b6']

# 使用SVD方法来进行矩阵分解
# 矩阵构造好了之后我们就要执行SVD矩阵分解了,
# 这里还需要一些额外的工具包来帮助我们完成计算,
# scipy就是其中一个好帮手了,里面已经封装好了SVD计算方法。

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


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

K=50
urm = data_sparse
MAX_PID = urm.shape[1]
MAX_UID = urm.shape[0]

U, S, Vt = compute_svd(urm, K)

uTest = [4,5,6,7,8,873,23]

uTest_recommended_items = compute_estimated_matrix(urm, U, S, Vt, uTest, K, True)

for user in uTest:
    print("当前待推荐用户编号 {}". 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("推荐编号: {} 推荐歌曲: {} 作者: {}".format(rank_value, list(song_details['title'])[0],list(song_details['artist_name'])[0]))
        rank_value+=1
# 这里对每一个用户都得到了其对应的推荐结果,并且将结果按照得分值进行排序。
#
# 本章我们选择了音乐数据集来进行个性化推荐任务,首先对数据进行预处理和整合,
# 选择两种方法分别完成推荐任务。
# 在相似度计算中根据用户所听过的歌曲在候选集中选择与其最相似的歌曲,
# 存在的问题就是计算时间消耗太多,每一个用户都需要重新计算一遍才能得出推荐结果。
# 在SVD矩阵分解的方法中,我们首先构建评分矩阵,对其进行SVD分解,
# 然后选择待推荐用户,还原得到其对所有歌曲的估测评分值,最后排序返回结果即可。
uTest = [27513]
#Get estimated rating for test user
print("Predictied ratings:")
uTest_recommended_items = compute_estimated_matrix(urm, U, S, Vt, uTest, K, True)

for user in uTest:
    print("当前待推荐用户编号 {}". 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("推荐编号: {} 推荐歌曲: {} 作者: {}".format(rank_value, list(song_details['title'])[0],list(song_details['artist_name'])[0]))
        rank_value+=1






文章来源:https://blog.csdn.net/m0_71778249/article/details/135157013
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。