网易云音乐推荐系统简单实现系列(2)

xiaoxiao2021-02-28  13

1. 歌曲序列建模

我们来分析一下现在的场景,我们实际是需要计算user或者item的相似度,协同过滤等neighborhood的方法是其中的一种。

关于相似度这个问题,我们不是第一次遇见,我们在NLP的机器学习问题中,需要把词映射成词向量,最简单的方式是one-hot,而为了达到更好的效果,我们通常需要捕捉一下近义词(比如“宾馆”和酒店),我们会用到一个方法,叫做word2vec(Google)。

2. word2vec简单理解

说起word2vec,其实就是把词映射成一定维度的稠密向量,同时保持住词和词之间的关联性,主要体现在(欧氏)距离的远近上。 咱们说的详细点(内容转自by路一瓢的博客):

word2vec也叫word embeddings,中文名“词向量”,作用就是将自然语言中的字词转为计算机可以理解的稠密向量(Dense Vector)。在word2vec出现之前,自然语言处理经常把字词转为离散的单独的符号,也就是One-Hot Encoder。

杭州 [0,0,0,0,0,0,0,1,0,……,0,0,0,0,0,0,0] 上海 [0,0,0,0,1,0,0,0,0,……,0,0,0,0,0,0,0] 宁波 [0,0,0,1,0,0,0,0,0,……,0,0,0,0,0,0,0] 北京 [0,0,0,0,0,0,0,0,0,……,1,0,0,0,0,0,0] 1234

比如上面的这个例子,在语料库中,杭州、上海、宁波、北京各对应一个向量,向量中只有一个值为1,其余都为0。但是使用One-Hot Encoder有以下问题。一方面,城市编码是随机的,向量之间相互独立,看不出城市之间可能存在的关联关系。其次,向量维度的大小取决于语料库中字词的多少。如果将世界所有城市名称对应的向量合为一个矩阵的话,那这个矩阵过于稀疏,并且会造成维度灾难。 使用Vector Representations可以有效解决这个问题。Word2Vec可以将One-Hot Encoder转化为低维度的连续值,也就是稠密向量,并且其中意思相近的词将被映射到向量空间中相近的位置。 如果将embed后的城市向量通过PCA降维后可视化展示出来,那就是这个样子。

我们可以发现,华盛顿和纽约聚集在一起,北京上海聚集在一起,且北京到上海的距离与华盛顿到纽约的距离相近。也就是说模型学习到了城市的地理位置,也学习到了城市地位的关系。

模型拆解:

word2vec模型其实就是简单化的神经网络。

输入是One-Hot Vector,Hidden Layer没有激活函数,也就是线性的单元。Output Layer维度跟Input Layer的维度一样,用的是Softmax回归。我们要获取的dense vector其实就是Hidden Layer的输出单元。有的地方定为Input Layer和Hidden Layer之间的权重,其实说的是一回事。

CBOW与Skip-Gram模式:

word2vec主要分为CBOW(Continuous Bag of Words)和Skip-Gram两种模式。CBOW是从原始语句推测目标字词;而Skip-Gram正好相反,是从目标字词推测出原始语句。CBOW对小型数据库比较合适,而Skip-Gram在大型语料中表现更好。 对同样一个句子:Hangzhou is a nice city。我们要构造一个语境与目标词汇的映射关系,其实就是input与label的关系。 这里假设滑窗尺寸为1(滑窗尺寸……这个……不懂自己google吧-_-|||) CBOW可以制造的映射关系为:[Hangzhou,a]—>is,[is,nice]—>a,[a,city]—>nice Skip-Gram可以制造的映射关系为(is,Hangzhou),(is,a),(a,is), (a,nice),(nice,a),(nice,city)

训练优化:

额,到这里,你可能会注意到,这个训练过程的参数规模非常巨大。假设语料库中有30000个不同的单词,hidden layer取128,word2vec两个权值矩阵维度都是[30000,128],在使用SGD对庞大的神经网络进行学习时,将是十分缓慢的。而且,你需要大量的训练数据来调整许多权重,避免过度拟合。数以百万计的重量数十亿倍的训练样本意味着训练这个模型将是一个野兽。 一般来说,有Hierarchical Softmax、Negative Sampling等方式来解决。

word2vector是在NLP中不可或缺的处理文本语言的方法。有一定难度。笔者也需要去啃google发的论文。论文在这(可预览下载):

Efficient Estimation of Word Representations in Vector Space

Distributed Representations of Words and Phrases and their Compositionality


