08 | Embedding实战:如何使用Spark生成Item2vec和Graph Embedding?
08 | Embedding实战:如何使用Spark生成Item2vec和Graph Embedding?
讲述:王喆
时长11:37大小10.61M
Item2vec:序列数据的处理
Item2vec:模型训练
Graph Embedding:数据准备
Graph Embedding:随机游走采样过程
小结
课后思考
赞 31
提建议
精选留言(62)
- 你笑起来真好看2020-10-20transitionMatrixAndItemDis 在生成中这样定义的话,会不会造成dirver端oom?
作者回复: 非常非常好的问题。细心的话可以发现transitionMatrixAndItemDis这个矩阵完全是在driver端。 难点在于如何分布式地处理转移概率矩阵,解决方案确实是业界的难点。另外在随机游走的时候因为肯定要访问全部的转移矩阵,所以理论上来讲需要把这个矩阵broadcast到所有节点,这又是一个容易oom的问题。 有好的思路可以欢迎分享!但不是一个短时间能够解决的业界难题。
共 5 条评论27 - fsc20162020-10-31老师,筛选出来的高分电影有900多部,随机游走出来的序列embdding后,只有500多部,这应该和序列数量,序列长度有关,比如序列数量不够,导致没有覆盖到全部高分电影。实际工作中,像序列数量,序列长度是不是要经过筛选,来保证所有item都会被embdding?
作者回复: 非常好的观察,推荐所有的同学都能好好观察数据并得出这样有用的结论。 确实是这样,由于一些长尾,冷门电影的存在(毕竟热门电影还是极少数的),导致概率转移矩阵中游走到冷门电影的概率其实非常小,所以在游走次数比较小的时候,很容易覆盖不到。 因为课程的示例程序采用了比较小的采样次数和游走长度,所以这个问题比较严重,工作中肯定要在算力允许的前提下,尽量增加采样次数,适当增加游走长度,来保证生成结果的稳定性和覆盖率。
共 3 条评论24 - Huntley2020-10-19想请教下老师,user embedding 也可以用相同的方法获得吗?如何构建用户关系图呢?是不是看过同一部电影的两个用户之间由一条无向边进行连接?和item embedding相比,有什么区别或注意事项吗?
作者回复: 在项目中用最简单的average pooling的方法生成了user embedding。也就是把用户评价过的高分高分电影的embedding进行平均。 user embedding的其他生成方式也很多,像你说的,也可以根据历史行为构建用户-物品的关系图,然后直接在其上进行随机游走,直接一同生成user 和item emb。 或者采用其他双塔结构的模型生成item和user emb等等。
11 - 浣熊当家2020-10-20老师请教下,我对item2vec和Graph Embedding的联系理解是否正确: 1. 联系:item2vec和Graph Embedding 都是为了下一步的相关性矩阵,而一段物品序列 2. 不同: item2vec使用同一用户历史时间序列下的滑动窗口, 而Graph embedding在同一用户的时间序列之上,还应用了不同用户,同一物品之间的图联系,所以说graph embedding可以生成更全面的物品与物品的连接。 所以是否可理解为graph embedding比item2vec的复杂,更完善?业界更流行的是不是想deep walk这种graph embedding的算法?展开
作者回复: 理解非常正确。 而且针对graph embedding方法,不要认为所有的图数据都是由序列数据生成的,有一些天然的图数据,比如知识图谱,是只能使用graph embedidng方法,不能使用item2vec的。所以graph embedding应用范围更广,保存的信息也更多。
共 3 条评论8 - 白夜2020-10-29老师,randomWalk这部在60核的机器上跑的很慢,,慢到无语,可以优化下吗,transitionMatrixAndItemDis._1与_2的size都是300000
作者回复: 300000的物品规模单机环境肯定比较吃力了,代码中的实现也是单机下的随机游走,不管再如何优化都比较困难。 生产环境请关注spark的图计算api graphX https://spark.apache.org/graphx/ 我认为比较好的教程 https://endymecy.gitbooks.io/spark-graphx-source-analysis/content/ 其实期待更多的同学能参与到项目的维护中来,我们不仅是一个课程,更是一个业界交流的平台,期待能看到新的进展。
共 2 条评论7 - 聪聪呀2020-10-21老师,我最近在研究使用graph embedding ,根据网上的git 代码跑了item2vec,EGES代码,我的推荐场景是视频推荐,同样的训练数据,我发现item2vec 推荐效果较好,但我发现EGES推荐效果不好(只用了ID,没有加其他特征)推荐结果相似度很低。所以想请教您,您觉得可能是什么原因引起的呢,您有没有EGES的demo
作者回复: EGES是希望融合更多side information,只用id信息并不是它的意义所在。而且除了这些原理上的分析外,我不主张给任何人模型效果好坏的建议,决定的因素太多了。 但总的来说,如果你只有id类的历史行为信息,使用EGES的意义不大。
共 2 条评论6 - 梁栋💝2021-01-06综上,随机游走因为需要完整转移概率矩阵的原因,受限于转移概率矩阵规模能否容纳到内存中。
作者回复: 是这样,内存问题是工程实现中最大的限制。我没有深入研究过spark的graphX,以及一些图计算的分布式引擎。如果有经验的同学可以谈谈如何进行分布式的转移矩阵处理。
5 - W2021-06-29老师你好,不知道我是不是哪里理解错了T_T,新手入门。我的问题是:评分低于3.5的电影被过滤了,相当于电影库里没有低于3.5分的电影,那么也就不会有对应低于3.5分的电影的embedding向量,为什么BatMan的推荐结果里还有低于3.5分的推荐结果呢?
作者回复: 这是个简单的脑筋急转弯问题吧。电影的3.5是平均分,你再想想。
4 - Geek_ee4f142021-01-25再请问下老师,游走的时候起始点没有做去重,会不会导致某些爆款成为起始点的概率更高,也不容易游走到冷门物品呀?
作者回复: 会的,但通常我们也希望更多的去推荐爆款商品,这还是跟你自己的观察和决定相关
4 - 王发庆2020-10-22老师,您好,请教您一个问题,在生成Embedding的时候我们都是全量生成的,在生产环境下我们能增量的去生成新节点的Embedding么?
作者回复: 不可以。Embedding冷启动的问题大家问的比较多,我找时间统一回复一下。 简单来说生成环境下对于冷启动物品需要指定一个默认embedding,或者基于一些其他的相似条件取相似embedding的平均。
5 - Capricornus2021-01-24userSeq.select("movieIdStr").rdd.map(r => r.getAs[String]("movieIdStr").split(" ").toSeq) 老师这里为什么需要转陈Seq,而不使用Array? val sortUdf: UserDefinedFunction = udf((rows: Seq[Row]) => { rows.map { case Row(movieId: String, timestamp: String) => (movieId, timestamp) } .sortBy { case (movieId, timestamp) => timestamp } .map { case (movieId, timestamp) => movieId } }) 老师这里case的作用是什么?不太能理解。展开
作者回复: 这两个问题自己去试验一下,比如自己换成array看看行不行。 scala和spark中有大量语法糖和一些复杂的类型转换,不建议在这上面花费太多时间,注重经验积累就好。
共 2 条评论3 - 花花花木兰2020-12-21老师,对于非序列数据用spark的word2vec模型是不是不合适?非序列数据用什么方法训练比较好?例如生鲜配送的场景,用户一次会购买很多菜,这些菜在一个订单中是没有先后时序的。
作者回复: 其实item2vec里面并没有区分什么先后顺序。把这堆菜中的item两两输入模型训练就好。
3 - tiger2020-11-12老师,用word2vec利用高评分的做出了的模型(相似推荐)有的物品会找不到,能不能把所有物品one hot编码输入里面,这样是不是就会找到所有的物品了?但是这样做有没有意义?
作者回复: 这是一个好的业务问题。我推荐大家自己思考一下,没有标准答案。 我的思考是把所有物品放进去可能会引入大量错误的关联,反而得不偿失。而缺少物品的问题最好通过调高采样数量的方式或者其他冷启动的方式解决。
3 - 泷2021-03-03思考题,变为Node2Wec,主要修改随机游走循环中的probDistribution,根据记录的前驱节点进行修正,p,q为修正权重,然后再归一化概率分布,后续步骤多一个prevElement = curElement。 刚接触scala,如有错误请指正。 var probDistribution = mutable.Map[String, Double]() if (prevElement != null) { val prevProbDis = transitionMatrix(prevElement) //获得前驱节点的“邻接矩阵”行 var accuP:Double = 0 var nonNormProb = mutable.Map[String, Double]() for ((item, prob) <- transitionMatrix(curElement)) { //前驱节点可达,那么距离为1 if (prevProbDis.contains(item)) { nonNormProb(item) = prob accuP = accuP + prob } else if (item == prevElement) { //是前驱节点,那么距离为0 nonNormProb(item) = prob * (1 / p) accuP = accuP + prob * (1 / p) } else { //其他情况,距离为2 nonNormProb(item) = prob * (1 / q) accuP = accuP + prob * (1 / q) } } for ((item, prob) <- nonNormProb) { //归一化概率分布 probDistribution(item) = prob / accuP } } else { //当前为firstItem,不进行概率修正(偷懒) probDistribution = transitionMatrix(curElement) }展开
作者回复: 粗看应该是没问题。 for ((item, prob) <- nonNormProb) { //归一化概率分布 probDistribution(item) = prob / accuP } 归一化这一步非常重要,上次有位同学提交了代码但是没有做归一化,所以就是错误的。
2 - tuomasiii2021-02-23想请问下老师如果新的user/item加入,embedding matrix形状变大。如果每天都end2end train from scratch,是不是太expensive了? 如果是手动扩容embedding再initialize with昨日的checkpoint继续训练新的user-item interactions,然后时间久一点再train from scratch,行得通吗?
作者回复: 手动扩容embedding 这个怎么实现?我感觉输入向量的维度都改变了吧,可以实现手动扩容吗? 感觉这个方法比较tricky,不太行得通。写了一篇相关文章,希望有所启发。 https://zhuanlan.zhihu.com/p/351390011
2 - tuomasiii2021-01-05想问一下在item2vec和graph embedding里面ttl都设置成了24小时,也就是说24小时候后这些embedding就会从redis里面evict掉吗? 还有就是如果有new user和new item我们都是需要train from scratch吗?还是说可以增量训练?
作者回复: 非常细心。 1、是的,ttl会让emb在24小时候失效,这一般是生产环境的做法,避免一些过期的错误emb留存。 2、emb几乎没有办法增量训练,需要重新训练生成new emb
3 - 陈威洋2020-12-06下午好,请问老师: 作为推荐系统工程师,在学习代码的时候,除了掌握代码实现了什么,还要把每一句代码的意思弄透彻吗?
作者回复: 看自己。如果你觉得自己有能力复现,当然不用弄懂每句代码的意思。
共 2 条评论3 - 范闲2020-11-23embeddding不可以用tf来求么?
作者回复: 当然可以,不仅可以用tensorflow,还可以用任何深度学习平台来训练。
3 - 萬里長空2020-11-05老师,这里想再问个内存的问题,生产环境中,比如2000万的商品,使用spark-word2vec来训练,维度设置为100,这里driver的内存一般要设置多大啊,我用了36g还是会存在内存问题,但是用gensim的单机训练,不到30g就训练好了
作者回复: spark word2vec没有做negative sampling,训练速度肯定慢一些。没有看过gensim源码,但应该使用了negative sampling加快训练速度。 课程中关于具体的参数,debug这些问题我可能都不会给具体的建议,因为实际问题的变量太多,跟你的数据量,物品量,节点数,memory都有关系,还是自己多尝试,谢谢。
共 2 条评论3 - fsc20162020-10-29老师,关于随机游走到下一部电影实现,文稿和github上代码不一致。文稿中这种实现,可以保证游走概率大的电影,优先被游走到嘛 文稿中: //从curElement到下一个跳的转移概率向量 val probDistribution = transferMatrix(curElement) val curCount = itemCount(curElement) val randomDouble = Random.nextDouble() var culCount:Long = 0 //根据转移概率向量随机决定下一跳的物品 breakable { for ((item, count) <- probDistribution) { culCount += count if (culCount >= randomDouble * curCount) { curElement = item break } }} github上: val probDistribution = transitionMatrix(curElement) val randomDouble = Random.nextDouble() breakable { for ((item, prob) <- probDistribution) { if (randomDouble >= prob){ curElement = item break }展开
作者回复: 其实是一致的,文稿中probDistribution中存储的是吓一跳物品的统计数量,github中存储的是跳转概率。结果都是一样的。 因为github项目在不断优化迭代,所以还是最好以github为准。文稿中的代码我会找时间更新。多谢细心的发现。
3