学习笔记|网易云音乐推荐系统特训_笔记

0介绍 音乐推荐是推荐系统里非常特殊的领域,虽然现在很多推荐系统都是作为一个应用存在于网站中,比如亚马逊的商品推荐和Netfix的电影推荐,但唯有音乐推荐可以支 持独立的个性化推荐网站,比如Last.fm 和豆瓣FM。
本课程将基于网易云音乐的歌单数据,从零开始构建一个音乐推荐系统。课程的主要内容包括:歌单数据解析、Surprise库的使用(here)、基于Surprise的矩阵分解实现、基于Tensorflow的矩阵分解实现、基于pyspark的协同过滤实现、基于协同过滤的推荐系统实现、评分预测、歌曲序列建模、冷启动问题等。
你将收获 实战歌单数据处理
实战协同过滤算法
实战矩阵分解算法
实战歌曲序列建模
适用人群 学过Python,有一定机器学习基础
1项目背景与数据准备 1.1 Surprise和LightFM 在推荐系统的建模过程中,我们将用到python库 Surprise(Simple Python RecommendatIon System Engine),是scikit系列中的一个(很多同学用过scikit-learn和scikit-image等库)。
Surprise比scikit-learn更适用于做推荐系统,LightFM也可以,比Surprise更新一些,多了几个方法。
安装

$ pip install numpy $ pip install scikit-surprise

安装报错
学习笔记|网易云音乐推荐系统特训_笔记
文章图片

解决办法:
pip对各个包的依赖解决的不是很好,所以建议可以用conda安装的尽量用conda安装,conda安装错误极少,安装命令如下:
conda install -c conda-forge scikit-surprise
点击 here
学习笔记|网易云音乐推荐系统特训_笔记
文章图片
方法1不想尝试
方法2
Python Extension Packages for Windows:https://www.lfd.uci.edu/~gohlke/pythonlibs/
里面没有这两个的whl文件
方法3安装Microsoft Visual C++ Build Tools 搞了好几次都是“安装包丢失或损坏”
学习笔记|网易云音乐推荐系统特训_笔记
文章图片

有人给出另一种安装包,或者是有人提出下面的安装证书,也不能解决
学习笔记|网易云音乐推荐系统特训_笔记
文章图片
还有人提出
将D:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\stdint.h文件拷贝到C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\ucrt\目录下
修改环境变量等,关键是,俺没有C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\ucrt\这个目录(不过装过vs的应该有装那个windows10 sdk啥的,装完之后会有这个目录,我虽然装了VS,前一段时间,卸载应用的时候,给把这个windows10 sdk卸载了,所以没有这个目录,不过按照下下面的【成功的方法】的步骤,再装一遍windows10 sdk,就有了这个目录)
最后,最后,看到了救星【Microsoft visual C++ build tools安装包丢失】https://blog.csdn.net/vans05/article/details/116994443
这篇博文很好的解决了我的问题,
成功的方法
首先说明,我已经安装过VS2019了
第一步,打开VS2019,选择工具-获取工具和功能
学习笔记|网易云音乐推荐系统特训_笔记
文章图片
第二步,按照下面,这样这样,最后点击右下角安装就好了
学习笔记|网易云音乐推荐系统特训_笔记
文章图片
第三步,就是重新运行pip install 即可。
成功展示
学习笔记|网易云音乐推荐系统特训_笔记
文章图片

学习笔记|网易云音乐推荐系统特训_笔记
文章图片

1.2数据获取 1.3数据解析 1.3.1原始数据=>歌单数据
抽取 歌单名称,歌单id,收藏数,所属分类 4个歌单维度的信息

抽取 歌曲id,歌曲名,歌手,歌曲热度 等4个维度信息歌曲的信息

学习笔记|网易云音乐推荐系统特训_笔记
文章图片

1.3.2歌单数据=>推荐系统格式数据
即将歌单数据解析为surprise库支持的格式
数据格式:(uid, iid, rating, timestamp)
# 解析成userid itemid rating timestamp行格式import json import sysdef is_null(s): return len(s.split(","))>2def parse_song_info(song_info): try: song_id, name, artist, popularity = song_info.split(":::") #return ",".join([song_id, name, artist, popularity]) return ",".join([song_id,"1.0",'1300000']) #song_id相当于item, playlist_id相当于user, "1.0"代表打分或者是收藏,“1300000”随便设计的一个时间戳,为了保证数据的格式 except Exception as e: #print e #print song_info return ""def parse_playlist_line(in_line): try: contents = in_line.strip().split("\t") name, tags, playlist_id, subscribed_count = contents[0].split("##") songs_info = map(lambda x:playlist_id+","+parse_song_info(x), contents[1:]) songs_info = filter(is_null, songs_info) return "\n".join(songs_info) except Exception as e: print(e) return Falsedef parse_file(in_file, out_file): out = open(out_file, 'w') for line in open(in_file, encoding='utf-8'): #encoding 防止乱码(UnicodeDecodeError) result = parse_playlist_line(line) if(result): out.write(result.strip()+"\n") out.close()

