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函数,定义为:

神经网络二值神经元 神经网络 r2_深度学习

图像如下:

神经网络二值神经元 神经网络 r2_神经网络二值神经元_02

Sigmoid的优良特性可以从图中看出,它能够把x ∈ R的输入”压缩“道x∈(0,1)去间,而(0,1)去间在机器学习中常用来表示以下意义:

a. 概率分布:可以通过Sigmoid函数将输出转译为概率

b. 信号强度:一般可以将0~1理解为某种信号的强度,如像素的颜色强度,1代表当前通道颜色最强,0代表当前通道无颜色。

缺点:

Sigmoid导函数图像:

神经网络二值神经元 神经网络 r2_激活函数_03

如果采用Sigmoid函数,除了输入层,其他层的值都呗Sigmoid变为0~1。所以有如下图像:

神经网络二值神经元 神经网络 r2_深度学习_04

当输入值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的偏导数如下(字不好看请谅解):


神经网络二值神经元 神经网络 r2_激活函数_05

可见若层数加深,会出现多个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)。

图像如下:

神经网络二值神经元 神经网络 r2_神经网络二值神经元_06

缺点:我们试想,当学习率过大时候

神经网络二值神经元 神经网络 r2_深度学习_07

就会变为一个负值,从而被ReLU变为0,当x=0时ReLU的导数明显是0,也就是说w会一直是0,我们称这个神经元被杀死了。

2.3 LeakyReLU

ReLU 函数在x<0时的导数值恒为0,也可能会造成梯度消失,为了解决这个问题LeakyReLU被提了出来。

神经网络二值神经元 神经网络 r2_神经网络二值神经元_08

图像如下:

神经网络二值神经元 神经网络 r2_深度学习_09

p为用户自行设置的超参数,可以设定为某较小的值,如0.02等。这样当权值为负数的时候,并不会导致神经元直接死亡。

2.4 Tanh

Tanh数学式如下:

神经网络二值神经元 神经网络 r2_深度学习_10

图像:

神经网络二值神经元 神经网络 r2_激活函数_11

可以推算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函数作为激活函数。

神经网络二值神经元 神经网络 r2_python_12

从数学式可见,Softmax将输出的元素之和作为分母,将该位置元素值作为分子,该分数就是Softmax的输出值。但是指数级的运算容易造成溢出现象,一般的做法是调整下Softmax公式:

神经网络二值神经元 神经网络 r2_深度学习_13

我们让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实践这一篇,这里用的就是均方误差函数,很好理解。

神经网络二值神经元 神经网络 r2_tensorflow_14

MSE误差函数的值总是大于等于0,若达到最小值0则输出等于真是标签,此时神经网络的参数达到最优状态了。

MSE误差函数广泛用于回归问题,实际上也可以用于分类问题。

keras.losses.MSE(y_onehot, o)
4.2 交叉熵误差函数

信息熵:1948年Claude Shannon将热力学中的熵引入信息论,用来衡量信息的不确定度。

神经网络二值神经元 神经网络 r2_tensorflow_15

log的底数可以取2,实际上也可以使用其他底数。举个例子,对于四分类问题,若one-hot数据为[0.1,0.1,0.1,0.7],则

神经网络二值神经元 神经网络 r2_深度学习_16

若为[0,0,0,1]则很容易算出=0。就是说越确定的数据熵越小,越不确定的数据熵越大。

交叉熵:

神经网络二值神经元 神经网络 r2_tensorflow_17

p我们理解为正确数据分布(如果您不理解什么是分布,请跳过推导),q我们理解为神经网络的输出分布。

KL散度:

神经网络二值神经元 神经网络 r2_python_18

KL散度是衡量两个分布之间距离的指标,p与q越相似,KL散度值便越小,当p=q时取得最小值0。

如此我们经过越算可以简化交叉熵为:

神经网络二值神经元 神经网络 r2_神经网络二值神经元_19

而p在神经网络中表示正确标签,如果是one-hot形式的正确标签,则H§=0。所以交叉熵退化为真是标签分布p与神经网络输出分布q之间的DL散度。

根据KL散度的定义,若p是one-hot形式,我们可以简化:

神经网络二值神经元 神经网络 r2_神经网络二值神经元_20

j是值最大的元素的索引,正确标签p(j)=1,其他索引p(i)=0。所以最终的表达式可以简化为:

神经网络二值神经元 神经网络 r2_深度学习_21

当神经网络在正确值索引处的预测值为1时,交叉熵达到最小值为0。