DeepCrossing是在AutoRec之后,微软完整的将深度学习应用在推荐系统的模型。其应用场景是搜索推荐广告中,解决了特征工程,稀疏向量稠密化,多层神经网路的优化拟合等问题。所使用的特征在论文中描述为两个大类数值型(文中couting feature)和类别型。如下图

对于数值型特征可以直接拼接在Embedding向量之后,类别多的特征需要经过Embedding过程。要多说一句,数值的统计特征包括了过去广告点击率,这个在以后实际应用中设计特征可以考虑。

其优化目标就是广告的点击率,即CTR,click through rate。其效果可以看论文的实现对比部分。这里简单介绍,

  1. 与传统模型DSSM进行对比;
  2. 与线上生产环境的模型进行对比;
  3. counting feature的重要性对比。

2. 算法架构

网络架构解决的问题是:

  • 离散特征过于稀疏的高维灾难问题;
  • 特征交叉自动组合问题;
  • 输出层中如何优化目标的设计问题。

网络架构图



总共包含Embedding,Stacking,Multiple ResidualUnits和Scoring 层

下面根据网络结构图分别说明各个模块的作用。

Embedding层

本层主要作用是降维。使用的是一个单层神经网路,具有如下形式,



针对每个类别的特征都有一个Embedding操作,但是如果由于高维基数特征太大了,对于目标相关部分排序较低的进行衍生构造。也能降低Embedding部分的参数数量提高训练速度例如,CampaignID十分巨大,但对于点击率排序后10000以外的使用衍生特征来处理,最后一个编号为10000,且添加衍生为将所有ID对应的历史点击率组合成10001维的稠密矩阵,各个元素分别为对应ID的历史CTR,最后一个元素为剩余ID的平均CTR。通过降维引入衍生特征的方式,可以有效的减少高基数特征带来的参数量剧增问题。

其中,每个特征的维度压缩到256维,如果小于256维则直接连接到Stacking层。

Stacking层

