推薦排序模型1——FM、FFM及python(xlearn)實現

排序模型在工業界已經有了很長時間的歷史,從基於策略規則的人工指定特徵權值開始,發展到LR線性模型,LR+GBDT半自動特徵組合模型,再到FM自動二階特徵組合模型及深度學習模型等不斷髮展。其中FM系列模型佔據比較重要的位置,本篇文章就FM模型進行分析和總結。

FM系列模型2——DeepFM及python(DeepCTR)實現


1,概述

在機器學習中,預測是一項基本的任務,所謂預測就是估計一個函數,該函數將一個n維的特徵向量x映射到一個目標域T:
D={(x(1),y(1)),(x(2),y(2)),...,(x(N),y(N))}D =\{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(N)},y^{(N)})\}

在傳統的線性模型中,每個特徵都是獨立的,如果需要考慮特徵與特徵之間的相互作用,可能需要人工對特徵進行交叉組合。非線性SVM可以對特徵進行核變換,但是在特徵高度稀疏的情況下,並不能很好的進行學習。由於推薦系統是一個高度係數的數據場景,由此產生了FM系列算法,包括FM,FFM,DeepFM等算法。

推薦系統中還有一個比較重要的因素就是速度,比如容易並行,可解釋、可拓展,這就是LR在工業界比較流行的原因之一。我們先回顧下線性模型和邏輯迴歸模型。

線性迴歸

y^(x)=w0+w1x1+w2x2+...+wnxn=w0+j=1nwixi\begin{aligned} \hat{y}(x) = & w_0 + w_1x_1 + w_2x_2 + ...+ w_nx_n\\ =&w_0 + \sum_{j=1}^nw_ix_i \end{aligned}

邏輯迴歸
核心函數:
hθ(x)=11+eθxh_{\theta}(x) = \frac{1}{1+e^{-\theta x}}
對於一個樣本:

P(y=1x,θ)=hθ(x)P(y=0x,θ)=1hθ(x)\begin{aligned} P(y=1|x,\theta) =& h_{\theta}(x)\\ P(y=0|x,\theta) = &1-h_{\theta}(x) \end{aligned}
那麼進一步,樣本x的概率表示爲:
P(yx;θ)=(hθ(x))y(1hθ(x))1yP(y|x;\theta)=(h_{\theta}(x))^y(1-h_{\theta}(x))^{1-y}
似然函數:
L(θ)=i=1mP(y(i)x(i);θ)L(\theta) = \prod_{i=1}^mP(y^{(i)}|x^{(i)};\theta)

接下來,取對數,最大似然,最小loss:
J(θ)=1mlog(L(θ))J(\theta) = -\frac{1}{m}log(L(\theta))
求導、梯度下降。。。

2,FM模型

我們說線性模型有個非常大的問題就是沒有考慮特徵之間的相互關係:

y^(x)=w0+w1x1+w2x2+...+wnxn=w0+j=1nwixi\begin{aligned} \hat{y}(x) = & w_0 + w_1x_1 + w_2x_2 + ...+ w_nx_n\\ =&w_0 + \sum_{j=1}^nw_ix_i \end{aligned}

事實上,如果特徵之間相互關聯,不同的特徵組合可以提高模型的表達能力,而大多數情況下,特徵之間都不是相互獨立的。
爲了表達這種關聯特性,接下來,我們將函數y^\hat{y}改成:
y^(x)=w0+j=1nwixi+i=1n1j=i+1nwijxixj\begin{aligned} \hat{y}(x) = &w_0 + \sum_{j=1}^nw_ix_i + \sum_{i=1}^{n-1}\sum_{j=i+1}^{n}w_{ij}x_ix_j \end{aligned}

這樣兩個特徵之間的關係也考慮了,不過很遺憾,這種直接在xixix_ix_i前面配上係數wijw_{ij}的方式在稀疏數據面前有很大的缺陷。

在數據稀疏性普遍存在的實際應用場景中,二次項參數的訓練是很困難的。其原因是:每個參數 wijw_ij的訓練需要大量xix_ixjx_j非零樣本,由於樣本數據本來就比較稀疏,滿xix_ixjx_j都非零的樣本將會非常少。訓練樣本的不足,很容易導致參數 wijw_{ij}不準確,最終將嚴重影響模型的性能。一句話,WijW_{ij}只有在觀察樣本中有xix_ixjx_j的情況下才能優化得到,對於觀察樣本中未出現過的特徵分量不能進行相應參數的評估.

對於推薦系統來講,高度稀疏的數據場景太普遍了。爲了克服這個困難我們引入輔助向量:

Vi=(vi1,vi2,...,vik)TV_i = (v_{i1},v_{i2},...,v_{ik})^T

將w改爲:
w^ij=ViTVj=l=1kvilvjl\hat{w}_{ij}=V_i^TV_j=\sum_{l=1}^kv_{il}v_{jl}

y^(x)=w0+j=1nwixi+i=1n1j=i+1n(ViTVj)xixj\begin{aligned} \hat{y}(x) = &w_0 + \sum_{j=1}^nw_ix_i + \sum_{i=1}^{n-1}\sum_{j=i+1}^{n}(V_i^TV_j)x_ix_j \end{aligned}

