AutoInt原理+实现代码+deepctr库实现

简介

AutoInt: Automatic Feature Interaction Learning viaSelf-Attentive Neural Networks。

AutoIn将transform中的 multi-head self attention引入CTR预估模型。实现了自动特征交叉学习以提升CTR预测任务的精度。

AutoInt的结构图如下:

在这里插入图片描述

该结构是典型的四段式深度学习CTR模型结构:输入,嵌入,特征提取,

AutoInt的目标是:将原始稀疏高维特征向量映射到低维空间,同时对高阶特征交互进行建模。

如上图所示,(1)稀疏特征向量x作为输入,然后是投影所有特征(包括类别型特征和数值型特征)的嵌入层进入同样的维度的低维空间。(2)将所有的嵌入后的低维fields输入到一个新的交互层中,该层被实现为一个多头自聚焦神经网络(a multi-head self-attentive neural network.)。对于每个交互层,通过注意机制对高阶特征进行组合,并利用多头部机制对不同的组合进行评估,将特征映射到不同的子空间中。通过叠加多个交互层,可以对不同阶的组合特征进行建模。

核心结构

输入和嵌入

在这里插入图片描述

模型的输入:
x = [ x 1 ; x 2 ; ⋯   ; x M ] \mathbf x = [\mathbf x_1; \mathbf x_2;\cdots;\mathbf x_M] x=[x1;x2;;xM]
其中M是总特征数目, x i x_i xi是第 i i i个特征表示,第i(m-1)为类别型特征,第mM个特征为数值型特征。

对于类别型特征, x i x_i xi是一个高维的稀疏的one-hot向量,AutoInt 采用如下方式进行降维
e i = V i x i \mathbf e_i = \mathbf V_i \mathbf x_i ei=Vixi
其中, V i \mathbf V_i Vi是field i 的嵌入矩阵。

注:若 x i \mathbf x_i xi是一个多值的one-hot向量(比如一个电影可以同时是Drama and Romance),则采用以下的方式进行降维:
e i = 1 q V i x i \mathbf e_i =\frac{1}{q} \mathbf V_i \mathbf x_i ei=q1Vixi
q是多值向量的值的个数。

数值型变量也采用同样的方式:
e m = v m x m \mathbf e_m = \mathbf v_m x_m em=vmxm
其中 v m v_m vm是嵌入向量, x m x_m xm是一个标量值

通常在CTR任务中我们对连续值特征对处理方式有三种:

  1. 进行归一化处理拼接到embedding向量侧
  2. 进行离散化处理作为类别特征
  3. 赋予其一个embedding向量,每次用特征值与embedding向量的乘积作为其最终表示

AutoInt采取的是第三种方式。

将类别型变量和数值型变量的embedding结果进行catenation。作为下一层的输入。

InteractingLayer(交互层)

在理解这个层之前,需要先了解Attention机制,可以参考:深入理解 Bert核心:Self-Attention与transformer

交互层使用多头注意力机制将特征投射到多个子空间中,在不同的子空间中可以捕获不同的特征交互模式。通过交互层的堆叠,可以捕获更高阶的交互模式。下图展示了在特定的子空间 h h h下,对于特征 e m \mathbf e_m em,交互层是如何计算其交互特征 e ~ m ( h ) \tilde{\mathbf e}^{(h)}_m e~m(h)

在这里插入图片描述

(1)首先,输入特征通过矩阵的线性乘法变换为不同注意力空间下的向量表示,对于每个特征 e m \mathbf e_m em

在特定的注意力空间 h h h中,都有三个向量表示:
E m h : Q u e r y = W Q u e r y ( h ) e m E m h : V a l u e = W V a l u e ( h ) e m E m h : K e y = W K e y ( h ) e m E^{h:Query}_m=\mathbf W^{(h)}_{Query}\mathbf e_m \\ E^{h:Value}_m=\mathbf W^{(h)}_{Value}\mathbf e_m \\ E^{h:Key}_m=\mathbf W^{(h)}_{Key} \mathbf e_m Emh:Query=WQuery(h)emEmh:Value=WValue(h)emEmh:Key=WKey(h)em
(2)计算 e m e_m em 与其他特征 e k e_k ek的相似度(向量内积): ϕ ( h ) ( e m , e k ) = < W Q u e r y ( h ) e m , W K e y ( h ) e k > \phi^{(h)}(\mathbf e_m,\mathbf e_k)= <\mathbf W^{(h)}_{Query}\mathbf e_m,\mathbf W^{(h)}_{Key} \mathbf e_k> ϕ(h)(em,ek)=<WQuery(h)em,WKey(h)ek>

