FFM原理及python實戰

基於域的分解機模型(FFM)

目錄

1、FFM的理解

2、FFM優化

3、優缺點

4. 使用FFM需要注意的地方

5、python實戰


1、FFM的理解

場感知分解機器(Field-aware Factorization Machine ,簡稱FFM)最初的概念來自於Yu-Chin Juan與其比賽隊員,它們借鑑了辣子Michael Jahrer的論文中field概念,提出了FM的升級版模型。通過引入field的概念,FFM把相同性質的特徵歸於同一個field。

給出一下輸入數據:

User

Movie

Genre

Price

YuChin

3Idiots

Comedy, Drama

$9.9

對於上面表格中的genre有兩個屬性Comedy, Drama,那麼這兩個特徵因該屬於一個field—Genre。

在FFM中,每一維特徵Xi,針對其它特徵的每一種field fj,都會學習一個隱向量Vi,fj。因此,隱向量不僅與特徵相關,也與field相關。

假設每條樣本的n個特徵屬於f個field,那麼FFM的二次項有nf個隱向量。而在FM模型中,每一維特徵的隱向量只有一個。因此可以把FM看作是FFM的特例,即把所有的特徵都歸屬到一個field是的FFM模型。根據FFM的field敏感特性,可以導出其模型表達式:

其中,fj是第j個特徵所屬的field。如果隱向量的長度爲k,那麼FFM的二交叉項參數就有nfk個,遠多於FM模型的nk個。此外,由於隱向量與field相關,FFM的交叉項並不能夠像FM那樣做化簡,其預測複雜度爲O(kn^2)。

對於上面的數據來說

PS:對於數值型特徵,實際應用中通常會把價格劃分爲若干個區間(即連續特徵離散化),然後再one-hot編碼,當然不是所有的連續型特徵都要做離散化,比如某廣告位、某類廣告/商品、抑或某類人羣統計的歷史CTR(pseudo-CTR)通常無需做離散化。

該條記錄可以編碼爲5個數值特徵,即User^YuChin, Movie^3Idiots, Genre^Comedy, Genre^Drama, Price^9.9。其中Genre^Comedy, Genre^Drama屬於同一個field。爲了說明FFM的樣本格式,我們把所有的特徵和對應的field映射成整數編號。

 

Field Name

Field Index

Feature Name

Feature Index

User

1

User^YuChin

1

Movie

2

Movie^3Idiots

2

Genre

3

Genre^Comedy

3

Price

4

Genre^Drama

4

 

 

Price^9.9

5

 

其中,紅色表示Field編碼,藍色表示Feature編碼,綠色表示樣本的組合特徵取值。二階交叉項的係數是通過與Field相關的隱向量的內積得到的,具體可見下圖。

 

其損失函數定義爲:

其中m爲樣本總個數,採用的loss爲logloss,y的取值爲-1和1。

2、FFM優化

這裏使用AdaGrad優化算法去求解參數,因爲目前一些研究顯示了該算法在矩陣分解上的有效性,矩陣分解屬於FFM的一個特例。具體訓練方法如下:

在隨機梯度的每一步中,對數據點(x,y)進行採樣,並更新上面公式中的<{v_{i,fj}} ,{v_{j,fi}}>{x_{i}}{x_{j}}。因爲X在CTR中是onehot表示,而且非常稀疏的,這裏只更新有非零值的維度。

使用AdaGrad去優化式子,x是稀疏的,只去更新那些x數據中非0項

對每個隱向量的維度d ,累積梯度平方的和:

η是超參數,需要人工去指定, W的初始化是通過從均勻分佈[0,1/√k]中隨機採樣,爲了防止√γ的值過大,γ的初始值設置爲1。

可以看出,隨着迭代的進行,每個參數的歷史梯度會慢慢累加,導致每個參數的學習率逐漸減小。另外,每個參數的學習率更新速度是不同的,與其歷史梯度有關,根據AdaGrad的特點,對於樣本比較稀疏的特徵,學習率高於樣本比較密集的特徵,因此每個參數既可以比較快速達到最優,也不會導致驗證誤差出現很大的震盪。

 

3、優缺點

FFM優點: 
增加field的概念,同一特徵針對不同field使用不同隱向量,模型建模更加準確

FFM缺點: 
計算複雜度比較高,參數個數爲nfk,計算複雜度爲O(kn2)  

 

4. 使用FFM需要注意的地方

•樣本歸一化。對樣本進行歸一化,否則容易造成數據溢出,梯度計算失敗

•特徵歸一化。爲了消除不同特徵取值範圍不同造成的問題,需要對特徵進行歸一化

•Early stopping。一定要設置該策略,FFM很容易過擬合

•省略零值特徵。零值特徵對模型沒有任何貢獻,省略零值特徵,可以提高FFM模型訓練和預測的速度,這也是稀疏樣本採用FFM的顯著優勢

 