目前這樣的表達式的複雜度是O(n^2),但是我們可以對其進行分解,使得複雜度變爲O(n)

在這裏插入圖片描述
二次項的參數數量減少爲 knkn個,遠少於多項式模型的參數數量。另外,參數因子化使得 xhxix_hx_i 的參數和 xixjx_ix_j 的參數不再是相互獨立的,因此我們可以在樣本稀疏的情況下相對合理地估計FM的二次項參數。具體來說, xhxix_hx_i 和 $ x_ix_j$ 的係數分別爲 vh,viv_h,v_ivi,vjv_i,v_j ,它們之間有共同項 viv_i 。也就是說,所有包含“ xix_i 的非零組合特徵”(存在某個 jij \not=i ,使得 xixj0x_ix_j\not=0 )的樣本都可以用來學習隱向量viv_i,這很大程度上避免了數據稀疏性造成的影響。而在多項式模型中, whiw_{hi}wijw_{ij}是相互獨立的。

到這裏,我們就可以通過設置loss函數,梯度下降等方法進行優化了。
在這裏插入圖片描述

3,FFM

FFM是FM的升級版模型,引入了field的概念。FFM把相同性質的特徵歸於同一個field。在FFM中,每一維特徵xix_i,針對每一種field fjf_j,都會學習到一個隱向量Vi,fjV_{i,f_j},因此,隱向量不僅與特徵相關,也與field相關。

設樣本一共有n個特徵, f 個field,那麼FFM的二次項有nf個隱向量。而在FM模型中,每一維特徵的隱向量只有一個。FM可以看做FFM的特例,即把所有特徵都歸屬到同一個field中。
在這裏插入圖片描述
如果隱向量的長度爲k,那麼FFM的二次項參數數量爲nfk,遠多於FM模型。此外由於隱向量與field相關,FFM二次項並不能夠化簡,時間複雜度爲O(kn^2)。需要注意的是由於FFM中的latent vector只需要學習特定的field,所以通常KFFM<<KFMK_{FFM} << K_{FM}

看一個經常用的例子:
在這裏插入圖片描述
其中,紅色部分對應的是Field,來自於原始特徵的個數;
藍色部分對應的是feature,來自於原始特徵onehot之後的個數。(連續型特徵不用one-hot)
對於特徵Feature:User=YuChin,有Movie=3Idiots、Genre=Comedy、Genre=Drama、Price四項要進行交叉組合:

在這裏插入圖片描述
綠色部分爲對應特徵one-hot之後的值,出現爲1,不出現爲0。對於連續型變量的處理,這裏採用的是使用實際值,當然,也可以對連續型變量離散化處理,再進行one-hot。
在這裏插入圖片描述
可以看到3和4的field是一樣的,特徵1與特徵3、特徵1與特徵4交叉的時候涉及到的都是域2和域3。

注意,在實際應用中,爲了使用FFM方法,所有的特徵必須轉換成“field_id:feat_id:value”格式,field_id代表特徵所屬field的編號,feat_id是特徵編號,value是特徵的值。

優點:FMM更加細緻的刻畫了特徵
缺點:但是使參數增加了F倍(F爲域的數量),也相應的增加了訓練難度,內存消耗大。而且不能像FM那樣分解,時間複雜度高

4,python xlearn 實現

實現FM & FFM的最流行的python庫有:LibFM、LibFFM、xlearn和tffm。其中,xLearn是一款高性能,易於使用且可擴展的機器學習軟件包,包括FM和FFM模型,可用於大規模解決機器學習問題。xlearn比libfm和libffm庫快得多,併爲模型測試和調優提供了更好的功能。這裏以xlearn實現FM和FFM算法。

建議大家不要用pip直接安裝(pip install xlearn),會報錯,到這裏直接下載對應版本的whl格式文件安裝。xlearn的API,數據可以從這下載

data_path = 'E:\PythonWorkSpace\\backups\\xlearn\\xlearn-master\demo\classification\criteo_ctr\small_train.txt'

import xlearn as xl

# Training task
ffm_model = xl.create_ffm()                # Use field-aware factorization machine (ffm)
ffm_model.setTrain(data_path)    # Set the path of training data

# parameter:
#  0. task: binary classification
#  1. learning rate : 0.2
#  2. regular lambda : 0.002
param = {'task':'binary', 'lr':0.2, 'lambda':0.002}
#  param = {'task':'reg', 'lr':0.2, 'lambda':0.002}

# Train model
ffm_model.fit(param, "./model.out")

對於 LR 和 FM 算法而言,我們的輸入數據格式必須是 CSV 或者 libsvm. 對於 FFM 算法而言,我們的輸入數據必須是 libffm 格式。

libsvm format:

   y index_1:value_1 index_2:value_2 ... index_n:value_n

   0   0:0.1   1:0.5   3:0.2   ...
   0   0:0.2   2:0.3   5:0.1   ...
   1   0:0.2   2:0.3   5:0.1   ...