那么问题来了,word2vec为什么能够学习到这样的结果? 因为我们相信“物以类聚,人以群分”、“一个人的层次与他身边最近的一些人是差不多的”。

同样的考虑,我们是不是可以认为,一个歌单里的歌曲,相互之间都有一定的关联性呢?就像句子中的词一样,答案是,是的!

3. 初识gensim

Google实现在word2vec库底层是C++写的,对于使用Python来编程的我来说,为了方(tou)便(lan),直接使用底层使用Numpy写的gensim库。它实现了word2vec功能,能够将单词转化为词向量。底层逻辑和google开发的一模一样,性能可能差一点点,但是API简单呀。

gensim是一个Python的自然语言处理库,能够将文档根据TF-IDF, LDA, LSI 等模型转化成向量模式,以便进行进一步的处理。

这个库怎么安装,怎么学习,有哪些API,详尽文档都在官网(gensim: Topic modelling for humans)中有。笔者不外加赘述,因为我也刚刚只学了一点。

4. 基于Word2Vec的网音乐音乐歌曲推荐系统

import json from random import shuffle import multiprocessing import gensim import csv def train_song2vec(): """ :return: 所有歌单song2Vec模型的训练和保存 """ songlist_sequence = [] # 读取网易云音乐原数据 for i in range(1, 1292): with open("neteasy_playlist_data/{0}.json".format(i), 'r', encoding='UTF-8') as load_f: load_dict = json.load(load_f) parse_songlist_get_sequence(load_dict, songlist_sequence) # 多进程计算 cores = multiprocessing.cpu_count() print('Using all {cores} cores'.format(cores=cores)) print('Training word2vec model...') model = gensim.models.Word2Vec(sentences=songlist_sequence, size=150, min_count=3, window=7, workers=cores) print('Save model..') model.save('songVec.model') def parse_songlist_get_sequence(load_dict, songlist_sequence): """ 解析每个歌单中的歌曲id信息 :param load_dict: 包含一个歌单中所有歌曲的原始列表 :param songlist_sequence: 一个歌单中所有给的id序列 :return: """ song_sequence = [] for item in load_dict['playlist']['tracks']: try: song = [item['id'], item['name'], item['ar'][0]['name'], item['pop']] song_id, *song_name, artist, pop = song song_sequence.append(str(song_id)) except: print('song format error') for i in range(len(song_sequence)): shuffle(song_sequence) # 这里的list()必须加上,要不songlist中歌曲根本就不是随机打乱序列,而是都相同序列 songlist_sequence.append(list(song_sequence)) def song_data_preprocessing(): """ 歌曲id到歌曲名字的映射 :return: 歌曲id到歌曲名字的映射字典,歌曲名字到歌曲id的映射字典 """ csv_reader = csv.reader(open('neteasy_song_id_to_name_data.csv', encoding='utf-8')) id_name_dic = {} name_id_dic = {} for row in csv_reader: id_name_dic[row[0]] = row[1] name_id_dic[row[1]] = row[0] return id_name_dic, name_id_dic train_song2vec() model_str = 'songVec.model' # 载入word2vec模型 model = gensim.models.Word2Vec.load(model_str) id_name_dic, name_id_dic = song_data_preprocessing() song_id_list = list(id_name_dic.keys())[4000:5000:200] for song_id in song_id_list: result_song_list = model.most_similar(song_id) print(song_id, id_name_dic[song_id]) print('\n相似歌曲和相似度分别为:') for song in result_song_list: print('\t' + id_name_dic[song[0]], song[1]) print('\n') """ E:\CodeLibrarySoftware\Anaconda3\lib\site-packages\gensim\utils.py:1197: UserWarning: detected Windows; aliasing e to chunkize_serial warnings.warn("detected Windows; aliasing chunkize to chunkize_serial") 526081111 到此为止-徐佳莹 相似歌曲和相似度分别为: 水星记-郭顶 0.6873336434364319 勇气-梁静茹 0.67500901222229 Turn It Around-neutral. 0.6673465967178345 藏-徐梦圆 0.650586724281311 Never let me go 私は息-blind.scream 0.6487569808959961 空空如也 -任然 0.6459102630615234 好久不见-陈奕迅 0.6457817554473877 晴天-周杰伦 0.6452314853668213 拥抱-徐秉龙 0.6448458433151245 Horizon-Janji 0.6448436379432678 534931634 Closure-Will Sparks 相似歌曲和相似度分别为: Something Just Like This-The Chainsmokers 0.7519105076789856 晴天-周杰伦 0.744801938533783 平凡之路-朴树 0.7447571754455566 Telescope-Tim Legend 0.7425273656845093 成都-赵雷 0.7423281669616699 Wolves-Selena Gomez 0.7414205074310303 起风了(Cover 高橋優)-买辣椒也用券 0.7403666973114014 Cold Water-Major Lazer 0.740134596824646 Lemon-米津玄師 0.7393931150436401 Byte-Martin Garrix 0.7393488883972168 518451047 一万光年-栗先达 相似歌曲和相似度分别为: 有何不可-许嵩 0.6557078957557678 等你下课 (with 杨瑞代)-周杰伦 0.6555135250091553 Good Time-Owl City 0.6511011123657227 Unity-TheFatRat 0.6496284008026123 POP TEAM EPIC-上坂すみれ 0.6476969718933105 一如年少模样-陈鸿宇 0.6472691297531128 Good Life-Kiso 0.643415629863739 初爱-杨宗纬 0.6386432647705078 晴天-周杰伦 0.6361370086669922 十九岁-赵雷 0.6357495784759521 447280882 ソラに行くいけない ~ Don't Go Sora-cybergodサイバー神 相似歌曲和相似度分别为: Light-San Holo 0.6541008949279785 ...Ready For It? (BloodPop® Remix)-Taylor Swift 0.6491278409957886 致姗姗来迟的你 -阿肆 0.6483579874038696 Into You-Matisse & Sadko 0.6470692753791809 告白气球-周杰伦 0.646735668182373 Forever-Martin Garrix 0.6460220813751221 Starset-Cody Sorenson 0.6446119546890259 【天官赐福花怜同人】明灯三千(Cover:刘菲)-HUI诙木 0.6442180275917053 Complicated-Dimitri Vegas & Like Mike 0.6436021327972412 等你下课 (with 杨瑞代)-周杰伦 0.6412654519081116 28524666 We Are One (Ole Ola)-Pitbull 相似歌曲和相似度分别为: Breathe-Jax Jones 0.6394559144973755 钟无艳-谢安琪 0.6272741556167603 There For You-Martin Garrix 0.6249080896377563 Cold Water-Major Lazer 0.6240158081054688 FRIENDS-Marshmello 0.6204439401626587 Epic Sax Guy-Sunstroke Project 0.6202854514122009 勇气-梁静茹 0.6187319159507751 Valkyrie-EnV 0.6186336874961853 舍不得-弦子 0.6149234771728516 Animals-Martin Garrix 0.6140772700309753 """

