天天看点

深度学习推荐系统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
           

继续阅读