tf2.0学习(三)——神经网络
之前的文章分别介绍了TensorFlow中张量的一些基本知识:
tf2.0学习(一)——基础知识
tf2.0学习(二)——进阶知识
现在介绍一下TensorFlow中关于神经网络的操作。
3.1 全连接层
全连接是有感知机发展起来的,由于感知机模型使用的是介跃激活函数,他是不连续不可导的,这严重制约了该模型的潜力。如果把激活函数换成平滑连续可到的函数,并堆叠多个网络层来增强网络的表达能力,就形成了我们接下来要介绍的全连接网络。
3.1.1 张量方式实现
TensorFlow中提供了很好的矩阵运算和反向传播算法,可以利用这一特性方便的用张量来实现全连接网络。
x = tf.random.uniform([2, 784]) # x为模拟的输入数据
# w1为随机初始化的参数w1
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
# b1为0初始化的bias
b1 = tf.Variable(tf.zeros([256]))
# 矩阵相乘 在加bias,即全连接过程
o1 = tf.matmul(x, w1) + b1
# relu激活函数
o1 = tf.nn.relu(o1)
o1.shape
TensorShape([2, 256])
3.1.2 层的方式实现
全连接作为很常用的网络成知识,TensorFlow提供了更高层、更方便的实现方式:tf.keras.layers.Dense(units, activation)。其中unis为输出的节点数,activation为激活函数。
# 输入数据
x = tf.random.normal([4, 28 * 28])
fc = tf.keras.layers.Dense(512, activation=tf.nn.relu)
h1 = fc(x)
h1.shape
TensorShape([4, 512])
如上所示可以很方便的构建全连接网络层。通过 fc.kernel 和 fc.bias 可以获取权重张量W和偏置张量b。
在进行参数优化时,可以用 fc.trainable_variables 获取待优化的参数。
3.2 神经网络
通过对神经层(例如全连接层)的层层堆叠,并且保证前一层的输出节点数与当前层的输入节点数匹配,即可以构建出任意层的神经网络。
3.2.1 张量的方式
x = tf.random.normal([4, 784])
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 184], stddev=0.1))
b2 = tf.Variable(tf.zeros([184]))
w3 = tf.Variable(tf.random.truncated_normal([184, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
with tf.GradientTape() as tape:
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
h3 = h2 @ w3 + b3
其中h3可以直接作为输出,也可以加个激活函数之后再作为输出,需要视情况而定。
如果希望TensorFlow对一个过程自动求导的话,需要把这个过程的前向传播放到tf.GradientTape()环境中,gradient()方法自动求解参数的梯 度,并利用 optimizers 对象更新参数。
3.2.2 层的方式
层的方式可以更加简洁高效的实现神经网络。
fc1 = tf.keras.layers.Dense(256, activation=tf.nn.relu)
fc2 = tf.keras.layers.Dense(184, activation=tf.nn.relu)
fc3 = tf.keras.layers.Dense(10)
x = tf.random.normal([4, 784])
h1 = fc1(x)
h2 = fc2(h1)
h3 = fc3(h2)
我们同样可以引入容器Sequential将多个神经层封装成一个大的类,可以更加简介:
model = tf.keras.Sequential([
tf.keras.layers.Dense(256, activation=tf.nn.relu),
tf.keras.layers.Dense(184, activation=tf.nn.relu),
tf.keras.layers.Dense(10)
])
out = model(x)
前向计算只需要调用一次网络大类对象,就可以按顺序计算完所有层。
3.3.3 优化目标
我们把神经网络从输入到输出的过程叫做前向传播。前向传播的过程也是数据从输入层经过第一层,第二层。。。直到输出层的过程。
前向传播的最后一步就是完成误差计算。
其中代表了利用参数化的神经网络模型,g称为误差函数,用来表述当前网络的与测试和真实值y之间的差距的度量。我们希望通过训练集的数据上学习到一组参数使得训练的误差L最小。
从另一个角度理解神经网络,他完成的是特征的维度变换的过程。比如 4 层的 MNIST 手写数字图片识别的全连接网络,它依次完成了784 → 256 → 128 → 64 → 10的特 征降维过程。
3.4 激活函数
激活函数实现了神经网络的非线性变换,但和阶越函数和符号函数等不同,激活函数一般是平滑可导的,更适合梯度下降算法。
3.4.1 Sigmoid
Sigmoid函数也叫Logistic函数,定义如下:
他的一个优良特性就是能把的输入压缩到的区间内。这个区间的数值常被用来表示一下意义:
- 概率分布
- 信号强度
# x为(-6,6)的10个数
x = tf.linspace(-6, 6, 10)
tf.nn.sigmoid(x)
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([0.00247262, 0.00931596, 0.0344452 , 0.11920292, 0.33924363,
0.66075637, 0.88079708, 0.9655548 , 0.99068404, 0.99752738])>
x中元素的范围是[-6, 6],映射到(0,1)区间。
3.4.2 ReLU
ReLU(REctified Linear Unit,修正线性单元)激活函数提出前,Sigmoid是最常用的神经网络激活函数。但Sigmoid函数在输入值较大或较小时,容易出现梯度值接近0的现象,梯度为0会是网络参数得不到有效的更新,导致训练不收敛。
x = tf.linspace(-6, 6, 10)
tf.nn.relu(x)
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([0. , 0. , 0. , 0. , 0. ,
0.66666667, 2. , 3.33333333, 4.66666667, 6. ])>
3.4.3 LeakyReLU
ReLU激活函数的问题在于,当x<0时,其梯度为0,同样回导致梯度消失的问题,LeakyReLU就是为解决这一问题而提出的。
其中p为用户设定的极小的超参数,如0.02等。
x = tf.linspace(-6, 6, 10)
tf.nn.leaky_relu(x, alpha=0.01)
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([-0.06 , -0.04666667, -0.03333333, -0.02 , -0.00666667,
0.66666667, 2. , 3.33333333, 4.66666667, 6. ])>
3.4.4 Tanh
Tanh函数能够将的输入压缩到的区间内。
x = tf.linspace(-6, 6, 10)
tf.nn.tanh(x)
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([-0.99998771, -0.99982316, -0.99745797, -0.96402758, -0.58278295,
0.58278295, 0.96402758, 0.99745797, 0.99982316, 0.99998771])>
3.5 输出层设计
最有一层作为神经网络的输出层,要根据具体的任务、场景来判断是否使用激活函数,以及使用什么样的激活函数。
- 普通实数空间
这种输出比较普遍,譬如房价预测、年龄预测、股票预测,都属于整个或部分连续的实数空间,输出层可以不加激活函数(或用relu)。 - [0, 1] 区间
二分类问题,常用Sigmoid作为激活函数。 - [0, 1]区间,且和为1
多分类问题,常用Softmax作为激活函数。
3.6 误差计算
在搭建完模型结构之后,下一步就是选择合适的误差函数来计算误差了。常见的误差函数有mse,mae,hinge loss,cross entrpy等,其中均方误差和交叉熵在深度学习中比较常见。
3.6.1 均方误差
tensorflow中:
o = tf.random.normal([2, 10])
y = tf.constant([1, 3])
y = tf.one_hot(y, depth=10)
loss = tf.keras.losses.MSE(y, o)
tf.reduce_sum(loss)
需要注意的是,tf.keras.losses.MSE()返回的是每个样本的均方误差,计算当前batch的均方误差时,需要tf.reduce_mean(loss)
也可以通过层的方式计算:
mse = tf.keras.losses.MeanSquaredError()
mse(y, o)
3.6.2 交叉熵
信息学中有个重要的概念叫熵。
熵用来衡量信息的不确定度。熵越大代表不确定性越大,信息量也就越大。
通过熵,引出交叉熵的定义:
通过变换,交叉熵可以分解为p的熵和p,q的KL散度的和:
其中KL散度为:
KL散度是用来衡量两个分布之间的距离的指标。当p与q之间的距离越大,KL散度也就越大。
最小化交叉熵损失函数的过程也是最大化正确类别的预测概率的过程。从这个角 度去理解交叉熵损失函数,非常地直观易懂。
3.7 神经网络的类型
全连接是最基本的网络类型,对后续其他神经网络的研究有巨大贡献。在学习过程中,全连接的前向传播和反向传播都比较容易理解。但由于全连接没有参数共享机制,当特征比较多时,会产生大量的参数,训练过程十分消耗资源。随着神经网络在计算机视觉、自然语言处理中广泛研究,产生了很多其他的神经网络。
3.7.1 卷积神经网络(CNN)
计算机视觉领域的核心问题,是如何识别、理解图片。由于图片的维度较高,特征较多,如果用传统的全连接网络,会产生巨大的参数量,从而使训练十分困难。通过利用局部相关性和权值共享的思想,CNN应运而生。CNN在计算机视觉领域的表现大大超过了其他算法,逐渐统治该领域。
其中CNN的平移不变性起着关键作用,图片中的目标不管被如何平移,都能得到相同的结果。卷积+最大池化对平移不变性起了关键作用。
3.7.2 循环神经网络(RNN)
除了具有空间结构的图片外,具有序列信息的文本、信号等也是一种十分常见的数据形式。CNN由于没有Memory机制,很难形成长程依赖,这对序列信息不太友好。RNN就是一种有一定Memory机制,一定程度上能解决长程依赖问题的神经网络。之后作为RNN的变种,LSTM、GRU等相对更具有长期记忆的模型提出来。
3.7.3 注意力机制(Attention)
RNN并不是NLP的终局,近年来随着注意力机制的提出,克服了RNN不灵活,难以并行等缺点。2017年google提出了纯注意力机制的网络Transformer,后边又出现了各种变种模型,Bert,GPT等。
3.7.4 图卷机网络
图片、文本等数据具有规则的空间、时间结构,这种数据我们称为欧几里得数据。CNN和RNN都十分擅长处理这些数据。但还有一种数据,如社交网络、交通网络、蛋白质分子等,是一种不规则的拓扑结构的数据。GCN随之被提出来处理这类数据。