糖豆做为国内最大的广场舞平台,全网的MAU已经超过4000万,每个月PGC和UCG生产的视频个数已经超过15万个,每个月用户观看的视频也超过100万个。然而以前糖豆APP首页主要仍是依赖内容编辑手工推荐来发现内容,天天的推荐量也是几十个而已。明显可见千人一面的内容分发效率比较低下,继而咱们于2016年12月初,启动了糖豆推荐系统的设计以及开发,目前截止到2017年1月初,已经完成第一期推荐系统的开发与评估。推荐项目立项伊始,我撰写了一篇总体架构与设计,本文和架构一文在部份内容有所重复,本文主要专一阐述推荐系统的开发、实现以及评估的细节。node
推荐系统的目的也能够简单总结成为如下两点:python
能够看到核心评估目标是用户的观看时长,相对直接易理解。固然评估过程,咱们遵循数据科学的评估体系,衡量了包括多种优化目标(RMSE,P@K,AUC/ROC,覆盖率等等)的指标。同时还根据AB测试,评估了总体推荐模块的CTR,播放时长等多项业务统计指标。mysql
相信自从Netfix公布他们的推荐架构以后[1],后续的推荐系统基本都会按照在线(online),近线(near line),离线(off line)三个部分来构建。虽然划分红三个模块,本质是推荐算法迭代时间窗口问题,根据用户行为数据,构建一个持续进化的系统。redis
糖豆推荐系统架构基本也是按照三个模块来构建。限于人力和时间,第一期主要实现了离线部分。架构图以下:算法
整个系统架构主要由数据、算法、策略、评估和服务层组成,相对清晰明了。sql
推荐系统算法在过去几十年有很是长足的发展和应用,总结下来基本包括基于内容、基于邻域,基于矩阵分解等类型。mongodb
隐语义模型其核心思想是经过潜在特征联系用户和物品,根据用户行为统计的自动聚类。LFM模型可以划分出多维度、软性、不一样权重的分类。它经过如下数学公式来表达用户对物品的兴趣,由两个低秩的矩阵来近似表达原有高阶矩阵。数据库
能够看到从矩阵计算问题,转化成优化问题。优化目标的数学形式化:session
这个形式化问题有多种解法,包括SVD,ALS等。Spark提供了包括mlib里的ALS,以及graphx里的SVD++。架构
ALS将矩阵计算转化成为一个最优化函数问题,经过最小化偏差的平方和计算最佳函数匹配。ALS在每次迭代期间,一个因子矩阵保持恒定,而另外一个使用最小二乘法求解。一样在求解另外一因子矩阵,保持新求解的因子矩阵固定不变。
Spark ALS的实现,每次迭代过程了为了减小通信消耗,只会传输两个因子矩阵(用户、物品)之一参与计算。这个实现是经过预计算矩阵的元数据,获得一个meta矩阵。这样就能够在用户和物品block之间只传输一组特征向量,来更新计算。
spark mlib实现了ALS算法,调用比较简单,稍微麻烦的是调参和评估。贴段python代码,注释比较详细了。
##初始化sparksession(spark 2.0以上引入) spark = SparkSession.builder.master('yarn-client').appName('recy_als_model:'+inUVMDate).config('spark.sql.warehouse.dir', '/user/hive/warehouse').enableHiveSupport().getOrCreate() #读入用户视频评分全量表 rateSql = "select * from da.recy_als_data_uvm where dt='"+inUVMDate+"'" #spark 读hive表 rating = spark.sql(rateSql) #分割训练集和测试集,0.8,0.2 (training, test) = rating.randomSplit([0.8, 0.2]) #ALS模型参数 ranks = [8, 10] lambdas = [0.01,0.05, 0.1] numIters = [20] bestModel = None bestValidationRmse = float("inf") bestRank = 0 bestLambda = -1.0 bestNumIter = -1 #调参 for rank, lmbda, numIter in itertools.product(ranks, lambdas, numIters): als = ALS(rank=rank,maxIter=numIter, regParam=lmbda, userCol="f_diu", itemCol="f_vid", ratingCol="f_rating", nonnegative=True) model = als.fit(training) #!!注意是随机取样,使用测试集评估模型,经过RMSE来评估模型。因为测试集中可能有模型中没出现过的user,那就会有预测值为nan。drop便可 predictions = model.transform(test).dropna('any') evaluator = RegressionEvaluator(metricName="rmse", labelCol="f_rating", predictionCol="prediction") validationRmse = evaluator.evaluate(predictions) print "RMSE (validation) = %f for the model trained with " % validationRmse + \ "rank = %d, lambda = %.1f, and numIter = %d." % (rank, lmbda, numIter) if (validationRmse < bestValidationRmse): bestModel = model bestValidationRmse = validationRmse bestRank = rank bestLambda = lmbda bestNumIter = numIter # evaluate the best model on the test set print "The best model was trained with rank = %d and lambda = %.1f, " % (bestRank, bestLambda) \ + "and numIter = %d, and its RMSE on the test set is %f." % (bestNumIter, bestValidationRmse) #保存预测结果 predictions = bestModel.transform(rating).dropna('any') predictPath = "hdfs://Ucluster/olap/da/recy_als_predict/"+inUVMDate+"/" predictions.repartition(200).write.mode('overwrite').save(predictPath, format="parquet") spark.stop()
spark ml库在逐步取代mlib库,咱们使用了ml,上面代码片断须要引入pyspark.ml相关的类。
咱们训练模型数据量基本在10亿量级,咱们计算集群总共16台8核,24G的datanode,训练时间大概30分钟。按照咱们用户和物品规模,若是直接使用模型预测推荐结果,候选集规模在万亿级别,是集群没法承受的。全部须要对预测的候选集作过滤,目前采用三种过滤方法。
基于物品的协同过滤算法是目前应用最普遍的推荐算法,由亚马逊提出[2],核心思想给用户推荐那些和他们以前喜欢物品类似的物品。类似度是基于用户对物品的行为来计算的,而非物品自己的属性。
基于物品的协同过滤算法主要分为如下两步:
核心是计算物品之间的类似度,咱们使用余弦类似度。
该算法惩罚了热门物品的权重,减轻热门视频和大量视频类似的可能性。
咱们基于spark sql实现了ItemCF,贴一段
spark = SparkSession.builder.master('yarn-client').appName('recy_icf_similarity:'+inDate).config('spark.sql.warehouse.dir', '/user/hive/warehouse').enableHiveSupport().getOrCreate() #指定spark 分区数 spark.sql("SET spark.sql.shuffle.partitions=2000") spark.sql("drop table if exists da.recy_icf_similarity_mid ") spark.sql("create table da.recy_icf_similarity_mid as select a.vid vid_1 , b.vid vid_2 , a.num num_1, b.num num_2, count(1) num_12 from da.recy_icf_similarity_pre a join da.recy_icf_similarity_pre b on (a.diu=b.diu) where a.vid<b.vid group by a.vid, b.vid, a.num, b.num") #计算余弦类似度 similarSql = " select vid_1, vid_2, num_12/sqrt(num_1*num_2) similarity from da.recy_icf_similarity_mid" similarDF = spark.sql(similarSql) similarDF.printSchema() # 保存结果 similarDF.repartition(300).write.mode('overwrite').save(similarDir, format="parquet") spark.stop()
抄底策略实际上是一个冷启动的问题,策略也很是多。
咱们目前只生效了热门策略,采用了Hack News的热门算法做为抄底策略,以下图:
咱们根据实验结果,肯定了G的取值。该算法同时保证了视频的热门程度和新鲜度。sql代码以下:
SELECT vid,title,createtime,hits_total,(if( hits_total>=1, hits_total - 1,hits_total)/power((TIMESTAMPDIFF(hour,createtime,now())+2),1.8)) as sc FROM `video` WHERE date(createtime) >=NOW() - INTERVAL 3 DAY ORDER BY `sc`
融合策略主要包括如下三类,固然还有ensemble相关的方法:
咱们主要在候选集上使用了mix merge,在结果产出时,采用了cascade merge合并LFM和ItemCF的结果。
根据用户diu,使用crc32 hash函数对用户取余,分别赋予AB两个类型。客户端拿到abtag后根据服务端数据流实现展现和数据埋点。
个性化推荐系统服务会在app首页打开后被调用,具体服务流程步骤以下:
推荐系统天天出一次推荐结果, 所以推荐结果须要按天区分
, 同时须要按diu来快速查询,能够采用的存储有hbase
,redis
等键值对数据库,mongodb
等文档型数据库,或者mysql
等传统关系型数据库
每一个用户的推荐数N=60, 存储占用180g
,决定采用hbase 根据rowkey字段作索引, 当咱们指定diu
和date
时,会快速返回rowkey在该范围内的结果。
采用融合多维度用户行为数据线性转换成显式反馈评分。因为采用了多维度数据,算法模型效果大幅提高,结果以下:
猜你喜欢模块已经在官方渠道测试将近三周,展示形式以下图:
经过AB测试,能够看到首页模块的点击率总体提高了10%,人均观看时长总体提高5%。目前能够看到,猜你喜欢模块效果略优于每日精选。
第一期开发的时间相对较短,人力也很是不足,期间还有不少数据分析、挖掘工做须要兼顾,总体工做相对简单。将来第二期,主要精力集中在近线和在线的模块开发,以及学习排序。