tensorflow2.1安装教程,遇到的问题及解决办法

一、神经网络计算过程及模型搭建


(一)人工智能三学派:


​ 我们常说的人工智能,就是让机器具备人的思维和意识。人工智能主要有三个学派,即行为主义、符号主义连接主义

  1. 行为主义是基于控制论的,是在构建感知、动作的控制系统。单脚站立是行为主义一个典型例子,通过感知要摔倒的方向,控制两只手的动作,保持身体的平衡。这就构建了一个感知、动作的控制系统,是典型的行为主义。
  2. 符号主义基于算数逻辑表达式。即在求解问题时,先把问题描述为表达式,再求解表达式。例如在求解某个问题时,利用 if case 等条件语句和若干计算公式描述出来,即使用了符号主义的方法,如专家系统。符号主义是能用公式描述的人工智能,它让计算机具备了理性思维。
  3. 连接主义仿造人脑内的神经元连接关系,使人类不仅具备理性思维,还具备无法用公式描述的感性思维,如对某些知识产生记忆。

​ 图 1.1 展示了人脑中的一根神经元,其中紫色部分为树突,其作为神经元的输入。黄色部分为轴突,其作为神经元的输出。人脑就是由 860 亿个这样的神经元首尾相接组成的网络。

图1.1 神经元示意图

​ 基于连接主义的神经网络模仿上图的神经元,使计算机具有感性思维。图 1.2展示了从出生到成年,人脑中神经网络的变化。

图 1.2 人脑神经网络变化示意图

​ 随着我们的成长,大量的数据通过视觉、听觉涌入大脑,使我们的神经网络连接,也就是这些神经元连接线上的权重发生了变化,有些线上的权重增强了,有些线上的权重减弱了。如图 1.3 所示。

图 1.3 神经网络权重变化示意图

(二)神经网络设计过程

​ 我们要用计算机模仿刚刚说到的神经网络连接关系,让计算机具备感性思维。

​ 首先,需要准备数据,数据量越大越好,要构成特征和标签对。如要识别猫,就要有大量猫的图片和这个图片是猫的标签,构成特征标签对。

​ 随后,搭建神经网络的网络结构,并通过反向传播,优化连线的权重,直到模型的识别准确率达到要求,得到最优的连线权重,把这个模型保存起来。

​ 最后,用保存的模型,输入从未见过的新数据,它会通过前向传播,输出概率值,概率值最大的一个,就是分类或预测的结果。图2.1展示了搭建与使用神经网络模型的流程。

图2.1 搭建与使用神经网络示意图

2.1 数据集介绍


​ 本讲中采用鸢尾花数据集,此数据集包含鸢尾花花萼长、花萼宽、花瓣长、花瓣宽及对应的类别。其中前 4 个属性作为输入特征,类别作为标签,0 代表狗尾草鸢尾,1 代表杂色鸢尾,2 代表弗吉尼亚鸢尾。人们通过对数据进行分析总结出了规律:通过测量花的花萼长、花萼宽、花瓣长、花瓣宽,可以得出鸢尾花的类别(如:花萼长>花萼宽且花瓣长/花瓣宽>2 ,则杂色鸢尾)。

​ 由上述可知,可通过 if 与 case 语句构成专家系统,进行判别分类。在本讲中,采用搭建神经网络的办法对其进行分类,即将鸢尾花花萼长、花萼宽、花瓣长、花瓣宽四个输入属性喂入搭建好的神经网络,网络优化参数得到模型,输出分类结果。

2.2 网络搭建与训练


​ 本讲中,我们搭建包含输入层与输出层的神经网络模型,通过对输入值乘权值,并于偏置值求和的方式得到输出值,图示如下。

图2.2 鸢尾花神经网络简要模型

​ 由图2.2可知输出 y = x ∗ w + b y=x*w+b y=x∗w+b,即所有的输入 x x x乘以各自线上的权重 w w w求和加上偏置项 b b b得到输出 y y y。由2.1部分对数据集的介绍可知,输入特征 x x x形状应为(1,4)即1行4列,输出y形状应为(1,3)即1行3列, w w w形状应为(4,3)即4行3列, b b b形状应为(3, )即有3个偏置项。

​ 搭建好基本网络后,需要输入特征数据,并对线上权重 w w w与偏置 b b b进行初始化。搭建的神经网络如图2.3所示, w w w, b b b,初始化矩阵如图2.4所示。在这里,我们输入标签为0的狗尾草鸢尾。

图2.3 鸢尾花神经网络展开模型

图2.4 权重与偏置初始化矩阵

​ 有了输入数据与线上权重等数据,即可按照 y = x ∗ w + b y=x*w+b y=x∗w+b方式进行前向传播,计算过程如图2.5所示。

图2.5 前向传播计算过程

​ 图2.5中输出 y y y中,1.01代表0类鸢尾得分,2.01代表1类鸢尾得分,-0.66代表2类鸢尾得分。通过输出y可以看出数值最大(可能性最高)的是1类鸢尾,而不是标签0类鸢尾。这是由于最初的参数w和b是随机产生的,现在输出的结果是蒙的。

​ 为了修正这一结果,我们用损失函数,定义预测值 y y y和标准答案(标签) y y y_的差距,损失函数可以定量的判断当前这组参数 w w w和 b b b的优劣,当损失函数最小时,即可得到最优 w w w的值和 b b b的值。

​ 损失函数的定义有多种方法,均方误差就是一种常用的损失函数,它计算每个前向传播输出 y y y和标准答案 y y y_的差求平方再求和再除以 n n n求平均值,表征了网络前向传播推理结果和标准答案之间的差距。

​ 通过上述对损失函数的介绍,其目的是寻找一组参数 w w w和 b b b使得损失函数最小。为达成这一目的,我们采用梯度下降的方法。损失函数的梯度表示损失函数
对各参数求偏导后的向量,损失函数梯度下降的方向,就是是损失函数减小的方向。梯度下降法即沿着损失函数梯度下降的方向,寻找损失函数的最小值,从而得到最优的参数。梯度下降法涉及的公式如下
w t + 1 = w t − l r ∗ ∂ l o s s ∂ w t b t + 1 = b t − l r ∗ ∂ l o s s ∂ b t w t + 1 ∗ x + b t + 1 → y \begin{aligned} & w_{t+1}=w_t-l r * \frac{\partial l o s s}{\partial w_t} \\ & b_{t+1}=b_t-l r * \frac{\partial l o s s}{\partial b_t} \\ & w_{t+1} * x+b_{t+1} \rightarrow y \end{aligned} ​wt+1​=wt​−lr∗∂wt​∂loss​bt+1​=bt​−lr∗∂bt​∂loss​wt+1​∗x+bt+1​→y​
​ 上式中, l r lr lr表示学习率,是一个超参数,表征梯度下降的速度。如学习率设置过小,参数更新会很慢,如果学习率设置过大,参数更新可能会跳过最小值。

​ 上述梯度下降更新的过程为反向传播,下面通过例子感受反向传播。利用如下公式对参数 w w w进行更新。
w t + 1 = w t − l r ∗ ∂ l o s s ∂ w t w_{t+1}=w_t-l r * \frac{\partial l o s s}{\partial w_t} wt+1​=wt​−lr∗∂wt​∂loss​
​ 设损失函数为 ( w + 1 ) 2 (w+1)^2 (w+1)2,则其对 w w w的偏导数为 2 w + 2 2w+2 2w+2。设 w w w在初始化时被随机初始化为5,学习率设置为0.2。则我们可按上述公式对 w w w进行更新:

​ 第一次参数为5,按上式计算即 5 − 0.2 × ( 2 × 5 + 2 ) = 2.6 5-0.2\times(2\times5+2)=2.6 5−0.2×(2×5+2)=2.6。

​ 同理第二次计算得到参数为1.16,第三次计算得到参数为0.296……

​ 画出损失函数 ( w + 1 ) 2 (w+1)^2 (w+1)2的图像,可知 w w w=-1时损失函数最小,我们反向传播优化参数的目的即为找到这个使损失函数最小的 w = − 1 w=-1 w=−1值。

(三)TensorFlow2.1基本概念与常见函数


3.1 基本概念


​ TensorFlow中的Tensor表示张量**,是多维数组、多维列表,表示张量的维数

​ 0阶张量叫做标量,表示的是一个单独的数,如123;

​ 1阶张量叫作向量,表示的是一个一维数组如[1,2,3];

​ 2阶张量叫作矩阵,表示的是一个二维数组,它可以有i行j列个元素,每个元素用它的行号和列号共同索引到,如在[[1,2,3],[4,5,6],[7,8,9]]中,2的索引即为第0行第1列。张量的阶数与方括号的数量相同,0个方括号即为0阶张量,1个方括号即为1阶张量。

​ 故张量可以表示0阶到n阶的数组。也可通过reshape的方式得到更高维度数组,举例如下:

c = np.arange(24).reshape(2,4,3)
print(c)

​ 输出结果:

[[[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]]
[[12 13 14] [15 16 17] [18 19 20] [21 22 23]]]
维数 名字 例子
0-D 0 标量 scalar s=1 2 3
1-D 1 向量 vector v=[1, 2, 3]
2-D 2 矩阵 matrix m=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
n-D 3 张量 tensor t=[ [ [n个

​ TensorFlow中数据类型包括32位整型(tf.int32)、32位浮点(tf.float32)、64位浮点(tf.float64)、布尔型(tf.bool)、字符串型(tf.string)

  • tf.int, tf.float……
tf.int 32, tf.float32, tf.float64
  • tf.bool
tf.constant([True, False])
  • tf.string
tf.constant(“Hello, world!”)

3.2 .如何创建一个张量


3.2.1 利用tf.constant


tf.constant(张量内容,dtype=数据类型(可选))

源码:p17_constant.py

import tensorflowas tfa=tf.constant([1,5],dtype=tf.int64)
print(a)
print(a.dtype)
print(a.shape)

​ 运行结果:

<tf.Tensor([1,5], shape=(2 , ) , dtype=int64)
<dtype: 'int64'>
(2,)

​ shape逗号隔开几个数字代表几维,数字2说明张量里有2个元素

注:去掉dtype项,不同电脑环境不同导致默认值不同,可能导致后续程序bug

3.2.2 将numpy格式化为Tensor格式


tf. convert_to_tensor(数据名,dtype=数据类型(可选))

源码:p18_convert_to_tensor.py

import tensorflowas tf
import numpyas np
a = np.arange(0, 5)
b = tf.convert_to_tensor( a, dtype=tf.int64 )
print(a)
print(b)

​ 运行结果:

[0 1 2 3 4]
tf.Tensor([0 1 2 3 4], shape=( 5 , ), dtype=int64

3.2.3 可采用不同函数创建不同值的张量


​ 张量维度:
​ 一维直接写个数
​ 二维用[行,列]
​ 多维用[n,m,j,k……]

  • 创建全为0的张量
tf. zeros(维度)
  • 创建全为1的张量
tf. ones(维度)
  • 创建全为指定值的张量
tf. fill(维度,指定值)

源码:p19_zeros_ones_fill.py

a = tf.zeros([2, 3])
b = tf.ones(4)
c = tf.fill([2, 2], 9)
print(a)
print(b)
print(c)

​ 运行结果:

tf.Tensor([[0. 0. 0.] [0. 0. 0.]], shape=(2, 3), dtype=float32)
tf.Tensor([1. 1. 1. 1.], shape=(4, ), dtype=float32)
tf.Tensor([[9 9] [9 9]], shape=(2, 2), dtype=int32)

3.2.4 可采用不同函数创建符合不同分布的张量


  • 生成正态分布的随机数,默认均值为0,标准差为1
tf. random.normal(维度,mean=均值,stddev=标准差)
  • 生成截断式正态分布的随机数
tf. random.truncated_normal (维度,mean=均值,stddev=标准差)

​ 在tf.truncated_normal,中如果随机生成数据的取值在(μ-2σ,μ+2σ)之外则重新进行生成,保证了生成值在均值附近。

​ μ:均值,σ:标准差

​ 标准差计算公式:

​ σ = ∑ i = 1 n ( x i − x ˉ ) 2 n \sigma=\sqrt{\dfrac{\sum\limits_{i=1}^n(x_i-\bar{x})^2}{n} } σ=ni=1∑n​(xi​−xˉ)2​ ​

源码:p21_random.normal.py

d = tf.random.normal([2, 2], mean=0.5, stddev=1)
print(d)
e = tf.random.truncated_normal([2, 2], mean=0.5, stddev=1)
print(e)

​ 运行结果:

tf.Tensor(
[[0.7925745 0.643315 ]
[1.4752257 0.2533372]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[ 1.3688478 1.0125661 ]
[ 0.17475659 -0.02224463]], shape=(2, 2), dtype=float32)

​ 生成均匀分布随机数(minval, maxval)

tf. random. uniform(维度,minval=最小值,maxval=最大值)

源码:p22_random.uniform.py

f = tf.random.uniform([2, 2], minval=0, maxval=1)
print(f)

​ 运行结果:

tf.Tensor(
[[0.28219545 0.15581512]
[0.77972126 0.47817433]], shape=(2, 2), dtype=float32)

3.3 常用函数


3.3.1 tf.cast,tf.reduce_min,tf.reduce_max


  • 强制tensor转换为该数据类型:
tf.cast(张量名,dtype=数据类型)
  • 计算张量维度上元素的最小值:
tf.reduce_min(张量名)
  • 计算张量维度上元素的最大值:
tf.reduce_max(张量名)

源码:p23_cast_reduce_minmax.py

x1 = tf.constant([1., 2., 3.],dtype=tf.float64)
print(x1)
x2 = tf.cast(x1, tf.int32)
print(x2)
print (tf.reduce_min(x2), tf.reduce_max(x2))

​ 运行结果:

tf.Tensor([1. 2. 3.], shape=(3,), dtype=float64)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(3, shape=(), dtype=intt32)

3.3.2 tf.reduce_mean,f.reduce_sum


​ 在一个二维张量或数组中,可以通过调整axis等于0或1控制执行维度。 axis=0代表跨行(经度,down),而axis=1代表跨列(纬度,across)。如果不指定axis,则所有元素参与计算。

  • 计算张量沿着指定维度的平均值
tf.reduce_mean(张量名,axis=操作轴)
  • 计算张量沿着指定维度的和
tf.reduce_sum(张量名,axis=操作轴)

源码:p25_reduce_meansum.py

x = tf.constant([[1, 2, 3], [2, 2, 3]])
print("x:", x)
print("mean of x:", tf.reduce_mean(x))  # 求x中所有数的均值
print("sum of x:", tf.reduce_sum(x, axis=1))  # 求每一行的和

​ 运行结果:

x: tf.Tensor(
[[1 2 3][2 2 3]], shape=(2, 3), dtype=int32)
mean of x: tf.Tensor(2, shape=(), dtype=int32)
sum of x: tf.Tensor([6 7], shape=(2,), dtype=int32)

3.3.3 tf.Variable


​ 可利用

tf.Variable(initial_value,trainable,validate_shape,name)

​ 函数可以将变量标记为“可训练”的,被它标记了的变量,会在反向传播中记录自己的梯度信息。

​ 其中initial_value默认为None,可以搭配tensorflow随机生成函数来初始化参数;trainable默认为True,表示可以后期被算法优化的,如果不想该变量被优化,即改为False;validate_shape默认为True,形状不接受更改,如果需要更改,validate_shape=False;name默认为None,给变量确定名称。

​ 举例如下:

w = tf.Variable(tf.random.normal([2, 2], mean=0, stddev=1))

​ 表示首先随机生成正态分布随机数,再给生成的随机数标记为可训练,这样在反向传播中就可以通过梯度下降更新参数w了。

3.3.4 tf.add,tf.subtract,tf.multiply,tf.divide


注:只有维度相同的张量才可以做四则运算

  • 实现两个张量的对应元素相加
tf.add(张量1,张量2)
  • 实现两个张量的对应元素相减
tf.subtract(张量1,张量2)
  • 实现两个张量的对应元素相乘
tf.multiply(张量1,张量2)
  • 实现两个张量的对应元素相除
tf.divide(张量1,张量2)

源码:p29_add_subtract_multiply_divide.py

a = tf.ones([1, 3])
b = tf.fill([1, 3], 3.)
print(a)
print(b)
print(tf.add(a,b))
print(tf.subtract(a,b))
print(tf.multiply(a,b))
print(tf.divide(b,a))

​ 运行结果:

tf.Tensor([[1. 1. 1.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32
tf.Tensor([[4. 4. 4.]], shape=(1, 3), dtype=float32)
tf.Tensor([[-2. -2. -2.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)

3.3.5 tf.square,tf.pow ,tf.sqrt


  • 计算某个张量的平方
tf.square(张量名)
  • 计算某个张量的n次方
tf.pow(张量名,n次方数)
  • 计算某个张量的开方
tf.sqrt(张量名)

源码:p30_square_pow_sqrt.py

a = tf.fill([1, 2], 3.)
print(a)
print(tf.pow(a, 3))
print(tf.square(a))
print(tf.sqrt(a))

​ 运行结果:

tf.Tensor([[3. 3.]], shape=(1, 2), dtype=float32)
tf.Tensor([[27. 27.]], shape=(1, 2), dtype=float32)
tf.Tensor([[9. 9.]], shape=(1, 2), dtype=float32)
tf.Tensor([[1.7320508 1.7320508]], shape=(1, 2), dtype=float32)

3.3.6 tf.matmul


  • 实现两个矩阵的相乘
tf.matmul(矩阵1,矩阵2)

源码:p31_matmul.py

a = tf.ones([3, 2])
b = tf.fill([2, 3], 3.)
print(tf.matmul(a, b))

​ 运行结果:

tf.Tensor(
[[6. 6. 6.]
[6. 6. 6.]
[6. 6. 6.]], shape=(3, 3), dtype=float32)

3.3.7 tf.data.Dataset.from_tensor_slices


  • 切分传入张量的第一维度,生成输入特征/标签对,构建数据集
data = tf.data.Dataset.from_tensor_slices((输入特征, 标签))

​ (Numpy和Tensor格式都可用该语句读入数据)

源码:p33_from_tensor_slices.py

features = tf.constant([12,23,10,17])
labels = tf.constant([0, 1, 1, 0])
dataset = tf.data.Dataset.from_tensor_slices((features, labels))
print(dataset)
for element in dataset:
print(element)

​ 运行结果:

<TensorSliceDatasetshapes: ((),()), types: (tf.int32, tf.int32)>
(<tf.Tensor: id=9, shape=(), dtype=int32, numpy=12>, <tf.Tensor: id=10, shape=(), dtype=int32, numpy=0>)
(<tf.Tensor: id=11, shape=(), dtype=int32, numpy=23>, <tf.Tensor: id=12, shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: id=13, shape=(), dtype=int32, numpy=10>, <tf.Tensor: id=14, shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: id=15, shape=(), dtype=int32, numpy=17>, <tf.Tensor: id=16, shape=(), dtype=int32, numpy=0>)

3.3.8 tf.GradientTape


  • with结构记录计算过程,gradient求出张量的梯度
with tf.GradientTape( ) as tape:
若干个计算过程
grad=tape.gradient(函数,对谁求导)

源码:p34_GradientTape.py

with tf.GradientTape( ) as tape:
w = tf.Variable(tf.constant(3.0))
loss = tf.pow(w,2)
grad = tape.gradient(loss,w)
print(grad)

​ 运行结果:

tf.Tensor(6.0, shape=(), dtype=float32)

​ 在上例中,损失函数为 w 2 w^2 w2, w w w当前取值为3,故计算方式为

∂ w 2 ∂ w = 2 w = 2 × 0.3 = 0.6 \dfrac{\partial w^2}{\partial w}=2w=2\times0.3=0.6 ∂w∂w2​=2w=2×0.3=0.6

3.3.9 enumerate


  • enumerate是python的内建函数,它可遍历每个元素(如列表、元组或字符串),组合为:索引元素,常在for循环中使用
enumerate(列表名)

源码:p35_enumerate.py

seq= ['one', 'two', 'three']
fori, element in enumerate(seq):
print(i, element)

​ 运行结果:

0 one
1 two
2 three

3.3.10 tf.one_hot


​ 独热编码(one-hot encoding):在分类问题中,常用独热码做标签,标记类别:1表示是,0表示非。
​ (0狗尾草鸢尾 1杂色鸢尾 2弗吉尼亚鸢尾)
​ 标 签: 1
​ 独热码:(0. 1. 0.)

  • tf.one_hot()函数将待转换数据,转换为one-hot形式的数据输出
tf.one_hot(待转换数据, depth=几分类)

源码:p37_one_hot.py

classes = 3
labels = tf.constant([1,0,2]) #输入的元素值最小为0,最大为2
output = tf.one_hot( labels, depth=classes )
print(output)

​ 运行结果:

[[0. 1. 0.]
[1. 0. 0.]
[0. 0. 1.]], shape=(3, 3), dtype=float32

3.3.11 tf.nn.softmax


​ 可利用tf.nn.softmax( )函数使前向传播的输出值符合概率分布,进而与独热码形式的标签作比较,其计算公式为 e y i ∑ j = 0 n e y i \frac{e^{y_i}}{\sum_{j=0}^n e^{y_i}} ∑j=0n​eyi​eyi​​,其中 y i y_i yi​是前向传播的输出。在前一部分,我们得到了前向传播的输出值,分别为1.01、2.01、-0.66,通过上述计算公式,可计算对应的概率值:

e y 0 e y 0 + e y 1 + e y 2 = 2.75 10.73 = 0.256 e y 1 e y 0 + e y 1 + e y 2 = 7.46 10.73 = 0.695 e y 2 e y 0 + e y 1 + e y 2 = 0.52 10.73 = 0.048 \begin{aligned} & \frac{e^{y_0}}{e^{y_0}+e^{y_1}+e^{y_2}}=\frac{2.75}{10.73}=0.256 \\ & \frac{e^{y_1}}{e^{y_0}+e^{y_1}+e^{y_2}}=\frac{7.46}{10.73}=0.695 \\ & \frac{e^{y_2}}{e^{y_0}+e^{y_1}+e^{y_2}}=\frac{0.52}{10.73}=0.048 \end{aligned} ​ey0​+ey1​+ey2​ey0​​=10.732.75​=0.256ey0​+ey1​+ey2​ey1​​=10.737.46​=0.695ey0​+ey1​+ey2​ey2​​=10.730.52​=0.048​

​ 上式中,0.256表示为0类鸢尾的概率是25.6%,0.695表示为1类鸢尾的概率是69.5%,0.048表示为2类鸢尾的概率是4.8%。程序实现如下:

源码:p39_softmax.py

y = tf.constant( [1.01, 2.01, -0.66] )
y_pro= tf.nn.softmax(y)
print("After softmax, y_prois:", y_pro)

输出结果:

After softmax, y_prois: tf.Tensor([0.255981740.695830460.0481878], shape=(3,), dtype=float32)

3.3.12 assign_sub


  • 赋值操作,更新参数的值并返回。
  • 调用assign_sub前,先用tf.Variable定义变量w为可训练(可自更新)
w.assign_sub(w要自减的内容)

源码:p40_assign_sub.py

w = tf.Variable(4)
w.assign_sub(1)
print(w)

​ 运行结果:

<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=3>

3.3.13 tf.argmax


  • 返回张量沿指定维度最大值的索引
tf.argmax(张量名,axis=操作轴)

源码:p41_argmax.py

import numpyas np
test = np.array([[1, 2, 3], [2, 3, 4], [5, 4, 3], [8, 7, 2]])
print(test)
print( tf.argmax(test, axis=0)) # 返回每一列(经度)最大值的索引
print( tf.argmax(test, axis=1)) # 返回每一行(纬度)最大值的索引

​ 运行结果:

[[1 2 3]
[2 3 4]
[5 4 3]
[8 7 2]]
tf.Tensor([3 3 1], shape=(3,), dtype=int64)
tf.Tensor([2 2 0 0], shape=(4,), dtype=int64)

(四)程序实现鸢尾花数据集分类


4.1 数据集回顾


​ 先回顾鸢尾花数据集,其提供了150组鸢尾花数据,每组包括鸢尾花的花萼长、花萼宽、花瓣长、花瓣宽4个输入特征,同时还给出了这一组特征对应的鸢尾花类别。类别包括狗尾鸢尾、杂色鸢尾、弗吉尼亚鸢尾三类, 分别用数字0、1、2表示。从sklearn包datasets 读入数据集,语法为:

  • 源码:p43_datasets.load_iris.py*
from sklearn.datasetsimport load_iris
x_data= datasets.load_iris().data 返回iris数据集所有输入特征
y_data= datasets.load_iris().target 返回iris数据集所有标签

​ 即从sklearn包中导出数据集,将输入特征赋值给x_data变量,将对应标签赋值给y_data变量。

4.2 程序实现


​ 我们用神经网络实现鸢尾花分类仅需要三步:

​ (1)准备数据,包括数据集读入、数据集乱序,把训练集和测试集中的数据配成输入特征和标签对,生成train和test即永不相见的训练集和测试集;

​ (2)搭建网络,定义神经网络中的所有可训练参数;

​ (3)优化这些可训练的参数,利用嵌套循环在with结构中求得损失函数loss对每个可训练参数的偏导数,更改这些可训练参数,为了查看效果,程序中可以加入每遍历一次数据集显示当前准确率,还可以画出准确率acc和损失函数loss的变化曲线图。

​ 以上部分的完整代码与解析如下:

源码:p45_iris.py

​ (1) 数据集读入:

from sklearn.datasets import datasets
x_data = datasets.load_iris().data # 返回iris数据集所有输入特征
y_data = datasets.load_iris().target # 返回iris数据集所有标签

​ (2) 数据集乱序:

np.random.seed(116) # 使用相同的seed,使输入特征/标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

​ (3) 数据集分割成永不相见的训练集和测试集:

x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

​ (4) 配成[输入特征,标签]对,每次喂入一小撮(batch):

train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

​ 上述四小部分代码实现了数据集读入、数据集乱序、将数据集分割成永不相见的训练集和测试集、将数据配成[输入特征,标签]对。人类在认识这个世界的时候信息是没有规律的,杂乱无章的涌入大脑的,所以喂入神经网络的数据集也需要被打乱顺序。

​ (2)部分实现了让数据集乱序,因为使用了同样的随机种子,所以打乱顺序后输入特征和标签仍然是一一对应的。

​ (3)部分将打乱后的前120个数据取出来作为训练集,后30个数据作为测试集,为了公正评判神经网络的效果,训练集和测试集没有交集。

​ (4)部分使用from_tensor_slices把训练集的输入特征和标签配对打包,将每32组输入特征标签对打包为一个batch,在喂入神经网络时会以batch为单位喂入。

​ (5) 定义神经网路中所有可训练参数:

w1 = tf.Variable(tf.random.truncated_normal([ 4, 3 ], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([ 3 ], stddev=0.1, seed=1))

​ (6) 嵌套循环迭代,with结构更新参数,显示当前loss:

for epoch in range(epoch): #数据集级别迭代
for step, (x_train, y_train) in enumerate(train_db): #batch级别迭代
with tf.GradientTape() as tape: # 记录梯度信息(前向传播过程计算y)(计算总loss)
grads = tape.gradient(loss, [ w1, b1 ])
w1.assign_sub(lr * grads[0]) #参数自更新
b1.assign_sub(lr * grads[1])
print("Epoch {}, loss: {}".format(epoch, loss_all/4))

​ 上述两部分完成了定义神经网路中所有可训练参数、嵌套循环迭代更新参数。

​ (5)部分定义了神经网络的所有可训练参数。只用了一层网络,因为输入特征是4个,输出节点数等于分类数,是3分类,故参数 w 1 w_1 w1​为4行3列的张量, b 1 b_1 b1​必须与 w 1 w_1 w1​的维度一致,所以是3。

​ (6)部分用两层for循环进行更新参数:第一层for循环是针对整个数据集进行循环,故用epoch表示;第二层for循环是针对batch的,用step表示。在with 结构中计算前向传播的预测结果y,计算损失函数loss
损失函数loss,分别对参数 w 1 w_1 w1​和参数 b 1 b_1 b1​计算偏导数,更新参数 w 1 w_1 w1​和参数 b 1 b_1 b1​的值,打印出这一轮epoch后的损失函数值。因为训练集有120组数据,batch是32,每个step只能喂入32组数据,需要batch级别循环4次,所以loss除以4,求得每次step迭代的平均loss。
​ (7) 计算当前参数前向传播后的准确率,显示当前准确率acc:

for x_test, y_test in test_db:
y = tf.matmul(h, w) + b # y为预测结果
y = tf.nn.softmax(y) # y符合概率分布
pred = tf.argmax(y, axis=1) # 返回y中最大值的索引即预测的分类
pred = tf.cast(pred, dtype=y_test.dtype) # 调整数据类型与标签一致
correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
correct = tf.reduce_sum (correct) # 将每个batch的correct数加起来
total_correct += int (correct) # 将所有batch中的correct数加起来
total_number += x_test.shape [0]
acc = total_correct / total_number
print("test_acc:", acc)

​ (8) acc / loss可视化:

plt.title('Acc Curve') # 图片标题
plt.xlabel('Epoch') # x轴名称
plt.ylabel('Acc') # y轴名称
plt.plot(test_acc, label="$Accuracy$") # 逐点画出test_acc值并连线
plt.legend()
plt.show()

​ 上述两部分完成了对准确率的计算并可视化准确率与loss。

​ (7)部分前向传播计算出y,使其符合概率分布并找到最大的概率值对应的索引号,调整数据类型与标签一致,如果预测值和标签相等则correct变量自加一,准确率即预测对了的数量除以测试集中的数据总数。

​ (9)部分可将计算出的准确率画成曲线图,通过设置图标题、设置x轴名称、设置y轴名称,标出每个epoch时的准确率并画出曲线,可用同样方法画出loss曲线。结果图如图4.1与4.2。

图4.1 训练过程loss曲线

图4.2 训练过程准确率曲线

二、神经网络优化


(一)神经网络(NN)复杂度


1.1 时间复杂度


即模型的运算次数,可用浮点运算次数(FPLOPs, FLoating-point OPerations)或者乘加运算次数衡量。

1.2 空间复杂度


空间复杂度(访存量),严格来讲包括两部分:总参数量 + 各层输出特征图。

  • 参数量:模型所有带参数的层的权重参数总量;
  • 特征图:模型在实时运行过程中每层所计算出的输出特征图大小。

(二) 学习率策略


2.1 指数衰减


指数衰减学习率是先使用较大的学习率来快速得到一个较优的解,然后随着迭代的继续,逐步减小学习率,使得模型在训练后期更加稳定。指数型学习率衰减法是最常用的衰减方法,在大量模型中都广泛使用。

tf.keras.optimizers.schedules.ExponentialDecay

tf.keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate, decay_steps, decay_rate, staircase=False, name=None
)

功能:指数衰减学习率策略.
等价API:tf.optimizers.schedules.ExponentialDecay
参数

  • initial_learning_rate: 初始学习率.
  • decay_steps: 衰减步数, staircase为True时有效.
  • decay_rate: 衰减率.
  • staircase: Bool型变量.如果为True, 学习率呈现阶梯型下降趋势.

返回:tf.keras.optimizers.schedules.ExponentialDecay(step)返回计算得到的学习率.
例子

N = 400
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
0.5,
decay_steps=10,
decay_rate=0.9,
staircase=False)
y = []
for global_step in range(N):
lr = lr_schedule(global_step)
y.append(lr)
x = range(N)
plt.figure(figsize=(8,6))
plt.plot(x, y, 'r-')
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Step')
plt.ylabel('Learning Rate')
plt.title('ExponentialDecay')
plt.show()

2.2 分段常数衰减


分段常数衰减可以让调试人员针对不同任务设置不同的学习率,进行精细调参,在任意步长后下降任意数值的learning rate,要求调试人员对模型和数据集有深刻认识.

tf.keras.optimizers.schedules.PiecewiseConstantDecay

tf.keras.optimizers.schedules.PiecewiseConstantDecay(
boundaries, values, name=None
)

功能:分段常数衰减学习率策略.
等价API:tf.optimizers.schedules.PiecewiseConstantDecay
参数

  • boundaries: [step_1, step_2, …, step_n]定义了在第几步进行学习率衰减.
  • values: [val_0, val_1, val_2, …, val_n]定义了学习率的初始值和后续衰减时的具体取值.

返回:tf.keras.optimizers.schedules.PiecewiseConstantDecay(step)返回计算得到的学习率.
例子

N = 400
lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay(
boundaries=[100, 200, 300],
values=[0.1, 0.05, 0.025, 0.001])
y = []
for global_step in range(N):
lr = lr_schedule(global_step)
y.append(lr)
x = range(N)
plt.figure(figsize=(8,6))
plt.plot(x, y, 'r-')
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Step')
plt.ylabel('Learning Rate')
plt.title('PiecewiseConstantDecay')
plt.show()

(三)激活函数


激活函数是用来加入非线性因素的,因为线性模型的表达能力不够。引入非线性激活函数,可使深层神经网络的表达能力更加强大。

优秀的激活函数应满足

  • 非线性: 激活函数非线性时,多层神经网络可逼近所有函数
  • 可微性: 优化器大多用梯度下降更新参数
  • 单调性: 当激活函数是单调的,能保证单层网络的损失函数是凸函数
  • 近似恒等性:$ f(x) \approx x$ 当参数初始化为随机小值时,神经网络更稳定

激活函数输出值的范围

  • 激活函数输出为有限值时,基于梯度的优化方法更稳定
  • 激活函数输出为无限值时,建议调小学习率

常见的激活函数有:sigmoid,tanh,ReLU,Leaky ReLU,PReLU,RReLU,ELU(Exponential Linear Units),softplus,softsign,softmax等


3.1 sigmoid


tf.math.sigmoid

f ( x ) = 1 1 + e − x f\left(x\right)=\frac{1}{1+e^{-x}} f(x)=1+e−x1​

tf.math.sigmoid(
x, name=None
)

功能:计算x每一个元素的sigmoid值.
等价API:tf.nn.sigmoid, tf.sigmoid
参数

  • x: 张量x.

返回:与x shape相同的张量.
例子

x = tf.constant([1., 2., 3.], )
print(tf.math.sigmoid(x))
# 等价实现
print(1/(1+tf.math.exp(-x)))
>>> tf.Tensor([0.7310586 0.880797 0.95257413], shape=(3,), dtype=float32)

优点

  1. 输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可用作输出层;
  2. 求导容易。

缺点

  1. 易造成梯度消失;
  2. 输出非0均值,收敛慢;
  3. 幂运算复杂,训练时间长。

sigmoid函数可应用在训练过程中。然而,当处理分类问题作出输出时,sigmoid却无能为力。简单地说,sigmoid函数只能处理两个类,不适用于多分类问题。而softmax可以有效解决这个问题,并且softmax函数大都运用在神经网路中的最后一层网络中,使得值得区间在(0,1)之间,而不是二分类的。

函数图像

导数图像

3.2 tanh


tf.math.tanh

f ( x ) = 1 − e − 2 x 1 + e − 2 x f(x)=\frac{1-e^{-2 x}}{1+e^{-2 x}} f(x)=1+e−2x1−e−2x​

tf.math.tanh(
x, name=None
)

功能:计算x每一个元素的双曲正切值.
等价API:tf.nn.tanh, tf.tanh
参数:

  • x: 张量x.

返回:与x shape相同的张量.
例子:

x = tf.constant([-float("inf"), -5, -0.5, 1, 1.2, 2, 3, float("inf")])
print(tf.math.tanh(x))
# 等价实现
print((tf.math.exp(x)-tf.math.exp(-x))/(tf.math.exp(x)+tf.math.exp(-x)))
>>> tf.Tensor([-1. -0.99990916 -0.46211717 0.7615942 0.8336547 0.9640276
0.9950547 1.], shape=(8,), dtype=float32)

优点

  1. 比sigmoid函数收敛速度更快。
  2. 相比sigmoid函数,其输出以0为中心。

缺点

  1. 易造成梯度消失;
  2. 幂运算复杂,训练时间长。

函数图像

导数图像

3.3 ReLU


tf.nn.relu

f ( x ) = max ⁡ ( 0 , x ) = { 0 x < 0 x x ≥ 0 f(x)=\max (0, x)= \begin{cases}0 & x<0 \\ x & x \geq 0\end{cases} f(x)=max(0,x)={0x​x<0x≥0​

tf.nn.relu(
features, name=None
)

功能:计算修正线性值(rectified linear):max(features, 0).
参数

  • features: 张量.

返回:与features shape相同的张量.
例子

print(tf.nn.relu([-2., 0., -0., 3.]))
>>> tf.Tensor([0. 0. -0. 3.], shape=(4,), dtype=float32)

优点

  1. 解决了梯度消失问题(在正区间);
  2. 只需判断输入是否大于0,计算速度快;
  3. 收敛速度远快于sigmoid和tanh,因为sigmoid和tanh涉及很多expensive的操作;
  4. 提供了神经网络的稀疏表达能力。

缺点

  1. 输出非0均值,收敛慢;
  2. Dead ReLU问题:某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。

函数图像

导数图像

3.4 Leaky ReLU


tf.nn.leaky_relu

f ( x ) = m a x ( α x , x ) f(x)=max(\alpha x,x) f(x)=max(αx,x)

tf.nn.leaky_relu(
features, alpha=0.2, name=None
)

功能:计算Leaky ReLU值.
参数

  • features: 张量.
  • alpha: x<0时的斜率值.

返回:与features shape相同的张量.
例子

print(tf.nn.leaky_relu([-2., 0., -0., 3.]))
>>> tf.Tensor([-0.4 0. -0. 3.], shape=(4,), dtype=float32)

理论上来讲,Leaky ReLU有ReLU的所有优点,外加不会有Dead ReLU问题,但是在实际操作当中,并没有完全证明Leaky ReLU总是好于ReLU。

函数图像

导数图像

3.5 softmax


tf.nn.softmax

σ ( z ) j = e z j ∑ k = 1 K e z k f o r j = 1 , … , K \sigma(\text{z})_j=\dfrac{e^{z_j}}{\sum_{k=1}^K e^{z_k}}\quad forj=1,\ldots,K σ(z)j​=∑k=1K​ezk​ezj​​forj=1,…,K

tf.nn.softmax(
logits, axis=None, name=None
)

功能:计算softmax激活值.
等价API:tf.math.softmax
参数

  • logits: 张量.
  • axis: 计算softmax所在的维度. 默认为-1,即最后一个维度.

返回:与logits shape相同的张量.
例子

logits = tf.constant([4., 5., 1.])
print(tf.nn.softmax(logits))
# 等价实现
print(tf.exp(logits) / tf.reduce_sum(tf.exp(logits)))
>>> tf.Tensor([0.26538792 0.7213992 0.01321289], shape=(3,), dtype=float32)

对神经网络全连接层输出进行变换,使其服从概率分布,即每个值都位于[0,1]区间且和为1。

3.6 建议


对于初学者的建议:

  1. 首选ReLU激活函数;
  2. 学习率设置较小值;
  3. 输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布;
  4. 初始化问题:初始参数中心化,即让随机生成的参数满足以0为均值, 2 当前层输入特征个数  \sqrt{\frac{2}{\text { 当前层输入特征个数 }}}  当前层输入特征个数 2​ ​为标准差的正态分布。

(四)损失函数


神经网络模型的效果及优化的目标是通过损失函数来定义的。回归和分类是监督学习中的两个大类。


4.1 均方误差损失函数


均方误差(Mean Square Error)是回归问题最常用的损失函数。回归问题解决的是对具体数值的预测,比如房价预测、销量预测等。这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数。均方误差定义如下:

M S E ( y , y ′ ) = Σ i = 1 n ( y i − y i ′ ) 2 n MSE(y,y^{'})=\dfrac{\Sigma_{i=1}^n(y_i-y_i^{'})^2}{n} MSE(y,y′)=nΣi=1n​(yi​−yi′​)2​

其中 y i y_{i} yi​为一个batch中第i个数据的真实值,而 y i ′ y^{'}_{i} yi′​为神经网络的预测值。

tf.keras.losses.MSE

tf.keras.losses.MSE(
y_true, y_pred
)

功能:计算y_true和y_pred的均方误差.
例子

y_true = tf.constant([0.5, 0.8])
y_pred = tf.constant([1.0, 1.0])
print(tf.keras.losses.MSE(y_true, y_pred))
# 等价实现
print(tf.reduce_mean(tf.square(y_true - y_pred)))
>>> tf.Tensor(0.145, shape=(), dtype=float32)

4.2 交叉熵损失函数


交叉熵(Cross Entropy)表征两个概率分布之间的距离,交叉熵越小说明二者分布越接近,是分类问题中使用较广泛的损失函数。

H ( y _ , y ) = − Σ y _ ∗ l n y H(y\_,y)=-\Sigma y\_*l n y H(y_,y)=−Σy_∗lny

其中 y - y\text{-} y-代表数据的真实值, y y y代表神经网络的预测值。
对于多分类问题,神经网络的输出一般不是概率分布,因此需要引入softmax层,使得输出服从概率分布。TensorFlow中可计算交叉熵损失函数的API有:

tf.keras.losses.categorical_crossentropy

tf.keras.losses.categorical_crossentropy(
y_true, y_pred, from_logits=False, label_smoothing=0
)

功能:计算交叉熵.
等价API:tf.losses.categorical_crossentropy
参数

  • y_true: 真实值.
  • y_pred: 预测值.
  • from_logits: y_pred是否为logits张量.
  • label_smoothing: [0,1]之间的小数.

返回:交叉熵损失值.
例子

y_true = [1, 0, 0]
y_pred1 = [0.5, 0.4, 0.1]
y_pred2 = [0.8, 0.1, 0.1]
print(tf.keras.losses.categorical_crossentropy(y_true, y_pred1))
print(tf.keras.losses.categorical_crossentropy(y_true, y_pred2))
# 等价实现
print(-tf.reduce_sum(y_true * tf.math.log(y_pred1)))
print(-tf.reduce_sum(y_true * tf.math.log(y_pred2)))
>>> tf.Tensor(0.6931472, shape=(), dtype=float32)
tf.Tensor(0.22314353, shape=(), dtype=float32)

tf.nn.softmax_cross_entropy_with_logits

tf.nn.softmax_cross_entropy_with_logits(
labels, logits, axis=-1, name=None
)

功能:logits经过softmax后,与labels进行交叉熵计算.
在机器学习中,对于多分类问题,把未经softmax归一化的向量值称为logits。logits经过softmax
层后,输出服从概率分布的向量。(源自回答)
参数

  • labels: 在类别这一维度上,每个向量应服从有效的概率分布. 例如,在labels的shape为[batch_size, num_classes]的情况下,labels[i]应服从概率分布.
  • logits: 每个类别的激活值,通常是线性层的输出. 激活值需要经过softmax归一化.
  • axis: 类别所在维度,默认是-1,即最后一个维度.

返回:softmax交叉熵损失值.

例子

labels = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
logits = [[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]]
print(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits))
# 等价实现
print(-tf.reduce_sum(labels * tf.math.log(tf.nn.softmax(logits)), axis=1))
>>> tf.Tensor([0.16984606 0.02474495], shape=(2,), dtype=float32)

tf.nn.sparse_softmax_cross_entropy_with_logits

tf.nn.sparse_softmax_cross_entropy_with_logits(
labels, logits, name=None
)

功能:labels经过one-hot编码,logits经过softmax,两者进行交叉熵计算. 通常labels的shape为[batch_size],logits的shape为[batch_size, num_classes]. sparse可理解为对labels进行稀疏化处理(即进行one-hot编码).
参数

  • labels: 标签的索引值.
  • logits: 每个类别的激活值,通常是线性层的输出. 激活值需要经过softmax归一化.

返回:softmax交叉熵损失值.
例子:(下例中先对labels进行one-hot编码为[[1,0,0], [0,1,0]],logits经过softmax变为[[0.844,0.114,0.042], [0.007,0.976,0.018]],两者再进行交叉熵运算)

labels = [0, 1]
logits = [[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]]
print(tf.nn.sparse_softmax_cross_entropy_with_logits(labels1, logits))
# 等价实现
print(-tf.reduce_sum(tf.one_hot(labels, tf.shape(logits)[1]) *
tf.math.log(tf.nn.softmax(logits)), axis=1))
>>> tf.Tensor([0.16984604 0.02474492], shape=(2,), dtype=float32)

4.3 自定义损失函数


根据具体任务和目的,可设计不同的损失函数。从老师课件和讲解中对于酸奶预测损失函数的设计,我们可以得知损失函数的定义能极大影响模型预测效果。好的损失函数设计对于模型训练能够起到良好的引导作用。

例如,我们可以看目标检测中的多种损失函数。目标检测的主要功能是定位和识别,损失函数的功能主要就是让定位更精确,识别准确率更高。目标检测任务的损失函数由分类损失(ClassificitionLoss)和回归损失(Bounding Box Regeression Loss)两部分构成。近几年来回归损失主要有Smooth L1 Loss(2015), IoULoss(2016 ACM), GIoU Loss(2019 CVPR), DIoU Loss & CIoU Loss(2020AAAI)等,分类损失有交叉熵、softmax loss、logloss、focal loss等。在此由于篇幅原因不细究,有兴趣的同学可自行研究。主要是给大家一个感性的认知:需要针对特定的背景、具体的任务设计损失函数。

(五)欠拟合与过拟合


欠拟合的解决方法

  • 增加输入特征项
  • 增加网络参数
  • 减少正则化参数

过拟合的解决方法

  • 数据清洗
  • 增大训练集
  • 采用正则化
  • 增大正则化参数

(六)优化器


优化算法可以分成一阶优化和二阶优化算法,其中一阶优化就是指的梯度算法及其变种,而二阶优化一般是用二阶导数(Hessian 矩阵)来计算,如牛顿法,由于需要计算Hessian阵和其逆矩阵,计算量较大,因此没有流行开来。这里主要总结一阶优化的各种梯度下降方法。

深度学习优化算法经历了SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam这样的发展历程。

定义:待优化参数 ω \omega ω,损失函数 f ( ω ) f(\omega) f(ω),初始学习率 α \alpha α,每次迭代一个batch,t表示当前batch迭代的总次数。

  1. 计算损失函数关于当前参数的梯度: g t = ∇ f ( ω t ) = ∂ f ∂ ω t g_t=\nabla f\bigl(\omega_t\bigr)=\dfrac{\partial f}{\partial\omega_t} gt​=∇f(ωt​)=∂ωt​∂f​
  2. 根据历史梯度计算一阶动量和二阶动量: m t = ϕ ( g 1 , g 2 , … , g t ) , V t = ψ ( g 1 , g 2 , … , g t ) m_t=\phi(g_1,g_2,\ldots,g_t),V_t=\psi(g_1,g_2,\ldots,g_t) mt​=ϕ(g1​,g2​,…,gt​),Vt​=ψ(g1​,g2​,…,gt​)
  3. 计算当前时刻的下降梯度: η t = α ⋅ m t / V t \eta_t=\alpha\cdot m_t/\sqrt{V_t} ηt​=α⋅mt​/Vt​ ​
  4. 根据下降梯度进行更新: ω t + 1 = ω t − η t \omega_{t+1}=\omega_t-\eta_t ωt+1​=ωt​−ηt​
    步骤3,4对于各算法都是一致的,主要差别体现在步骤1和2上。

6.1 SGD


6.1.1 vanilla SGD


最初版本的SGD没有动量的概念

m t = g t , V t = 1 m_t=g_t,V_t=1 mt​=gt​,Vt​=1

梯度下降是最简单的:

η t = α ⋅ g t \eta_t=\alpha\cdot g_t ηt​=α⋅gt​

vanilla SGD最大的缺点是下降速度慢,而且可能会在沟壑的两边持续震荡,停留在一个局部最优点。
代码实现:

# sgd
w1.assign_sub(learning_rate * grads[0])
b1.assign_sub(learning_rate * grads[1])

6.1.2 SGD with Momentum


动量法是一种使梯度向量向相关方向加速变化,抑制震荡,最终实现加速收敛的方法。
(Momentum is a method that helps accelerate SGD in the right direction and dampens oscillations. It adds a fraction $\begin{array}{c}\beta\end{array} $of the update vector of the past time step to the current update vector. The momentum term increases for dimensions whose gradients point in the same directions and reduces updates for dimensions whose gradients change directions.)

为了抑制SGD的震荡,SGDM认为梯度下降过程可以加入惯性。下坡的时候,如果发现是陡坡,那就利用惯性跑的快一些。SGDM全称是SGD with Momentum,在SGD基础上引入了一阶动量:

m t = β 1 ⋅ m t − 1 + ( 1 − β 1 ) ⋅ g t m_t=\beta_1\cdot m_{t-1}+(1-\beta_1)\cdot g_t mt​=β1​⋅mt−1​+(1−β1​)⋅gt​

一阶动量是各个时刻梯度方向的指数移动平均值,约等于最近 1 / ( 1 − β 1 ) 1/(1-\beta_{1}) 1/(1−β1​)个时刻的梯度向量和的平均值。也就是说,t 时刻的下降方向,不仅由当前点的梯度方向决定,而且由此前累积的下降方向决定。 β 1 \beta_1 β1​的经验值为0.9,这就意味着下降方向主要偏向此前累积的下降方向,并略微偏向当前时刻的下降方向。

代码实现

# sgd-momentun
beta = 0.9
m_w = beta * m_w + (1 - beta) * grads[0]
m_b = beta * m_b + (1 - beta) * grads[1]
w1.assign_sub(learning_rate * m_w)
b1.assign_sub(learning_rate * m_b)

6.1.3 SGD with Nesterov Acceleration


SGD 还有一个问题是会被困在一个局部最优点里。就像被一个小盆地周围的矮山挡住了视野,看不到更远的更深的沟壑。
NAG全称Nesterov Accelerated Gradient,是在SGD、SGDM的基础上的进一步改进,改进点在于步骤1。我们知道在时刻t的主要下降方向是由累积动量决定的,自己的梯度方向说了也不算,那与其看当前梯度方向,不如先看看如果跟着累积动量走了一步,那个时候再怎么走。因此,NAG在步骤1不计算当前位置的梯度方向,而是计算如果按照累积动量走了一步,考虑这个新地方的梯度方向。此时的梯度就变成了:

g t = ∇ f ( ω t − α ⋅ m t − 1 ) g_t=\nabla f(\omega_t-\alpha\cdot m_{t-1}) gt​=∇f(ωt​−α⋅mt−1​)

我们用这个梯度带入 SGDM 中计算 的式子里去,然后再计算当前时刻应有的梯度并更新这一次的参数。
其基本思路如下图

首先,按照原来的更新方向更新一步(棕色线),然后计算该新位置的梯度方向(红色线),然后用这个梯度方向修正最终的更新方向(绿色线)。上图中描述了两步的更新示意图,其中蓝色线是标准momentum更新路径。

6.2 AdaGrad


TensorFlow API: tf.keras.optimizers.Adagrad

上述SGD算法一直存在一个超参数(Hyper-parameter),即学习率。超参数是训练前需要手动选择的参数,前缀“hyper”就是用于区别训练过程中可自动更新的参数。学习率可以理解为参数 沿着梯度 反方向变化的步长。
SGD对所有的参数使用统一的、固定的学习率,一个自然的想法是对每个参数设置不同的学习率,然而在大型网络中这是不切实际的。因此,为解决此问题,AdaGrad算法被提出,其做法是给学习率一个缩放比例,从而达到了自适应学习率的效果(Ada = Adaptive)。其思想是:对于频繁更新的参数,不希望被单个样本影响太大,我们给它们很小的学习率;对于偶尔出现的参数,希望能多得到一些信息,我们给它较大的学习率。
那怎么样度量历史更新频率呢?为此引入二阶动量——该维度上,所有梯度值的平方和:

V t = ∑ τ = 1 t g τ 2 V_t=\sum\limits_{\tau=1}^{t}g_\tau^2 Vt​=τ=1∑t​gτ2​

回顾步骤 3 中的下降梯度: η t = α ⋅ m t / V t \eta_t=\alpha\cdot m_t/\sqrt{V_t} ηt​=α⋅mt​/Vt​ ​ 可视为 η t = α V t ⋅ m t \eta_t=\frac{\alpha}{\sqrt{V_t}}\cdot m_t ηt​=Vt​ ​α​⋅mt​ ,即对学习率进行缩放。(一般为了防止分母为 0 ,会对二阶动量加一个平滑项,即 η t = α ⋅ m t / V t + ε \eta_{t}=\alpha\cdot m_{t}/\sqrt{V_{t}+\varepsilon} ηt​=α⋅mt​/Vt​+ε ​, E \mathcal{E} E是一个非常小的数。)

AdaGrad 在稀疏数据场景下表现最好。因为对于频繁出现的参数,学习率衰减得快;对于稀疏的参数,学习率衰减得更慢。然而在实际很多情况下,二阶动量呈单调递增,累计从训练开始的梯度,学习率会很快减至 0 ,导致参数不再更新,训练过程提前结束。

代码实现:

# adagrad
v_w += tf.square(grads[0])
v_b += tf.square(grads[1])
w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

6.3 RMSProp


TensorFlow API: tf.keras.optimizers.RMSprop
RMSProp算法的全称叫 Root Mean Square Prop,是由Geoffrey E. Hinton提出的一种优化算法(Hinton的课件见下图)。由于 AdaGrad 的学习率衰减太过激进,考虑改变二阶动量的计算策略:不累计全部梯度,只关注过去某一窗口内的梯度。修改的思路很直接,前面我们说过,指数移动平均值大约是过去一段时间的平均值,反映“局部的”参数信息,因此我们用这个方法来计算二阶累积动量:

m t = g t V t = β 2 ⋅ V t − 1 + ( 1 − β 2 ) ⋅ g t 2 m_t=g_t\quad V_t=\beta_2\cdot V_{t-1}+(1-\beta_2)\cdot g_t^2 mt​=gt​Vt​=β2​⋅Vt−1​+(1−β2​)⋅gt2​

η t = α V t ⋅ m t = α β 2 ⋅ V t − 1 + ( 1 − β 2 ) ⋅ g t 2 ⋅ g t ω t + 1 = ω t − η t \begin{array}{c}\eta_t=\dfrac{\alpha}{\sqrt{V_t}}\cdot m_t=\dfrac{\alpha}{\sqrt{\beta_2\cdot V_{t-1}+(1-\beta_2)\cdot g_t^2}}\cdot g_t\\ \omega_{t+1}=\omega_t-\eta_t\end{array} ηt​=Vt​ ​α​⋅mt​=β2​⋅Vt−1​+(1−β2​)⋅gt2​ ​α​⋅gt​ωt+1​=ωt​−ηt​​

下图是来自Hinton的Lecture:

代码实现:

# RMSProp
beta = 0.9
v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

6.4 AdaDelta


TensorFlow API: tf.keras.optimizers.Adadelta
为解决AdaGrad的学习率递减太快的问题,RMSProp和AdaDelta几乎同时独立被提出。
我们先看论文的AdaDelta算法,下图来自原论文:

对于上图算法的一点解释, 是梯度 RM S [ g ] t \textit{RM}S[g]_t RMS[g]t​的均方根(Root Mean Square), R M S [ Δ x ] t − 1 RMS[\Delta x]_{t-1} RMS[Δx]t−1​ 是 Δ x \Delta x Δx的均方根:

R M S [ g ] t = E [ g 2 ] t R M S [ Δ x ] t − 1 = E [ Δ x 2 ] t − 1 \begin{array}{c}RMS[g]_t=\sqrt{E[g^2]_t}\\ RMS[\Delta x]_{t-1}=\sqrt{E[\Delta x^2]_{t-1}}\end{array} RMS[g]t​=E[g2]t​ ​RMS[Δx]t−1​=E[Δx2]t−1​ ​​

我们可以看到AdaDelta与RMSprop仅仅是分子项不同,为了与前面公式保持一致,在此用 U t \sqrt{U_t} Ut​ ​表示 η \eta η的均方根:

m t = g t V t = β 2 ⋅ V t − 1 + ( 1 − β 2 ) ⋅ g t 2 m_t=g_t\quad V_t=\beta_2\cdot V_{t-1}+(1-\beta_2)\cdot g_t^2 mt​=gt​Vt​=β2​⋅Vt−1​+(1−β2​)⋅gt2​

η t = U t − 1 V t ⋅ m t = U t − 1 β 2 ⋅ V t − 1 + ( 1 − β 2 ) ⋅ g t 2 ⋅ g t U t = β 2 ⋅ U t − 1 + ( 1 − β 2 ) ⋅ η t 2 ω t + 1 = ω t − η t \begin{aligned}\eta_t=\frac{\sqrt{U_{t-1}}}{\sqrt{V_t}}\cdot m_t=\frac{\sqrt{U_{t-1}}}{\sqrt{\beta_2\cdot V_{t-1}+(1-\beta_2)\cdot g_t^2}}\cdot g_t\\ U_t=\beta_2\cdot U_{t-1}+(1-\beta_2)\cdot\eta_t^2\\ \omega_{t+1}=\omega_t-\eta_t\end{aligned} ηt​=Vt​ ​Ut−1​ ​​⋅mt​=β2​⋅Vt−1​+(1−β2​)⋅gt2​ ​Ut−1​ ​​⋅gt​Ut​=β2​⋅Ut−1​+(1−β2​)⋅ηt2​ωt+1​=ωt​−ηt​​

代码实现:

# AdaDelta
beta = 0.999
v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
delta_w = tf.sqrt(u_w) * grads[0] / tf.sqrt(v_w)
delta_b = tf.sqrt(u_b) * grads[1] / tf.sqrt(v_b)
u_w = beta * u_w + (1 - beta) * tf.square(delta_w)
u_b = beta * u_b + (1 - beta) * tf.square(delta_b)
w1.assign_sub(delta_w)
b1.assign_sub(delta_b)

6.5 Adam


TensorFlow API: tf.keras.optimizers.Adam
Adam名字来源是adaptive moment estimation。Our method is designed to combine theadvantages of two recently popular methods: AdaGrad (Duchi et al., 2011), which works wellwith sparse gradients, and RMSProp (Tieleman & Hinton, 2012), which works well in on-line andnon-stationary settings。也就是说,adam融合了Adagrad和RMSprop的思想。
谈到这里,Adam的出现就很自然而然了——它们是前述方法的集大成者。我们看到,SGDM在SGD基础上增加了一阶动量,AdaGrad、RMSProp和AdaDelta在SGD基础上增加了二阶动量。把一阶动量和二阶动量结合起来,再修正偏差,就是Adam了。
SGDM的一阶动量:

m t = β 1 ⋅ m t − 1 + ( 1 − β 1 ) ⋅ g t m_t=\beta_1\cdot m_{t-1}+(1-\beta_1)\cdot g_t mt​=β1​⋅mt−1​+(1−β1​)⋅gt​

加上RMSProp的二阶动量:

V t = β 2 ⋅ V t − 1 + ( 1 − β 2 ) ⋅ g t 2 V_t=\beta_2\cdot V_{t-1}+(1-\beta_2)\cdot g_t^2 Vt​=β2​⋅Vt−1​+(1−β2​)⋅gt2​

加上RMSProp的二阶动量:
其中,参数经验值是 β 1 = 0.9 , β 2 = 0.999 \beta_1=0.9,\beta_2=0.999 β1​=0.9,β2​=0.999 。

一阶动量和二阶动量都是按照指数移动平均值进行计算的。初始化 m 0 = 0 , V 0 = 0 m_0=0,V_0=0 m0​=0,V0​=0,在初期,迭
代得到的 m t m_t mt​会接近于0。我们可以通过对 m t m_t mt​进行偏差修正来解决这一问题:

m ^ t = m t 1 − β 1 t V ^ t = V t 1 − β 2 t \begin{array}{c}\hat m_t=\dfrac{m_t}{1-\beta_1^t}\\ \hat V_t=\dfrac{V_t}{1-\beta_2^t}\end{array} m^t​=1−β1t​mt​​V^t​=1−β2t​Vt​​​

代码实现:

# adam
m_w = beta1 * m_w + (1 - beta1) * grads[0]
m_b = beta1 * m_b + (1 - beta1) * grads[1]
v_w = beta2 * v_w + (1 - beta2) * tf.square(grads[0])
v_b = beta2 * v_b + (1 - beta2) * tf.square(grads[1])
m_w_correction = m_w / (1 - tf.pow(beta1, int(global_step)))
m_b_correction = m_b / (1 - tf.pow(beta1, int(global_step)))
v_w_correction = v_w / (1 - tf.pow(beta2, int(global_step)))
v_b_correction = v_b / (1 - tf.pow(beta2, int(global_step)))
w1.assign_sub(learning_rate * m_w_correction / tf.sqrt(v_w_correction))
b1.assign_sub(learning_rate * m_b_correction / tf.sqrt(v_b_correction))

6.6 优化器选择


很难说某一个优化器在所有情况下都表现很好,我们需要根据具体任务选取优化器。一些优化器在计算机视觉任务表现很好,另一些在涉及RNN网络时表现很好,甚至在稀疏数据情况下表现更出色。
总结上述,基于原始SGD增加动量和Nesterov动量,RMSProp是针对AdaGrad学习率衰减过快的改进,它与AdaDelta非常相似,不同的一点在于AdaDelta采用参数更新的均方根(RMS)作为分子。Adam在RMSProp的基础上增加动量和偏差修正。如果数据是稀疏的,建议用自适用方法,即Adagrad, RMSprop, Adadelta, Adam。RMSprop, Adadelta, Adam 在很多情况下的效果是相似的。随着梯度变的稀疏,Adam 比 RMSprop 效果会好。总的来说,Adam整体上是最好的选择。
然而很多论文仅使用不带动量的vanilla SGD和简单的学习率衰减策略。SGD通常能够达到最小点,但是相对于其他优化器可能要采用更长的时间。采取合适的初始化方法和学习率策略,SGD更加可靠,但也有可能陷于鞍点和极小值点。因此,当在训练大型的、复杂的深度神经网络时,我们想要快速收敛,应采用自适应学习率策略的优化器。
如果是刚入门,优先考虑Adam或者SGD+Nesterov Momentum。

算法没有好坏,最适合数据的才是最好的,永远记住:No free lunch theorem。

6.7 优化算法的常用tricks


  1. 首先,各大算法孰优孰劣并无定论。如果是刚入门,优先考虑SGD+Nesterov Momentum或者
    Adam.(Standford 231n : The two recommended updates to use are either
    SGD+Nesterov Momentum or Adam)

  2. 选择你熟悉的算法——这样你可以更加熟练地利用你的经验进行调参。

  3. 充分了解你的数据——如果模型是非常稀疏的,那么优先考虑自适应学习率的算法。

  4. 根据你的需求来选择——在模型设计实验过程中,要快速验证新模型的效果,可以先用Adam进
    行快速实验优化;在模型上线或者结果发布前,可以用精调的SGD进行模型的极致优化。

  5. 先用小数据集进行实验。有论文研究指出,随机梯度下降算法的收敛速度和数据集的大小的关系
    不大。(The mathematics of stochastic gradient descent are amazingly independent of
    the training set size. In particular, the asymptotic SGD convergence rates are
    independent from the sample size.)因此可以先用一个具有代表性的小数据集进行实验,测试
    一下最好的优化算法,并通过参数搜索来寻找最优的训练参数。

  6. 考虑不同算法的组合。先用Adam进行快速下降,而后再换到SGD进行充分的调优。

  7. 充分打乱数据集(shuffle)。这样在使用自适应学习率算法的时候,可以避免某些特征集中出
    现,而导致的有时学习过度、有时学习不足,使得下降方向出现偏差的问题。在每一轮迭代后对训
    练数据打乱是一个不错的主意。

  8. 训练过程中持续监控训练数据和验证数据上的目标函数值以及精度或者AUC等指标的变化情况。
    对训练数据的监控是要保证模型进行了充分的训练——下降方向正确,且学习率足够高;对验证数
    据的监控是为了避免出现过拟合。

  9. 制定一个合适的学习率衰减策略。可以使用分段常数衰减策略,比如每过多少个epoch就衰减一
    次;或者利用精度或者AUC等性能指标来监控,当测试集上的指标不变或者下跌时,就降低学习
    率。

  10. Early stopping。如Geoff Hinton所说:“Early Stopping是美好的免费午餐”。你因此必须在训
    练的过程中时常在验证集上监测误差,在验证集上如果损失函数不再显著地降低,那么应该提前结
    束训练。

  11. 算法参数的初始值选择。 初始值不同,获得的最小值也有可能不同,因此梯度下降求得的只是局
    部最小值;当然如果损失函数是凸函数则一定是最优解。由于有局部最优解的风险,需要多次用不
    同初始值运行算法,关键损失函数的最小值,选择损失函数最小化的初值。

三、神经网络搭建八股


(一)tf.keras搭建网络八股


1.1 keras介绍


tf.keras是tensorflow2引入的高封装度的框架,可以用于快速搭建神经网络模型,keras为支持快速实验而生,能够把想法迅速转换为结果,是深度学习框架之中最终易上手的一个,它提供了一致而简洁的API,能够极大地减少一般应用下的工作量,提高代码地封装程度和复用性。
Keras官方文档:
https://tensorflow.google.cn/api_docs/python/tf
深度学习编程框架中的API众多,就算是从业很久的算法工程师也不可能记住所有的API。由于本课程时间有限,只覆盖了tensorflow2系列中的最常用的几个API,仍然还有很多需要在今后的实践中继续学习,这时我们就需要参考tensorflow的官方文档,通过阅读源码和注释的方法学习API。通常有两种方法。

第一种:在pycharm集成开发环境中查看框架源码

将鼠标放置在函数上按住Ctrl键,会显示函数的基本信息。
按住Ctrl键点击函数会跳转到函数的源代码部分,使用者可以根据源码和注释进一步了解函数的实现方法。

第二种:在tensorflow官网中查询函数文档

1.2 搭建神经网络六步法


tf.keras搭建神经网络六步法

  1. import相关模块,如import tensorflow as tf。
  2. 指定输入网络的训练集和测试集,如指定训练集的输入x_train和标签y_train,测试集的输入x_test和标签y_test。
  3. 逐层搭建网络结构,model = tf.keras.models.Sequential()。
  4. 在model.compile()中配置训练方法,选择训练时使用的优化器、损失函数和最终评价指标。
  5. 在model.fit()中执行训练过程,告知训练集和测试集的输入值和标签、每个batch的大小(batchsize)和数据集的迭代次数(epoch)。
  6. 使用model.summary()打印网络结构,统计参数数目。

(二)iris数据集代码复现


import tensorflow as tf
from sklearn import datasets
import numpy as npx_train = datasets.load_iris().data
y_train = datasets.load_iris().targetnp.random.seed(116)
np.random.shuffle(x_train)
np.random.seed(116)
np.random.shuffle(y_train)
tf.random.set_seed(116)model = tf.keras.models.Sequential([tf.keras.layers.Dense(3, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())
])model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.1),loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),metrics=['sparse_categorical_accuracy'])model.fit(x_train, y_train, batch_size=32, epochs=500, validation_split=0.2, validation_freq=20)model.summary()

第一步:import相关模块:

import tensorflow as tf
from sklearn import datasets
import numpy as np

第二步:指定输入网络地训练集和测试集:

x_train = datasets.load_iris().data
y_train = datasets.load_iris().target

其中测试集的输入特征x_test和标签y_test可以像x_train和y_train一样直接从数据集获取,也可以如上述在fit中按比例从训练集中划分,本例选择从训练集中划分,所以只需加载x_train,y_train即可。

np.random.seed(116)
np.random.shuffle(x_train)
np.random.seed(116)
np.random.shuffle(y_train)
tf.random.set_seed(116)

以上代码实现了数据集的乱序。

第三步:逐层搭建网络结构:

model = tf.keras.models.Sequential([
tf.keras.layers.Dense(3, activation='softmax',
kernel_regularizer=tf.keras.regularizers.l2())
])

如上所示,本例使用了单层全连接网络,第一个参数表示神经元个数,第二个参数表示网络所使用的激活函数,第三个参数表示选用的正则化方法。
使用Sequential可以快速搭建网络结构,但是如果网络包含跳连等其他复杂网络结构,Sequential就无法表示了。这就需要使用class来声明网络结构。

class MyModel(Model):
def __init__(self):
super(MyModel, self).__init__()
//初始化网络结构
def call(self, x):
y = self.d1(x)
return y

使用class类封装网络结构,如上所示是一个class模板,MyModel表示声明的神经网络的名字,括号中的Model表示创建的类需要继承tensorflow库中的Model类。类中需要定义两个函数,init()函数为类的构造函数用于初始化类的参数,spuer(MyModel,self).init()这行表示初始化父类的参数。之后便可初始化网络结构,搭建出神经网络所需的各种网络结构块。call()函数中调用__init__()函数中完成初始化的网络块,实现前向传播并返回推理值。使用class方式搭建鸢尾花网络结构的代码如下所示。

class IrisModel(Model):
def __init__(self):
super(IrisModel, self).__init__()
self.d1 = Dense(3, activation='sigmoid', kernel_regularizer=tf.keras.regularizers.l2())
def call(self, x):
y = self.d1(x)
return y

搭建好网络结构后只需要使用Model=MyModel()构建类的对象,就可以使用该模型了。

import tensorflow as tf
from tensorflow.keras.layers import Dense
from tensorflow.keras import Model
from sklearn import datasets
import numpy as npx_train = datasets.load_iris().data
y_train = datasets.load_iris().targetnp.random.seed(116)
np.random.shuffle(x_train)
np.random.seed(116)
np.random.shuffle(y_train)
tf.random.set_seed(116)class IrisModel(Model):def __init__(self):super(IrisModel, self).__init__()self.d1 = Dense(3, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())def call(self, x):y = self.d1(x)return ymodel = IrisModel()model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.1),loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),metrics=['sparse_categorical_accuracy'])model.fit(x_train, y_train, batch_size=32, epochs=500, validation_split=0.2, validation_freq=20)
model.summary()

对比使用Sequential()方法和class方法,有两点区别:
①import中添加了Model模块和Dense层、Flatten层。
②使用class声明网络结构,model = IrisModel()初始化模型对象。

第四步:在model.compile()中配置训练方法:

model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.1),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False,
metrics=['sparse_categorical_accuracy'])

如上所示,本例使用SGD优化器,并将学习率设置为0.1,选择SparseCategoricalCrossentrop作为损失函数。由于神经网络输出使用了softmax激活函数,使得输出是概率分布,而不是原始输出,所以需要将from_logits参数设置为False。鸢尾花数据集给的标签是0,1,2这样的数值,而网络前向传播的输出为概率分布,所以metrics需要设置为sparse_categorical_accuracy。

第五步:在model.fit()中执行训练过程:

model.fit(x_train,y_train,batch_size=32,epochs=500, validation_split = 0.2,validation_freq=20)

在fit中执行训练过程,x_train,y_train分别表示网络的输入特征和标签,batch_size表示一次喂入神经网络的数据量,epochs表示数据集的迭代次数validation_split表示数据集中测试集的划分比例,validation_freq表示每迭代20次在测试集上测试一次准确率。

第六步:使用model.summary()打印网络结构,统计参数数目:

model.summary()

(三)MNIST数据集


3.1 介绍


MNIST数据集一共有7万张图片,是28×28像素的0到9手写数字数据集,其中6万张用于训练,1万张用于测试。每张图片包括784(28×28)个像素点,使用全连接网络时可将784个像素点组成长度为784的一维数组,作为输入特征。

数据集图片如下所示:

3.2 导入数据集


keras函数库中提供了使用mnist数据集的接口,代码如下所示,可以使用load_data()直接从mnist中读取测试集和训练集。

mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

输入全连接网络时需要先将数据拉直为一维数组,把784个像素点的灰度值作为输入特征输入神经网络。

tf.keras.layers.Flatten()

使用plt库中的两个函数可视化训练集中的图片。

plt.imshow(x_train[0],cmap=’gray’)
plt.show()

使用print打印出训练集中第一个样本以二位数组的形式打印出来,如下所示。

print(“x_train[0]:”,x_train[0])

打印出第一个样本的标签,为5。

print(“y_train[0]:”,y_train[0])
y_train[0]:5

打印出测试集样本的形状,共有10000个28行28列的三维数据。

print(“x_test.shape:”x_test.shape)
x_test.shape:(10000,28,28)

3.3 训练MNIST数据集


使用Sequential实现手写数字识别

import tensorflow as tfmnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),tf.keras.layers.Dense(128, activation='relu'),tf.keras.layers.Dense(10, activation='softmax')
])model.compile(optimizer='adam',loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),metrics=['sparse_categorical_accuracy'])model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1)
model.summary()