进一步思考

所以我们用word2vec学会了哪些歌曲和哪些歌曲最相近。 我们来思考一些很现实同时又很难解决的问题。比如:

冷启动问题

我们经常会遇到冷启动的问题,比如没有任何信息的歌曲,我们如何对它做推荐呢? · 如果是歌手发行的新歌曲,我们怎么进行推荐呢? · 如果我听完了(并收藏)了一首很冷门的歌,怎么进行推荐呢? 我们知道新歌(或者小众的歌)是非常难和其他的歌关联上的,我们有的信息太少了(很少有用户在它上面发生行为)

1.1 一种解决办法当然是推荐热门的歌曲,但是其实没从个人兴趣除法,我们知道这并不是最好的办法,并没有太大的用处。

1.2 我们把问题的粒度放粗一点,用同样的思路,比如一个可考虑的解决方案是,我们把歌曲的粒度上升到对应的歌手,把刚才的song_list替换成 artist_list,重新用word2vec建模,这样我们可以得到和一个歌手最相关(接近)的歌手,再推荐这个歌手最热门的歌曲,相对1.1的方法针对性强一点。

用户兴趣预测问题

我们刚才完成的功能,类似网易云音乐里针对一首歌的“相似音乐”,那么问题又来了,如果我们现在要对一个user用这套song2vec的方式推荐,我们怎么做呢? ·每个人的兴趣都是有时效性的,这意味着说,3年前我喜欢杨宗纬的歌,去年我喜欢周杰伦的歌,而今年我可能就改摇滚路线,喜欢汪峰的歌了。 ·每一首歌的热度也是不一样的,有一些热门的歌,如果用户能喜欢,当然是首选。

那么,我们来做一个粗暴一点点的处理,把这两个维度拉进来,一起来针对一个用户做推荐。