5、python實戰

我們這裏使用iris_data數據集進行測試

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
#下載數據
iris_data = load_iris()
X = iris_data['data']
y = iris_data['target'] == 2
X_train,X_test,y_train, y_test = train_test_split(X,y, test_size=0.3, random_state=0)
#處理數據,轉化成dataframe格式,並轉換成整型
data_train=pd.DataFrame(X_train, columns=['x1','x2','x3','x4'])
data_test=pd.DataFrame(X_test, columns=['x1','x2','x3','x4'])

data_train['int1'] = data_train['x1'].map(int)
data_train['int2'] = data_train['x2'].map(int)
data_train['int3'] = data_train['x3'].map(int)
data_train['int4'] = data_train['x4'].map(int)
data_train['clicked'] = y_train*1

data_test['int1'] = data_test['x1'].map(int)
data_test['int2'] = data_test['x2'].map(int)
data_test['int3'] = data_test['x3'].map(int)
data_test['int4'] = data_test['x4'].map(int)
data_test['clicked'] = y_test*1

#整合數據
data_train = data_train[['int1','int2','int3','clicked']]
data_test = data_test[['int1','int2','int3','clicked']]

#轉換成libffm format:
#即:label,field_1:index_1:value_1,field_2:index_2:value_2 ...
ffm_fit = FFMFormatPandas()

ffm_train_data = ffm_fit.fit_transform(data_train, y='clicked')
ffm_test_data = ffm_fit.fit_transform(data_test, y='clicked')

import os
cwd = os.getcwd()#獲取當前路徑
train_data = cwd + '/ffm_train_data.txt'#創建一個TXT文件
test_data = cwd + '/ffm_test_data.txt'#創建一個TXT文件

f=open(train_data,'w')
for line in ffm_train_data:
	data=line+"\n"
	f.write(data)#寫入

f1=open(test_data,'w')
for line in ffm_test_data:
    data1 = line+"\n"
    f1.write(data1)#寫入

使用xlearn進行訓練

#通過 pip 安裝 xLearn
- brew install cmake
- sudo pip install xlearn
import xlearn as xl

# Training task
ffm_model = xl.create_ffm()                # Use field-aware factorization machine (ffm)
ffm_model.setTrain("./ffm_train_data.txt")    # Set the path of training dataset
ffm_model.setValidate("./ffm_test_data.txt")  # Set the path of validation dataset

# Parameters:
#  0. task: binary classification
#  1. learning rate: 0.2
#  2. regular lambda: 0.002
#  3. evaluation metric: accuracy
param = {'task':'binary', 'lr':0.2, 'lambda':0.002, 'metric':'acc'}

# Start to train
# The trained model will be stored in model.out
ffm_model.setTXTModel("./irismodel.txt")
ffm_model.fit(param, './irismodel.out')

# Prediction task
ffm_model.setTest("./ffm_test_data.txt")  # Set the path of test dataset
ffm_model.setSigmoid()                 # Convert output to 0-1

# Start to predict
# The output result will be stored in output.txt
ffm_model.predict("./irismodel.out", "./irisoutput.txt")

可以看到預測acc=0.91

我們用FM模型做一下對比

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from pyfm import pylibfm
from sklearn.feature_extraction import DictVectorizer

def load_data():
    """
    調用sklearn的iris數據集,篩選正負樣本並構造切分訓練測試數據集
    """
    iris_data = load_iris()
    X = iris_data['data']
    y = iris_data['target'] == 2
    data = [ {v: k for k, v in dict(zip(i, range(len(i)))).items()}  for i in X]
    X_train,X_test,y_train, y_test = train_test_split(data,y, test_size=0.3, random_state=0)
    return X_train,X_test,y_train, y_test

X_train,X_test,y_train, y_test = load_data()

v = DictVectorizer()
X_train = v.fit_transform(X_train)
X_test = v.transform(X_test)

fm = pylibfm.FM(num_factors=2, 
                num_iter=20, 
                verbose=True, 
                task="classification", 
                initial_learning_rate=0.01, 
                learning_rate_schedule="optimal")

fm.fit(X_train, y_train)

y_preds = fm.predict(X_test)
y_preds_label = y_preds > 0.5
from sklearn.metrics import log_loss,accuracy_score
print ("Validation log loss: %.4f" % log_loss(y_test, y_preds))
print ("accuracy: %.4f" % accuracy_score(y_test, y_preds_label))
Validation log loss: 0.3590
accuracy: 0.7556

而且這裏我們FFM的lr=0.2,FM的lr=0.01

FFM確實比FM更精準一些

 

 

參考:

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

https://xlearn-doc-cn.readthedocs.io/en/latest/index.html

 

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