Modelo de recomendación de aprendizaje profundo: DeepCrossing

Modelo de recomendación de aprendizaje profundo: DeepCrossing

El enlace de referencia de este artículo es solo para aprendizaje personal: https://github.com/datawhalechina/team-learning-rs/tree/master/DeepRecommendationModel

Recomendación de libros de aprendizaje relacionados: "Sistema de recomendación de aprendizaje profundo" Wang Zhe

1. Antecedentes de Deep Crossing

En 2016, Microsoft propuso el modelo Deep Crossing, que tiene como objetivo resolver el problema de la combinación de funciones en la ingeniería de funciones y reducir el costo de tiempo de la combinación de funciones de mano de obra. A través del modelo para aprender automáticamente la combinación de funciones, también puede lograr buenos resultados, y se puede utilizar en varias tareas. Muestra buena estabilidad. A diferencia de FNN y PNN introducidos anteriormente, Deep Crossing no usa características cruzadas explícitas, sino que usa la estructura de red residual para extraer la relación entre las características. Este artículo realizará un análisis detallado de DeepCrossing desde el principio hasta los detalles de implementación.

El escenario de aplicación del modelo DeepCrossing es la recomendación de publicidad de búsqueda en el motor de búsqueda de Microsoft Bing. Una vez que el usuario ingresa el término de búsqueda, el motor de búsqueda devuelve no solo resultados relevantes sino también anuncios relacionados con el término de búsqueda. El objetivo de optimización de Deep Crossing es predecir un determinado anuncio. El hecho de que los usuarios hagan clic sigue siendo una cuestión de predicción de la tasa de clics.

1. Estructura y principio del modelo DeepCrossing

Para completar el entrenamiento de un extremo a otro, el modelo DeepCrossing necesita resolver los siguientes problemas en la estructura de la red interna:

  1. Las características discretas son demasiado escasas después de la codificación, lo que no conduce a la entrada directa al entrenamiento de la red neuronal, y el problema de la densificación de los vectores de características dispersas debe resolverse
  2. Cómo resolver el problema de la combinación cruzada automática de funciones.
  3. Cómo lograr el objetivo de optimización establecido por el problema en la capa de salida

DeepCrossing configura diferentes capas de redes neuronales para resolver los problemas anteriores.

El modelo completo contiene cuatro estructuras: incrustación, apilamiento, unidad residual y capa de puntuación.

El siguiente análisis de la estructura de cada capa:

1. Capa de incrustación: convierta características categóricas dispersas en vectores de incrustación densos, principalmente basados ​​en la estructura de capas clásica totalmente conectada. Generalmente, la dimensión del vector de incrustación debe ser mucho más pequeña que la del vector de características dispersas original. En este caso, la característica # 2 century representa una característica numérica, se puede ver que la característica numérica no necesita pasar por la capa de incrustación, sino que entra directamente en la capa de apilamiento.

2. Capa de apilamiento: la función de la capa de apilamiento (capa de apilamiento) es relativamente simple, consiste en unir diferentes características de incrustación y características numéricas, que también se denomina capa de concatenación.

3. Capa de unidades residuales múltiples: la estructura principal es un perceptrón multicapa. En comparación con el perceptrón multicapa estándar como unidad básica de la red neuronal, el modelo Deep Crossing utiliza una red residual multicapa (multicapa residual Network) como la implementación de MLP. A través de la red residual multicapa, cada dimensión del vector de características se combina completamente de forma cruzada.

4. Capa de puntuación: la capa de puntuación se utiliza como capa de salida para ajustar el objetivo de optimización. Para problemas de clasificación binaria, como la estimación de CTR, la capa de puntuación a menudo utiliza un modelo de regresión logística. Para problemas de clasificación múltiple, como imágenes, La capa a menudo usa el modelo softmax.

2.1 Capa de incrustación

Al convertir características categóricas dispersas en densos vectores de incrustación, las dimensiones de la incrustación serán mucho más pequeñas que los vectores de características dispersas originales. La incrustación es una técnica de uso común en NLP, donde la característica n. ° 1 representa la característica de categoría (vector de características dispersas después de la codificación one-hot) y la característica n. ° 2 es una característica numérica, sin incrustación, va directamente a la capa de apilamiento. Con respecto a la implementación de la capa de incrustación, una capa completamente conectada suele ser suficiente, y hay capas bien implementadas en Tensorflow que se pueden usar directamente. Es similar a incrustar tecnología en NLP, como Word2Vec, modelo de lenguaje, etc.

2.2 Capa de apilamiento

Esta capa es para unir diferentes características de incrustación y características numéricas para formar un nuevo vector de características que contiene todas las características. Esta capa generalmente se llama capa de conexión. La implementación específica es la siguiente. Primero, todas las características numéricas se unen y luego, todas las incorporaciones se empalman y, finalmente, las características numéricas y las características de inclusión se empalman como la entrada de DNN, donde TF se empalma a través de la capa Concatnate.

