推薦系統中模型訓練及使用流程的標準化

圖片1.png

文章做者:梁超 騰訊 高級工程師python

編輯整理:Hoh Xilweb

內容來源:DataFun AI Talk算法

出品社區:DataFun數組

注:歡迎轉載,轉載請在留言區內留言。框架

導讀:本次分享的主題爲推薦系統中模型訓練及使用流程的標準化。在整個推薦系統中,點擊率 ( CTR ) 預估模型是最爲重要,也是最爲複雜的部分。不管是使用線性模型仍是當前流行的深度模型,在模型結構肯定後,模型的迭代主要在於特徵的選擇及處理方面。於是,如何科學地管理特徵,就顯得尤其重要。在實踐中,咱們對特徵的採集、配置、處理流程以及輸出形式進行了標準化:經過配置文件和代碼模板管理特徵的聲明及追加,特徵的選取及預處理等流程。因爲使用哪些特徵、如何處理特徵等流程均在同一份配置文件中定義,於是,該方案能夠保證離線訓練和在線預測時特徵處理使用方式的代碼級一致性。函數

▌一. 推薦系統oop

1.業務簡介編碼

圖片2.png

這是咱們的產品每天快報,會涉及首頁以及數十個子頻道,對於這些頻道咱們都須要作召回以及排序模型。如何高效的管理這麼多的頻道呢?咱們就須要一個很好的系統來管理全部的特徵和模型。spa

2.推薦系統流程設計

圖片3.png

簡單回顧下推薦系統的流程,整個推薦系統須要從數以百萬計的內容池中篩選出數以十計的文章推薦給最終的用戶。在這個過程當中主要涉及三個步驟:

第一步,從百萬量級中經過環境特徵,用戶特徵,物品特徵等信息來找出千級別的文章。

第二步,經過排序模型來預估點擊率或者預估用戶對這篇文章的偏好程度。

最後,經過一些運營規則和多樣性方面的考量 ( 好比用戶喜歡王者榮耀,但不能推薦給用戶都是這類的視頻或文章 ),最終呈現給用戶10篇左右的文章。

3.經常使用推薦模型

圖片4.png

經常使用的推薦模型有 LR、FM、DNN、W&D、DeepFM、DIN 等模型,對於推薦系統,不管使用哪一種模型,都須要如下幾個模塊:

樣本蒐集:訓練模型離不開大量的訓練樣本,因此須要進行樣本 ( 特徵和標籤 ) 的蒐集;

特徵配置:實際的推薦系統中會有上百個特徵供模型選擇,在模型版本迭代的過程當中,有些特徵會被捨棄,有些特徵會新加進來;於是,咱們就須要配置蒐集哪些特徵、使用哪些特徵,在迭代過程當中,還須要保證現有模型訓練和預測服務的穩定性;

特徵處理:對於每一個特徵,好比用戶 ID,該如何離散化成一個最終使用的int型的數字,就須要通過必定的特徵處理;

模型訓練&模型預測:特徵處理完以後,如何餵給模型訓練程序以及線上的預測模型,如何在修改了特徵配置以後,無需人工修改訓練和預測代碼,從而下降工做量、減小 bug 的引入,都是咱們須要考慮的工做。

4.排序流程圖

圖片5.png

上圖爲排序系統的流程圖:

以用戶 ID 特徵 ( userID ) 爲例,在線預測時,會首先把 userID 填入某一個變量中,並經過某種 hash 函數把它變成整數類型 ( 好比 C++ long 類型 ),再輸入到模型中;與此同時,咱們須要把在線的特徵記錄到日誌中,做爲模型訓練的樣本。

離線訓練:將特徵日誌和用戶行爲 ( 是否點擊、是否點贊、消費時長等 ) 日誌結合起來,造成最終帶有標籤的訓練樣本,再經過一樣的特徵處理流程,造成訓練樣本進行模型的訓練。

▌二. 推薦系統中模型迭代的痛點

與研究中給定的數據集不一樣,推薦系統中的模型須要不斷地迭代調優。在平常的工做中,咱們經常須要在保證現有模型服務穩定的前提下,不斷地增長新的特徵,訓練新的模型。因而,咱們會面臨下圖所示的諸多問題。

