從一階線性模型到FFM

前言

說到CTR,CVR預估,最近幾年無論是kaggle比賽,還是推薦算法、計算廣告中,使用FFM模型可以是一種標準通用的解決方案,因爲傳統的模型在遇到離散的類別特徵時,一般都是想方設法將其連續化,如做Id Embedding等,而FFM模型原生支持處理稀疏的類別特徵,而且擅長挖掘出特徵間的交叉性質,不僅效果好而且時間複雜度也不高,因此具有較高的應用價值,在很多企業中有實際的應用。

本文以一階模型作爲引入,分析了爲什麼二階模型是必要的,然後給出了具有實用性的二階模型FM以及改良版的FFM,最後簡要說明了一下FFM的實現思路。

1.從一階Linear Model開始

假設我們有下圖這樣的推薦數據,對於第一行樣本,我們用語言翻譯:用戶1在語境3下對物體2有一個點擊行爲(click=1);這裏,用戶(user),語境(context)和物體(item)都是特徵,點擊行爲是label,我們要用一個模型去擬合這個label,使這個模型能夠預測一個用戶在某語境下對某物體的的點擊率。最簡單的模型爲線性模型,即:
y=iCwixi y= \sum_{i \in C} w_ix_i
其中,CC爲各特徵中的非零項索引的集合,xix_i爲非零項對應的值,由於這裏都是類別特徵(Category Feature),所以非零項xix_i等於1,wiw_i 爲非零項對應的權值,是模型要學習的參數。我們把第一行數據帶入式子得到圖中的等式如下:

在這裏插入圖片描述

讓我們分析一下線性模型的參數空間大小,假設我們一共只有10個用戶,10種物體,10種語境,那麼參數WW由三個向量組成:WuserW_{user}WitemW_{item}WcontextW_{context},每個向量的長度爲10,向量中第ii個元素的取值代表對應權值xix_i,那麼我們的參數空間一共是30。

這裏只是一個最簡單的例子,其實每個參數xix_i也可以用一個長度爲kk的向量表示,那麼WuserW_{user}WitemW_{item}WcontextW_{context}則分別爲三個10×k10 \times k的矩陣。

2. 二階模型(Degree-2 Polynomial Model)

前面一階線性模型的缺點是無法挖掘特徵之間的相關性,各個特徵參數都是獨立學習,而往往特徵之間是有關聯的,例如用戶在中秋節的時候更容易對月餅發生點擊或者購買行爲,此時語境是中秋節,物體是月餅,當這兩個條件同時滿足時,點擊更有可能發生。爲了考慮特徵之間的相關性,我們有了二階模型,簡單的二階模型是一階模型的直接擴展:參數只有在任意兩個特徵同時取值時才確定。二階模型的表達爲:

y=j1,j2Cwj1,j2xj1xj2 y = \sum_{j_1,j_2 \in C} w_{j_1,j_2} x_{j_1}x_{j_2}
其中,CC爲任意兩個特徵中的非零項索引組合的集合,而wj1.j2w_{j_1.j_2}是某兩個特徵的非零項索引同時爲j1j_1j2j_2時的參數。同樣,我們把第一行數據帶入二階模型如下圖:

在這裏插入圖片描述

此時,二階模型的參數擴展到了一個N×NN \times N的矩陣WWNN爲所有特徵取值空間的和,假設user,item,context都只有10個不同值,那麼N=30N=30,參數空間N2=900N^2=900需要注意的是矩陣WW爲一個對稱矩陣,這是因爲我們並不區分兩個特徵的取值順序,如Wuser=1,item=2=Witem=2,use=1W_{user=1,item=2}=W_{item=2,use=1},這兩個參數在矩陣中處於對稱位置;而且同一特徵的不同取值參數是沒有意義的,如Wuser=1,user=i,iNW_{user=1,user=i},i \in N是不存在的,一個用戶不可能存在着兩種取值,因此WW矩陣中有一大半參數是無效的,如上圖所示,我們把這些參數在矩陣中可以置爲0,但是其參數空間複雜度仍然爲O(N2)O(N^2)

3. Factorization Machines (FM)