1.3.3保存 歌单id=>歌单名 和 歌曲id=>歌曲名 的字典
pickle提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。here
pickle.dump(obj, file[, protocol]) 序列化对象,并将结果数据流写入到文件对象中。参数protocol是序列化模式,默认值为0,表示以文本的形式序列化。protocol的值还可以是1或2,表示以二进制的形式序列化。 pickle.load(file) 反序列化对象。将文件中的数据解析为一个Python对象。 其中要注意的是,在load(file)的时候,要让python能够找到类的定义,否则会报错

import pickle import sysdef parse_playlist_get_info(in_line, playlist_dic, song_dic): contents = in_line.strip().split("\t") name, tags, playlist_id, subscribed_count = contents[0].split("##") playlist_dic[playlist_id] = name for song in contents[1:]: try: song_id, song_name, artist, popularity = song.split(":::") song_dic[song_id] = song_name+"\t"+artist except: # 捕捉异常格式的歌曲 print("song format error") print(song+"\n") # in_file歌单数据, out_playlist(playlist_id:name), out_song(song_id:song_name+'\t'+artist) def parse_file(in_file, out_playlist, out_song): #从歌单id到歌单名称的映射字典 playlist_dic = {} #从歌曲id到歌曲名称的映射字典 song_dic = {} for line in open(in_file, encoding='utf-8'): parse_playlist_get_info(line, playlist_dic, song_dic) #把映射字典保存在二进制文件中 pickle.dump(playlist_dic, open(out_playlist,"wb")) #可以通过 playlist_dic = pickle.load(open("playlist.pkl","rb"))重新载入 pickle.dump(song_dic, open(out_song,"wb"))

2模型构建与算法调参 这个好像讲了什么又好像没讲什么
3基于movelens数据集的推荐预测 数据集,可以了解一下here
4基于网易云音乐数据的推荐预测 4.1使用协同过滤基于网易云音乐数据构建模型并进行预测 4.1.1构建数据集
import pickle from surprise import Reader from surprise.dataset import Dataset path = './data/output/popular/' # 重建歌单id到歌单名的映射字典 id_name_dic = pickle.load(open(path+"popular_playlist.pkl","rb")) print("加载歌单id到歌单名的映射字典完成...") # 重建歌单名到歌单id的映射字典 name_id_dic = {} for playlist_id in id_name_dic: name_id_dic[id_name_dic[playlist_id]] = playlist_id print("加载歌单名到歌单id的映射字典完成...") # 指定文件所在路径 file_path = path+'popular_music_suprise_format.txt' #os.path.expanduser('~/.surprise_data/ml-1m/users.dat') # 告诉文本阅读器,文本的格式是怎么样的 reader = Reader(line_format='user item rating timestamp', sep=',')# 从文件读取数据 music_data = https://www.it610.com/article/Dataset.load_from_file(file_path, reader=reader) # 计算歌曲和歌曲之间的相似度 print("构建数据集...") trainset = music_data.build_full_trainset() # build_full_trainset()将所有的数据构建成训练集

4.1.2训练数据集
from surprise import KNNBaseline print("开始训练模型...") #sim_options = {'user_based': False} #algo = KNNBaseline(sim_options=sim_options) algo = KNNBaseline() algo.fit(trainset)# current_playlist = name_id_dic.keys()[39] current_playlist = id_name_dic.get('392991828') print("歌单名称", current_playlist)# 取出近邻 # 映射名字到id playlist_id = name_id_dic[current_playlist] print("歌单id", playlist_id) # 取出来对应的内部user id => to_inner_uid inner id更适合surprise计算 playlist_inner_id = algo.trainset.to_inner_uid(playlist_id) print("内部id", playlist_inner_id)playlist_neighbors = algo.get_neighbors(playlist_inner_id, k=10)# 把歌曲id转成歌曲名字 # to_raw_uid映射回去 playlist_neighbors = (algo.trainset.to_raw_uid(inner_id) for inner_id in playlist_neighbors) playlist_neighbors = (id_name_dic[playlist_id] for playlist_id in playlist_neighbors)print() print("和歌单 《", current_playlist, "》 最接近的10个歌单为:\n") for playlist in playlist_neighbors: print(playlist, algo.trainset.to_inner_uid(name_id_dic[playlist]))