#将所有的dense特征拼接到一起
dense_dnn_list = list(dense_input_dict.values())
dense_dnn_inputs = Concatenate(axis=1)(dense_dnn_list) # B x n (n表示数值特征的数量)

# 因为需要将其与dense特征拼接到一起所以需要Flatten,不进行Flatten的Embedding层输出的维度为:Bx1xdim
sparse_dnn_list = concat_embedding_list(dnn_feature_columns, sparse_input_dict, embedding_layer_dict, flatten=True) 

sparse_dnn_inputs = Concatenate(axis=1)(sparse_dnn_list) # B x m*dim (n表示类别特征的数量,dim表示embedding的维度)

# 将dense特征和Sparse特征拼接到一起
dnn_inputs = Concatenate(axis=1)([dense_dnn_inputs, sparse_dnn_inputs]) # B x (n + m*dim)

2.3 Capa de múltiples unidades residuales

La estructura principal de esta capa es MLP, pero DeepCrossing usa la conexión de la red residual. La red residual multicapa combina completamente las diversas dimensiones del vector de características, de modo que el modelo puede capturar más características no lineales e información de características combinadas, y aumentar la capacidad expresiva del modelo. La estructura de la red residual se muestra en la siguiente figura:

imagen-20210217174914659

El modelo de Deep Crossing usa una unidad residual ligeramente modificada, no usa un kernel de convolución y en su lugar usa una red neuronal de dos capas. Podemos ver que la unidad residual se realiza agregando las características de entrada originales a través de dos capas de transformación ReLU. La implementación del código específico es la siguiente:

# DNN残差块的定义
class ResidualBlock(Layer):
    def __init__(self, units): # units表示的是DNN隐藏层神经元数量
        super(ResidualBlock, self).__init__()
        self.units = units

    def build(self, input_shape):
        out_dim = input_shape[-1]
        self.dnn1 = Dense(self.units, activation='relu')
        self.dnn2 = Dense(out_dim, activation='relu') # 保证输入的维度和输出的维度一致才能进行残差连接
    def call(self, inputs):
        x = inputs
        x = self.dnn1(x)
        x = self.dnn2(x)
        x = Activation('relu')(x + inputs) # 残差操作
        return x

2.4 Capa de puntuación

Esta es la capa de salida, para ajustarse al objetivo de optimización. Para el problema de dos clases de estimación de CTR, Scoring a menudo usa regresión logística El modelo profundiza la profundidad de la red superponiendo múltiples bloques residuales y finalmente convierte el resultado en un valor de probabilidad para la salida.

# block_nums表示DNN残差块的数量
def get_dnn_logits(dnn_inputs, block_nums=3):
    dnn_out = dnn_inputs
    for i in range(block_nums):
        dnn_out = ResidualBlock(64)(dnn_out)
    
    # 将dnn的输出转化成logits
    dnn_logits = Dense(1, activation='sigmoid')(dnn_out)

    return dnn_logits总结

Esta es la estructura de DeepCrossing, que es relativamente clara y simple, no introduce una estructura de modelo especial, sino una red neuronal multicapa Embedding + convencional. Pero la aparición de este modelo de red tiene un significado revolucionario. No hay ingeniería de características artificiales involucrada en el modelo DeepCrossing. Solo necesita un procesamiento de características simple. Las características originales se ingresan a la capa de red neuronal a través de la capa de incrustación, y se cruzan y aprenden automáticamente. En comparación con FM, FFM solo tiene un modelo con funciones de cruce de funciones de segundo orden. DeepCrossing puede realizar un "cruce profundo" entre funciones ajustando la profundidad de la red neuronal. Este es también el origen del nombre de Deep Crossing. .

 

Dos, código completo de combate real

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 import keras 
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


def data_process(data_df, dense_features, sparse_features):
    """
    简单处理特征,包括填充缺失值,数值处理,类别编码
    param data_df: DataFrame格式的数据
    param dense_features: 数值特征名称列表
    param sparse_features: 类别特征名称列表
    """
    data_df[dense_features] = data_df[dense_features].fillna(0.0)
    for f in dense_features:
        data_df[f] = data_df[f].apply(lambda x: np.log(x+1) if x > -1 else -1)
        
    data_df[sparse_features] = data_df[sparse_features].fillna("-1")
    for f in sparse_features:
        lbe = LabelEncoder()
        data_df[f] = lbe.fit_transform(data_df[f])
    
    return data_df[dense_features + sparse_features]


def build_input_layers(feature_columns):
    """
    构建输入层
    param feature_columns: 数据集中的所有特征对应的特征标记之
    """
    # 构建Input层字典,并以dense和sparse两类字典的形式返回
    dense_input_dict, sparse_input_dict = {}, {}

    for fc in feature_columns:
        if isinstance(fc, SparseFeat):
            sparse_input_dict[fc.name] = Input(shape=(1, ), name=fc.name)
        elif isinstance(fc, DenseFeat):
            dense_input_dict[fc.name] = Input(shape=(fc.dimension, ), name=fc.name)
        
    return dense_input_dict, sparse_input_dict


