tensorflow基础3神经网络
- tensorflow基础3神经网络
- 1.全连接层的实现
- 1.1 通过张量的方式实现
- 1.2 通过层的方式实现
- 2. 激活函数
- 2.1 sigmoid
- 2.2 ReLU
- 2.3 LeakyReLU
- 2.4 Tanh
- 3. 输出层的设计
- 3.1 普通实数
- 3.2 [0,1]区间
- 3.3 [0,1]区间和为1
- 3.3 [-1,1]
- 4. 误差计算
- 4.1 均方误差函数
- 4.2 交叉熵误差函数
tensorflow基础3神经网络
(本文不会涉及较深的理论,只有简单的数学求导)
1.全连接层的实现
1.1 通过张量的方式实现
#第一层
w1 = tf.Variable(tf.random.normal([784,256],stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
#第二层
w2 = tf.Variable(tf.random.normal([256,128],stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
#第三层
w3 = tf.Variable(tf.random.normal([128,10],stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
在训练的时候我们按照网络层的顺序,将上一层的输出作为下一层的输入即可:
with tf.GradientTape() as tape:
#layer1
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1) #使用relu激活函数
#layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
#output
out = h2 @ w3 + b3
1.2 通过层的方式实现
对于常规的网络层,tensorflow具有更加简洁高效的实现方式:
from tensorflow.keras import layers,Sequential
fc1 = layers.Dense(256, activation=tf.nn.relu)
fc2 = layers.Dense(128, activation=tf.nn.relu)
fc3 = layers.Dense(10, activation=None)
layers.Dense(Units,activation):Units指的是输出节点数,acvtivation是激活函数类型。我们新建了fc1,fc2,fc3这三个实例后并不会立即创建权值张量W和偏执张量b,因为我们只指定了输出节点数,输入节点数根据第一个运算时输入的shape确定。当我们调用一次build函数或者进行一次向前计算时才能完成网络参数的构建:
x = tf.random.normal([4,28*28])
h1 = fc1(x)
h2 = fc2(h1)
h3 = fc3(h2)
#一次向前传播计算
Dense类提供了返回权值和偏执值的方法:
W1 = fc1.kernel
b1 = fc1.bias
W2 = fc2.kernel
b2 = fc2.bias
W3 = fc3.kernel
b3 = fc3.bias
print("W1",W1.shape,'\n',"b1",b1.shape,'\n',"W2",W2.shape,'\n',"b2",b2.shape,'\n',"W3",W3.shape,"\n","b3",b3.shape)
"""
W1 (784, 256)
b1 (256,)
W2 (256, 128)
b2 (128,)
W3 (128, 10)
b3 (10,)
"""
同时我们还可以返回层的待优化参数列表,不需要优化的参数列表和全部参数列表:
trainable_Variable = fc1.trainable_variables
#待优化参数列表
non_trainable_Variable = fc1.non_trainable_variables
#不需要优化的参数列表
Variable = fc1.variables
#全部参数列表
tensorflow提供了一个非常简单的向前传播计算,用Sequential容器把层封装成一个网络大类:
model = Sequential([layers.Dense(256, activation=tf.nn.relu),
layers.Dense(128, activation=tf.nn.relu),
layers.Dense(10, activation=None)
])
x = tf.random.normal([4,28*28])
out = model(x)#一次向前传播
2. 激活函数
(本小节会涉及一点理论问题)
2.1 sigmoid
Sigmoid函数又称为Logistic函数,定义为:
图像如下:
Sigmoid的优良特性可以从图中看出,它能够把x ∈ R的输入”压缩“道x∈(0,1)去间,而(0,1)去间在机器学习中常用来表示以下意义:
a. 概率分布:可以通过Sigmoid函数将输出转译为概率
b. 信号强度:一般可以将0~1理解为某种信号的强度,如像素的颜色强度,1代表当前通道颜色最强,0代表当前通道无颜色。
缺点:
Sigmoid导函数图像:
如果采用Sigmoid函数,除了输入层,其他层的值都呗Sigmoid变为0~1。所以有如下图像:
当输入值01时,导数值0.20.25,也就是求一次Sigmoid(x)的导数,x缩小了4~5倍。全连接层除了输出层都用Sigmoid激活函数,那么参数的导数将会变得极其微小,几乎为0,使得网络参数长时间只更新了极微小值甚至得不到更新,这称为梯度弥散或梯度消失。
**例子如下:**设有四层神经网络,第四层是输出层不用sigmoid
a1 = x @ W1 + b1
z1 = sigmoid(a1)
a2 = z1 @ W2 + b2
z2 = sigmoid(a2)
a3 = z2 @ W3 + b3
z3 = sigmoid(a3)
out = z3 @ W4 + b4
则我们求out对b1的偏导数如下(字不好看请谅解):
可见若层数加深,会出现多个sigmoid(ai)导数,而它的值是在0.2~0.25之间的,AlexNet模型有8层,假设7层用sigmoid,求导值很容易得出为W8W7W6W5W4W3W2 *sigmoid(ai)^7,
即使是0.25^7 这也是非常微小的值,很容易导致梯度消失。
2.2 ReLU
ReLU函数:REctified Linear Unit,线性修正单元。2012年提出的8层AlexNet模型采用了一种名叫ReLU的激活函数,有效缓解了梯度弥散现象。
ReLU(x) = max (0, x)。
图像如下:
缺点:我们试想,当学习率过大时候
就会变为一个负值,从而被ReLU变为0,当x=0时ReLU的导数明显是0,也就是说w会一直是0,我们称这个神经元被杀死了。
2.3 LeakyReLU
ReLU 函数在x<0时的导数值恒为0,也可能会造成梯度消失,为了解决这个问题LeakyReLU被提了出来。
图像如下:
p为用户自行设置的超参数,可以设定为某较小的值,如0.02等。这样当权值为负数的时候,并不会导致神经元直接死亡。
2.4 Tanh
Tanh数学式如下:
图像:
可以推算tanh(x) = 2 * sigmoid(2*x)-1。也就是tanh激活函数可以有Sigmoid函数缩放平移后实现。
3. 输出层的设计
设Oi为输出层输出的张量的元素。
3.1 普通实数
Oi∈R:这一类问题比较普遍,预测年龄或者股票走势等都属于整个或者部分连续的实数空间。输出层不采用激活函数,直接计算输出值与真实值的误差即可。
3.2 [0,1]区间
常见于图片生成,二分类问题等。在机器学习中,一般会将图片的像素值归一化到[0,1]区间,如果直接使用输出层的值,像素的值范围会分布在整个实数空间。所以我们一般在输出层添加一个激活函数,Sigmoid函数正好具有如此功能。
3.3 [0,1]区间和为1
输出值Oi∈[0,1],且Oi和为1。这种设定在多分类问题中最为常见。例如判断一种水果是香蕉、苹果、还是菠萝的概率。这时输出层就可以使用Softmax函数作为激活函数。
从数学式可见,Softmax将输出的元素之和作为分母,将该位置元素值作为分子,该分数就是Softmax的输出值。但是指数级的运算容易造成溢出现象,一般的做法是调整下Softmax公式:
我们让C`为最大元素值的负数就好了。
不过tensorflow提供了一个方便的方法。事实上在计算交叉熵误差(后面会说)时候也会发生溢出现象,所以tensorflow提供了一个统一的接口,将Softmax与交叉熵损失函数同时实现,并处理了数值不稳定的异常,一般情况下我们使用这些接口函数就可以了。
tf.keras.losses.categorical_crossentropy(y_true, y_pred, from_logits=False)
"""
y_true:one-hot形式的正确标签
y_pred:预测值(未经过Softmax)
from_logits:设置为True则在内部进行Softmax函数计算,一般为True
"""
3.3 [-1,1]
我们介绍的tanh激活函数就是将值归化到[-1,1]之间的,实际上任何一个区间都可以通过公式变化得到,只不过常用的就以上几个。
4. 误差计算
4.1 均方误差函数
如果你看过我的tensorflow mnist实践这一篇,这里用的就是均方误差函数,很好理解。
MSE误差函数的值总是大于等于0,若达到最小值0则输出等于真是标签,此时神经网络的参数达到最优状态了。
MSE误差函数广泛用于回归问题,实际上也可以用于分类问题。
keras.losses.MSE(y_onehot, o)
4.2 交叉熵误差函数
信息熵:1948年Claude Shannon将热力学中的熵引入信息论,用来衡量信息的不确定度。
log的底数可以取2,实际上也可以使用其他底数。举个例子,对于四分类问题,若one-hot数据为[0.1,0.1,0.1,0.7],则
若为[0,0,0,1]则很容易算出=0。就是说越确定的数据熵越小,越不确定的数据熵越大。
交叉熵:
p我们理解为正确数据分布(如果您不理解什么是分布,请跳过推导),q我们理解为神经网络的输出分布。
KL散度:
KL散度是衡量两个分布之间距离的指标,p与q越相似,KL散度值便越小,当p=q时取得最小值0。
如此我们经过越算可以简化交叉熵为:
而p在神经网络中表示正确标签,如果是one-hot形式的正确标签,则H§=0。所以交叉熵退化为真是标签分布p与神经网络输出分布q之间的DL散度。
根据KL散度的定义,若p是one-hot形式,我们可以简化:
j是值最大的元素的索引,正确标签p(j)=1,其他索引p(i)=0。所以最终的表达式可以简化为:
当神经网络在正确值索引处的预测值为1时,交叉熵达到最小值为0。