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

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 sys

def is_null(s): 
    return len(s.split(","))>2

def 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 False
        

def 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的值还可以是12,表示以二进制的形式序列化。
pickle.load(file)
  反序列化对象。将文件中的数据解析为一个Python对象。
其中要注意的是,在load(file)的时候,要让python能够找到类的定义,否则会报错
import pickle
import sys

def 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 = 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 shuffle

path = "./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的协同过滤算法实现

您的鼓励是我源源不断创作的动力~

感谢观看!

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weightOneMillion

感谢看官们的供粮~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值