簡單二階模型的缺點是:

  1. 一個參數只有在兩個特徵同時取某數纔會參與計算和更新,而對於不同的樣本,要使兩個特徵的取值重複概率很小,導致WW中大部分的位置由於樣本中從未出現過而得不到計算和更新
  2. 在實際應用中,由於特徵的取值有可能有幾百萬種,如用戶的數量,導致NN非常大,使矩陣WW特別稀疏,去維護這樣一個大矩陣儲存開銷太大

爲了解決這些問題,我們可以用類似矩陣分解的思路去解決:把矩陣WW分解成兩個小矩陣,用這兩個小矩陣的相乘結果W^\hat W來近似WW。具體來說,每個矩陣的元素wj1,j2w_{j_1,j_2}由兩個向量vj1v_{j_1}vj2v_{j_2}相乘表示,同樣,我們用CC代表任意兩個特徵中的非零項組合的集合,因此,FM模型能被表示爲:
y=j1,j2C(vj1vj2)xj1xj2 y = \sum_{j_1,j_2 \in C} (v_{j_1} \cdot v_{j_2}) x_{j_1} x_{j_2}

爲什麼FM能夠避免簡單二階模型中的第一個問題呢? 我們用同樣的例子給出了一個FM的計算如下圖,我們可以發現:向量vuser=1v_{user=1}在參數Wuser=1,item=2W_{user=1,item=2}Wuser=1,context=3W_{user=1,context=3}中都參與了計算,導致vuser=1v_{user=1}的更新條件變得寬鬆,只要是滿足出現Wuser=1,item/contextW_{user=1,item/context}的,vuser=1v_{user=1}都能隨之參與計算和更新,這個向量其實就代表了關於用戶1的特徵表達(feature representation)或者畫像(profile)。

在這裏插入圖片描述

爲什麼FM能夠解決簡單二階模型中的空間複雜度問題呢? FM中,矩陣WW被拆分爲了兩個N×kN \times k矩陣相乘,因此參數空間爲2×N×k2 \times N \times k,但於kk通常很小,遠小於N,而且是一個常數,因此其空間複雜度降爲了O(N)O(N)。這裏的kk是一個超參數,控制着信息壓縮後的保留的程度:較小的kk保留的信息少,但是計算快;較大的kk保留的信息多,雖然計算慢,但模型效果和精度較好。

那麼,最重要一個問題來了,爲什麼矩陣WW能夠被拆解爲兩個小矩陣相乘呢? 爲了回答這個問題,我們需要知道FM模型和矩陣分解之間的關係。我們知道,一個N×NN \times N的方陣可以被分解爲它的特徵向量與特徵值矩陣:W=VΣVTW=V\Sigma V^T,其中VV是標準化的特徵向量v1,,vNv_1,\dots, v_N組成的N×NN\times N維矩陣,而Σ\Sigma是主對角線爲特徵值λ1,,λN\lambda_1,\dots,\lambda_NN×NN \times N維矩陣。FM在這裏並沒有採用所有的特徵向量而是用了前kk個特徵向量(按照特徵值大小排列),通常情況下,前10%甚至前1%的特徵值就能佔據全部特徵值之和的99%,因此FM分解出的VV矩陣(N×kN \times k)計算出來的是真實WW的一種近似: WW^=VVTW \approx \hat W = V V^T 還有一個問題,Σ\Sigma去哪了呢?嚴格來說,FM的分解應該這麼寫W^=VΣ^VT\hat W = V \hat \Sigma V^T,此時Σ^\hat \Sigma是主對角線爲前kk個特徵值的k×kk\times k維矩陣,也是模型要學習的參數,其實乘以Σ^\hat \Sigma可以看做是對特徵向量的伸縮變換,並不會改變其向量方向(因爲它是一個主對角線纔有非零值的矩陣),因此每個特徵向量經過了同樣的伸縮變換後,其向量的相對位置並沒有發生變化,相當於乘以了一個常數,所以最後計算出來的得分yy的相對大小不會發生變化。個人猜測:正是因爲,有時候我們並不關心預測出來的點擊率最大是多少(絕對值),而只關心哪個物體點擊率最大(相對值),因此這個Σ^\hat \Sigma經常被忽略。