把每个用户喜欢(收藏)过的歌,沿着时间轴排好,同时由近到远给不同的衰减因子(比如最近一首歌是1,前一首歌是0.98,再前一首是0.98^2,以此类推…..),同时我们针对不同的歌曲热度,给定不同的推荐因子(比如热度100的是1,热度80的是0.9….),每首歌都可以拿回一个song2vec的推荐列表和对应的相似度,对相似度一时间衰减因子和热度权重进行加权,最后的结果排序后,展示给用户。

推荐系统到底有哪些问题?

我们知道,推荐系统的使命是为用户和物品建立连接,建立的方式是提前找出那些隐藏的连接呈现给用户,这是一个预测问题。所以推荐系统的预测问题模式,从达成的连接目标角度区分,有两大类: 1、评分预测; 2、行为预测。

因为评分和行为是用户对推荐结果的两类反馈,我们给他们推荐了一个或多个物品,目的是希望他们“消费”,这种消费反应在用户行为上,比如“点击查看”,信息咨询类的还有“阅读完成”,视频音乐类的有“播放完成”,电商类的“加入购物车”等。

整个行为呈现一个漏斗形状,从曝光到最终消费完成。最后在用户完成消费后,产品方一般还希望他们告诉自己消费的体验,这时候就有评分了;所以不同推荐系统的任务也不同,有的直接去预测用户如果消费完之后会给多少评分,更多的推荐系统则会分层,致力于预测用户的行为。

下面我分别详细说一下这两类问题。

评分预测

评分预测相关算法模型研究的兴盛,最大的助攻是 Netflix 举办的推荐算法大赛。

评分预测要干的事情是这样的:假如用户消费完一个物品之后会给出一个打分,比如通常是 1~5 分,或者有的网站用星星的颗数表示,也是一样。

我们就想能不能提前预测一个用户对每一个物品会打多少分,找出那些他可能会打高分,但是还没消费的物品,然后装作若无其事地呈现在他面前,惊不惊喜,意不意外?

说干就干,怎么干呢?正如王小波给李银河写的信那样:不能胡干。一个朴素的思想是:建立一个模型,这个模型会给用户历史上打过分的物品去预测分数。

预测分数和实际分数之间会有误差,我们根据这个误差去调整模型参数,让这个误差越来越小,最后得到的这个模型理论上就可以为我们干活了。事实上,这其实就是个机器学习里面的回归问题。

Netflix 比赛的评判标准就是 RMSE ,即均方根误差,怎么算的呢?

这个公式中的 t 表示每一个样本,n 表示总共的样本数,有帽子的 yt 就是模型预测出的分数,是我们交的作业,秃顶的 yt 就是用户自己打的分数,是标准答案,然后一个样本一个样本地对答案,模型预测分数和用户自己打分相减,这就是我们预测的误差。

由于误差有正数也有负数,而我们只关心绝对值大小,所以再给误差求平方,这就是名字中的“方”的来源,再对所有样本的误差平方求平均值,这就是名字中“均”的来源,因为我们对误差都平方了,所以最后再对均值开方根,这就是名字中的“根”的来源。这个过程就是求均方根误差。

评分预测问题常见于各种点评类产品(如:书影音的点评),但评分类推荐存在以下问题: 1、数据不易收集,我刚才说过,用户给出评分意味着他已经完成了前面所有的漏斗环节;

2、数据质量不能保证,伪造评分数据门槛低,同时真实的评分数据又处在转化漏斗最后一环,门槛高;

3、评分的分布不稳定,整体评分在不同时期会差别很大,个人评分在不同时期标准不同,人和人之间的标准差别很大。

用户爸爸们给产品施舍的评分数据,我们又叫做显式反馈,意思是他们非常清晰明白地告诉了我们,他们对这个物品的态度;与之相对的还有隐式反馈,通常就是各类用户行为,也就是另一类推荐系统问题:行为预测。

行为预测

实际上,用户爸爸们每天要在不同的 App 或者网站之间不停批阅奏章,日理万机,非常忙,所以能够提交的像评分这种显式反馈数据很少。

但是没关系,只要用户来了,就会有各种行为数据产生,从登录刷新,到购买收藏,都是用户行为,这类数据是用户们在自觉自愿的情况下产生的,数据量比显式反馈多很多。

用户的行为通常呈现漏斗关系,我希望用户最终达成的行为可能不是那么容易得到的,比如购买,比如建立一个社交关系,比如完整消费一个长内容,通常是从登录刷新开始,逐层经历漏斗流失。

而推荐系统肩负的使命自然是达成用户行为,也就是连接越多越好。这也是这一类推荐系统问题的关注点。

