Sistema de recomendación de aprendizaje profundo (4) Modelo Wide&Deep y su aplicación en el conjunto de datos de Criteo

Sistema de recomendación de aprendizaje profundo (4) Modelo Wide&Deep y su aplicación en el conjunto de datos de Criteo

En 2016, con la introducción de una gran cantidad de excelentes modelos de aprendizaje profundo, como Deep Crossing de Microsoft, Wide&Deep, FNN y PNN de Google, el sistema de recomendación ha entrado por completo en la era del aprendizaje profundo y sigue siendo la corriente principal en la actualidad. El modelo recomendado tiene principalmente los dos desarrollos siguientes:

  • En comparación con los modelos tradicionales de aprendizaje automático, los modelos de aprendizaje profundo tienen capacidades expresivas más fuertes y pueden extraer más patrones ocultos en los datos.

  • La estructura del modelo de aprendizaje profundo es muy flexible y se puede ajustar de manera flexible según los escenarios comerciales y las características de los datos para adaptar perfectamente el modelo a los escenarios de la aplicación.

El modelo de recomendación de aprendizaje profundo, con el perceptrón multicapa (MLP) como núcleo, evoluciona cambiando la estructura de la red neuronal.

inserte la descripción de la imagen aquí

1 Principio del modelo amplio y profundo

1.1 Antecedentes del modelo Wide&Deep

  • Los modelos simples, como el filtrado colaborativo, la regresión logística, etc., pueden aprender combinaciones de características concurrentes de alta frecuencia a partir de datos históricos, pero sus capacidades de generalización son insuficientes.

  • Al igual que la descomposición matricial, las redes de incrustación y aprendizaje profundo pueden utilizar la transitividad de la correlación para explorar combinaciones de características que no han aparecido en datos históricos y extraer patrones de correlación potenciales en los datos. Sin embargo, para algunos escenarios específicos (distribución de datos de cola larga, la co (La matriz de ocurrencia es escasa y de alto rango), es difícil aprender de manera efectiva representaciones de baja dimensión, lo que resulta en una generalización excesiva de las recomendaciones.

Por ello, en 2016, Google propuso el modelo Wide&Deep, que combina perfectamente el modelo lineal con DNN 提高模型泛化能力的同时,兼顾模型的记忆性. Wide&Deep, un modo de conexión paralela entre modelos lineales y DNN, se convirtió más tarde en un modo clásico en el campo de recomendación y sentó las bases para modelos de aprendizaje profundo posteriores, lo que supuso un cambio histórico.

1.2 Capacidad de memoria y capacidad de generalización del modelo.

1.2.1 Comprensión de la capacidad de memoria

记忆能力Puede entenderse como la 共现频率capacidad del modelo para aprender y utilizar directamente elementos y características en datos históricos.

En términos generales, el filtrado colaborativo y la regresión logística tienen una fuerte "capacidad de memoria". Debido a que estos modelos son relativamente simples, los datos originales a menudo pueden afectar directamente los resultados de las recomendaciones y producir recomendaciones similares a estas reglas, lo que equivale a que el modelo recuerde directamente la distribución 如果点击A, 就推荐B. características de los datos históricos y utilizar estos recuerdos para hacer recomendaciones.

以谷歌APP推荐场景为例理解一下:

Supongamos que durante el proceso de capacitación del modelo de recomendación de Google Play, se configuran las siguientes funciones combinadas: AND (user_installed_app=netflix,
impresion_app=pandora), que representa que el usuario ha instalado la aplicación Netflix y ha visto la aplicación Pandora en la aplicación. tienda.

Si utiliza "Si Pandora eventualmente se instalará" como etiqueta, puede contar fácilmente la frecuencia de coexistencia entre las características de netfilx y pandora y la etiqueta de instalación de Pandora. Por ejemplo, la frecuencia de coexistencia de los dos es tan alta como el 10%. Al diseñar el modelo, esperamos que tan pronto como el modelo encuentre esta característica, recomiende la aplicación Pandora (grabada en la mente como un recuerdo profundo). punto) Esta es la llamada " Capacidad de memoria".