使用class实现手写数字识别

import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras import Modelmnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0class MnistModel(Model):def __init__(self):super(MnistModel, self).__init__()self.flatten = Flatten()self.d1 = Dense(128, activation='relu')self.d2 = Dense(10, activation='softmax')def call(self, x):x = self.flatten(x)x = self.d1(x)y = self.d2(x)return ymodel = MnistModel()model.compile(optimizer='adam',loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),metrics=['sparse_categorical_accuracy'])model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1)
model.summary()

值得注意的是训练时需要将输入特征的灰度值归一化到[0,1]区间,这可以使网络更快收敛。

训练时每个step给出的是训练集accuracy不具有参考价值,有实际评判价值的是validation_freq中设置的隔若干轮输出的测试集accuracy。

(四)Fashion_mnist数据集


Fashion_mnist数据集具有mnist近乎所有的特征,包括60000张训练图片和10000张测试图片,图片被分为十类,每张图像为28×28的分辨率。
类别如下所示:

图片事例如下所示:

由于Fashion_mnist数据集和mnist数据集具有相似的属性,所以对于mnist只需讲mnist数据集的加载换成Fashion_mnist就可以训练Fashion数据集了。

代码如下所示:

import tensorflow as tffashion = tf.keras.datasets.fashion_mnist
(x_train, y_train),(x_test, y_test) = fashion.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),tf.keras.layers.Dense(128, activation='relu'),tf.keras.layers.Dense(10, activation='softmax')
])model.compile(optimizer='adam',loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),metrics=['sparse_categorical_accuracy'])model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1)
model.summary()

