蘑菇街,我的買手街!懂得其程序背後推薦原理,買到更好的東西

推薦一直是電商平臺的重要流量入口。以往在電商平臺上,推薦的場景更多的覆蓋在交易的各個環節,比如詳情頁、購物車、訂單及支付等。近年來推薦發展逐漸的多樣化,場景上逐漸覆蓋到各流量入口,推薦的實體也擴展到活動、類目、運營位等。

蘑菇街作為一家社會化導購電商平臺,近1年推薦業務發展也非常快。早期我們更多進行商品的推薦促進成交,在16年321和雙11大促活動中引入了個性化猜你喜歡,帶來非常大的效果提升,接下去推薦作為一種常規的資源位效率提升手段,滲透到更多的場景中。包括導購類目、專輯甚至搜索提示詞都是通過推薦系統來支持。目前接入的推薦場景已經有一百多個。

本文將介紹蘑菇街的推薦系統工程實現,主要介紹在線推薦服務、埋點及效果統計。

推薦流程

完成一次個性化推薦,一般可以分成召回、排序兩個步驟。
召回:圈出候選集,一般召回策略會有很多種,比如召回用戶實時點擊過的相似商品。
排序:即點擊率預估,從候選集中進行在線排序。

以上是一個簡單的推薦猜你喜歡實現。首先算法離線訓練好與該商品的相似商品集,當在線用戶請求過來的時候,有一個用戶實時點擊過的相似商品召回策略,該策略先獲取用戶實時點擊過的商品列表,再取每個商品的相似商品。從而得到一個候選集。再通過在線排序進行點擊率預估。取其中top商品從而得到一個推薦結果集。

實際的場景中,我們將在離線訓練、召回策略、排序策略等環節不斷優化。
召回策略中,除了支持實時個性化外,還可以不斷髮掘新的策略,進行策略ee,引入強化學習進行意圖識別等。在線排序中支持lr、gbdt等模型外,還需要平衡性能與特徵維度,支持模型的在線學習等。

系統架構

整個推薦系統可以分為在線服務層、近實時計算層、離線計算層3大塊。

在線服務:包含abtest實驗、結果集召回、點擊率預估、字段補全、埋點幾部分。近實時計算:根據用戶實時行為,提取用戶實時特徵、在線模型訓練。離線計算:根據用戶歷史行為,進行相關性訓練、商品初排分、離線特徵提取等。

在線推薦服務

我們將系統分成推薦投放系統prism、精排系統kepler、推薦引擎、用戶特徵服務、abtest、字段補全服務。

prism:推薦統一接口,負責召回規則、abtest、埋點、字段補全。kepler:點擊率預估。各打散置頂等業務層排序。用戶特徵:離線和實時的用戶特徵存儲。推薦引擎:離線算法訓練的結果集存儲。字段補全:商品等正排信息補全。比如價格、標題等。

推薦投放prism

作為通用化的推薦平臺,接入100+個推薦場景,可以分成20+類推薦規則。並且推薦的實體也包含商品、店鋪、社會化內容、類目詞等等。我們希望提供一個推薦平臺,讓算法工程師自助實現推薦需求。

投放框架層的功能如下:
1. 提供統一的推薦接口。
2. 各個場景的召回策略規則。可熱部署。
3. 提供投放sdk,提供通用數據源接口、工具類。方便算法推薦規則編寫。
4. 提供測試框架
5. 算法實驗以及埋點統計
6. 推薦工作臺

推薦投放整體架構圖

主要包含投放框架、推薦策略(腳本)、投放sdk、測試框架、工作臺幾部分。以下分別介紹。

推薦實體關係

投放框架的實體模型分為場景、實驗、腳本、配置。腳本里面承載了具體的推薦邏輯,為了腳本複用,增加了對腳本的配置。

推薦策略

推薦策略是整個推薦邏輯的核心,比如猜你喜歡場景,有用戶實時特徵做相關性的召回策略,也有用戶離線特徵的相關性召回策略。並且按照一定的配比merge出結果集,再調用精排系統的lr模型做點擊率預估。這些都是需要寫在策略腳本里。

我們為每個算法團隊分配這樣一個策略腳本工程。腳本本質是一個java類,算法迭代時提交代碼到git,通過推薦工作臺可視化界面,來完成熱部署功能。

腳本層面再封裝一層召回策略池,在候選集觸發層進行策略ee。

投放sdk

sdk是為了降低編寫策略的成本,封裝了底層數據源調用,比如用戶信息、離線推薦結果集等,並且提供了算法的常用實現工具類。

數據源接口通過spi的方式,還提供了本地調試的實現。開發機在通過測試框架調用數據源接口時,會被代理到訪問遠程服務器的接口獲取數據。方便開發本機調試程序。

測試框架和穩定性