Cuando un modelo como la regresión logística encuentra una característica tan fuerte, aumentará el peso y recordará directamente esta característica.
Sin embargo, para modelos como las redes neuronales, las características se procesan en múltiples capas y se cruzan constantemente con otras características, por lo que la fuerte memoria especial del modelo no es tan profunda como la de un modelo simple.

1.2.2 Comprensión de la capacidad de generalización

泛化能力puede entenderse como 模型传递特征的相关性, 以及发掘稀疏甚至从未出现过的稀有特征与最终标签相关性的能力.

Por ejemplo, la descomposición matricial, la incrustación, etc., permiten a los usuarios o elementos con datos escasos generar vectores latentes, obteniendo así puntuaciones de recomendación respaldadas por datos, pasando datos globales a elementos escasos y mejorando las capacidades de generalización.

Otro ejemplo son las redes neuronales, que pueden explorar en profundidad patrones potenciales en los datos y mejorar la generalización mediante la combinación automática de características.

Por lo tanto, la motivación directa del modelo Wide&Deep es integrar los dos, de modo que el modelo tenga tanto la "capacidad de memoria" del modelo simple como la "capacidad de generalización" de la red neuronal, que también es la combinación de memoria y generalización. .Intento inicial de un gran patrón.

1.3 Estructura del modelo amplio y profundo

inserte la descripción de la imagen aquí

El modelo clásico de W&D se muestra en la figura:

  • La parte izquierda es la parte ancha, que es un modelo lineal simple, y la parte derecha es la parte profunda, un modelo DNN clásico.

  • El modelo W&D conecta las sumas de las capas de entrada individuales Wide部分y Embedding+多层的全连接的部分(deep部分)las ingresa juntas en la capa de salida final para obtener el resultado de la predicción.

  • 单层的wide层善于处理大量的稀疏的id类特征La parte profunda utiliza una intersección de características profunda para extraer los patrones de datos detrás de las características. Finalmente, utilizando la regresión logística, la parte de la capa de salida se combina con Deep para formar un modelo unificado.

1.3.1 Parte ancha

inserte la descripción de la imagen aquí

  • Para el optimizador utilizado durante el entrenamiento de partes amplias 带正则的FTRL算法(Follow-the-regularized-leader), FTRL puede considerarse como un método de descenso de gradiente estocástico con buena dispersión y buena precisión. Este algoritmo presta gran atención a la naturaleza dispersa del modelo.

  • En otras palabras, el modelo W&D utiliza L1 FTRL 想让Wide部分变得更加的稀疏,即Wide部分的大部分参数都为0, que comprime en gran medida las dimensiones de los pesos del modelo y los vectores de características.

  • Las características que quedan después de entrenar la parte amplia del modelo son muy importantes 模型的“记忆能力”可以理解为发现"直接的",“暴力的”,“显然的”关联规则的能力.

1.3.2 Parte profunda

  • Principalmente la parte profunda 一个Embedding+MLP的神经网络模型.

  • Las características dispersas a gran escala se transforman en características densas de baja dimensión mediante la incrustación. Luego, las características se empalman y se ingresan en MLP para extraer los patrones de datos ocultos detrás de las características.

  • Hay dos tipos de características de entrada, una son características numéricas y la otra son características categóricas (después de la incrustación).

  • A medida que aumenta el número de capas del modelo DNN, las características intermedias se vuelven más abstractas, lo que mejora la capacidad de generalización del modelo.

  • Para la parte profunda del modelo DNN, el autor utiliza métodos de aprendizaje profundo de uso común 优化器AdaGrad, que también permiten que el modelo obtenga soluciones más precisas.

1.3.3 Estructura del modelo detallada de Wide&Deep

inserte la descripción de la imagen aquí