人工智能实践:tensorflow笔记相关推荐

  1. 什么是人工智能?——TensorFlow笔记一

    基本概念: 什么是人工智能 强人工智能(BOTTOM-UP AI) 弱人工智能(TOP-DOWN AI) 人工智能研究价值: 重要人物: 消费级人工智能产品: 国外 国内 人工智能先锋: 什么是人工智 ...

  2. 人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-1)

    人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-1) 说明 一.神经网络计算过程 1. 人工智能三学派 2. 神经网络设计过程 2.1 人脑中的神经网络形成过程 2.2 计算机模仿 ...

  3. 人工智能实践:Tensorflow2.0笔记 北京大学MOOC(2-1)

    人工智能实践:Tensorflow2.0笔记 北京大学MOOC(2-1) 说明 一.神经网络的优化 1. 神经网络复杂度 2. 学习率策略 2.1 学习率概念回顾 2.2 动态调整学习率 2.2.1 ...

  4. 人工智能实践:TensorFlow笔记学习(八)—— 卷积神经网络实践

    大纲 7.1  复现已有的卷积神经网络 7.2  用vgg16实现图片识别 目标 掌握复现已有网络,用vgg16实现图片识别 7.1  复现已有的卷积神经网络 VGGNet是Karen simonya ...

  5. 人工智能实践:TensorFlow笔记学习(六)—— 全连接网络实践

    输入手写数字输出识别结果 大纲 6.1 输入手写数字图片输出识别结果 6.2 制作数据集 目标 1.实现断点续训 2.输入真实图片,输出预测结果 3.制作数据集,实现特定应用 6.1  输入手写数字图 ...

  6. 人工智能实践:TensorFlow笔记学习(五)—— 全连接网络基础

    MNIST数据集输出手写数字识别准确率 大纲 5.1 MNIST数据集 5.2 模块化搭建神经网络 5.3 手写数字识别准确率输出 目标 利用MNIST数据集巩固模块化搭建神经网路的八股,实践前向传播 ...

  7. 人工智能实践:TensorFlow笔记学习(四)—— 神经网络优化

    神经网络优化  大纲 4.1 损失函数 4.2 学习率 4.3 滑动平均 4.4 正则化 4.5 神经网络搭建八股 目标 掌握神经网络优化方法 4.1 损失函数 神经元模型:用数学公式表示为:,f为激 ...

  8. 人工智能实践:TensorFlow笔记学习(三)——TensorFlow框架

    搭建神经网络 大纲 3.1 张量.计算图.会话 3.2 前向传播 3.3 反向传播 目标 搭建神经网络,总结搭建八股 3.1 张量.计算图.会话 一.基本概念 基于Tensorflow的NN:用张量表 ...

  9. 人工智能实践:TensorFlow笔记学习(二)—— Python语法串讲

    Python语法串讲  大纲 2.1 Linux指令.HelloWorld 2.2 列表.元组.字典 2.3 条件语句 2.4 循环语句 2.5 turtle模块 2.6 函数.模块.包 2.7 类. ...

  10. 人工智能实践:TensorFlow笔记学习(一)—— 人工智能概述

    概  述 一. 基本概念  1.什么是人工智能  人工智能的概念:机器模拟人的意识和思维 重要人物:艾伦·麦席森·图灵(Alan Mathison Turing) 人物简介:1912年6月23日-19 ...

