糖豆推薦系統第一期開發與評估報告

1.緣起

糖豆做爲國內最大的廣場舞平臺,全網的MAU已經超過4000萬,每個月PGC和UCG生產的視頻個數已經超過15萬個,每個月用戶觀看的視頻也超過100萬個。然而以前糖豆APP首頁主要仍是依賴內容編輯手工推薦來發現內容,天天的推薦量也是幾十個而已。明顯可見千人一面的內容分發效率比較低下,繼而咱們於2016年12月初,啓動了糖豆推薦系統的設計以及開發,目前截止到2017年1月初,已經完成第一期推薦系統的開發與評估。推薦項目立項伊始,我撰寫了一篇總體架構與設計,本文和架構一文在部份內容有所重複,本文主要專一闡述推薦系統的開發、實現以及評估的細節。node

推薦系統的目的也能夠簡單總結成爲如下兩點:python

  • 根據用戶我的興趣分發內容,爲生產者和消費者打造更加合理的流量分發體系。
  • 提升用戶觀看時長,從而進一步到達提高產品留存。

能夠看到核心評估目標是用戶的觀看時長,相對直接易理解。固然評估過程,咱們遵循數據科學的評估體系,衡量了包括多種優化目標(RMSE,P@K,AUC/ROC,覆蓋率等等)的指標。同時還根據AB測試,評估了總體推薦模塊的CTR,播放時長等多項業務統計指標。mysql

2.架構

相信自從Netfix公佈他們的推薦架構以後[1],後續的推薦系統基本都會按照在線(online),近線(near line),離線(off line)三個部分來構建。雖然劃分紅三個模塊,本質是推薦算法迭代時間窗口問題,根據用戶行爲數據,構建一個持續進化的系統。redis

糖豆推薦系統架構基本也是按照三個模塊來構建。限於人力和時間,第一期主要實現了離線部分。架構圖以下:算法


推薦系統架構1.0

整個系統架構主要由數據、算法、策略、評估和服務層組成,相對清晰明瞭。sql

  1. 數據層,主要數據來源包括用戶行爲日誌以及數據庫。咱們在16年10月份~11月份,對整個日誌收容、分析和挖掘流程作了改造。
    • 收容:同時將日誌離線和在線的pipeline完全分離。
    • 解析:本來基於MR的ETL所有改成Spark任務,在集羣機器數量不變狀況下,總體效率基本提升了兩倍以上,Spark具有很好的取代MR的潛力。
    • 挖掘:Spark MLib集成了多種機器學習算法,原有基於Mahout的算法基本能夠替代實現。
  2. 算法層架構一圖中,黑字部分是咱們實現了的算法,藍字部分都是計劃中但未實現的算法。
  3. 策略層:
    • 融合算法,主要包括如下三種,目前咱們同時使用了級聯聯合以及混合融合。
    • 業務過濾,目前暫時沒作。
    • 推薦排序,目前的排序對用戶隱式反饋行爲(包括播放時長、下載、收藏等指標)作線性加權以及歸一化處理,獲得一個0~5分之間的評分,做爲LFM的數據集,經過模型獲得預測的打分,最後按照視頻打分以及視頻建立時間作倒序排序。後續咱們會引入學習排序(LTR)算法,來持續改進推薦結果排序質量。LTR,包括PointWise,PairWise,ListWise三類算法。預期將來先使用PointWise類別的算法。

3.算法實現

推薦系統算法在過去幾十年有很是長足的發展和應用,總結下來基本包括基於內容、基於鄰域,基於矩陣分解等類型。mongodb

  1. 基於鄰域:核心思想是,爲用戶推薦與之屬性、行爲類似的物品。鄰域就是興趣類似的數學表達。它包括UserCF和ItemCF,基礎研究深刻,在性能、可解釋性上效果都不錯,因此應用也十分普遍。
  2. 基於矩陣分解:也就是隱語義模型,在文本挖掘範圍首先被提出。矩陣分解是一系列複雜算法(LSM,LSI,LDA,Topic Model)的數學基礎。它包括特徵值分解、奇異值分解等,有具體計算方法包括SVD,Funk-SVD,ALS,SVD++等。