主要是将Embedding部分的各个特征的向量进行拼接,小于256维度或者数值型特征不需要Embedding的直接拼接(如Feature #2)。

得到\(X^O=[X^O_0, X^O_1,...,X^O_k]\)的拼接向量。

Residual Layers

首先是残差单元结构为:

这个残差模块与ResNet的不同是没有使用卷积操作,而是ReLu与线性部分的前向传播加(element-wise add)上输入再经过ReLu得到输出。



作者通过各种类型各种大小的实验发现,DeepCrossing具有很好的鲁棒性,推测可能是因为残差结构能起到类似于正则的效果,残差结构能更敏感的捕获输入输出之间的信息差 ,引入特征的交叉和非线性。

残差网络解决的问题:

  • 网络深度增加后,过拟合,通过残差网络的短路操作,起到正则化的作用,减少过拟合;
  • 网络深度增加后,梯度消失,所以使用ReLu激活函数,且短路操作相当于将上上层的梯度传递到下层,收敛更快。

原结构使用了五个残差块,每个残差块的维度是512,512,256,128,64。

Scoring Layer

计算得分,即目标函数(objective function)的应用层。

二分类使用Sigmoid函数,多分类使用softmax函数。

3. 代码实现

基于TensorFlow2.0 和Keras API来实现模型结构。

根据上节每个模块,需要分别实现各个模型的结构,然后组合在一个即可。(原始论文的部分使用的CNTK实现且GPU加速,获得了效率的显著提高)

导包

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from sklearn.model_selection import train_test_split
import gc

Embedding模块

这里自己实现,不使用tf自带的embedding。

class EmbeddingBlock(keras.layers.Layer):
def __init__(self, emb_dim, input_shapes):
super(EmbeddingBlock, self).__init__()
self.input_shapes = input_shapes
self.listlayer = []
for shape in self.input_shapes:
self.listlayer.append(keras.layers.Dense(emb_dim, input_shape=(shape, ), activation='relu')) def call(self, X):
stacking = []
last_col = 0
for idx, shape in enumerate(self.input_shapes): # 离散值的onehot维度部分
stacking.append(self.listlayer[idx](X[:, last_col:last_col+shape]))
last_col += shape
stacking.append(X[:, last_col:]) # 连续值
X = tf.concat(stacking, axis=1)
return X

这里主要是将输入X的前一部分作为需要embedding的部分,后部分作为不需要embedding的部分,然后并行运算,并最后连接在一起。

定义残差层

这里分为两个模块分别定义,没有使用函数,而是直接继承Keras的API。

class Residual(keras.models.Model):
def __init__(self,hidden_units=None, feature_dim=None) -> None:
super(Residual, self).__init__()
self.relu_layer = keras.layers.Dense(units=hidden_units, input_shape=(feature_dim,), activation='relu')
self.linear_layer = keras.layers.Dense(units=feature_dim, input_shape=(hidden_units,)) # 为了后续相加,要回归原来的维度 def call(self, X):
X1 = self.relu_layer(X)
X2 = self.linear_layer(X1)
y = keras.activations.relu(tf.add(X, X2)) # or tf.nn.relu, X+X2
return y class ResidualLayer(keras.layers.Layer):
def __init__(self, units_list=None, feature_dim=None) -> None:
super(ResidualLayer, self).__init__()
self.listlayer = []
for unit in units_list:
self.listlayer.append(Residual(unit, feature_dim)) def call(self, X):
for layer in self.listlayer:
X = layer(X)
return X

串联整个模型DeepCrossing

class DeepCrossing(keras.models.Model):
def __init__(self, emb_dim, emb_shapes, residual_units, feature_dims) -> None:
super().__init__()
self.emb = EmbeddingBlock(emb_dim=emb_dim, input_shapes=emb_shapes)
self.stacking_dim = emb_dim*len(emb_shapes) + feature_dims - np.array(emb_shapes).sum()
self.residual_layer = ResidualLayer(residual_units, self.stacking_dim)
self.score_layer = keras.layers.Dense(units=1, input_shape=(self.stacking_dim,), activation='sigmoid') def call(self, X):
X = self.emb(X)
X = self.residual_layer(X)
X = self.score_layer(X)
return X

4. 数据验证

说个小插曲,使用的数据是MovieLens,在train_test_split的时候会有一个报错 大概是MemoryError的问题,因为使用的列比较多。后来就抽取了一千条数据来验证模型。估计使用迭代器和tf.data的生成器会比较好操作。

合并数据

rating = pd.read_csv('./ratings.dat', sep='::', names=['UserID', 'MovieID', 'Rating', 'Timestamp'])
user = pd.read_csv('./users.dat', sep='::', names=['UserID', 'Gender', 'Age', 'Occupation', 'ZipCode'])
movie = pd.read_csv('./movies.dat', sep='::', names=['MovieID', 'Title', 'Genres']) data = pd.merge(left=rating, right=user, how='inner', on='UserID')
data = data.merge(movie, on='MovieID')

构造标签

为了保证正负样本相对平衡,契合评分层的二分类模型,这里直接将3分以上的认为是正样本(也可以定义为多分类 使用softmax层作为评分层)。

data['label'] = (data['Rating'] > 3).astype(np.int)

处理数据

把电影名字的时间抽取出来

data['Year'] = data['Title'].apply(lambda x: x[-5:-1]).astype(int)
data['Title'] = data['Title'].apply(lambda x: x[:-7])

为了方便,不使用Title作为特征(否则使用Token然后Embedding处理也是很好的)。

统计各个特征数量,以便确定谁要Embedding层:

tmp = data.copy()
for col in ['Gender', 'Occupation', 'ZipCode', 'Title', 'Genres']:
print(col, tmp[col].unique().shape[0])
=============================================
Gender 2
Occupation 21
ZipCode 3439
Title 3664
Genres 301

oenhot处理并合并:

dummy_col = ['ZipCode', 'Genres', 'Gender', 'Occupation']
tmp1 = pd.get_dummies(tmp[dummy_col],
prefix=dummy_col,
columns=dummy_col)
resDF = pd.concat([tmp1, tmp[[ 'Age', 'Year','Timestamp','UserID', 'MovieID', 'label']] ], axis=1)

构造Dataset

X = resDF.iloc[:1000,:-3]
y = resDF.iloc[:1000, -1]
num_or_size_splits = [int(y.shape[0]*0.9), int(y.shape[0]*0.1 + 0.5)]
num_or_size_splits # [900, 100] X = tf.constant(X.values, dtype=tf.float32)
y = tf.constant(y.to_list(), dtype=tf.float32)
X_train, X_test = tf.split(X, num_or_size_splits, axis=0)
y_train, y_test = tf.split(y, num_or_size_splits, axis=0) BATCH = 128
train_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train)).batch(BATCH).shuffle(2).repeat()
test_ds = tf.data.Dataset.from_tensor_slices((X_test, y_test)).batch(32)

训练模型