測試框架是解決算法本地測試存在,將問題提前暴露。主要的功能有:
1. 配置模擬,還原線上的各種配置場景。
2. 數據源接口,如上所述。
3. 線上用戶請求採樣,可自定義請求回放後的數據分析邏輯。
4. 對當前腳本的性能評估。通過線上採樣請求來做為請求數據源。

此外在腳本越來越多以後,防止出現一個腳本性能問題,我們做了容量規劃。每日使用線上採樣請求,凌晨對系統進行壓測。觀察系統容量。

推薦存儲

主要承載了用戶特徵以及離線推薦結果集。存儲系統對讀寫性能要求非常高。
1. 整條推薦鏈路我們希望在50ms內完成。一次複雜的推薦請求會請求上百次(以存儲中的key為單位)存儲數據。存儲需要在1ms內返回。
2. 對時延要求比較高,比如我們需要收集用戶的曝光行為數據做降權,同時曝光數據的量非常大,對內存有挑戰。

存儲方案

早期我們使用redis來存儲,後來自研了蘑菇街的存儲引擎,採用mmap方式,定製了一套List格式的存儲數據結構。

存儲採用mmap方式,天然解決了很多問題:
1. mmap的方式,擁有內存的讀寫性能。
2. 操作系統保證落盤,避免重啟時數據丟失。當然出現宕機情況下,系統能夠從hdfs以及消息中間件中恢復數據。
3. 堆外內存,在大量寫數據場景時不會影響gc。造成性能抖動。

採用自寫存儲的方式,同時也為我們提供更多擴展性:
1. 召回+過濾邏輯,算法離線推薦訓練一般是天級別訓練一次,我們實時過濾一些下架等狀態不符合的商品。算法訓練的數據也可以減少,比如只需要訓練一份item to item的相關性結果集,如果女裝類目需要使用可以在線從結果集裡面做類目過濾。

系統實現上我們會存算法訓練結果的索引表和商品正排表,並且支持一些基礎篩選語句。
2. 性能提升,針對一個推薦場景查詢上百次存儲,我們提供批量查詢接口,並且在存儲服務端提供並行方式,單個查詢速度非常快,由單獨一個線程執行很快會結束,上下文切換頻繁導致並行性能並不好。我們做了一個策略優化,將線程分成n組,上百個查詢會均勻落到其中一個,每個分組同步執行。
3. 自定義插件,將常用的召回邏輯抽象成插件,比如查詢用戶實時曝光過但未點擊的商品,在某些業務場景做降權。插件邏輯裡獲取用戶的曝光商品列表和點擊商品列表做剔除。方便業務開發並且統一邏輯。同時也利用了存儲系統的計算資源(存儲是io密集型)。

存儲的瓶頸在於內存,我們目前按照垂直業務劃分部署,單個業務(比如item 2 item結果集)目前還未能超過單機內存限制。還未做分片方案。

存儲系統結構

大致分成接口、腳本、索引、正排幾個部分。

接口支持分實例部署,每個實例可以部署多臺機器。每臺機器狀態對等。腳本層將自定義一些插件,抽象常用的邏輯。索引層即推薦算法訓練結果集。算法spark離線訓練放到hdfs中,觸發存儲去取,支持全量和行更新。針對用戶實時特徵場景,也支持從消息隊列corgi中增量更新。正排存放商品的基礎信息,用於做篩選過濾,來源於我們搜索dump平臺的全量(hdfs)和增量消息。

精排系統kepler

精排系統的職責是對候選集進行排序,其中核心點在於模型和特徵。理想情況下系統儘可能支持多的模型和特徵,但是在線計算需要較小的時延,這就要求我們要平衡效果和性能。

模型一般有線性和線性,目前我們支持LR和GBDT。模型一般離線更新,針對雙11大促等場景我們也會使用在線學習實時更新。

線性模型公式:

x是特徵,θ是權重。一個模型通常有幾十維特徵,這些特徵的計算和存儲就成為系統最大的挑戰。以下是我們的幾點應對方式:
1. 控制候選集數量在千級別。候選集一大整體計算就比較慢,rt也會上升。
2. 實體(商品)特徵本地存儲,每次需要排序1000個商品,本地存儲可以極大緩解網絡壓力。同樣我們內嵌了推薦引擎的存儲模塊,擁有內存的速度,又解決持久化的問題。
3. 針對內存瓶頸,我們將用戶相關特徵遷移到遠程,考慮每次查詢只會查幾次用戶特徵數據,開銷不大。
4. 並行計算,複雜模型下,組裝特徵和計算還是比較費時,為了提升rt我們進行並行計算,充分利用cpu的資源。在系統容量不變的情況下提升rt。

系統架構圖