(3)计算softmax归一化注意力分布: a m , k ( h ) = e x p ( ϕ ( h ) ( e m , e k ) ) ∑ l = 1 M e x p ( ϕ ( h ) ( e m , e l ) ) a^{(h)}_{m,k}=\frac{exp(\phi^{(h)}(e_m,e_k))}{\sum_{l=1}^M{exp(\phi^{(h)}(e_m,e_l))}} am,k(h)=l=1Mexp(ϕ(h)(em,el))exp(ϕ(h)(em,ek))

(4)通过加权求和的方式得到特征m的新特征: e ~ m ( h ) = ∑ k = 1 M a m , k ( h ) E m h : V a l u e \tilde{e}^{(h)}_m=\sum_{k=1}^Ma^{(h)}_{m,k}E^{h:Value}_m e~m(h)=k=1Mam,k(h)Emh:Value

假设有 H H H个注意力空间,对每个注意力空间下的结果进行拼接,得到特征 m m m最终的结果为 e ~ m = e ~ m ( 1 ) ⊕ e ~ m ( 2 ) ⊕ ⋯ ⊕ e ~ m ( H ) \tilde{e}_m=\tilde{e}^{(1)}_m\oplus\tilde{e}^{(2)}_m\oplus\dots\oplus\tilde{e}^{(H)}_m e~m=e~m(1)e~m(2)e~m(H),其中 ⊕ \oplus 是连接操作符,H多头transform机制中的头的个数。

为了保留一些原始信息作为下一层的输入,AutoInt使用残差神经网络:
e m R e s = R e L U ( e ~ m + W R e s e m ) e^{Res}_m=ReLU(\tilde{e}_m+W_{Res}e_m) emRes=ReLU(e~m+WResem)
最后,将每个特征的结果拼接,计算最终的输出值:
y ^ = σ ( w T ( e 1 R e s ⊕ e 2 R e s … e M R e s ) + b ) \hat{y}=\sigma(w^T(e^{Res}_1\oplus e^{Res}_2 \dots e^{Res}_M)+b) y^=σ(wT(e1Rese2ReseMRes)+b)

损失函数

AutoInt采用log loss 作为模型的损失函数:
L o g l o s s = − 1 N ∑ y i N ( y j log ⁡ y ^ j + ( 1 − y j ) log ⁡ ( 1 − log ⁡ y ^ j ) ) Logloss = - \frac{1}{N}\sum_{y_i}^{N}(y_j\log \hat y_j + (1-y_j)\log(1-\log \hat y_j )) Logloss=N1yiN(yjlogy^j+(1yj)log(1logy^j))
模型需要更新的参数为:
V i v m W Q u e r y ( h ) W V a l u e ( h ) W K e y ( h ) W r e s w b \mathbf V_i \quad \mathbf v_m \quad \mathbf W^{(h)}_{Query} \quad \mathbf W^{(h)}_{Value} \quad \mathbf W^{(h)}_{Key} \quad \mathbf W_{res} \quad \mathbf w \quad \mathbf b VivmWQuery(h)WValue(h)WKey(h)Wreswb

tensorflow 2.0 实现AutoInt

此代码基于tensorflow 2.0 实现AutoInt。

引入必要的库

import tensorflow as tf
import numpy as np
import pandas as pd
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
import tensorflow.keras.backend as K
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder

import os
os.environ['CUDA_VISIABLE_DEVICES'] = '0'
%matplotlib inline

数据预处理

Criteo 数据集一共39个特征,1个label特征,如下图所示:以 l 开头的是数值型特征,以 C 开头的是类别型特征。

train = pd.read_csv('./criteo_sampled_data.csv')
cols = data.columns.values
train.head()

在这里插入图片描述

首先,我们对数据进行简单的处理。

定义特征组

dense_feats = [f for f in cols if f[0] == 'I']
sparse_feats = [f for f in cols if f[0] == 'C']