net = DeepCrossing(emb_dim=128,
emb_shapes=[3439, 301],
residual_units=[256, 128, 64],
feature_dims=len(resDF.columns)-3)
net.compile(loss='binary_crossentropy',
optimizer=keras.optimizers.Adam(lr=0.01),
metrics=['accuracy']) net.fit(train_ds, epochs=5, steps_per_epoch=X.shape[0]//BATCH)
Train for 7 steps
Epoch 1/5
7/7 [==============================] - 5s 690ms/step - loss: 160474554.2098 - accuracy: 0.6192
Epoch 2/5
7/7 [==============================] - 0s 7ms/step - loss: 30519627.1429 - accuracy: 0.7679
Epoch 3/5
7/7 [==============================] - 0s 8ms/step - loss: 6237030.3564 - accuracy: 0.8692
Epoch 4/5
7/7 [==============================] - 0s 7ms/step - loss: 6711226.7366 - accuracy: 0.7461
Epoch 5/5
7/7 [==============================] - 0s 7ms/step - loss: 3462408.0007 - accuracy: 0.7345

测试集验证:

loss, acc = net.evaluate(test_ds)
print('loss: ', loss, ' acc: ', acc) =================================
loss: 1159263.28125 acc: 0.93

4. 小结

Deep Crossing模型没有引入现代流行的注意力机制,序列模型的特殊结构,但是相比FM,FFM模型只具备二阶特征交叉能力来说,这模型可以更深层次的交叉,且独立特征之外,没有人工设计的组合特征。

推荐模型DeepCrossing: 原理介绍与TensorFlow2.0实现的更多相关文章

  1. 推荐模型AutoRec:原理介绍与TensorFlow2.0实现

    1. 简介 本篇文章先简单介绍论文思路,然后使用Tensoflow2.0.Keras API复现算法部分.包括: 自定义模型 自定义损失函数 自定义评价指标RMSE 就题目而言<AutoRec: ...

  2. 推荐模型NeuralCF:原理介绍与TensorFlow2.0实现

    1. 简介 NCF是协同过滤在神经网络上的实现--神经网络协同过滤.由新加坡国立大学与2017年提出. 我们知道,在协同过滤的基础上发展来的矩阵分解取得了巨大的成就,但是矩阵分解得到低维隐向量求内积是 ...

  3. 一文上手TensorFlow2.0(一)

    目录: Tensorflow2.0 介绍 Tensorflow 常见基本概念 从1.x 到2.0 的变化 Tensorflow2.0 的架构 Tensorflow2.0 的安装(CPU和GPU) Te ...

  4. TensorFlow模型部署到服务器---TensorFlow2.0

    前言 ​ 当一个TensorFlow模型训练出来的时候,为了投入到实际应用,所以就需要部署到服务器上.由于我本次所做的项目是一个javaweb的图像识别项目.所有我就想去寻找一下java调用Tenso ...

  5. 『TensorFlow2.0正式版教程』极简安装TF2.0正式版(CPU&GPU)教程

    0 前言 TensorFlow 2.0,今天凌晨,正式放出了2.0版本. 不少网友表示,TensorFlow 2.0比PyTorch更好用,已经准备全面转向这个新升级的深度学习框架了. ​ 本篇文章就 ...

  6. Google工程师亲授 Tensorflow2.0-入门到进阶

    第1章 Tensorfow简介与环境搭建 本门课程的入门章节,简要介绍了tensorflow是什么,详细介绍了Tensorflow历史版本变迁以及tensorflow的架构和强大特性.并在Tensor ...

  7. 04 MapReduce原理介绍

    大数据实战(上) # MapReduce原理介绍 大纲: * Mapreduce介绍 * MapReduce2运行原理 * shuffle及排序    定义 * Mapreduce 最早是由googl ...

  8. [转]MySQL主从复制原理介绍

    MySQL主从复制原理介绍 一.复制的原理 MySQL 复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新.删除等等).每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的更新,以 ...

  9. ThinkPHP 的模型使用详细介绍--模型的核心(七)

    原文:ThinkPHP 的模型使用详细介绍--模型的核心(七) 注意:本节是ThinkPhp框架对数据操作的核心处理部分 大家还是在这里看清楚可以将其剪切放到代码编辑器中查看 本章节给大家着重介绍模型 ...

随机推荐

  1. 多线程之ThreadLocal类

    深入研究java.lang.ThreadLocal类 0.前言 ThreadLocal(线程变量副本)Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量.采用空间换 ...

  2. 爬虫入门五 gooseeker

    title: 爬虫入门五 gooseeker date: 2020-03-16 16:00:00 categories: python tags: crawler gooseeker是一个简单的爬虫软 ...

  3. ysoserial Commons Collections3反序列化研究

    0x00 前言 在ysoserial中,官方是没给gadget,这儿经过文章分析我认为的gadget,继承自AbstractTranslate的类被Javassist插桩后返回一个被修改过的templ ...

  4. linux下新建用户

    新建用户的两种方式: 一步步创建 useradd -m user1 #-m 是建立家目录 passwd user1 #设置密码 usermod -a -G root user1 #加入管理员 chsh ...

  5. js & document.designMode

    js & document.designMode js 一键开启页面编辑模式 var mode = document.designMode; document.designMode = val ...

  6. vue 的 computed 属性在什么时间执行

    vue 的 computed 属性在什么时间执行

  7. CVS、SVN、Git、GitHub :版本控制系统

    1 1 1 Git常用命令 1 1 1 1 1 1 https://www.codecademy.com/learn/learn-git Learn Git You have now been int ...

  8. React Hooks: useEffect All In One

    React Hooks: useEffect All In One useEffect https://reactjs.org/docs/hooks-effect.html https://react ...

  9. VAST重磅出击,NGK网络搜索量超越ETH!

    Wechat指数中,NGK超越ETH,NGK搜索指数是157648点位,单日环比上涨11.95%,ETH搜索指数是115604点位,就连区块链标杆的BTC也仅仅只有171669点位,我们可清楚的看到N ...

  10. 市值达万亿?总量仅10万枚的VAST,先兑换先得!

    据了解,SPC第一轮.第二轮空投已经结束,两轮空投下来共发放了400万枚SPC.NGK所有算力持有者有效账户基本获得了SPC空投奖励,甚至有的NGK算力持有者获得了数千枚SPC. 而为了进一步奖励NG ...