整個精排服務同時為搜索排序(topn)、推薦(prism)提供排序服務。輸入商品列表,輸出排序後的商品列表。整個鏈路包含當前模型參數獲取、特徵數據準備、特徵預處理及打分、預測、業務層排序、業務打散等環節。

keplerService是接口層,服務會按照業務分實例部署。每個實例的內存狀態也不同。應對一塊業務,比如推薦、搜索。接口層除了需要傳需要排序的商品列表,還需要傳模型code。模型配置獲取,排序後臺會配置好每個模型的算法、特徵、權重等信息。同步到配置服務器(metabase)下發到kepler系統。特徵數據準備,針對當前算法預先加載特徵數據,特徵數據較多,統一獲取特徵可以批量操作節約性能。特徵部分存在本地,用戶數據需要訪問遠程服務。rerank包括特徵預處理、打分、預測。這部分可以多線程並行執行。減少時延。業務排序,只要執行業務置頂、加權等邏輯,業務打散,針對一個結果集可能太同質,會進行類目、店鋪等打散。

數據流

推薦算法固然非常重要,但是缺少穩定可靠的數據流,算法的效果追蹤就沒有說服力。早期蘑菇街的埋點耦合到各個業務層,並且嚴重依賴url,一方面維護工作量很大,另一方面系統重構,產品迭代整個打點經常發生丟失,我們在15年重建了abtest和數據流體系,經歷1年左右時間已經徹底解決頑疾,並且為算法業務提供了很大的擴展性。

我們推行一套打點規範,命名為acm。acm中包含了我們推薦的位置信息、實驗信息、算法自定義埋點信息等。每個推薦商品都會有自己的acm標識。推薦接口端統一生成並且和終端約定好統一的埋點格式。

acm讓我們徹底拋棄以往對url的依賴,同時自定義信息能夠幫助算法實現各種分析需求,比如分析各召回策略的曝光、點擊、交易佔比。也為在線特徵分析以及強化學習提供了數據源。

以搜索排序場景舉例:

在一個常規的算法實驗過程中,流程如下:
1. 算法在abtest控制檯進行實驗切分。
2. 實驗信息會通過zk推送給推薦投放系統。
3. 投放端進行實驗分流,執行召回排序邏輯,為每個商品進行埋點,透出結果集。
4. 終端統一埋點,發送給日誌收集服務器。
5. acm採集系統將收集到acm日誌流,執行清洗反作弊等邏輯。輸出實時消息流,並且定時保存到離線hive表中。
6. acm通用聚合系統將多維度聚合資源位、實驗等維度的統計信息,持久化到es、db。
7. 可視化組件可以自定義從db中拿到多維度數據,進行實時、離線數據的監控分析。

其他

在線推薦還有一個字段補全服務,採用redis存儲,存儲的數據用protobuf進行序列化。

總結

系統歷時1年半多的發展,基本實現我們平臺化的目標。算法同學可以專注在算法效果提升,工程同學可以專注框架升級。

最早我們只有一個推薦引擎做離線推薦,隨著業務接入越來越多,場景定製的召回策略凸顯出來,我們開始搭建了推薦投放系統。算法為了提升效果,實時個性化就是基本需求,於是我們搭建了用戶特徵服務。緊接著就是精排系統進行點擊率預估。基本上主流算法功能都能夠實現。

數據流系統是我們一開始就規劃的,我們在做推薦的同時也負責搜索服務,一直吃數據質量問題的苦頭。一開始我們想解決abtest跟效果追蹤的問題,隨著項目進行,我們發現順帶也解決了算法策略數據分析的問題,打下了很好的基礎。

在16年中,蘑菇街和美麗說技術體系合併,並且蘑菇街推薦在16年321大促上表現優異,推薦場景發展迅速。系統一下子接入了很多業務方,支持的算法也包含了北京的團隊。此時我們就考慮進行平臺化,將算法操作自助化,不依賴工程的日常發佈。之後,我們將算法腳本發佈的權利交於算法時,考慮到系統穩定性,我們開始做了一系列的保障工作。

關於後續,我們第一優先的還是優化細節,降低使用成本,提升系統效率。實實在在的讓各方受益。其次會在算法的效果上進行嘗試,比如召回策略上引入強化學習,排序特徵擴維度等。當然存儲是最大的挑戰,當前單機部署能夠滿足現有業務,我們也進行按照業務算法做分組部署,在業務擴大10倍的情況下,單個業務算法結果可能就會突破單機限制,存儲架構就需要升級,支持分片。

碼農三哥,一名普通程序員,會點java軟件開發,對AI人工智能有點興趣,後續會每日分享些關於互聯網技術方面的文章,感興趣的朋友可以關注我,相信一定會有所收穫。

想轉型或剛步入程序員Java開發的朋友,有問題可以留言或私信我!