def build_embedding_layers(feature_columns, input_layers_dict, is_linear):
    # 定义一个embedding层对应的字典
    embedding_layers_dict = dict()
    
    # 将特征中的sparse特征筛选出来
    sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), feature_columns)) if feature_columns else []
    
    # 如果是用于线性部分的embedding层,其维度为1,否则维度就是自己定义的embedding维度
    if is_linear:
        for fc in sparse_feature_columns:
            embedding_layers_dict[fc.name] = Embedding(fc.vocabulary_size + 1, 1, name='1d_emb_' + fc.name)
    else:
        for fc in sparse_feature_columns:
            embedding_layers_dict[fc.name] = Embedding(fc.vocabulary_size + 1, fc.embedding_dim, name='kd_emb_' + fc.name)
    
    return embedding_layers_dict


# 将所有的sparse特征embedding拼接
def concat_embedding_list(feature_columns, input_layer_dict, embedding_layer_dict, flatten=False):
    # 将sparse特征筛选出来
    sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), feature_columns))

    embedding_list = []
    for fc in sparse_feature_columns:
        _input = input_layer_dict[fc.name] # 获取输入层 
        _embed = embedding_layer_dict[fc.name] # B x 1 x dim  获取对应的embedding层
        embed = _embed(_input) # B x dim  将input层输入到embedding层中

        # 是否需要flatten, 如果embedding列表最终是直接输入到Dense层中,需要进行Flatten,否则不需要
        if flatten:
            embed = Flatten()(embed)
        
        embedding_list.append(embed)
    
    return embedding_list 


# DNN残差块的定义
class ResidualBlock(Layer):
    def __init__(self, units): # units表示的是DNN隐藏层神经元数量
        super(ResidualBlock, self).__init__()
        self.units = units

    def build(self, input_shape):
        out_dim = input_shape[-1]
        self.dnn1 = Dense(self.units, activation='relu')
        self.dnn2 = Dense(out_dim, activation='relu') # 保证输入的维度和输出的维度一致才能进行残差连接
    def call(self, inputs):
        x = inputs
        x = self.dnn1(x)
        x = self.dnn2(x)
        x = Activation('relu')(x + inputs) # 残差操作
        return x


# block_nums表示DNN残差块的数量
def get_dnn_logits(dnn_inputs, block_nums=3):
    dnn_out = dnn_inputs
    for i in range(block_nums):
        dnn_out = ResidualBlock(64)(dnn_out)
    
    # 将dnn的输出转化成logits
    dnn_logits = Dense(1, activation='sigmoid')(dnn_out)

    return dnn_logits


def DeepCrossing(dnn_feature_columns):
    # 构建输入层,即所有特征对应的Input()层,这里使用字典的形式返回,方便后续构建模型
    dense_input_dict, sparse_input_dict = build_input_layers(dnn_feature_columns)
    # 构建模型的输入层,模型的输入层不能是字典的形式,应该将字典的形式转换成列表的形式
    # 注意:这里实际的输入与Input()层的对应,是通过模型输入时候的字典数据的key与对应name的Input层
    input_layers = list(dense_input_dict.values()) + list(sparse_input_dict.values())
    
    # 构建维度为k的embedding层,这里使用字典的形式返回,方便后面搭建模型
    embedding_layer_dict = build_embedding_layers(dnn_feature_columns, sparse_input_dict, is_linear=False)

    #将所有的dense特征拼接到一起
    dense_dnn_list = list(dense_input_dict.values())
    dense_dnn_inputs = Concatenate(axis=1)(dense_dnn_list) # B x n (n表示数值特征的数量)

    # 因为需要将其与dense特征拼接到一起所以需要Flatten,不进行Flatten的Embedding层输出的维度为:Bx1xdim
    sparse_dnn_list = concat_embedding_list(dnn_feature_columns, sparse_input_dict, embedding_layer_dict, flatten=True) 

    sparse_dnn_inputs = Concatenate(axis=1)(sparse_dnn_list) # B x m*dim (n表示类别特征的数量,dim表示embedding的维度)

    # 将dense特征和Sparse特征拼接到一起
    dnn_inputs = Concatenate(axis=1)([dense_dnn_inputs, sparse_dnn_inputs]) # B x (n + m*dim)

    # 输入到dnn中,需要提前定义需要几个残差块
    output_layer = get_dnn_logits(dnn_inputs, block_nums=3)

    model = Model(input_layers, output_layer)
    return model


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']

    # 将特征做标记
    dnn_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].nunique(),embedding_dim=4)
                            for feat in sparse_features] + [DenseFeat(feat, 1,)
                            for feat in dense_features]

    # 构建DeepCrossing模型
    history = DeepCrossing(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, )

 

Supongo que te gusta

Origin blog.csdn.net/qq_36816848/article/details/114851120
Recomendado
Clasificación