3.1 LFM

隱語義模型其核心思想是經過潛在特徵聯繫用戶和物品,根據用戶行爲統計的自動聚類。LFM模型可以劃分出多維度、軟性、不一樣權重的分類。它經過如下數學公式來表達用戶對物品的興趣,由兩個低秩的矩陣來近似表達原有高階矩陣。數據庫


矩陣分解

能夠看到從矩陣計算問題,轉化成優化問題。優化目標的數學形式化:session


優化形式化

這個形式化問題有多種解法,包括SVD,ALS等。Spark提供了包括mlib裏的ALS,以及graphx裏的SVD++。架構

3.1.1 ALS(最小交替二乘法)

ALS將矩陣計算轉化成爲一個最優化函數問題,經過最小化偏差的平方和計算最佳函數匹配。ALS在每次迭代期間,一個因子矩陣保持恆定,而另外一個使用最小二乘法求解。一樣在求解另外一因子矩陣,保持新求解的因子矩陣固定不變。

Spark ALS的實現,每次迭代過程了爲了減小通信消耗,只會傳輸兩個因子矩陣(用戶、物品)之一參與計算。這個實現是經過預計算矩陣的元數據,獲得一個meta矩陣。這樣就能夠在用戶和物品block之間只傳輸一組特徵向量,來更新計算。


ALS
  • 優勢,不受到用戶和數據質量影響。全局性求解,單一模型效果最好。
  • 缺點,增量更新緩慢。

3.1.2 Spark實現

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相關的類。

3.1.3 候選集問題

咱們訓練模型數據量基本在10億量級,咱們計算集羣總共16臺8核,24G的datanode,訓練時間大概30分鐘。按照咱們用戶和物品規模,若是直接使用模型預測推薦結果,候選集規模在萬億級別,是集羣沒法承受的。全部須要對預測的候選集作過濾,目前採用三種過濾方法。

  1. 看過的做者。將用戶過去30天看過的做者的做品做爲候選集。這個作法合理清晰,可是存在所謂的「信息繭房」問題,也就是說容易出現多樣性不足。
  2. 看過的類似的視頻。根據ItemCF算法獲得類似的視頻。將過去看過30天的Top10的相似視頻看成候選集。
  3. 看過的類似的標籤的視頻。將用戶看過的視頻相同類型標籤的視頻做爲候選集。依賴專家知識,在具體到咱們的舞蹈視頻上,咱們編輯提供的標籤只能覆蓋極少的視頻。因爲這種作法傾向於PGC做者,在測試後期再也不使用。

3.2 ItemCF(基於物品的協同過濾)

基於物品的協同過濾算法是目前應用最普遍的推薦算法,由亞馬遜提出[2],核心思想給用戶推薦那些和他們以前喜歡物品類似的物品。類似度是基於用戶對物品的行爲來計算的,而非物品自己的屬性。

3.2.1 算法原理

基於物品的協同過濾算法主要分爲如下兩步:

  1. 計算物品之間的類似度
  2. 根據物品的類似度和用戶歷史行爲給用戶生成推薦列表

核心是計算物品之間的類似度,咱們使用餘弦類似度。


餘弦類似度

該算法懲罰了熱門物品的權重,減輕熱門視頻和大量視頻類似的可能性。

3.2.2 Spark實現

咱們基於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()

3.3 抄底策略

抄底策略實際上是一個冷啓動的問題,策略也很是多。

  • 近期熱門item、新item。
  • 編輯精選。
  • 新品類上線。
  • 同城熱門。

咱們目前只生效了熱門策略,採用了Hack News的熱門算法做爲抄底策略,以下圖:


熱門算法
  • P表示視頻觀看次數。
  • T表示距離視頻發佈時間(單位爲小時),加上2是爲了防止最新的視頻致使分母太小。
  • G表示"重力因子"(gravityth power),即爲視頻衰減係數。

熱門算法衰減係數

咱們根據實驗結果,肯定了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`

3.4 算法融合

融合策略主要包括如下三類,固然還有ensemble相關的方法:

  • 加權融合(Weight Merge):根據經驗值對不一樣算法賦給不一樣的權重,對各個算法產生的候選集按照給定的權重進行加權,而後再按照權重排序
  • 級聯融合(Cascade Merge): 優先採用效果好的算法,當產生的候選集大小不足以知足目標值時,再使用效果次好的算法。
  • 混合融合(Mix Merge): 不一樣的算法按照不一樣的比例產生必定量的候選集,而後疊加產生最終總的候選集。

咱們主要在候選集上使用了mix merge,在結果產出時,採用了cascade merge合併LFM和ItemCF的結果。

4.服務實現

4.1 AB分桶服務

根據用戶diu,使用crc32 hash函數對用戶取餘,分別賦予AB兩個類型。客戶端拿到abtag後根據服務端數據流實現展現和數據埋點。

4.2 推薦服務

個性化推薦系統服務會在app首頁打開後被調用,具體服務流程步驟以下:

  1. 經過用戶DIU獲取推薦模型導出的數據列表
  2. 判斷推薦的數據列表是否爲空
  3. 推薦的數據列表若是不爲空,則執行5
  4. 推薦的數據列表若是爲空,則獲取抄底的推薦列表,而後執行5
  5. 從推薦的數據列表中過濾點目前首頁已經展示的視頻
  6. 根據推薦的分數和視頻建立時間,將列表進行排序
  7. 返回結果

流程圖

4.3 存儲選型

推薦系統天天出一次推薦結果, 所以推薦結果須要按天區分, 同時須要按diu來快速查詢,能夠採用的存儲有hbaseredis等鍵值對數據庫,mongodb等文檔型數據庫,或者
mysql等傳統關係型數據庫

  • hbase 鍵值對存儲,存儲量大,查詢速度快,穩定性取決於集羣是否高可用,如高可用,可優先選擇
  • redis 鍵值對存儲,存儲量較大,熱數據基於內存存儲,查詢速度快,能夠考慮,不過當每一個人的推薦結果N較大時,要考慮存儲大小
  • mongodb 文檔型數據庫,存儲量大,熱數據一樣存儲在內存,索引速度接近於redis, 結構化,易維護,能夠考慮
  • mysql 關係型數據庫, 存儲量較大,基於文件索引機制,查詢速度較上述存儲來講,理論值較低,能夠做爲備選。

每一個用戶的推薦數N=60, 存儲佔用180g,決定採用hbase 根據rowkey字段作索引, 當咱們指定diudate時,會快速返回rowkey在該範圍內的結果。

5.效果評估

5.1 離線評估

採用融合多維度用戶行爲數據線性轉換成顯式反饋評分。因爲採用了多維度數據,算法模型效果大幅提高,結果以下:

  • RMSE從4.1提高到1.0。(Netfix大賽冠軍大概在0.8左右)
  • P@K從0.6705提高到0.938。
  • 預測覆蓋率爲99%,推薦覆蓋率爲90%。

5.2 A/B測試

猜你喜歡模塊已經在官方渠道測試將近三週,展示形式以下圖:


猜你喜歡

經過AB測試,能夠看到首頁模塊的點擊率總體提高了10%,人均觀看時長總體提高5%。目前能夠看到,猜你喜歡模塊效果略優於每日精選。

6.改進與展望

第一期開發的時間相對較短,人力也很是不足,期間還有不少數據分析、挖掘工做須要兼顧,總體工做相對簡單。將來第二期,主要精力集中在近線和在線的模塊開發,以及學習排序。

相關文章
相關標籤/搜索