基於域的分解機模型(FFM)
目錄
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()。
對於上面的數據來說
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)進行採樣,並更新上面公式中的。因爲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