对于数值型特征,用 0 填充,然后进行log变换。

def process_dense_feats(data,feats):
    d  = data.copy()
    d = d[feats].fillna(0.0)
    for f in feats:
        d[f] = d[f].apply(lambda x: np.log(x+1) if x>-1 else -1)
    return d
data_dense = process_dense_feats(train, dense_feats)

对于类别型特征,用’-1’填充,然后进行类别编码。

def process_spares_feats(data,feats):
    d = data.copy()
    d = d[feats].fillna('-1')
    for f in feats:
        d[f] = LabelEncoder().fit_transform(d[f])
    return d
data_sparse = process_spares_feats(train,sparse_feats)

合并处理后的特征,加入label

total_data = pd.concat([data_dense,data_sparse],axis=1)
total_data['label'] = train['label']

模型的构建

embedding层

dense特征Embedding

k=8 # embedding 维度

# 构造输入
dense_inputs = []
for f in dense_feats:
    _input = Input([1], name=f)
    dense_inputs.append(_input)
# 对输入进行嵌入    
dense_kd_embed = []
for i, _input in enumerate(dense_inputs):
    f = dense_feats[i]
    embed = tf.Variable(tf.random.truncated_normal(shape=(1, k), stddev=0.01), name=f)
    
    scaled_embed = tf.expand_dims(_input * embed, axis=1)

    dense_kd_embed.append(scaled_embed)    
 
print(dense_kd_embed)

在这里插入图片描述

sparse特征Embedding

# 构造输入
sparse_inputs = []
for f in sparse_feats:
    _input = Input([1], name=f)
    sparse_inputs.append(_input)
    
# 对输入进行嵌入 
sparse_kd_embed = []
for i, _input in enumerate(sparse_inputs):
    f = sparse_feats[i]
    voc_size = data[f].nunique()
    _embed = Embedding(voc_size+1, k, embeddings_regularizer=tf.keras.regularizers.l2(0.5))(_input)
    sparse_kd_embed.append(_embed)
    
print(sparse_kd_embed)    

在这里插入图片描述

合并embedding层

input_embeds = dense_kd_embed + sparse_kd_embed
embed_map = Concatenate(axis=1)(input_embeds)  # ?, 39, 8

Interacting Layer

def auto_interacting(embed_map, d=6, n_attention_head=2):
    """
    实现单层 AutoInt Interacting Layer
    @param embed_map: 输入的embedding feature map, (?, n_feats, n_dim)
    @param d: Q,K,V映射后的维度
    @param n_attention_head: multi-head attention的个数
    """
    assert len(embed_map.shape) == 3, "Input embedding feature map must be 3-D tensor."
    
    k = embed_map.shape[-1]
    
    # 存储多个self-attention的结果
    attention_heads = []
    W_Q = []
    W_K = []
    W_V = []
    
    # 1.构建多个attention
    for i in range(n_attention_head):
        # 初始化W_Q, W_K, W_V
        W_Q.append(tf.Variable(tf.random.truncated_normal(shape=(k, d)), name="query_"+str(i)))  # k, d 8,6
        W_K.append(tf.Variable(tf.random.truncated_normal(shape=(k, d)), name="key_"+str(i)))  # k, d
        W_V.append(tf.Variable(tf.random.truncated_normal(shape=(k, d)), name="value_"+str(i)))  # k, d
     
    for i in range(n_attention_head):
        # 映射到d维空间
        embed_q = tf.matmul(embed_map, W_Q[i])  # ?, 39, d
        embed_k = tf.matmul(embed_map, W_K[i])  # ?, 39, d
        embed_v = tf.matmul(embed_map, W_V[i])  # ?, 39, d
    
        # 计算attention
        energy = tf.matmul(embed_q, tf.transpose(embed_k, [0, 2, 1]))  # ?, 39, 39
        attention = tf.nn.softmax(energy)  # ?, 39, 39
    
        attention_output = tf.matmul(attention, embed_v)  # ?, 39, d
        attention_heads.append(attention_output)
    
    # 2.concat multi head
    multi_attention_output = Concatenate(axis=-1)(attention_heads)  # ?, 39, n_attention_head*d
    
    # 3.ResNet
    w_res = tf.Variable(tf.random.truncated_normal(shape=(k, d*n_attention_head)), name="w_res_"+str(i))  # k, d*n_attention_head
    output = Activation("relu")(multi_attention_output + tf.matmul(embed_map, w_res))  # ?, 39, d*n_attention_head)
    
    return output