圖片6.png

▌三. Write once,run anywhere 的特徵處理標準

要設計一套特徵處理的標準,咱們首要面臨的問題是如何描述特徵處理的流程 ( C++ 代碼?protobuf?XML?)。基於如下幾點考慮:

  1. 儘可能減小人工寫代碼的量;
  2. 易於查看和維護;
  3. 易於迭代,咱們設計了一套基於 CSV 格式的特徵處理框架。

首先,咱們來看一下,在模型訓練方面,業界是怎麼作的。

圖片7.png

在工業界,對於的模型訓練和預測部分,TensorFlow 等框架已經作得比較完善了。那麼,TensorFlow 是如何定義整個數據流的呢?它是用計算圖來定義的,以兩個變量相加爲例,代碼很是簡單,若是轉化爲 pb ( 上圖左下角 ) 也只有這幾行。但實際上呢?這裏給出兩組數據:229行,2.6KB;1200行,13.5KB。229行,2.6KB 爲上面的加法操做轉化爲實際的 pb 的大小,而若是有10個加法操做的計算圖,則須要1200行,13.5KB。因此,用通用的計算圖來定義特徵的處理流程,雖然很靈活,但卻很是不利於人來閱讀和管理。在系統設計的過程當中,咱們指望全部的特徵定義及其處理流程均可以一目瞭然的看到。

圖片8.png

如上圖所示,在考慮到樣本蒐集、特徵配置、特徵處理、模型訓練、模型預測等需求後,咱們選用了 CSV 來管理整個過程,CSV 中的每一行定義了一個特徵,包含了特徵的名稱、類型、序列化後的位置、處理方式等信息,且能夠靈活地增長列來定義新的功能。

下面沿着以前提到模型迭代的痛點,依次看下咱們是如何經過一個 CSV 來解決的:

痛點1:快速增長特徵

圖片9.png

首先舊的流程中,咱們都須要聲明一個變量來臨時存儲在線所須要的特徵,編寫特徵填充代碼,同時還須要編寫特徵變換代碼、特徵序列化代碼、特徵反序列化代碼以及特徵監控代碼。以上種種功能,都須要針對每個變量進行獨立的編寫。

咱們新的流程中,只有在 CSV 中定義變量處理方式和編寫特徵填充代碼兩個部分:

如上圖右下角有4個特徵,假設用戶 ID,用戶性別以及 itemID 是已有的特徵。如今,咱們須要新加一個特徵,咱們就會在這個表格第四行新加用戶 Tag 特徵,同時定義下這個特徵的類型以及在日誌中的位置,是屬於用戶特徵仍是物品特徵,剩下的步驟則經過一個 python 腳本和一個代碼模板來生成新的 C++ 程序自動完成。增長了這個變量後,特徵日誌中會增長上圖右上角所示的 tag 信息。

痛點2:在線、離線特徵的一致性

圖片10.png

模型訓練所需的特徵須要和在線預測時的特徵徹底一致。在工業界中,通常會將在線特徵 dump 到日誌中,訓練時結合標籤生成完整的訓練樣本,從而保證在線、離線特徵的一致性。然而,舊的流程中,針對每一個特徵的序列化,都須要手寫代碼,反序列化亦然,這就大大增長了算法工程師的工做量,且容易引人 bug。

咱們的作法是把特徵的類型進行了標準化,抽象出4種標準的類型 ( 整形、稀疏整形、字符串、稀疏字符串 ),它們都繼承自基類 Feature,這個類會包含特徵處理的方方面面,如生成特徵、序列化、反序列化。咱們只須要保證每種特徵類型的特徵的序列化和反序列化函數是正確的,就能夠保證在線的特徵和離線特徵是徹底一致的。

痛點3:特徵配置及特徵處理

① 特徵配置

圖片11.png

特徵的配置包含兩方面的內容:蒐集哪些特徵及模型使用哪些特徵。