总结:
基本步骤:1、构建训练集trainset
2、定义预测模型并配置参数等,使用fit训练数据
3、获取,需要被预测的原始的r_iid或者r_uid,转换成内部的i_iid或者i_uid
4、使用get_neighbors和i_iid或者i_uid获得K个推荐值
5、再根据推荐的i_iid或者i_uid列表,获得原始的r_iid或者r_uid
6、最后,根据原始的r_iid或者r_uid,获得用户可以看得懂的歌单名称。
4.1.3预测
#内部编码的4号用户,这里的用户,实际上指的是歌单 user_inner_id = 4 # 内部编码为4的用户打分的item-rating的字典列表 user_rating = trainset.ur[user_inner_id] # defaultdict of list, This is a dictionary containing lists of tuples of the form (item_inner_id, rating) # 获得用户打分的item items = map(lambda x:x[0], user_rating) for song in items: # predict这里应该用原始的id print(algo.predict(user_inner_id, song, r_ui=1), song_id_name_dic[algo.trainset.to_raw_iid(song)]) # 由于最初数据处理的时候,所有打分都设成了1导致这里的预测结果有问题,之前尝试提到的Jaccard similarity

学习笔记|网易云音乐推荐系统特训_笔记
文章图片

4.1.4算法评估
# 使用协同过滤baseline from surprise import KNNBaseline algo = KNNBaseline() result = cross_validate(algo, music_data, measures=['RMSE', 'MAE'], cv=5, verbose=True)

5word2vec简介与Song2Vec实现 word2vec,能做到,更准确,比one-hot更智能,根据语境寻找词与词的相近度
因为我们相信“物以类聚,人以群分” “一个人的层次与他身边最近的一些人是差不多的”
但是word2vec是用C++写的,我们这里用python的库gensim
扩展【Python multiprocessing使用详解】
multiprocessing包是Python中的多进程管理包。
与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。
5.1训练模型并保存
import multiprocessing import gensim import sys from random import shufflepath = "./data/output/popular/"def parse_playlist_get_sequence(in_line, playlist_sequence): song_sequence = [] contents = in_line.strip().split("\t") # 解析歌单序列 for song in contents[1:]: try: song_id, song_name, artist, popularity = song.split(":::") song_sequence.append(song_id) except: print("song format error") print(song+"\n") for i in range(len(song_sequence)): shuffle(song_sequence)# 歌单中的歌曲id顺序被打乱 playlist_sequence.append(song_sequence)def train_song2vec(in_file, out_file): #所有歌单序列 playlist_sequence = [] #遍历所有歌单 for line in open(in_file,encoding='utf-8'): parse_playlist_get_sequence(line, playlist_sequence) #使用word2vec训练 cores = multiprocessing.cpu_count() print("using all "+str(cores)+" cores") print("Training word2vec model...") # 训练模型,这里的一个songid相当于一个分词,一个歌单相当于一篇文章 model = gensim.models.Word2Vec(sentences=playlist_sequence, min_count=3, window=7, workers=cores) print("Saving model...") model.save(out_file)# ./data/popular.playlist内容格式:歌单名称##tags##歌单ID##订阅量 + \t +id:::name:::artists::popularity song_sequence_file = "./data/popular.playlist" model_file = "./model/song2vec.model" train_song2vec(song_sequence_file, model_file)

5.2加载模型
import pickle # 加载歌曲的id——name+"\t"+artist song_dic = pickle.load(open(path+"popular_song.pkl","rb")) # 加载训练模型 model = gensim.models.Word2Vec.load(model_file)

5.3预测
# 获取十个歌曲的id song_id_list = list(song_dic.keys())[1000:1500:50]for song_id in song_id_list: result_song_list = model.wv.most_similar(song_id) print(song_id, song_dic[song_id]) print("\n相似歌曲 和 相似度 分别为:") for song in result_song_list: print("\t", song_dic[song[0]], song[1]) print("\n")

运行结果:
学习笔记|网易云音乐推荐系统特训_笔记
文章图片

拓展:因为不熟悉gensim和word2vec,先去学习了一些,总结的一些笔记
gensimAPI学习——word2vec
gensim实战01——word2vec
我觉得,基于word2vec的推荐,应该实现的是基于内容的推荐(欢迎指教)
6冷启动问题与用户兴趣预测问题 做推荐系统,应该有了解过冷启动的问题
推荐个性化推荐算法总结这篇文章。
7使用Tensorflow实现基于矩阵分解的推荐系统 【学习笔记|网易云音乐推荐系统特训_笔记】学了几天的神经网络和tensorflow,还是没看懂老师的代码
等什么时候学会了再说吧
8基于Spark的协同过滤算法实现

    推荐阅读