最新文章

  1. wordpress给后台文章列表增加自定义排序栏
  2. gcc中extra qualification错误
  3. python 遍历目录_Python遍历目录的4种方法实例介绍
  4. ML之FE:特征工程之数据处理常用案例总结(数值型数据处理、类别型数据处理)及其代码实现
  5. java数据类型后加三个点...
  6. 处理接口超时_架构设计|异步请求如何同步处理?
  7. tensorflow 目标分割_Tensorflow中的控制流和优化器
  8. 错过了面试,公司招满人了_您可能错过了Google令人赞叹的AI公告。
  9. 3-JAVA中的JSON操作
  10. 庆祝自开博来首篇浏览数过万的随笔诞生 - [原创]从程序员角度分析安徽电信HTTP劫持的无耻行径......
  11. 一路风景---我期待的是师生关系
  12. script标签async和defer的区别及作用
  13. 身为华人,我再一次不淡定了,中文输入项目启动
  14. 【牛腩】牛腩新闻发布系统总结
  15. 条码打印软件中如何设置条形码下面的字符间距?
  16. Redis学习(三) - Redis客户端对比及配置(SpringBoot)
  17. PDCA理念融入软件测试
  18. Geforce 错误代码 ERROR CODE:0x0003问题方法
  19. 圆角矩形不是圆:圆角的画法和二阶连续性
  20. 双线性插值 java方式实现

热门文章

  1. Armv8架构虚拟化
  2. VCS仿真学习(5)Debugging with DVE
  3. 图片与mat文件的转换
  4. 知识付费产品复购率低成难题
  5. 湖北武汉施工员报考施工员安全意识的建立方法建筑七大员报考
  6. Tab页面知识整理及其方法分析
  7. PXE和Cobble实现自动装机
  8. 怎么登录163vip邮箱,登录方式有哪些?
  9. Win10系统CLSID大全
  10. matlab 动态优化,基于Matlab的测控系统动态性能优化与仿真