En la imagen de arriba, podemos aprender en detalle qué funciones se utilizan como entradas para la parte Profunda y qué funciones se utilizan como entradas para la parte Amplia del modelo Amplio y Profundo diseñado por el equipo de recomendación de Google Play.

  • La entrada a la parte Amplia son solo 已安装应用和曝光应用dos tipos de características, donde las aplicaciones instaladas representan el comportamiento histórico del usuario y las aplicaciones expuestas representan las aplicaciones actuales que se recomendarán. La razón para elegir estos dos tipos de funciones es aprovechar al máximo 记忆能力las fuertes ventajas de la parte Amplia.

  • La entrada de la parte profunda es un conjunto completo de vectores de características, incluida la edad del usuario (Edad), la cantidad de aplicaciones instaladas (#Instalaciones de aplicaciones), el tipo de dispositivo (Clase de dispositivo), las aplicaciones instaladas (Aplicación instalada por el usuario), la aplicación de exposición (Impresión). Aplicación), etc. Las características de categoría, como aplicaciones instaladas y aplicaciones expuestas, deben ingresarse en la capa de incrustación concatenada a través de la capa de incrustación, empalmarse en 1200 dimensiones y luego pasar a través de tres capas de capas ReLU completamente conectadas y finalmente ingresarse en la capa de salida LogLoss. Vector de incrustación.

1.4 Código de modelo ancho y profundo

import torch.nn as nn
import torch.nn.functional as F
import torch


class Linear(nn.Module):
    """
    Linear part
    """

    def __init__(self, input_dim):
        super(Linear, self).__init__()
        self.linear = nn.Linear(in_features=input_dim, out_features=1)

    def forward(self, x):
        return self.linear(x)


class Dnn(nn.Module):
    """
    Dnn part
    """

    def __init__(self, hidden_units, dropout=0.5):
        """
        hidden_units: 列表, 每个元素表示每一层的神经单元个数, 比如[256, 128, 64], 两层网络, 第一层神经单元128, 第二层64, 第一个维度是输入维度
        dropout: 失活率
        """
        super(Dnn, self).__init__()

        self.dnn_network = nn.ModuleList(
            [nn.Linear(layer[0], layer[1]) for layer in list(zip(hidden_units[:-1], hidden_units[1:]))])

        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x):
        for linear in self.dnn_network:
            x = linear(x)
            x = F.relu(x)

        x = self.dropout(x)
        return x


'''
WideDeep模型:
   主要包括Wide部分和Deep部分
'''
class WideDeep(nn.Module):

    def __init__(self, feature_info, hidden_units, embed_dim=8):
        """
               DeepCrossing:
                   feature_info: 特征信息(数值特征, 类别特征, 类别特征embedding映射)
                   hidden_units: 列表, 隐藏单元
                   dropout: Dropout层的失活比例
                   embed_dim: embedding维度
               """
        super(WideDeep, self).__init__()

        self.dense_features, self.sparse_features, self.sparse_features_map = feature_info

        # embedding层, 这里需要一个列表的形式, 因为每个类别特征都需要embedding
        self.embed_layers = nn.ModuleDict(
            {
    
    
                'embed_' + str(key): nn.Embedding(num_embeddings=val, embedding_dim=embed_dim)
                for key, val in self.sparse_features_map.items()
            }
        )


        # 统计embedding_dim的总维度
        # 一个离散型(类别型)变量 通过embedding层变为8纬
        embed_dim_sum = sum([embed_dim] * len(self.sparse_features))
        # 总维度 = 数值型特征的纬度 + 离散型变量经过embedding后的纬度
        dim_sum = len(self.dense_features) + embed_dim_sum
        hidden_units.insert(0, dim_sum)
        # dnn网络
        self.dnn_network = Dnn(hidden_units)

        # 线性层
        self.linear = Linear(input_dim=len(self.dense_features))

        # 最终的线性层
        self.final_linear = nn.Linear(hidden_units[-1], 1)

    def forward(self, x):
        # 1、先把输入向量x分成两部分处理、因为数值型和类别型的处理方式不一样
        dense_input, sparse_inputs = x[:, :len(self.dense_features)], x[:, len(self.dense_features):]
        # 2、转换为long形
        sparse_inputs = sparse_inputs.long()

        # 2、不同的类别特征分别embedding
        sparse_embeds = [
            self.embed_layers['embed_' + key](sparse_inputs[:, i]) for key, i in
            zip(self.sparse_features_map.keys(), range(sparse_inputs.shape[1]))
        ]
        # 3、把类别型特征进行拼接,即emdedding后,由3行转换为1行
        sparse_embeds = torch.cat(sparse_embeds, axis=-1)
        # 4、数值型和类别型特征进行拼接
        dnn_input = torch.cat([sparse_embeds, dense_input], axis=-1)

        # Wide部分,使用的特征为数值型类型
        wide_out = self.linear(dense_input)

        # Deep部分,使用全部特征
        deep_out = self.dnn_network(dnn_input)

        deep_out = self.final_linear(deep_out)

        # out  将Wide部分的输出和Deep部分的输出进行合并
        outputs = F.sigmoid(0.5 * (wide_out + deep_out))

        return outputs



