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. C++ inline与operator

    title: C++ inline与operator date: 2020-03-10 categories: c++ tags: [c++] inline修饰符,operator关键字 1.inli ...

  2. 2019牛客多校第四场B xor(线性基求交)题解

    题意: 传送门 给\(n\)个集合,每个集合有一些数.给出\(m\)个询问,再给出\(l\)和\(r\)和一个数\(v\),问你任意的\(i \in[l,r]\)的集合,能不能找出子集异或为\(v\) ...

  3. 项目管理工具看板 All In One

    项目管理工具看板 All In One Trello https://trello.com/ 我们来总结一下 Trello 是 Atlassian 旗下公司,所有使用 Trello 的人都将使用 At ...

  4. windows10 浏览器跑分对比!

    2015-12-12 windows10 浏览器跑分对比! YOUR BROWSER SCORES!                MaxScore=555http://html5test.com/i ...

  5. webpack-cli bugs All In One

    webpack-cli bugs All In One Error: Cannot find module 'webpack-cli/bin/config-yargs' webpack version ...

  6. How to enable HTTPS for local development in macOS using Chrome

    How to enable HTTPS for local development in macOS using Chrome HTTPS, macOS, Chrome local HTTPS htt ...

  7. ES6 Set vs ES5 Array

    ES6 Set vs ES5 Array Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Ob ...

  8. UX & feedback & instant visual feedback

    UX & feedback & instant visual feedback Select an element on the page https://ant.design/com ...

  9. ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await

    ES Next & Arrow function & Promise & Iterator & Generator yield & Async Await co ...

  10. NGK公链全面服务旅游经济

    有数据显示,2019 年全球旅游总收入已达 6.5万亿美元, 占全球 GDP 的 7.3%,旅游业发展所创造的收益,于全球经济的重要性,不言而喻. 在旅游产业蓬勃发展的同时,中心化运营模式下却仍存在痛 ...