通过循环建立多个交互层

def build_autoint(x0, n_layers):
    xl = x0
    for i in range(n_layers):
        xl = auto_interacting(xl, d=6, n_attention_head=2)
    
    return xl

调用

# 构建3层interacting layer
autoint_layer = build_autoint(embed_map, 3)

展开返回的结果:

autoint_layer = Flatten()(autoint_layer)

输出层

output_layer = Dense(1, activation="sigmoid")(autoint_layer)

模型的编译

# 声明模型:指定输入和输出
model = Model(dense_inputs+sparse_inputs, output_layer)

# 模型的编译,指定loss、优化器、评估指标
model.compile(optimizer="adam", 
              loss="binary_crossentropy", 
              metrics=["binary_crossentropy", tf.keras.metrics.AUC(name='auc')])

模型的训练与验证

切分训练集和验证集

train_data = total_data.loc[:500000-1]
valid_data = total_data.loc[500000:]

train_dense_x = [train_data[f].values for f in dense_feats]
train_sparse_x = [train_data[f].values for f in sparse_feats]

train_label = [train_data['label'].values]

val_dense_x = [valid_data[f].values for f in dense_feats]
val_sparse_x = [valid_data[f].values for f in sparse_feats]
val_label = [valid_data['label'].values]

此处注意模型的输入与输出的格式:

在这里插入图片描述

在这里插入图片描述

将数据装入list容器。

模型的训练与验证

model.fit(train_dense_x+train_sparse_x, 
          train_label, epochs=1, batch_size=256,
          validation_data=(val_dense_x+val_sparse_x, val_label), 
         )

deepctr库实现AutoInt

使用库之前,要先进行安装

!pip install deepctr

引入必要的库

import pandas as pd
import tensorflow as tf
from sklearn.metrics import log_loss,roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder,MinMaxScaler
from deepctr.models import AutoInt
from deepctr.feature_column import SparseFeat,DenseFeat,get_feature_names
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0'

读取并处理数据

data = pd.read_csv('criteo_sampled_data.csv')
sparse_features = ['C' + str(i) for i in range(1, 27)]
dense_features = ['I' + str(i) for i in range(1, 14)]
data[sparse_features] = data[sparse_features].fillna('-1', )
data[dense_features] = data[dense_features].fillna(0, )
target = ['label']

对sparse_features进行LabelEncoder,对dense_features进行归一化

for f in sparse_features:
    data[f] = LabelEncoder().fit_transform(data[f])
data[dense_features] = MinMaxScaler(feature_range=(0,1)).fit_transform(data[dense_features])    

对特征进行嵌入


fixlen_feature_columns = [SparseFeat(feat,data[feat].nunique(),embedding_dim=4) for feat in sparse_features] + \
                         [DenseFeat(feat,1,) for feat in dense_features]
    
linear_feature_columns = fixlen_feature_columns
dnn_feature_columns = fixlen_feature_columns
fixlen_feature_names = get_feature_names(fixlen_feature_columns)

构造输入特征,有字典形式和list包裹形式:

# 方式一
train, test = train_test_split(data, test_size=0.2)
train_model_input = [train[name].values for name in fixlen_feature_names]
test_model_input = [test[name].values for name in fixlen_feature_names]

# 方式二
train, test = train_test_split(data, test_size=0.2)
train_model_input = {name: train[name].values for name in fixlen_feature_names}
test_model_input = {name: test[name].values for name in fixlen_feature_names}

模型的声明和编译

model = AutoInt(linear_feature_columns,dnn_feature_columns,att_layer_num=3, att_embedding_size=8, att_head_num=2)
model.compile('adam','binary_crossentropy',metrics=[tf.keras.metrics.AUC(name='auc')])


模型的训练

history = model.fit(train_model_input,train[target].values,batch_size=256, epochs=1,
                   validation_data = (test_model_input,test[target].values)
                   
                   )

模型的预测

pred_ans = model.predict(test_model_input, batch_size=256)
print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))

完整代码

参考:

AutoInt:进行自动特征学习的CTR模型

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值