if __name__ == '__main__':
    x = torch.rand(size=(1, 5), dtype=torch.float32)
    feature_info = [
        ['I1', 'I2'],  # 连续性特征
        ['C1', 'C2', 'C3'],  # 离散型特征
        {
    
    
            'C1': 20,
            'C2': 20,
            'C3': 20
        }
    ]

    # 建立模型
    hidden_units = [256, 128, 64]

    net = WideDeep(feature_info, hidden_units)
    print(net)
    print(net(x))
WideDeep(
  (embed_layers): ModuleDict(
    (embed_C1): Embedding(20, 8)
    (embed_C2): Embedding(20, 8)
    (embed_C3): Embedding(20, 8)
  )
  (dnn_network): Dnn(
    (dnn_network): ModuleList(
      (0): Linear(in_features=26, out_features=256, bias=True)
      (1): Linear(in_features=256, out_features=128, bias=True)
      (2): Linear(in_features=128, out_features=64, bias=True)
    )
    (dropout): Dropout(p=0.5, inplace=False)
  )
  (linear): Linear(
    (linear): Linear(in_features=2, out_features=1, bias=True)
  )
  (final_linear): Linear(in_features=64, out_features=1, bias=True)
)
tensor([[0.6531]], grad_fn=<SigmoidBackward0>)

2 Aplicación del modelo Wide&Deep en el conjunto de datos de Criteo

Para el preprocesamiento de datos y algunas funciones o clases, consulte:

Sistema de recomendación de aprendizaje profundo (2) Deep Crossing y su aplicación en el conjunto de datos de Criteo

2.1 Preparar datos de entrenamiento

import pandas as pd

import torch
from torch.utils.data import TensorDataset, Dataset, DataLoader

import torch.nn as nn
from sklearn.metrics import auc, roc_auc_score, roc_curve

import warnings
warnings.filterwarnings('ignore')
# 封装为函数
def prepared_data(file_path):
    # 读入训练集,验证集和测试集
    train_set = pd.read_csv(file_path + 'train_set.csv')
    val_set = pd.read_csv(file_path + 'val_set.csv')
    test_set = pd.read_csv(file_path + 'test.csv')

    # 这里需要把特征分成数值型和离散型
    # 因为后面的模型里面离散型的特征需要embedding, 而数值型的特征直接进入了stacking层, 处理方式会不一样
    data_df = pd.concat((train_set, val_set, test_set))

    # 数值型特征直接放入stacking层
    dense_features = ['I' + str(i) for i in range(1, 14)]
    # 离散型特征需要需要进行embedding处理
    sparse_features = ['C' + str(i) for i in range(1, 27)]

    # 定义一个稀疏特征的embedding映射, 字典{key: value},
    # key表示每个稀疏特征, value表示数据集data_df对应列的不同取值个数, 作为embedding输入维度
    sparse_feas_map = {
    
    }
    for key in sparse_features:
        sparse_feas_map[key] = data_df[key].nunique()


    feature_info = [dense_features, sparse_features, sparse_feas_map]  # 这里把特征信息进行封装, 建立模型的时候作为参数传入

    # 把数据构建成数据管道
    dl_train_dataset = TensorDataset(
        # 特征信息
        torch.tensor(train_set.drop(columns='Label').values).float(),
        # 标签信息
        torch.tensor(train_set['Label'].values).float()
    )

    dl_val_dataset = TensorDataset(
        # 特征信息
        torch.tensor(val_set.drop(columns='Label').values).float(),
        # 标签信息
        torch.tensor(val_set['Label'].values).float()
    )
    dl_train = DataLoader(dl_train_dataset, shuffle=True, batch_size=16)
    dl_vaild = DataLoader(dl_val_dataset, shuffle=True, batch_size=16)
    return feature_info,dl_train,dl_vaild,test_set
