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

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

一. 推薦系統

1. 業務簡介

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

2. 推薦系統流程

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

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

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

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

3. 常用推薦模型

常用的推薦模型有 LR、FM、DNN、W&D、DeepFM、DIN 等模型,對於推薦系統,無論使用哪種模型,都需要以下幾個模塊:

  • 樣本蒐集:訓練模型離不開大量的訓練樣本,所以需要進行樣本 ( 特徵和標籤 ) 的蒐集;
  • 特徵配置:實際的推薦系統中會有上百個特徵供模型選擇,在模型版本迭代的過程中,有些特徵會被捨棄,有些特徵會新加進來;因而,我們就需要配置蒐集哪些特徵、使用哪些特徵,在迭代過程中,還需要保證現有模型訓練和預測服務的穩定性;
  • 特徵處理:對於每個特徵,比如用戶 ID,該如何離散化成一個最終使用的int型的數字,就需要經過一定的特徵處理;
  • 模型訓練&模型預測:特徵處理完之後,如何餵給模型訓練程序以及線上的預測模型,如何在修改了特徵配置之後,無需人工修改訓練和預測代碼,從而降低工作量、減少 bug 的引入,都是我們需要考慮的工作。

4. 排序流程圖

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

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

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

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

與研究中給定的數據集不同,推薦系統中的模型需要不斷地迭代調優。在日常的工作中,我們常常需要在保證現有模型服務穩定的前提下,不斷地增加新的特徵,訓練新的模型。於是,我們會面臨下圖所示的諸多問題。

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

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

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

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

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

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

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

痛點1:快速增加特徵

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

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

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

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

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

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

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

① 特徵配置

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

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

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

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

② 特徵處理

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

仍以 tag 特徵爲例:

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

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

那麼,如果我們既需要保留 tag 信息 ( 第一種變換 ),又需要 tag 分>0.5的個數作爲特徵呢?我們只需要在 CSV 中重新聲明一個變量,並在特徵賦值部分將其特徵設爲第一個變量的內容,並進行相應變換即可 ( 實際中,可以直接在賦值部分寫統計函數即可 )。

痛點4:支持多種模型

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

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

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

有了訓練樣本之後,如何進行模型訓練?我們提供了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:特徵監控

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

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

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

痛點6:樣本過濾 & 加權

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

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

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

Ranking 流程圖

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

  • 首先通過特徵配置文件和一個代碼模板,管理所有的特徵。
  • 通過腳本配置文件生成 hpp 代碼,模型預測時使用該代碼進行特徵的變換。
  • 在重排序確定要展示給用戶哪些物品之後,重複一遍特徵填充的過程,然後再把可能產生曝光的物品特徵序列化到特徵日誌中。
  • 在離線過程中,將特徵日誌通過反序列化的方法,重新填充整個特徵類。通過同樣的特徵變換代碼,變換成和線上完全一致的特徵 ( 針對同一版模型 ),等到樣本標籤從客服端返回之後,生成最終的訓練樣本,供模型訓練。

四. 總結

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

作者介紹

樑超

騰訊 | 高級工程師

本文來自 DataFun 社區

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&mid=2247495039&idx=1&sn=35905df6cd4ef33d2ce505d284d3c8ad&chksm=fbd75f13cca0d605c17bf7a8b3b899b7798a590008625fcffbf00d77c2451249bbdc086d6b18&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章