在實踐中,咱們須要保證已有模型的穩定性,並不斷地蒐集新的特徵。爲此,咱們將特徵蒐集服務與 ranking 服務相分離,但複用特徵填充代碼。服務分離有兩個好處:

  1. 在特徵蒐集服務中新增所需蒐集的特徵無需更改 ranking 服務;
  2. 在 ranking 階段,通常有數千個物品,而咱們的特徵蒐集服務只蒐集返回給用戶的十來個物品的特徵,大大減少了日誌量。

蒐集到的特徵是模型訓練和預測所需特徵的超集。當須要進行模型的訓練或預測時,咱們只需在 CSV 中使用 is_using 列來控制是否使用某一特徵。須要進行模型迭代時,只須要 CSV 中的配置,並從新生成一份代碼便可。此外,交叉特徵也只須要在 CSV 中進行配置便可,而且,因爲對特徵類進行了標準化,咱們能夠輕鬆支持任意個特徵的交叉。

② 特徵處理

圖片12.png

若是咱們像 TensorFlow 那樣定義一個很是靈活的計算圖的話,雖然是很好的,可是不利於模型的管理。所以,咱們把單個特徵的處理抽象成了3步:特徵填充 ( 手工編寫代碼或經由其餘特徵變換而來 );特徵值和特徵權重變換;特徵值和特徵權重向量聯合變換 ( 支持屢次變換 )。

仍以 tag 特徵爲例:

如上圖所示,通常狀況下,每一個特徵都會有 tag 值 ( key ) 和權重 ( value ),咱們會先將 key 進行離散化,好比 hash;並對權重作必定的變換,好比設爲1;以後,對整個 key、value 向量進行聯合變換,好比,key 乘以10,value 不變 ( 舉例用,通常不作變換 )。

此外,有時咱們須要一些統計信息,好比 tag 分數大於0.5的 tag 數量。那麼,就能夠真正用到特徵值和特徵權重向量聯合變換,咱們只須要在這一步統計整個 key、value 向量中 value>0.5 的個數便可。

圖片13.png

那麼,若是咱們既須要保留 tag 信息 ( 第一種變換 ),又須要 tag 分>0.5的個數做爲特徵呢?咱們只須要在 CSV 中從新聲明一個變量,並在特徵賦值部分將其特徵設爲第一個變量的內容,並進行相應變換便可 ( 實際中,能夠直接在賦值部分寫統計函數便可 )。

痛點4:支持多種模型

圖片14.png

咱們的系統支持兩種訓練樣本格式:libsvm 和 sparse tensor 數組。

其中,libsvm 是線性模型的主流格式;而 sparse tensor 則是 tensorflow 中的支持稀疏特徵的主流格式 ( tensor 能夠視爲 sparse tensor 的特例 )。

以上圖中的樣本 ( 省略了標籤部分 ) 變換過程爲例,該樣本中包含兩個物品信息,於是會生成兩條樣本。對於 libsvm 格式,只須要將每一個特徵變換後的結果存儲到一個向量中便可。對於 TensorFlow 等框架,內部都是用矩陣來進行運算的。矩陣又會分爲兩種:稠密的矩陣和稀疏的矩陣。同時,稠密矩陣又是稀疏矩陣的特例。因此,咱們會將全部的特徵所有以稀疏矩陣的形式喂到模型中,方便程序統一的處理。仍是以稀疏特徵 tag 爲例,該特徵會用戶兩個稀疏矩陣 ( 特徵值矩陣和權重矩陣 ) 來進行表示,且共用下標和形狀,特徵值就是剛剛通過離散化的特徵值,這裏沒有用到權重,因此所有設爲1。咱們能夠看到,雖然它是一個稀疏矩陣,可是它是一個2x2的矩陣,每一個都有元素,因此能夠用稀疏矩陣來表示稠密矩陣。

圖片15.png

有了訓練樣本以後,如何進行模型訓練?咱們提供了3種方式:

