神经网络是由多个神经元组成,一个神经元由以下几个关键点组成:
1. 神经元的拟合原理
单个神经元的网络模型如下图所示。
z为输出的结果;x为输入;w为权重;b为偏执值。w和b可以理解为两个变量。
模型每次的学习都是为了调整w和b从而得到了一个合适的值,最终由这个值配合运算公式所形成的逻辑就是神经网络的模型。
其实这个模型是根据仿生学得来的。
我们把w和b赋予合适的值时,再配合合适的激活函数,就会发现它可以产生很好的拟合效果。
1. 正向传播
前文描述的过程叫做正向传播,数据是从输入到输出的流向传递过来的。当然,它是在一个假设有合适的w和b的基础上,才可以实现对现实环境的正确拟合。但是,在实际过程中我们无法得知w和b的值具体是多少才算是正常的。
于是我们加入了一个训练过程,通过反向误差的传递的方法让模型自动来修正,最终产生一个合适的权重。
2. 反向传播
反向传播的意义很明确——告诉我们需要将w和b调整到多少。在刚开始没有得到合适的权重时,正向传播生成的结果与实际的标签是有误差的,反向传播就是要把这个误差传递给权重,让权重做适当地调整来达到一个合适的输出。
在实际训练过程中,很难一次将其调整到位,而是通过多次迭代一点一点的将其修正,最终知道模型的输出值与实际标签值的误差小于某个阈值为止。
如何将输出的误差转化为权重的误差,这里面使用的就是BP算法。
BP算法又称“误差反向传播算法”。我们最终的目的,是要让正向传播的输出结果与标签间的误差最小化,这就是反向传播的核心思想。
正向传播的模型是清晰地,所以很容易得出一个关于由b和w组成的对于输出的表达式。接着,也可以得出一个描述损失值的表达式(将输出值与标签值直接相减,或是做平方差等运算)。
为了要让这个损失值变得最小化,我们运用数学知识,选择一个损失值的表达式让这个表达式有最小值,接着通过对其求导的方式,找到最小值时刻的函数切线斜率(也就是梯度),从而让w和b的值沿着这个梯度来调整。
至于每次调整多少,我们引入一个叫做“学习率”的参数来控制,这样通过不断的迭代,使误差逐步接近最小值,最终达到我们的目标。
2. 激活函数——加入非线性因素,解决线性模型缺陷
激活函数的主要作用就是用来加入非线性因素的,以解决线性模型表达能力不足的缺陷,在整个神经网络里起到至关重要的作用。
因为神经网络的数学基础是处处可微的,所以选取的激活函数要能保证数据输入和输出也是可微的。
在神经网络里常用的激活函数有Sigmoid、Tanh和relu等,下面逐一介绍。
1. Sigmoid函数
Sigmoid函数曲线中,x可以是正无穷到负无穷,但是对应的y却只有0~1的范围,所以,经过Sigmoid函数输出的函数都会落在0~1的区间里,即Sigmoid函数能够把输入的值“压缩”到0~1之间。
在TensorFlow中对应的函数为:
tf.nn.sigmoid(x, name=None)
从图像上看,随着x趋近正负无穷大,y对应的值越来越接近1或-1,这种情况叫做饱和。处于饱和态的激活函数意味着,当x=100和x=1000时的反应都是一样的,这样的特征转换相当于将1000大于100十倍这个信息給丢失了。
2. Tanh函数
Tanh函数可以说是Sigmoid函数的值域升级版,由Sigmoid函数的0~1之间升级到-1~1。但是Tanh函数也不能完全替代Sigmoid函数,在某些输出需要大于0的情况下,还是要用Sigmoid函数。
Tanh函数也是常用的非线性激活函数,其数学形式为
Tanh函数曲线,其x取值也是从正无穷到负无穷,对应的y值变为-1~1之间,相对于Sigmoid函数有更广的值域。
在TensorFlow中对应的函数
tf.nn.tanh(x, name=None)
显而易见,Tanh函数跟Sigmoid函数有一样的缺陷,也是饱和问题,所以在使用Tanh函数时,要注意输入值的绝对值不能过大,否则模型无法训练。
3. ReLU函数
除了前面介绍的Sigmoid函数和Tanh函数之外,还有一个更为常用的激活函数(也称为Rectifier)。其数学形式为
该式非常简单,大于0的留下,否则一律为0。ReLU函数应用的广泛性与它的优势是分不开的,这种对正向信号的重视,忽视了负向信号的特性,与我们人类神经元细胞对信号的反映极其相似。所以在神经网络中取得了很好的拟合效果。
另外由于ReLU函数运算简单,大大地提升了机器的运行效率,也是ReLU函数一个很大的优点。
与ReLU函数类似的还有Softplus函数。二者的区别在于:Softplus函数会更加平滑,但是计算量很大,而且对于小于0的值保留的相对更多一点。Softplus函数公式为。
虽然ReLU函数在信号响应上有很多优势,但这仅仅在正向传播方面。由于对其负值的全部舍去,因此很容易使模型输出全零从而无法再进行训练。
例如,随机初始化的w加入值中有个值是负值,其对应的正值输入值也就被全部屏蔽了,同理,对应负值输入值反而被激活了。这显然不是我们想要的结果。于是在基于ReLU的基础上有演化出了一些变种函数。
在TensorFlow中,关于ReLU函数的实现,有两种对应的函数:
注意:relu6存在的原因是防止梯度爆炸,当节点和层数特别多而且输出都为正时,它们的加和会是一个很大的值,尤其在经历几层变换之后,最终的值可能会离目标值相差太远,这会导致网络抖动得较厉害,最终很难收敛。
在TensorFlow中,Softplus函数对应的函数为:tf.nn.softplus(features, name=None)
在TensorFlow中,Elus函数对应的函数为:tf.nn.elu(features, name=None)
在TensorFlow中,Leaky relus公式没有专门的函数,不过可以利用现有函数组成而得到:tf.maximun(x, leak * x, name = name)(leak为传入的参数,可以设为0.01等)
4. Swish函数
Swish函数是谷歌公司发现的一个效果更优于ReLU的激活函数。经过测试,在保持所有的模型参数不变的情况下,只要把原来模型中的ReLU激活函数修改为Swish激活函数,模型的准确率均有提升。。
其中为x的缩放参数,一般情况取默认值1即可。在使用了BN算法的情况下,还需要对x的缩放值进行调节。
在TensorFlow的低版本中,没有单独的Swish函数,可以手动封装,代码如下:
def Swish(x, beta = 1):
return x * tf.nn.sigmoid(x * beta)
5. 激活函数总结
神经网络中,运算特征是不断进行循环计算,所以在每代循环过程中,每个神经元的值也是在不断变化的。这就导致了Tanh函数在特征相差明显时的效果会很好,在循环过程中其会不断扩大特征效果并显示出来。
但有时当计算的特征间的相差虽比较复杂却没有明显区别,或是特证间的相差不是特别大时,就需要更细微的分类判断,这时Sigmoid函数的效果就会更好一些。
后来出现的ReLU激活函数的优势是,经过其处理后的数据有更好的稀疏性。即,将数据转化为只有最大数值,其他都为0。这种变换可以近似程度地最大保留数据特征,用大多数元素为0的稀疏矩阵来实现。
实际上,神经网络在不断反复计算中,就变成了ReLU函数在不断尝试如何用一个大多数为0的矩阵来表达数据特征。以稀疏性数据来表达原有数据特征的方法,使得神经网络在迭代运算中能够取得又快又好的效果,所以目前大多用max(0,x)来代替Sigmod函数。
3. softmax算法——处理分类问题
对于前面讲的激活函数,其输出值只有两种(0、1,或-1、1,或0、x),而现实生活中需要对某一问题进行多种分类,例如前面的图片分类例子,这时就需要使用softmax算法。
softmax,看名字就知道,就是如果判断输入属于某一个类的概率大于属于其他类的概率,那么这个类对应的值就逼近于1,其他类的值就逼近于0。该算法的主要应用就是多分类,而且是互斥的,即只能属于其中的一个类。与sigmoid类的激活函数不同的是,一般的激活函数只能分两类,所以可以理解成Softmax是Sigmoid类的激活函数的扩展。
把所有值用e的n次方计算出来,求和后算每个值占的比率,保证总和为1,一般就可以认为softmax得出的就是概率。
这里的指的就是
注意:对于要生成的多个类任务中不是互斥关系的任务,一般会使用多个二分类来组成。
softmax原理很简单,为一个简单的Softmax网络模型,输入X1和X2,要准备生成Y1、Y2和Y3三个类。
对于属于y1类的概率,可以转化成输入x1满足某个条件的概率,与x2满足某个条件的概率的乘积。
在网络模型里把等式两边都取ln。这样,ln后的属于y1类的概率就可以转化成,ln后的x1满足某个条件的概率加上ln后的x2满足某个条件的概率。这样y1=x1w11+x2w12=ln后y1的概率了。这也是softmax公式中要进行一次e的logits次方的原因。
注意:等式两边取ln是神经网络中常用的技巧,主要用来将概率的乘法转变成加法,即。然后在后续计算中再将其转为e的x次方,还原成原来的值。
举例:某个样本经过生成的值y1为5,y2为3,y3为2。那么对应的概率就为y1=5/10=0.5,y2=3/10,y3=2/10,于是取最大的值y1为最终的分类。
softmax在机器学习中有非常广泛的应用,前面介绍过MNIST的每一张图片都表示一个数字,从0到9.。我们希望得到给定图片代表每个数字的概率。例如,训练的模型可能推测一张包含9的图片代表数字9的概率是80%,但是判断它是8的概率是5%(因为8和9都有上半部分相似的小圆),判断它代表其他数字的概率值更小。于是取最大概率的对应数值,就是这个图片的分类了。这是一个使用softmax回归模型的经典案例。
注意:在实际使用中,softmax伴随的分类标签都为one_hot编码,而且这里还有个小技巧,在softmax时需要将目标分成几类,就在最后这层放几个节点。
常用的分类函数:
4. 损失函数——用真实值与预测值的距离来指导模型的收敛方向
损失函数是绝对网络学习质量的关键。无论什么样的网络结构,如果使用的损失函数不正确,最终都将难以训练出正确的模型。
1. 损失函数
损失函数用于描述模型预测值与真实值的差距大小。一般有两种比较常见的算法——均值平方差(MSE)和交叉熵。
均值平方差(Mean Squared Error, MSE)也称“均方误差”,在神经网络中主要是表达预测值与真实值之间的差异,在数理统计中,均方误差是指参数估计值与参数真值之差平方的期望值。,主要是对每一个真实值与预测值相减的平方取平均值。
均方误差越小,表明模型越好。类似的损失算法还有均方误差RMSE(将MSE开平方)、平均绝对值误差MAD(对一个真实值与预测值相减的绝对值取平均值)等。
注意:在神经网络计算时,预测值要与真实值控制在同样的数据分布内,假设将预测值经过Sigmoid激活函数得到取值范围在0~1之间,那么真实值也归一化成0~1之间。这样在做loss计算时才会有较好的效果。
交叉熵(crossentropy)也是loss算法的一种,一般用在分类问题上,表达的意识为输入样本属于某一类的概率。,其中y代表真实值分类(0或1),a代表预测值。
交叉熵也是值越小,代表预测结果越准。
注意:这里用于计算的a也是通过分布统一化处理的(或是经过Sigmoid函数激活的),取值范围在0~1之间。如果真实值和预测值都是1,前面一项y * ln(a)就是1 * ln(a)等于0,后一项(1 - y) * ln(1-a)也就是0 * ln(0)等于0,loss为0,反之loss函数为其他数。
总结:损失函数的选取取决于输入标签数据的类型:如果输入的是实数、无界的值,损失函数使用平方差;如果输入标签是位矢量(分类标志),使用交叉熵会更适合。
2. TensorFlow中常见的loss函数
均值平方差:在TensorFlow没有单独的MSE函数,不过由于公式比较简单,往往开发者都会自己组合,例如:
MSE = tf.reduce_mean(tf.pow(tf.sub(logits, outputs), 2.0))
MSE = tf.reduce_mean(tf.square(tf.sub(logits, outputs)))
MSE = tf.reduce_mean(tf.square(logits - outputs))
代码中logits代表标签值,outputs代表预测值。
同样也可以组合其他类似loss,例如:
Rmse = tf.sqrt(tf.reduce_mean(tf.pow(tf.sub(logits, outputs), 2.0)))
mad = tf.reduce_mean(tf.complex_abs(tf.sub(logits, outputs)))
交叉熵:
在TensorFlow中常见的交叉熵函数有:
当然,也可以像MSE那样使用自己组合的公式计算交叉熵,举例,对于softmax后的结果logits我们可以对其使用公式-tf.reduce_sum(labels * tf.log(logits), 1)就等同于softmax_cross_entropy_with_logits得到的结果。
5. 梯度下降——让模型逼近最小偏差
1. 梯度下降的作用及分类
梯度下降法是一个最优化算法,通常也称为最速下降法,常用于机器学习和人工智能中递归性地逼近最小偏差模型,梯度下降的方向也就是用负梯度方向为搜索方向,沿着梯度下降的方向求解极小值。
在训练过程中,每次的正向传播后都会得到输出值与真实值的损失值,这个损失值越小,代表模型越好,于是梯度下降的算法就用在这里,帮助寻找最小的那个损失值,从而可以反推出对应的学习参数b和w,达到优化模型的效果。
常用的梯度下降方法可以分为批量梯度下降、随机梯度下降和小批量梯度下降。
2. TensorFlow中的梯度下降函数
在TensorFlow中是通过一个叫做Optimizer的优化器类进行训练优化的。对于不同算法的优化器,在TensorFlow中会有不同的类。
在训练过程中,先实例化一个优化函数如tf.train.GradientDescentOptimizer,并基于一定的学习率进行梯度优化训练:
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
接着使用一个minimize()的操作,里面传入损失值节点loss,再启动一个外层的循环,优化器就会按照循环的次数一次次沿着loss最小值的方向优化参数了。
整个过程中的求导和反向传播操作,都是在优化器里自动完成的。
3. 退化学习率——在训练的速度与精度之间找到平衡
前面介绍的每个优化器的第一个参数learning_rate就是代表学习率。
设置学习率的大小,就是在精度和速度之间找到一个平衡:
这里介绍设置学习率的方法——退化学习率。
退化学习率又叫学习率衰减,它的本意是希望在训练过程中对于学习率大和小的优点都能为我们所用,也就是当训练刚开始时使用大的学习率加快速度,训练到一定程度后使用小的学习率来提高精度,这时可以使用学习率衰减的方法:
def exponential_decay(learning_rate, global_step, decay_steps, decay_rate, staircase=False, name=None):
学习率的衰减速度是由global_step和decay_steps来决定的。
decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
staircase值默认为False。当为True时,将没有衰减功能,只是使用上面的公式初始化一个学习率的值而已。
learning_rate = tf.train.exponential_decay(starter_learning_rate, global_step, 100000, 0.96)
这种方式定义的学习率就是退化学习率,它的意思是当前迭代到global_step步,学习率每一步都按照每10万步缩小到0.96%的速度衰退。
有时还需要对已经训练好的模型进行微调,可以指定不同层使用不同的学习率,这个在后面章节中会详细介绍。
注意:通过增大批次处理样本的数量也可以起到退化学习率的效果。但是这种方法要求训练时的最小批次要与实际应用中的最小批次一致。一旦满足该条件时,建议优先选择增大批次数量的方法,因为这样会省去一些开发量和训练中的计算量。
6. 初始化学习参数
在定义学习参数时可以通过get_Variable和Variable两个方式,对于一个网络模型,参数不同的初始化情况,对网络的影响会很大,所以在TensorFlow提供了很多具有不同特性的初始化函数。在使用get_variable时,get_variable的定义如下:
def get_variable(name, shape=None, dtype=None, initializer=None, regularizer=None,trainable=None, collections=None, catching_device=None, partitioner=None,validate_shape=True,use_resource=None,custom_getter=None)
其中,参数initializer就是初始化参数。
另外,在tf.contrib.layers函数中还有个tf.contrib.layers.xavier_initializer初始化函数,用来在所有层中保持梯度大体相同。
对于Variable定义的变量
注意:一般常用的初始化函数为tf.truncated_normal函数,因为该函数有截断功能,可以生成相对比较温和的初始值。
7. 单个神经元的扩展——Maxout函数
在早期,单个神经元出现之后,为了得到更好的拟合效果,又出现了一种Maxout网络。
Maxout网络可以理解为单个神经元的扩展,主要是扩展单个神经元里面的激活函数,正常的单个神经元如下图所示:
Maxout是将激活函数变成一个网络选择器,原理就是将多个神经元并列地放在一起,从它们的输出结果中找到最大的那个,代表对特征影响最敏感,然后取这个神经元的结果参与后面的运算。
它的公式可以理解成:
z1 = w1 * x + b1
z2 = w2 * x + b2
z3 = w3 * x + b3
z4 = w4 * x + b4
z5 = w5 * x + b5
.......
out = max(z1, z2, z3, z4, z5......)
为什么要这样做呢?在前面我们学习了一个神经元的作用,类似人类的神经细胞,不同的神经元会因为输入的不同而产生不同的输出,即不同的细胞关心的信号不同。依赖于这个原理,现在的做法就是相当于同时使用多个神经元放在一起,哪个有效果就用哪个。所以这样的网络会有很好的拟合效果。