CSV format:

   y value_1 value_2 .. value_n

   0      0.1     0.2     0.2   ...
   1      0.2     0.3     0.1   ...
   0      0.1     0.2     0.4   ...

libffm format:

   y field_1:index_1:value_1 field_2:index_2:value_2   ...

   0   0:0:0.1   1:1:0.5   2:3:0.2   ...
   0   0:0:0.2   1:2:0.3   2:5:0.1   ...
   1   0:0:0.2   1:2:0.3   2:5:0.1   ...

這裏我們採用FFM模型,我們看下所用的部分數據:

1 0:0:0.3651 2:1163:0.3651 3:8672:0.3651 4:2183:0.3651 5:2332:0.3651 6:185:0.3651 7:2569:0.3651 8:8131:0.3651 9:5483:0.3651 10:215:0.3651 11:1520:0.3651 12:1232:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:2434:0.50000 16:7755:0.50000
0 1:1988:0.3651 2:400:0.3651 3:1243:0.3651 4:2183:0.3651 5:2847:0.3651 6:185:0.3651 7:7196:0.3651 8:1343:0.3651 9:3899:0.3651 10:9493:0.3651 11:1520:0.3651 12:9714:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:7148:0.50000 16:1828:0.50000
0 1:7633:0.3651 2:8195:0.3651 3:9952:0.3651 4:9619:0.3651 5:9882:0.3651 6:185:0.3651 7:3479:0.3651 8:3373:0.3651 9:7873:0.3651 10:5989:0.3651 11:1520:0.3651 12:4520:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:3723:0.50000 16:928:0.50000
1 1:7593:0.3651 2:9126:0.3651 3:9952:0.3651 4:2183:0.3651 5:5525:0.3651 6:5918:0.3651 7:1969:0.3651 8:3240:0.3651 9:3899:0.3651 10:3387:0.3651 11:1520:0.3651 12:9714:0.3651 13:2738:0.3651 14:2935:0.3651 15:5428:0.3651 17:8692:0.50000 16:1861:0.50000
0 1:4222:0.3651 2:1163:0.3651 3:8672:0.3651 4:2183:0.3651 5:3780:0.3651 6:185:0.3651 7:2569:0.3651 8:8131:0.3651 9:3899:0.3651 10:215:0.3651 11:1520:0.3651 12:2565:0.3651 13:2738:0.3651 14:8813:0.3651 15:6145:0.3651 17:2434:0.50000 16:7755:0.50000

本數據集是二分類(0,1),每個特徵值都表示爲“field_id:feat_id:value”的形式

結果如下,共有18個域,9991個特徵

[ WARNING    ] Validation file(dataset) not found, xLearn has already disable early-stopping.
[------------] xLearn uses 8 threads for training task.
[ ACTION     ] Read Problem ...
[------------] First check if the text file has been already converted to binary format.
[------------] Binary file (E:\PythonWorkSpace\backups\xlearn\xlearn-master\demo\classification\criteo_ctr\small_train.txt.bin) NOT found. Convert text file to binary file.
[------------] Number of Feature: 9991
[------------] Number of Field: 18
[------------] Time cost for reading problem: 0.19 (sec)
[ ACTION     ] Initialize model ...
[------------] Model size: 5.56 MB
[------------] Time cost for model initial: 0.03 (sec)
[ ACTION     ] Start to train ...
[------------] Epoch      Train log_loss     Time cost (sec)
[   10%      ]     1            0.606436                0.00
[   20%      ]     2            0.536963                0.00
[   30%      ]     3            0.515709                0.00
[   40%      ]     4            0.507723                0.00
[   50%      ]     5            0.492301                0.00
[   60%      ]     6            0.481908                0.00
[   70%      ]     7            0.471874                0.00
[   80%      ]     8            0.464164                0.00
[   90%      ]     9            0.455754                0.00
[  100%      ]    10            0.448856                0.00
[ ACTION     ] Start to save model ...
[------------] Model file: ./model.out
[------------] Time cost for saving model: 0.02 (sec)
[ ACTION     ] Finish training
[ ACTION     ] Clear the xLearn environment ...
[------------] Total time cost: 0.30 (sec)

我們發現,xLearn 訓練過後在當前文件夾下產生了一個叫 model.out 的新文件。這個文件用來存儲訓練後的模型,我們可以用這個模型在未來進行預測:


ffm_model.setSigmoid()
# ffm_model.setSign() #將結果轉化爲0,1
ffm_model.setTest("./small_test.txt")
ffm_model.predict("./model.out", "./output.txt")

結果:

0.171177
0.403148
0.342638
0.406686
0.245445
0.309569
0.223888
0.192466
0.362394
0.25107
0.407777
...

更多的使用方法,大家可以到相應API內部查看。

參考資料:
https://www.cnblogs.com/pinard/p/6370127.html#!comments
https://zhuanlan.zhihu.com/p/37963267
https://blog.csdn.net/pql925/article/details/79021464
https://www.jianshu.com/p/27679d6f3949
https://www.jianshu.com/p/1db7404689c5
https://www.cnblogs.com/wkang/p/9788012.html

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