天天看點

深度學習推薦系統03

深度學習推薦系統03

經曆了前兩次的學習,下面我們進入了DeepFM部分。

1.動機

對于推薦系統而言,最初的推薦模型基本就是基于協同過濾的itemCF與userCF模型,這兩種模型都是基于使用者的曆史購買記錄,構架物品或使用者的相似度矩陣,雖然推薦的都是擁護購買相關的商品,但是問題在于其推薦新商品的能力不強,因為新的商品在其中沒有記錄。

為解決對于新商品的預測能力,DNN的方法應運而生,DNN的方法解決了特征交叉的問題,但在經過多層神經網絡後,單個特征及組合特征在後邊的影響了較小,故在上一篇部落格記錄的wide&deep模型中,模型引入了wide部分,不過此方法也存在着一定的問題,就是在模型的最後強行将低階特征與高階特征進行組合,這樣最後預測的結果就會偏向低階特征。為解決這一問題,DeepFM模型橫空出世

2.結構與原理

DeepFM原理圖如下:

深度學習推薦系統03

其對于特征處理的Embeding部分是與wide&deep相同的,其差別是僅僅将wide&deep的wide部分替換為了FM模型。其FM部分公式如下:

y ^ F M ( x ) = w 0 + ∑ i = 1 N w i x i + ∑ i = 1 N ∑ j = i + 1 N v i T v j x i x j \hat{y}_{FM}(x) = w_0+\sum_{i=1}^N w_ix_i + \sum_{i=1}^N \sum_{j=i+1}^N v_i^T v_j x_ix_j y^​FM​(x)=w0​+i=1∑N​wi​xi​+i=1∑N​j=i+1∑N​viT​vj​xi​xj​

對于FM模型,我們可以将其了解為二階的邏輯回歸,我們将公式的最後一項蓋住,這就是一個邏輯回歸的公式,其輸入的特征可以參考wide&deep的wide部分。

3.代碼實作

代碼實作的主題部分如下:

def DeepFM(linear_feature_columns, dnn_feature_columns):
    # 建構輸入層,即所有特征對應的Input()層,這裡使用字典的形式傳回,友善後續構模組化型
    dense_input_dict, sparse_input_dict = build_input_layers(linear_feature_columns + dnn_feature_columns)

    # 将linear部分的特征中sparse特征篩選出來,後面用來做1維的embedding
    linear_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), linear_feature_columns))

    # 構模組化型的輸入層,模型的輸入層不能是字典的形式,應該将字典的形式轉換成清單的形式
    # 注意:這裡實際的輸入與Input()層的對應,是通過模型輸入時候的字典資料的key與對應name的Input層
    input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())

    # linear_logits由兩部分組成,分别是dense特征的logits和sparse特征的logits
    linear_logits = get_linear_logits(dense_input_dict, sparse_input_dict, linear_sparse_feature_columns)

    # 建構次元為k的embedding層,這裡使用字典的形式傳回,友善後面搭模組化型
    # embedding層使用者建構FM交叉部分和DNN的輸入部分
    embedding_layers = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)

    # 将輸入到dnn中的所有sparse特征篩選出來
    dnn_sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), dnn_feature_columns))

    fm_logits = get_fm_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers) # 隻考慮二階項

    # 将所有的Embedding都拼起來,一起輸入到dnn中
    dnn_logits = get_dnn_logits(sparse_input_dict, dnn_sparse_feature_columns, embedding_layers)
    
    # 将linear,FM,dnn的logits相加作為最終的logits
    output_logits = Add()([linear_logits, fm_logits, dnn_logits])

    # 這裡的激活函數使用sigmoid
    output_layers = Activation("sigmoid")(output_logits)

    model = Model(input_layers, output_layers)
    return model
           

其中裡面的build_input_layers(建構輸入層)函數、linear_logits(建構線性回歸)函數及build_embedding_layers(建構embeding層)函數與上一篇部落格中相同,在此不再多做贅述,主要的差別為get_fm_logits函數。

def get_fm_logits(sparse_input_dict, sparse_feature_columns, dnn_embedding_layers):
    # 将特征中的sparse特征篩選出來
    sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), sparse_feature_columns))

    # 隻考慮sparse的二階交叉,将所有的embedding拼接到一起進行FM計算
    # 因為類别型資料輸入的隻有0和1是以不需要考慮将隐向量與x相乘,直接對隐向量進行操作即可
    sparse_kd_embed = []
    for fc in sparse_feature_columns:
        feat_input = sparse_input_dict[fc.name]
        _embed = dnn_embedding_layers[fc.name](feat_input) # B x 1 x k
        sparse_kd_embed.append(_embed)

    # 将所有sparse的embedding拼接起來,得到 (n, k)的矩陣,其中n為特征數,k為embedding大小
    concat_sparse_kd_embed = Concatenate(axis=1)(sparse_kd_embed) # B x n x k
    fm_cross_out = FM_Layer()(concat_sparse_kd_embed)

    return fm_cross_out
           

此函數的的作用為提取稀疏特征的Embeding層,并将其輸出輸入到FM模型,最終得到預測結果。

主函數調用順序如下:

if __name__ == "__main__":
    # 讀取資料
    data = pd.read_csv('./data/criteo_sample.txt')

    # 劃分dense和sparse特征
    columns = data.columns.values
    dense_features = [feat for feat in columns if 'I' in feat]
    sparse_features = [feat for feat in columns if 'C' in feat]

    # 簡單的資料預處理
    train_data = data_process(data, dense_features, sparse_features)
    train_data['label'] = data['label']

    # 将特征分組,分成linear部分和dnn部分(根據實際場景進行選擇),并将分組之後的特征做标記(使用DenseFeat, SparseFeat)
    linear_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
                            for i,feat in enumerate(sparse_features)] + [DenseFeat(feat, 1,)
                            for feat in dense_features]

    dnn_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
                            for i,feat in enumerate(sparse_features)] + [DenseFeat(feat, 1,)
                            for feat in dense_features]

    # 建構DeepFM模型
    history = DeepFM(linear_feature_columns, dnn_feature_columns)
    history.summary()
    history.compile(optimizer="adam", 
                loss="binary_crossentropy", 
                metrics=["binary_crossentropy", tf.keras.metrics.AUC(name='auc')])

    # 将輸入資料轉化成字典的形式輸入
    train_model_input = {name: data[name] for name in dense_features + sparse_features}
    # 模型訓練
    history.fit(train_model_input, train_data['label'].values,
            batch_size=64, epochs=5, validation_split=0.2, )
           

導包語句:

import warnings
warnings.filterwarnings("ignore")
import itertools
import pandas as pd
import numpy as np
from tqdm import tqdm
from collections import namedtuple

import tensorflow as tf
from tensorflow.keras.layers import *
from tensorflow.keras.models import *

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import  MinMaxScaler, LabelEncoder

from utils import SparseFeat, DenseFeat, VarLenSparseFeat
           

繼續閱讀