4. Field-aware Factorization Machines (FFM)

FM模型雖然有着不錯的效果,但是在某種程度上來說它還是比較“粗糙”的。爲什麼呢?因爲各向量的更新條件過於寬鬆,比如參數Wuser=1,item=2W_{user=1,item=2}Wuser=1,context=3W_{user=1,context=3}都會讓vuser=1v_{user=1}參與計算和更新,這樣有可能導致一個問題:若Wuser=1,context=3W_{user=1,context=3}出現的次數遠大於Wuser=1,item=2W_{user=1,item=2},那麼用戶向量的更新基本上被Wuser=1,context=3W_{user=1,context=3}主導,而覆蓋掉Wuser=1,item=2W_{user=1,item=2}的更新。出現這種情況的根本原因是由於各特徵的取值分佈不相同,這是我們無法避免的,那麼一種改進版的FM可以解決這個問題:我們讓向量的更新在特徵間保持獨立,具體來說,我們把vuser=1v_{user=1}拆分成vuser=1,itemv_{user=1,item}vuser=1,contextv_{user=1,context}兩部分,當出現Wuser=1,item=i,iNW_{user=1,item=i},i \in N時,我們讓vuser=1,itemv_{user=1,item}參與計算和更新;當出現Wuser=1,context=i,iNW_{user=1,context=i} ,i \in N時,我們讓vuser=1,contextv_{user=1,context}這部分參與計算和更新,這樣,關於這個用戶向量的更新就在各個特徵間獨立開了,因此這個向量能夠區分不同的特徵,我們說它是Field-aware,這裏的Filed就是指的特徵,所以這個改進版方法就是Field-aware FM (FFM)。FFM的表達式爲:

y=j1,j2C(vj1,f2vj2,f1)xj1xj2 y = \sum_{j_1,j_2 \in C} (v_{j_1,f_2}\cdot v_{j_2,f_1}) x_{j_1}x_{j_2}
其中,vj1,f2v_{j_1,f_2}vj1v_{j_1}向量中與特徵vj2v_{j_2}向量交叉的那部分,下圖給出了FFM模型計算的例子:

在這裏插入圖片描述

假設數據集中一共有M個特徵,FFM把FM的參數空間擴展了M1M-1倍,因爲此時的VV矩陣爲N×(M1)kN \times (M-1)k維。在上圖中,由於我們一共只有3域特徵(用戶,語境,物體),每個向量只需要拆分成兩塊分別於與其他兩域特徵交叉即可,若每塊向量長度爲k,那麼VV矩陣的維度爲N×2kN \times 2k,參數空間是FM的兩倍。

5.FFM 實現思路

無論是一階、二階、FM和FFM模型,其模型更新都可以使用梯度下降法,它的結構更像一個二維的一層神經網絡,每次正向傳播只有少部分非零的交叉特徵所在的神經元是激活狀態,反向傳播的時候也只有這些激活的神經元參數得到更新。與標準的神經網絡更新一樣,對於迴歸任務,損失函數選用平方差誤差;對於分類任務,選用交叉熵損失函數。

有了前面的介紹,我們已經知道了FFM的基本原理,那麼如何進行高效地實現呢?其實非常簡單,我們只需要維護一張非常大的哈希表,hash_key = feature_id + feature_value,對應的hash_value= weight_vector,其中,feature_id爲特徵所在的id,feature_value爲此特徵的值,舉個例子,對於vuser=12,itemv_{user=12,item},假設user特徵id=12,此時的key爲hash後的112(前面的1表示的是user特徵,後面的12表示的是用戶的id值),我們通過這個key在表中查到對應的權值向量,然後在這個向量中取出與item特徵交叉的那部分向量即爲vuser=1,itemv_{user=1,item}

Reference

https://www.csie.ntu.edu.tw/~r01922136/slides/ffm.pdf

https://zhuanlan.zhihu.com/p/31386807

https://tech.meituan.com/2016/03/03/deep-understanding-of-ffm-principles-and-practices.html

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