推荐系统预测行为方式有很多,常见的有两种:直接预测行为本身发生的概率,和预测物品的相对排序。直接预测用户行为这一类技术,有一个更烂大街的名字,叫做 CTR 预估。这里的 C 原本是点击行为 Click,但这个解决问题的模式可以引申到任何其他用户行为,如收藏、购买。

CTR 意思就是 Click Through Rate,即“点击率”。把每一个推荐给用户的物品按照“会否点击”二分类,构建分类模型,预估其中一种分类的概率,就是 CTR 预估。

行为预测说白了,就是利用隐式反馈数据预测隐式反馈的发生概率。也因此,各家互联网产品要高度重视隐式反馈,归纳起来有以下几点原因。

1、数据比显式反馈更加稠密。诚然,评分数据总体来说是很稀疏的,之前 Netflix 的百万美元挑战赛给出的数据稀疏度大概是 1.2%,毕竟评分数据是要消耗更多注意力的数据。

2、隐式反馈更代表用户的真实想法,比如你不是很赞成川普的观点,但还是想经常看到他的内容(以便吐槽他),这是显式反馈无法捕捉的。而人们在 Quora 上投出一些赞成票也许只是为了鼓励一下作者,或者表达一些作者的同情,甚至只是因为政治正确而投,实际上对内容很难说真正感兴趣。

3、隐式反馈常常和模型的目标函数关联更密切,也因此通常更容易在 AB 测试中和测试指标挂钩。这个好理解,比如 CTR 预估当然关注的是点击这个隐式反馈。

用户给出较高评分的先决条件是用户要有“评分”的行为,所以行为预测解决的是推荐系统的 80% 问题,评分预测解决的是最后那 20% 的问题,行为预测就像是我们剁手买买买后,可爱的商品要先乘坐飞机,飞跃千山万水到所在区域来,而评分预测则是快递员最终将东西递交到你手上这个过程。

几个常见顽疾

讨论了两大类推荐系统的问题后,我们再来看几个推荐系统的隐藏顽疾。之所以说这些是隐藏顽疾,是因为它们还没有很好的通用解决方案,并且不容易被重视,这几个顽疾分别是: 1、冷启动问题; 2、探索与利用问题; 3、安全问题。

冷启动问题

推荐系统是数据贪婪型应用,所谓数据贪婪型应用,就是对数据的需求绝无足够的那一天。冷启动问题广泛存在于互联网产品中,但我们这里仅仅限于推荐系统的冷启动。

新用户或者不活跃用户,以及新物品或展示次数较少的物品,这些用户和物品,由于缺乏相关数据,很是空虚寂寞冷,因此就是冷启动问题的关注对象。

关于“如何解决冷启动”本身,有伪命题的嫌疑,因为通常的解决方式就是给它加热:想办法引入数据,想办法从已有数据中主动学习(一种半监督学习)。我们会在后面的文章中详细讨论冷启动的问题。

探索与利用问题

探索与利用,行话又叫做 EE 问题。假如我们已经知道了用户的喜好,一般有三种对待方式: 1、全部给他推荐他目前肯定感兴趣的物品; 2、无视他的兴趣,按照其他逻辑给他推荐,如编辑推荐、随机推荐、按时间先后推荐等等; 3、大部分给他推荐感兴趣的,小部分去试探新的兴趣,如同一边收割长好的韭菜,一边播种新的韭菜。

哪一种更科学和持久呢?显然是第三种,那么如何平衡这里的“大部分”和“小部分”呢?

这就是 Exploit 和 Explore 问题的核心了。Exploit 意为“开采”,对用户身上已经探明的兴趣加以利用,Explore 意为“探索”,探明用户身上还不知道的兴趣。

我们会在后面的文章中详细讨论 EE 问题,欢迎订阅我的专栏–《推荐系统36式》,已在极客时间App上线。

安全问题

凡是系统就有漏洞,凡是漏洞有利可图,就一定有人去图,推荐系统也不例外。如果你正在一款流量非常大的产品上构建推荐系统,那么一定要考虑推荐系统攻击问题。

推荐系统被攻击的影响大致有以下几个: 1、给出不靠谱的推荐结果,影响用户体验并最终影响品牌形象; 2、收集了不靠谱的脏数据,这个影响会一直持续留存在产品中,很难完全消除; 3、损失了产品的商业利益,这个是直接的经济损失。

源码全部都在Github上:recommend_system_learning

转载请注明原文地址: https://www.6miu.com/read-2650296.html

最新回复(0)