一.前向传播算法
以tensorflow游乐场为例子。在神经网络中,有若干个层。比如上图中就有一个输入层,一个隐藏层和一个输出层。那么在神经网络中,我们是如何通过计算得到最后的结果呢?先来看下面一张图:
一个最简单的神经元结构输出就是所有输入的加权和。也就是说如果要计算上图神经元的取值,我们可以这么来做:x1 * w1 + x2 * w2 + x3 * w3(+1在这里暂不考虑)。
下面再来看一个三层的全连接网络:
根据上面的结论我们来类推上面这个三层全连接神经网络的计算过程:
首先来看a11是怎么得到的。因为这里讨论的是全连接神经网络,所以a11会与输入的每一个神经元相连。(这里我们同样不考虑偏置项+1)每一条连线上都有一个权重,这里我们用W(1,1)代表输入层第一个神经元和隐藏层第一个神经元之间的权重,其它的以此类推。所以我们很容易地得出a11 = x1 * W(1,1) + x2 * W(2,1) + x3 * W(3,1) 。
a12和a13也可以用类似的方法计算到。
类似地,输出层中节点的取值就是第一层的加权和:b21 = a11 * a(2,1) + a12 * a(2,2) + a13 * a(3,2)。这就是一个最简单的神经网络前向传播算法。
前向传播算法可以表示为矩阵乘法,如下:
将输入层的三个神经元组织成1x3的矩阵x=[x1,x2,x3]。第一个隐藏层的权重W(1)组织成一个3*3的矩阵:
W(1) = [
W(1,1) W(1,2) W(1,3)
W(2,1) W(2,2) W(2,3)
W(3,1) W(3,2) W(3,3)
]
这样通过矩阵乘法可以得到隐藏层三个节点所组成的向量的取值:
其中f为激活函数,b为偏置项。在这里暂时不做介绍。以上计算在tensorflow中我们可以这样表示:
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
y就是上述神经网络前向传播之后的结果。tf.matmul实现了矩阵乘法的功能。在这里我们暂时忽略偏置项和激活函数,在后面我们会详细介绍他们。在这里w1,w2是神经网络的参数,那么我们是怎么获得的呢?来看下面代码。
w1 = tf.Variable(tf.random_normal([3,3],stddev=2))
w2 = tf.Variable(tf.random_normal([1,3],stddev=2))
这段代码调用了TensorFlow的变量声明函数tf.Variable,并给出了初始值。tf.random_normal([3,3],stddev=2)会产生一个3*3的矩阵,矩阵元素的均值为0,标准差为2的随机数。tf.random_normal函数可以通过参数mean来指定平均值,在没有指定时默认为0。通过满足正态分布的随机数来初始化神经网络的参数是一个常用方法。
在神经网络中,偏置项(bias)通常会使用常数来设置初始值。代码如下
biases = tf.Variable(tf.zeros([3]))
以上代码会生成一个初始值全部为0,长度为3的变量。
下面我们通过一段TensorFlow代码来实现上述神经网络的前向传播过程:
#下面是一个简单的神经网络例子
import tensorflow as tf
#定义神经网络参数(权重)
#在这里是3个输入变量,一个3节点隐藏层,一个输出层 。这里还通过seed参数设置了随机数种子。
w1 = tf.Variable(tf.random_normal([3,3],stddev=2,seed=1))
w2 = tf.Variable(tf.random_normal([3,1],stddev=2,seed=1))
#模拟输入向量,这里暂时定义为一个常量
x = tf.constant([0.7,0.9,1.1])
#通过矩阵相乘得到前向传播结果
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)
#通过定义一个tf.Session来运行上述过程
#在运行前我们需要初始化所有变量
sess = tf.Session()
#tf.global_variables_initializer可以拿到所有定义的变量
init_op = tf.global_variables_initializer()
sess.run(init_op)
#打印传播结果
print(sess.run(y))
sess.close()
以上就是一个最简单的TensorFlow前向传播的过程。
二.激活函数
2.1线性模型的局限性(可以简单理解为直线无法表示曲线上的点)
之前在TensorFlow游乐场中已经介绍过线性变换不能解决非线性分类问题的例子,下面我们来介绍具体在数学上的原理。刚才我们介绍的前向传播算法中,抛开激活函数,那么我们模型的输出仅仅为输入的加权和。假设一个模型的输出z和输入x(i)满足以下关系,那么这个模型就是一个线性模型。
其中W、b为模型的参数。当输入x只有一个的时候(这里指输入层只有一个节点),x和z行成了二维坐标系上的一条直线。当模型有n个输入的时候,x和y形成了n+1维空间中的一个平面。而一个线性模型通过输入得到输出的函数被称之为一个线性变换。线性模型的最大特点就是任意线性模型的组合还是一个线性的。下面我们通过一个矩阵乘法的例子来说明多层神经网络依旧是一个线性变换。在之前的前向传播中,前向传播的公式可以表示为:
a(1) = x * W1 , y=a(1) * W2
其中x为输入,W为参数。整理一下以上公式可以得到整个模型的输出为:
y = (x * W1) * W2
根据矩阵的乘法结合律有
y = x * (W1 * W2)
W1 * W2实际上可以表示为一个新的参数W_new
W_new = W1 * W2 =
[W1(1,1) W1(1,2) W1(1,3)
W1(2,1) W1(2,2) W1(2,3)] * [W2(1,1)
W2(2,1)
W2(3,1)]
= [W1(1,1) * W2(1,1) + W1(1,2) * W2(2,1) + W1(1,3) * W2(3,1)
W1(2,1) * W2(2,2) + W1(2,3) * W2(2,1) + W1(1,3) * W2(3,1)]
= [W1_new
W2_new]
这样输入和输出的关系就可以表示为:
y = xW_new = [x1 x2] * [W1_new
W2_new]
=[W1_new * x1 + W2_new * x2]
所以综上面的推倒可以看出,虽然这个模型有两层(不算输入层),但它和单层神经网络的表达能力没有区别,依然还是一个线性变换。线性变换只能解决线性分类问题,如果我们想要解决非线性问题,这里就需要激活函数出场了。
2.2常见激活函数
下面我们展示几个常用的激活函数:
sigmod函数
sigmod的数学公式是:在sigmod函数中我们可以看到,其输出是在(0,1)这个开区间内,这点很有意思,可以联想到概率,但是严格意义上讲,不要当成概率。sigmod函数曾经是比较流行的,它可以想象成一个神经元的放电率,在中间斜率比较大的地方是神经元的敏感区,在两边斜率很平缓的地方是神经元的抑制区。
当然,流行也是曾经流行,这说明函数本身是有一定的缺陷的。
- 当输入稍微远离了坐标原点,函数的梯度就变得很小了,几乎为零。在神经网络反向传播的过程中,我们都是通过微分的链式法则来计算各个权重w的微分的。当反向传播经过了sigmod函数,这个链条上的微分就很小很小了,况且还可能经过很多个sigmod函数,最后会导致权重w对损失函数几乎没影响,这样不利于权重的优化,这个问题叫做梯度饱和,也可以叫梯度弥散。
- 函数输出不是以0为中心的,这样会使权重更新效率降低。对于这个缺陷,在斯坦福的课程里面有详细的解释。
- sigmod函数要进行指数运算,这个对于计算机来说是比较慢的。
tanh函数
tanh的数学公式是:tanh是双曲正切函数,tanh函数和sigmod函数的曲线是比较相近的,咱们来比较一下看看。首先相同的是,这两个函数在输入很大或是很小的时候,输出都几乎平滑,梯度很小,不利于权重更新;不同的是输出区间,tanh的输出区间是在(-1,1)之间,而且整个函数是以0为中心的,这个特点比sigmod的好。
一般二分类问题中,隐藏层用tanh函数,输出层用sigmod函数。不过这些也都不是一成不变的,具体使用什么激活函数,还是要根据具体的问题来具体分析。
可以看到这些激活函数的图像都不是一条直线,所以通过这些函数,每一个节点都不是线性变换。
a = tf.nn.tanh(tf.matmul(x,w1) + biases1)
y = tf.nn.tanh(tf.matmul(a,w2) + biases2)
三.偏置项
下面我们来介绍偏置项在神经网络中的作用。首先我们先用最简单的一输出一输入神经元来说明: 这里我们使用上面介绍过的sigmod函数来作为激活函数,可以看到,这时候可以改变函数图像的只是权重w0的取值。 举一个最直观的例子,如果此时我们想在输入为2的时候让输出为0,不管w0怎么变换我们都没有办法实现input为2,output为0。这里我们就需要用到偏置项。当我们加入偏置项后,网络结构如下:
下图是加入偏置项后,函数的图像。简单来说,偏置项就是可以让原函数实现左移和右移操作,来覆盖原来覆盖不到的点。
网友评论