首先,“神经网络” 这个词来源于脑科学,但其实目前计算机中的神经网络跟脑科学中的神经网络没太大关系,只能算是受其启发,个人感觉有点像飞机和鸟的关系。
从线性模型说起
先从最简单的线性模型说起,高中我们学过的线性回归就是线性模型,求解线性回归模型最经典的解法是最小二乘法。
写成向量的形式:
回想高中学过的内容,通过会给我们一堆二维点的坐标值,需要找到一条直线来拟合这一堆点,如下图所示
二维情况我们需要求解的 w 向量其实只有一个值,对应的拟合函数 f(x) 就是一条直线。
若将上述示例扩展一下:将给定的二维点扩展为高维空间的点。我们要找的拟合函数就变成了一个超平面,但求解方法还是一样的。
感知机(Perceptron)模型
在学习神经网络的时候,还有一个经常听到的词汇叫做 “感知机(Perceptron)”,感知机模型通常喜欢用下面的图来表示:
上面我们提到的线性模型,通常用来做回归,拟合数据点。而感知机模型通常用来做二分类,其数学描述为:
可以看到,感知机模型是在线性模型的基础上再套用了一个函数 f,函数 f 是符号函数,其数学形式为:
结果为 +1 时,表示正类,-1 表示 负类。感知机模型的思想可以归纳为:找到一个线性超平面,可以将高维空间的两类点完美的分开。
很明显,感知机模型的缺点是:数据集必须是线性可分的。例如下面这样的数据集用感知机模型是无法准确分类的。
非线性模型 - 逻辑回归(Logistic Regression,LR)
线性模型不能解决线性不可分的问题,为了解决这个问题,很自然的,需要引入非线性模型。
最简单的非线性模型就是逻辑回归了。逻辑回归这个名字很容易让人迷惑,误以为是用来处理回归问题的,事实上,逻辑回归模型更多的是用来处理分类问题。
逻辑回归是数学表达为:
仔细一看,跟上面提到的感知机模型的数学描述一样。是的,目前看就是一样的,都是在线性模型的基础上套一个函数 f,区别也就在函数 f 的选取上,感知机模型使用符号函数 sign 作为 f,逻辑回归则使用的是鼎鼎大名的 sigmoid 函数。
嗯,sigmoid 函数很有名,在机器学习界应该算家喻户晓,它的形状是一个 S 形,如下:
sigmoid 函数的数学公式为:
sigmoid 函数的厉害之处在于:它的值域是 (0, 1)。对于任意大小的输入,它都可以将其压缩至 (0, 1) 之间,更好玩的是,它是关于 (0, 0.5) 这个点中心对称的。所以做二分类时,当逻辑回归的输出值大于 0.5 的时候,判断为正类;输出值小于 0.5 的时候,判断为负类。这个输出值也可以作为类别判断的概率值来使用。
LR 在推荐领域应用非常广泛,最常用来做 CTR 预估,可以算是机器学习中最经典的模型之一。
但是,读到这里的读者其实会发现,在同样的参数 w, b 下,LR 和感知机模型的分类结果其实是一样的,只是输出的数值不一样。为什么 LR 应用这么广泛,而感知机模型几乎没有实际应用呢?
关于这个问题,我的理解是 LR 的输出更加直观,直接可以作为分类概率值使用,适合用来做排序和调整分类的概率阈值。
另外还有一个问题:LR 模型本质上也可以看做线性模型,对于线性不可分的数据,它其实也是无能为力的,为何它还能得到广泛的应用?
这个问题的答案是:既然 LR 模型无法处理线性不可分数据,那我们就将线性不可分数据做一个预处理,使其变成线性可分的数据。听起来比较玄乎,其实这个操作有一个专有名词,叫做 “特征交叉”, 做推荐的朋友肯定不会陌生,“特征交叉” 的过程实际就是将数据从线性不可分转为线性可分的过程。类似的还有一个例子就是:核函数在 SVM 中应用。SVM 本质上也是线性模型,但其可以通过核函数映射,将低维线性不可分数据转为高维线性可分数据,从而达到分类线性不可分数据的目的。
所以,总结一下,LR 虽然通过 sigmoid 函数引入了非线性,但事实上它还是不能自动处理线性不可分数据,必须通过人工的特征交叉来解决。那有没有一种方法,可以不需要人工干预也能够处理线性不可分数据呢?
答案必然是有的,也就是下面讲的:神经网络。
神经网络
首先说结论,神经网络毫无疑问是一个强大的非线性模型,只要参数够多,可以拟合任意函数。
先上图:
可以看到,神经网络主体分成三个部分:输入层(Input Layer),隐藏层(Hidden Layer),输出层(Output Layer)。每一层的基本单元是 “神经元(Neuron)”。
输入层就是输入数据层,隐藏层可以理解为是一个黑盒子,里面有很多参数,输出层就是输出数据层。
“神经元” 是什么呢?貌似生物里面学过,还有 “突触”、“神经递质” 等一系列吊吊哒的东西。不过,很可惜,这里的 “神经元” 更那些没什么关系,就像飞机发动机跟鸟类心脏没什么关系一样。
这里的 “神经元” 其实我们在前面见过,它就是 LR 换了个马甲的样子,嗯,所以皮肤很重要。下面是它的数学描述:
是的,又跟感知机和 LR 的数学描述一样。区别还是在套用的 f 函数上。现在呢,在神经网络中,这个 f 函数也有了一个专有名词,叫做 “激活函数(activation function)”,嗯,不是咱们公司的 “激活”。再看个图好理解:
每个神经元的输出可以理解成 LR 的输出,所以其实也可以先把神经网络粗暴的理解成很多个 LR 的组合体。
每一层的输出又作为下一层输入,堆叠很多这样的层,就叫做深度神经网络了,研究这些深度神经网络的学问又叫深度学习了。那有没有浅度学习呢,答案是没有的,一般 3, 4 层网络就号称深度网络了。
当然,神经网络毕竟发展了这么多年,也不能简单的用 LR 组合体来解释,其中还是有不少细微差别的。下面一一道来。
激活函数
首先是激活函数,早期的神经网络最常用的激活是 sigmoid 和 tahn 函数(用 sigmoid 的话就是 LR 组合体了)。它俩的形状都是 S 形,值域一个是 (0, 1),一个是 (-1, 1),差不多,都起到了引入非线性和压缩输入范围的效果。
但事实上,S 形的激活函数都有一个问题,那就是容易造成梯度消失(至于什么是梯度消失,需要先理解反向传播、梯度下降的概念,这个以后再讲,或者读者自行查资料,不过这里可以先记住结论,就是 sigmoid 和 tahn 作为激活函数都有大问题)。
机器学习界的扛把子 Hinton 老爷子受到生物学研究的启发,于 2010 年提出了一种全新的激活函数,叫做 ReLU(Rectified Linear Units,线性整流单元),效果拔群,远超 sigmoid 和 tahn,又由 12 年的 AlexNet 发扬光大,目前已经成为神经网络的标配(标配的意思是,还有其他配置,例如 leaky_relu,PReLU,ELU,Maxout 等,不过还是 ReLU 应用最广泛)。
ReLU 的神奇之处在于,它已经被证明很好、很强大,但它的数学公式可以算极简,如下:
ReLU 函数图像如下:
极简,极好。
ReLU 解决了 sigmoid/tahn 函数的梯度消失的问题,但较真的话,ReLU 也有一个问题,就是它在小于 0 那一段是没有梯度的,会导致神经元 “死掉”,事实上,实际证明这个问题不大,可能的原因是神经元可能处于 “死死活活” 的状态,这次是输入它死了,下次的输入它又复活了。
至于为什么 ReLU 这么简单,效果还这么好?嗯,这方面研究很多,我比较认可的解释是:咱生物身上的激活函数就是这么工作的。
下面列几个常见的激活函数,有兴趣的同学可以自行搜索研究。
Batch Normalization
Batch Normalization,简称 BN,目前跟 ReLU 一样,也算是神经网络的一个标配(RNN 除外,RNN 不适合用 BN,不过有类似的 Layer Normalization),加上 BN 之后,一般能够使得网络收敛速度更快,效果更好。
BN 也跟 ReLU 一样,有个特点:简单,强大。
BN 深层次的思想暂且不提,有兴趣的读者可以看原始论文(强烈建议看一看,Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift),BN 的简单思想就是将每一层神经网络的输入做一次归一化,使其均值为 0,方差为 1,这样可以最大限度的减小深层网络与线层网络之间的耦合,使得训练真正的深度网络成为可能(例如训练千层饼网络 ResNet-1202)。
另外 Batch Normalization 中的 Batch 指的是在 Batch 维度做归一化,属于纵向归一化,而 Layer Normalization 则是横向归一化,不依赖 batch 维度。这里不展开细讲,有兴趣的同学可以自己搜索相关文档深入。
需要注意的是,BN 的使用场景是神经网络输入数据的 batch 不能太小,太小的话效果可能会很差。还有就是需要注意 BN 在 train 和 inference 阶段的不同,在研究 BN 原理的时候需要搞懂这些。
BN 的另一个好处是使得 sigmoid 和 tahn 这种存在梯度饱和区的激活函数也可以用在超深神经网络中,虽然效果没有 ReLU 好 😃,但不加 BN 的话,用 sigmoid 和 tahn 训练超深网络根本没法收敛。
最后,虽然 BN 应用广泛,但受其启发,学术界还是提出了很多其他的 Normalization 方法,比如 Instance Normalizaion,Weight Normalizaion,RNN 可用的 Layer Normalization,CNN 专用的 Group Normalization,有兴趣的可以研究看看。最后,放一张图,一图胜万言:
图片来自于论文 Group Normalization。
Dropout
经典正则化方法有 L1, L2 正则,这里不讲,神经网络中还有一种经典的正则化方法,就是 Dropout,也是扛把子 Hinton 提出来的。特点么,也是一个:简单,强大。
Dropout 一般用于全连接层(Fully Connected Layer),在 CNN 中用的少,RNN 中的 Dropout 需要特殊处理,不展开。
下面以全连接层的 Dropout 为例,如图所示:
思想就是,在前向传播的过程中随机 drop 掉一些连接,注意,重点是随机。
另外,dropout 很容易让人忽视的一点是其在 train 和 test 阶段的不同,假设 dropout 保留的神经元比例是 p,如果在 train 阶段不做任何处理,那么在 inference 阶段输出就要乘上 p,如下图所示:
当然,很多深度学习框架会选择在 train 的时候乘上 1/p,在 inference 阶段不做处理,目的是减少 inference 时间,TensorFlow 就是这样处理的。
对 dropout 有效的解释也很多,有一种是,每次随机 dropout 一些连接,使得每次的网络结构不一样,相当同时在训练很多不同的网络,最后 inference 的结果是这些网络做 ensemble 的结果;另一种是,随机 dropout 一些连接,可以减小同一层神经元之间耦合度,dropout 逼迫每个神经元都只能依赖自己,大大减小了互相耦合造成的过拟合情况。
有兴趣可以看看 dropout 原始论文:Dropout: A Simple Way to Prevent Neural Networks from Overfitting
权重初始化
不同于简单的 LR 模型,神经网络参数众多,损失函数平面存在非常多的局部最优点,所以参数初始化是非常重要的(不过随着 BN 的出世,神经网络对初始化参数的敏感程度大大降低了)。
常用的有两种权重初始化方法: Glorot/Xavier 初始化和 MSRA 初始化。
Glorot/Xavier 初始化针对的是激活函数为 sigmoid 的情况,MSRA 初始化则针对激活函数为 ReLU 的情况做了优化。
TensorFlow 中变量默认的初始化方法是 Glorot/Xavier 初始化。
Glorot/Xavier 初始化方法是将权重按均值为 0,方差为
的分布进行初始化,其中 n_in 为输入神经元个数,n_out 是输出神经元的个数。这个公式是基于 sigmoid 激活函数推导出来的。
MSRA 初始化针对 ReLU 激活函数做了修正,只需要将 Glorot/Xavier 初始化方差乘以 2 就可以了。
有兴趣可以看看原始论文:
Xavier Initialization: Understanding the difficulty of training deep feedforward neural networks
MSRA Initialization: Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification
总结
从线性模型到神经网络,我们发现其实神经网络可以近似简单粗暴的理解为多个 LR 的组合体。后面我们继续探索了神经网络更深入更细节的东西,包含激活函数、Batch Normalization、dropout、权重初始化。每个点其实还可以更深入挖掘,限于篇幅,这里不展开,有兴趣可以自行探索。
最后给一个很好玩的网站:
http://playground.tensorflow.org
试试加多少层、多少神经元可以完美分开各个数据集,enjoy!