糖豆做爲國內最大的廣場舞平臺,全網的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%。目前能夠看到,猜你喜歡模塊效果略優於每日精選。
第一期開發的時間相對較短,人力也很是不足,期間還有不少數據分析、挖掘工做須要兼顧,總體工做相對簡單。將來第二期,主要精力集中在近線和在線的模塊開發,以及學習排序。