[阿里DIN] 从论文源码梳理深度学习几个概念
目录
- [阿里DIN] 从论文源码梳理深度学习几个概念
- 0x00 摘要
- 0x01 全连接层
- 1.1 全连接层作用
- 1.2 CNN
- 1.3 RNN
- 1.4 DIN使用
- 0x02 prelu
- 2.1 激活函数作用
- 2.2 prelu 和 sigmoid 之间对比
- 2.3 DIN使用
- 0x03 Batch Normalization
- 3.1 DIN使用
- 3.2 BN层作用
- 3.3 BN思想
- 3.3.1 Internal Covariate Shift
- 3.3.2 基本思想
- 0xFF 参考s
0x00 摘要
本文基于阿里推荐 DIN 和 DIEN 代码,梳理了下深度学习一些概念,以及TensorFlow中的相关实现。
因为篇幅所限,所以之前的整体代码讲解中,很多细节没有深入,所以本文会就这些细节进行探讨,旨在帮助小伙伴们详细了解每一的步骤以及为什么要这样做。
涉及概念有:全连接层,prelu,batch normalization等。
0x01 全连接层
1.1 全连接层作用
全连接层则起到将学到的 “分布式特征表示” 映射到 ”样本标签空间” 的作用。
全连接的核心操作就是矩阵向量乘积 y = Wx。本质就是由一个特征空间线性变换到另一个特征空间。目标空间的任一维——也就是隐层的一个 cell——都认为会受到源空间的每一维的影响。可以说,目标向量是源向量的加权和。
泰勒公式就是用多项式函数去拟合光滑函数。全连接层中一层的一个神经元就可以看成一个多项式,我们用许多神经元去拟合数据分布,但是只用一层 fully-connected layer 有时候没法解决非线性问题,而如果有两层或以上 fully-connected layer 就可以很好地解决非线性问题了。
1.2 CNN
全连接层之前的作用是提取特征,全连接层的作用是分类。
卷积取的是局部特征,全连接就是把以前的局部特征重新通过权值矩阵组装成完整的图。因为用到了所有的局部特征,所以叫全连接。
我们现在的任务是去区别一图片是不是猫:
在CNN中,全连接层把特征representation整合到一起,输出为一个值。这样做可以大大减少特征位置对分类带来的影响。 比如识别一个图片中的猫,猫在不同的位置,输出的 feature 值相同,但是位置不同。对于电脑来说,特征值相同,但是特征值位置不同,那分类结果也可能不一样,而这时全连接层 filter 的作用就相当于 喵在哪我不管 我只要喵 于是我让filter去把这个喵找到,实际就是把 feature map 整合成一个值:这个值大,哦,有喵;这个值小,那就可能没喵,和这个喵在哪关系不大了有没有,鲁棒性有大大增强了有没有
最后总结就是,卷积神经网络中全连接层的设计,属于人们在传统特征提取 分类思维下的一种"迁移学习"思想,但在这种end-to-end的模型中,其用于分类的功能其实是被弱化了,而全连接层参数过多的缺点也激励着人们设计出更好的模型替代之达到更好的效果。同时,也将促进我们更深入地探讨其中的奥秘。
1.3 RNN
为什么模型最后需要加入全连接层?
从模型拟合能力上说,不是必须的;从模型设计角度上说,是常见的。因为假如用RNN做一维时序数据的回归任务,RNN的隐元包含的是一些时序状态的信息,主要是在time上的拟合能力,与问题定义的输出之间通常具有比较明显的gap,和任务想要获得的输出之间还存在一个映射,最后的fc就是用来完成这道任务的。
1.4 DIN使用
DIN中,大量使用了FCN,比如:
代码语言:javascript复制query = tf.layers.dense(query, facts_size, activation=None, name='f1' stag)
query = prelu(query)
0x02 prelu
2.1 激活函数作用
首先我们要明白激活函数的作用是:增加模型的非线性表达能力。
深度学习的目的是用一堆神经元堆出一个函数大致的样子,然后通过大量的数据去反向拟合出这个函数的各个参数,最终勾勒出函数的完整形状。
那如果激活函数只是线性函数,那一层层的线性函数堆起来还是线性的,这年头线性函数能干啥呀?
肯定不行,这样整个网络表现能力有限,所以要引入非线性的激活函数进来。
就是铅笔不够画的,咱得上带颜色、笔触更丰富的油画笔。
2.2 prelu 和 sigmoid 之间对比
具体 prelu 和 sigmoid 之间对比如下。
- 首先,sigmoid 有一个“梯度消失”的问题。
- 梯度消失什么意思呢?就是我们希望对每个神经元,激励函数都能尽量区分出z值变化,这样每个神经元的表达能力更强,但sigmoid明显在|z|>4的区间的梯度就不够看了,即它的梯度消失了。
- 相比之下,ReLU输出就很稳定,因为他z>0区间就是一个线性函数!不存在sigmoid的梯度消失的问题。
- 其次,另一个ReLU给力的地方就是稀疏度问题。
- 我们希望每个神经元都能最大化的发挥它筛选的作用,符合某一个特征的中间值,使劲儿放大;不符合的,一刀切掉。
- 反观sigmoid就要黏糊的多。这个函数是很对称很美,但它面对负的z值仍然剪不断理还乱,会输出一个小的激励值(tanh会好一些但仍不能避免),形成所谓的“稠密表示”。
- 最后,ReLU运算速度快,max肯定比幂指数快的多。
2.3 DIN使用
DIN中使用如下,可以看到,在全连接函数中,Batch Normalization 之后就接入了激活函数:
代码语言:javascript复制def prelu(_x, scope=''):
"""parametric ReLU activation"""
with tf.variable_scope(name_or_scope=scope, default_name="prelu"):
_alpha = tf.get_variable("prelu_" scope, shape=_x.get_shape()[-1],
dtype=_x.dtype,
initializer=tf.constant_initializer(0.1))
return tf.maximum(0.0, _x) _alpha * tf.minimum(0.0, _x)
def build_fcn_net(self, inp, use_dice = False):
bn1 = tf.layers.batch_normalization(inputs=inp, name='bn1')
dnn1 = tf.layers.dense(bn1, 200, activation=None, name='f1')
if use_dice:
dnn1 = dice(dnn1, name='dice_1')
else:
dnn1 = prelu(dnn1, 'prelu1')
0x03 Batch Normalization
3.1 DIN使用
在DIN中,全连接层构建中,上来就进行了Batch Normalization。
代码语言:javascript复制def build_fcn_net(self, inp, use_dice = False):
bn1 = tf.layers.batch_normalization(inputs=inp, name='bn1')
dnn1 = tf.layers.dense(bn1, 200, activation=None, name='f1')
if use_dice:
dnn1 = dice(dnn1, name='dice_1')
else:
dnn1 = prelu(dnn1, 'prelu1')
所以我们要看看 Batch Normalization 究竟起到什么作用。
3.2 BN层作用
BN层的作用主要有三个:
- 加快网络的训练和收敛的速度;
- 控制梯度爆炸防止梯度消失;
- 防止过拟合;
3.3 BN思想
机器学习领域有个很重要的假设:IID独立同分布假设,就是假设训练数据和测试数据是满足相同分布的,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障。BatchNorm就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布的。
3.3.1 Internal Covariate Shift
“Internal Covariate Shift”问题是指:对于深度学习这种包含很多隐层的网络结构,在训练过程中,因为各层参数老在变,所以每个隐层都会面临covariate shift的问题。Internal指的是深层网络的隐层,是发生在网络内部的事情,而不是covariate shift问题只发生在输入层。
因此,就有了Batch Normalization的基本思想:能不能让每个隐层节点的激活输入分布固定下来呢?这样就避免了“Internal Covariate Shift”问题了。
BN不是凭空拍脑袋拍出来的好点子,它是有启发来源的:之前的研究表明如果在图像处理中对输入图像进行白化(Whiten)操作的话——所谓白化,就是对输入数据分布变换到0均值,单位方差的正态分布——那么神经网络会较快收敛,那么BN作者就开始推论了:图像是深度神经网络的输入层,做白化能加快收敛,那么其实对于深度网络来说,其中某个隐层的神经元是下一层的输入,意思是其实深度神经网络的每一个隐层都是输入层,不过是相对下一层来说而已,那么能不能对每个隐层都做白化呢?这就是启发BN产生的原初想法,而BN也确实就是这么做的,可以理解为对深层神经网络每个隐层神经元的激活值做简化版本的白化操作。
3.3.2 基本思想
详细解释Batch Normalization的基本思想:对于每个隐层神经元,把逐渐向非线性函数映射后向取值区间极限饱和区靠拢的输入分布强制拉回到均值为0方差为1的比较标准的正态分布,使得非线性变换函数的输入值落入对输入比较敏感的区域,以此避免梯度消失问题。因为梯度一直都能保持比较大的状态,所以很明显对神经网络的参数调整效率比较高,就是变动大,就是说向损失函数最优值迈动的步子大,也就是说收敛地快。
有的读者一般会提出一个疑问:如果都通过BN,那么不就跟把非线性函数替换成线性函数效果相同了?这意味着什么?我们知道,如果是多层的线性函数变换其实这个深层是没有意义的,因为多层线性网络跟一层线性网络是等价的。这意味着网络的表达能力下降了,这也意味着深度的意义就没有了。
所以BN为了保证非线性的获得,对变换后的满足均值为0方差为1的x又进行了scale加上shift操作(y=scale*x shift),每个神经元增加了两个参数scale和shift参数,这两个参数是通过训练学习到的,意思是通过scale和shift把这个值从标准正态分布左移或者由移一点并长胖一点或者变瘦一点,每个实例挪动的程度不一样,这样等价于非线性函数的值从正中心周围的线性区往非线性区动了动。
核心思想是想找到一个线性和非线性的较好平衡点,既能享受非线性的较强表达能力的好处,又避免太靠非线性区两头使得网络收敛速度太慢。