file_path = './preprocessed_data/'

feature_info,dl_train,dl_vaild,test_set = prepared_data(file_path)

2.2 Establecer un modelo amplio y profundo

from _01_wide_deep import WideDeep

hidden_units = [256, 128, 64]
net = WideDeep(feature_info, hidden_units)

2.3 Entrenamiento modelo

from AnimatorClass import Animator
from TimerClass import Timer


# 模型的相关设置
def metric_func(y_pred, y_true):
    pred = y_pred.data
    y = y_true.data
    return roc_auc_score(y, pred)


def try_gpu(i=0):
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{
      
      i}')
    return torch.device('cpu')


def train_ch(net, dl_train, dl_vaild, num_epochs, lr, device):
    """⽤GPU训练模型"""
    print('training on', device)
    net.to(device)
    # 二值交叉熵损失
    loss_func = nn.BCELoss()
    # 注意:这里没有使用原理提到的优化器
    optimizer = torch.optim.Adam(params=net.parameters(), lr=lr)

    animator = Animator(xlabel='epoch', xlim=[1, num_epochs],legend=['train loss', 'train auc', 'val loss', 'val auc']
                        ,figsize=(8.0, 6.0))
    timer, num_batches = Timer(), len(dl_train)
    log_step_freq = 10

    for epoch in range(1, num_epochs + 1):
        # 训练阶段
        net.train()
        loss_sum = 0.0
        metric_sum = 0.0

        for step, (features, labels) in enumerate(dl_train, 1):
            timer.start()
            # 梯度清零
            optimizer.zero_grad()

            # 正向传播
            predictions = net(features)
            loss = loss_func(predictions, labels.unsqueeze(1) )
            try:          # 这里就是如果当前批次里面的y只有一个类别, 跳过去
                metric = metric_func(predictions, labels)
            except ValueError:
                pass

            # 反向传播求梯度
            loss.backward()
            optimizer.step()
            timer.stop()

            # 打印batch级别日志
            loss_sum += loss.item()
            metric_sum += metric.item()

            if step % log_step_freq == 0:
                animator.add(epoch + step / num_batches,(loss_sum/step, metric_sum/step, None, None))

        # 验证阶段
        net.eval()
        val_loss_sum = 0.0
        val_metric_sum = 0.0


        for val_step, (features, labels) in enumerate(dl_vaild, 1):
            with torch.no_grad():
                predictions = net(features)
                val_loss = loss_func(predictions, labels.unsqueeze(1))
                try:
                    val_metric = metric_func(predictions, labels)
                except ValueError:
                    pass

            val_loss_sum += val_loss.item()
            val_metric_sum += val_metric.item()

            if val_step % log_step_freq == 0:
                animator.add(epoch + val_step / num_batches, (None,None,val_loss_sum / val_step , val_metric_sum / val_step))

        print(f'final: loss {
      
      loss_sum/len(dl_train):.3f}, auc {
      
      metric_sum/len(dl_train):.3f},'
              f' val loss {
      
      val_loss_sum/len(dl_vaild):.3f}, val auc {
      
      val_metric_sum/len(dl_vaild):.3f}')
        print(f'{
      
      num_batches * num_epochs / timer.sum():.1f} examples/sec on {
      
      str(device)}')
lr, num_epochs = 0.001, 10
# 其实发生了过拟合
train_ch(net, dl_train, dl_vaild, num_epochs, lr, try_gpu())

inserte la descripción de la imagen aquí

2.4 Predicción del modelo

y_pred_probs = net(torch.tensor(test_set.values).float())
y_pred = torch.where(
    y_pred_probs>0.5,
    torch.ones_like(y_pred_probs),
    torch.zeros_like(y_pred_probs)
)
y_pred.data[:10]

Supongo que te gusta

Origin blog.csdn.net/qq_44665283/article/details/132632267
Recomendado
Clasificación