經過將 CSV 轉換爲一個 hpp 文件以後,咱們會編譯出一個專門用於將原始特徵日誌轉換爲訓練樣本的可執行程序,並經過 hadoop streaming 方式,生成 libsvm 格式的訓練樣本。這種方式有兩個缺點:增長了流程的複雜性,且耗費存儲資源。原始的特徵日誌至關於進行了壓縮 ( 多個物品共用一組用戶特徵 ),展開以後至關於每條樣本對應的用戶特徵是重複的,且會生成大量的交叉特徵,這會致使文件的大小增長10倍以上。

第二種形式,則是將生成的 hpp 文件經過 JNI 編譯成一個 SO,能夠直接在 Spark 上調用,生成 libsvm 格式的 RDD 進行訓練,該方案避免了訓練樣本佔用磁盤空間的問題,但流程仍較爲複雜。

最後,則是咱們目前使用的動態編譯 so 的形式。因爲 tensorflow 模型訓練程序是 python 編寫的,而咱們的 CSV 轉 hpp 程序也是 python 編寫的,於是,咱們在使用 tensorflow 訓練前,會檢測 CSV 是否更新,而後動態的決定是否從新編譯自定義的 tensorflow 算子。在訓練時,該算子會將原始特徵日誌轉換爲 sparse tensor 格式的訓練樣本。此外,使用配置文件還有一個好處:訓練程序還會讀取 CSV 中額外的配置信息,從而知道有多少個特徵每一個特徵 embedding 的維度、大小,是否須要 attention 機制等信息,供模型訓練使用。

痛點5:特徵監控

圖片16.png

因爲推薦系統的複雜性,咱們須要對各個環節進行必要的監控,從而保證出現問題時能夠及時知道。以 tag 興趣分分佈爲例:

相似於特徵的變換流程,咱們會在 CSV 中配置監控函數。如上圖所示,爲 tag 特徵的 value 分桶監控過程。

首先,對 tag 的興趣分進行分桶,好比這裏有兩個興趣分,咱們能夠把它們分紅10段,0.9~1是一段,0~0.1是一段等等,再把這些序列化後的字符串經過上報系統進行上報,而後展現在右邊的曲線中。經過這些曲線,咱們能夠對比同一區間內特徵數量的同比、環比等信息,從而在特徵分佈變化劇烈時及時進行告警。

痛點6:樣本過濾 & 加權

圖片17.png

咱們實際的特徵格式如上圖左側所示,咱們會在用戶特徵和物品特徵後面分別加上幾列,會統計某一段時間內用戶或物品的曝光次數,點擊次數,以及消費時長。

若是某一用戶短期內曝光超過1000次,或者消費時長特別長,或者點擊率特別高,則多是機器刷量的,咱們就會將這些樣本過濾掉。

此外,對於一條樣本,即便用戶點擊了,若是消費時長太短,咱們也會將其設爲負樣本或者過濾掉,或者設一個比較小的權重。

Ranking 流程圖

圖片18.png

最後看一下完整的系統流程圖:

首先經過特徵配置文件和一個代碼模板,管理全部的特徵。

經過腳本配置文件生成 hpp 代碼,模型預測時使用該代碼進行特徵的變換。

在重排序肯定要展現給用戶哪些物品以後,重複一遍特徵填充的過程,而後再把可能產生曝光的物品特徵序列化到特徵日誌中。

在離線過程當中,將特徵日誌經過反序列化的方法,從新填充整個特徵類。經過一樣的特徵變換代碼,變換成和線上徹底一致的特徵 ( 針對同一版模型 ),等到樣本標籤從客服端返回以後,生成最終的訓練樣本,供模型訓練。

▌四. 總結

咱們將推薦系統中特徵處理的流程進行了標準化,該標準化體如今特徵類型的標準化和特徵處理過程的標準化兩方面。咱們經過一個 CSV 文件完成了特徵配置、特徵蒐集、特徵處理、模型訓練 ( 部分 )、模型預測 ( 部分 ) 的管理工做,大大下降了人工的編碼量,提升了工做效率、下降了人爲引入 bug 的可能性。

分享嘉賓

640.webp.jpg

梁超

騰訊 | 高級工程師
——END——

更多精彩內容歡迎關注:

640.webp.jpg

相關文章
相關標籤/搜索