2025年8月

动量优化

想象一下,一个保龄球在光滑的表面上沿着平缓的坡度滚动:它开始速度很慢,在很快会获得动量,直到最终达到终极速度(如果有摩擦或空气阻力)。相比之下,常规的梯度下降法只是在斜坡上采取小的、常规的步骤,因此算法将花费更多时间到达底部

回想一下梯度下降通过直接减去权重的成本函数$J(\theta)$的梯度乘以学习率$\eta(\nabla_\theta J(\theta))$来更新权重$\theta$。等式是:$\theta\gets\theta-\eta\nabla_\theta J(\theta)$。它不关心较早的梯度是什么。如果局部梯度很小,则它会走得非常缓慢。
动量优化非常关心先前的梯度是什么:在每次迭代时,它都会从动量向量$m$(乘以学习率$\eta$)中减去局部梯度,并通过添加该动量向量来更新权重。换句话说,梯度是用于加速度而不是速度。为了模拟某种摩擦机制并防止动量变得过大,该算法引入了一个新的超参数$\beta$,称为动量,必须将其设置为0(高摩擦)和1(无摩擦)之间。典型的动量值为0.9。
动量算法:

  1. $m\gets\beta m-\eta\nabla_\theta J(\theta)$
  2. $\theta\gets\theta+m$

你可以轻松地验证,如果梯度保持恒定,则最终速度(即权重更新的最大大小)等于该梯度乘以学习率$\eta$在乘以$1/(1+\beta)$(忽略符号)。例如,如果$\beta=0.9$,则最终速度等于梯度乘以学习率的10倍!这使得动量优化比梯度下降要更快地从平台逃脱。梯度下降相当快地沿着陡峭的斜坡下降,但是沿着山谷下降需要很长时间。相反,动量优化将沿着山谷滚动得越来越快,直到达到谷底(最优解)。在不使用批量归一化的深层神经网络中,较高的层通常会得到比率不同的输入,因此使用动量优化会有所帮助。它还可以帮助绕过局部优化问题。

由于这种动量势头,优化器可能会稍微过调,然后又回来,再次过调,在稳定于最小点之前会多次振荡。这是在系统中有一些摩擦力的原因之一:它消除了这些振荡,从而加快了收敛速度。

在Keras中实现动量优化,只需要使用SGD优化器并设置其超参数momentum:

optimizer=keras.optimizers.SGD(lr=1e-4,momentum=.9)

动量优化的一个缺点是它增加了另一个超参数来调整。但是,动量0.9通常在实践中效果很好,几乎总是比常规的“梯度下降”更快。

Nesterov加速度

Nesterov加速梯度(Nesterov Accelerated Gradient,NAG)方法也被称为 Nesterov 动量优化,它不是在局部位置$\theta$,而是在$\theta+\beta m$处沿动量方向稍微提前处测量成本函数的梯度

Nesterov 加速梯度算法:

  1. $m\gets\beta m-\eta\nabla_\theta J(\theta+\beta m)$
  2. $\theta\gets\theta+m$

这种小的调整有效是因为通常动量向量会指向正确的方向(即朝向最优解),因为使用在该方向上测得更远的梯度而不是原始位置上的梯度会稍微准确些。

Nesterov 更新会最终稍微接近最优解。一段时间后,这些小的改进累加起来,NAG 就比常规的动量优化要快得多。此外,当动量推动权重跨越谷底时,$\nabla_1$继续推动越过谷底,而$\nabla_2$推回谷底。这有助于减少振荡,因为 NAG 收敛更快。

NAG 通常比常规动量更快,要使用它,只需要在创建 SGD 优化器时设置nesterov=True 即可:

optimizer=keras.optimizers.SGD(lr=1e-4,momentum=.9,nesterov=True)

常规与 Nesterov 动量优化:前者使用在动量步骤之前计算梯度,而后者使用在动量步骤后计算的梯度

AdaGrad

再次考虑拉长的碗状问题:梯度下降从快速沿最陡的坡度下降开始,该坡度没有直接指向全局最优解,然后非常缓慢地下沉到谷底。如果算法可以纠正其方向,使它更多地指向全局最优解,那将是很好的。AdaGrad 算法通过沿最陡峭的维度按比例缩小梯度向量

AdaGrad 算法:

  1. $s\gets s+\nabla_\theta J(\theta)\otimes\nabla_\theta J(\theta)$
  2. $\theta\gets\theta-\eta\nabla_\theta J(\theta)\oslash\sqrt{s+\epsilon}$

第一步将梯度的平方累加到向量${s}$中($\otimes$符号表示逐元素相乘)。此向量化形式等效于针对向量${s}$中的每个元素$s_i$计算$s_i\gets s_i+(\partial J(\theta)/\partial\theta_i)^2$。换句话说,每个$s_i$累加关于参数$\theta_i$的成本函数偏导数的平方。如果成本函数沿第$i$个维度陡峭,则$s_i$将在每次迭代中变得越来越大。

第二步几乎与“梯度下降”相同,但有一个很大的区别:梯度向量按比例因子$\sqrt{{s}+\epsilon}$缩小了($\oslash$符号代表逐元素相除,而$\epsilon$是避免被除以零的平滑项,通常设置为$10^{-10}$)。此向量化形式等效于对所有参数$\theta_i$同时计算$\theta_i\gets\theta_i-\eta\ \partial J(\theta)/\partial\theta_i\ /\ \sqrt{{s_i}+\epsilon}$ 。

简而言之,该算法会降低学习率,但是对于陡峭的维度,它的执行速度要比对缓慢下降的维度的执行速度更快。这称为自适应学习率。它有助于将结果更新更直接的指向全局最优解。另一个好处是,它几乎不需要调整学习率超参数$\eta$。
AdaGrad与梯度下降法:前者可以较早地修正其方向,使之指向最优解
对于简单的二次问题,AdaGrad经常表现良好,但是在训练神经网络时,它往往停止得太早。学习率按比例缩小,以至于算法在最终达到全局最优解之前完全停止了。因此,即使KerasAdaGrad优化器。

RMSProp

AdaGrad有下降太快,永远不会收敛到全局最优解的风险。RMSProp算法通过只是累加最近迭代中的梯度(而不是自训练开始以来的所有梯度)来解决这个问题。它通过在第一步中使用指数衰减。
RMSProp算法:

  1. $s\gets\beta s\ \nabla_\theta J(\theta)\otimes\nabla_\theta J(\theta)$
  2. $\theta\gets\theta-\eta\ \nabla_\theta J(\theta)\oslash\sqrt{s+\epsilon}$
    衰减率$\beta$通常设置为0.9。默认值通常效果很好。
    Keras有RMSProp优化器:

    optimizer=keras.optimizers.RMSProp(lr=1e-4,rho=.9)

    rho参数对应于公式中的$\beta$,除了非常简单的问题外,该优化器几乎总是比AdaGrad表现更好。实际上,直到Adam优化出现之前,它一直是许多研究人员首选的优化算法。

    Adam 和 Nadam 优化

    Adam代表自适应矩估计,结合了动量优化和RMSProp的思想:就像动量优化一样,它跟踪过去梯度的指数衰减平均值。就像RMSProp一样,它跟踪过去平方梯度的指数衰减平均值。
    Adma算法:

  3. $m\gets\beta_1m-(1-\beta_1)\ \nabla_\theta J(\theta)$
  4. $s\gets\beta_2s+(1-\beta_2)\ \nabla_\theta J(\theta)\otimes\nabla_\theta J(\theta)$
  5. $\hat{m}\gets\frac{m}{1-\beta_1^t}$
  6. $\hat{s}\gets\frac{s}{1-\beta_2^t}$
  7. $\theta\gets\theta-\eta\hat{m}\oslash\sqrt{\hat{s}+\epsilon}$

在此等式中,$t$表示迭代次数(从1开始)。
如果只看步骤1、2和5,你会发现Adam与动量优化和RMSProp非常相似。唯一的区别是步骤1计算的是指数衰减的平均值,而不是指数衰减的总和,但除了常数因子(衰减平均值是衰减总和的$1-\beta_1$倍)外,它们实际上是等效的。第三步和第四步在技术上有些细节:由于ms初始化为0,因此在训练开始时它们会偏向0,这两个步骤将有助于在训练开始时提高ms

动量衰减超参数$\beta_1$通常初始化为0.9,而缩放衰减超参数$\beta_2$通常被初始化为0.999。平滑项$\epsilon$通常会初始化为一个很小的数字。这些是Adam类的默认值(准确说,epsilon的默认值为None,它告诉Keras使用keras.backend.espilon(),默认值为$10^{-10}$。可以使用keras.backend.set_epsilon()来改变)。这是使用Keras来创建Adam优化器的方法:

optimizer=keras.optimizers.Adam(lr=1e-4,beta_1=.9,beta_2=.999)

由于Adam是一种自适应学习率算法(如AdaGrad和RMSProp),因此对学习率超参数$\eta$需要较少的调整。通常可以使用默认值$\eta=.001$,这使得Adam比梯度下降更易于使用。

Adam的两个变体

AdaMax

在Adam算法的步骤2中,Adma累加了s中的梯度平方(对于最近的梯度,权重更大)。在第5步中,如果忽略$\epsilon$和第三步和第四步,Adma将以s的平凡根按比例缩小参数更新。简而言之,Adam按时间衰减梯度的$\ell_2$范数按比例缩小参数更新($\ell_2$范数是平方和的平方根)。与Adam在用一篇论文中介绍的AdaMax将$\ell_2$范数替换为$\ell_\infty$(一种表达最大值的新颖方法)。具体来说,它用$s\gets max(\beta_2s,\nabla_\theta J(\theta))$替换公式中的步骤2,它删除第4步,在第5步中,将梯度更新按比例s缩小,这是时间衰减梯度的最大值。实际上,这可以使AdaMax比Adam更稳定,但这确实取决于数据集,通常Adam的表现更好。因此,用Adam在某些任务上遇到问题,这时可以尝试的另一种优化器。

Nadam

Nadam优化是Adam加上 Nesterov 技巧,因此其收敛速度通常比Adam稍快。研究人员Timothy Dozat在各种任务上对许多不同的优化器进行了比较,发现Nadam总体上胜过Adam,但有时不如RMSProp。

自适应优化方法(包括RMSProp、Adam和Nadam优化)通常很棒,可以快速收敛到一个好的解决方案,因此,当对模型的性能感到失望时,尝试改用Nesterov 加速梯度:你的数据可能对自适应梯度过敏。

到目前为止讨论的所有优化技术都仅仅依赖于一阶片导数(Jacobians)。一些优化文献还包含了基于二阶片导数(Hessian)的一些算法。这些算法很难应用于深度神经网络,因为每个输出有$n^2$的Hessian(其中n是参数的数量),而不是每个输出只有n个Jacobians。由于DNN通常具有成千上万的参数,因此二阶优化算法甚至不适合储存在内存中,即使可以,计算Hessian也太慢了

训练稀疏模型

所有的优化算法都产生了密集模型,这意味着大多数参数都是非零的。如果运行时需要一个非常快的模型,或者需要占用更少的内存,就更需要一个稀疏模型。

实现这一点的一个简单方法是像往常一样训练模型,然后去掉很小的权重(将它们设置为零)。但这通常不会导致非常稀疏的模型,而且可能降低模型的性能。

一个更好的选择是在训练时使用强$\ell_1$正则化,因为它会迫使优化器产生尽可能的为零的权重

学习率调度

找到一个好的学习率很重要。如果设置得太高,训练可能会发散。如果设置地太低,训练最终会收敛到最优解,但是这将花费很长时间。如果它设置得稍微有点高,它一开始会很快,但是最终会围绕最优解振荡,不会真正稳定下来。如果算力有限,则可能必须先终端训练,然后才能正确收敛,从而产程次优解。

与恒定学习率相比,对学习率进行调度可以更快地找到一个最优解

幂调度

将学习率设置为迭代次数$t$的函数:$\eta(t)=\eta_0/(1+t/s)^c$。初始学习率$\eta_0$、幂$c$(通常设置为1)和步骤$s$是超参数。学习率在每一步都会下降。在$s$个步骤之后,它下降到$\eta_0/2$。再在$s$个步骤之后,它下降到$\eta_0/3$,以此类推,此调度开始下降,然后越来越慢。幂调度需要调整$\eta_0$和$s$可能还有$c$。

指数调度

将学习率设置为$\eta(t)=\eta_0\ 0.1^{\frac{t}{s}}$。学习率每s步将逐渐下降10倍。幂调度越来越缓慢地降低学习率,而指数调度则使学习率每s步降低10倍。

分段恒定调度

对一些轮次使用恒定的学习率(例如对5个轮次,$\eta_0=0.1$),对于另外一些轮次使用较小的学习率(例如,对于50轮次,$\eta_0=0.001$)。以此类推。

性能调度

每N步测量一次验证误差(就像提前停止一样),并且当误差停止下降时,将学习率降低$\lambda$倍

1周期调度1

与其他方法相反,1周期调度从提高初始学习率$\eta_0$开始,在训练过程中增长至$\eta_1$。然后,它在训练的后半部分将学习率再次线性降低到$\eta_0$,通过将学习率降低几个数量级(仍然是线性的)来完成最后几个轮次。使用与找到最优学习率相同的方法来选择最大学习率$\eta_1$,而初始学习率$\eta_0$大约要低10倍。

使用动量优化来训练深度神经网络进行语音识别时一些最受欢迎的学习率调度的性能。在这种情况下,性能调度和指数调度都表现良好。更倾向于指数调度,因为它易于微调

在Keras中实现幂调度是最简单的选择:只需在创建优化器时设置超参数decay即可:

optimizer=keras.optimizers.SGD(lr=0.01,decay=1e-4)

decay是s(学习率除以多个数字单位所需要的步数)的倒数,Keras假定c=1.
指数调度和分段调度也非常简单,需要定义一个函数,该函数采用当前轮次并返回学习率。

指数调度:

def exponential_decay_fn(epoch):
    return 0.01*0.1**(epoch/20)

如果不想对$\eta_0$和$s$进行硬编码,则可以创建一个返回配置函数的函数:

def exponential_decay(lr0,s):
    def exponential_decay_fn(epoch):
        return lr0*0.1**(epoch/s)
    return exponential_decay_fn

接下来,创建一个LearningRateScheduler回调函数,为其提供调度函数,然后将此回调函数传递给fit()方法:

lr_scheduler=keras.callbacks.LearningRateScheduler(exponential_decay_fn)
history=model.fit(X_train_scaled,y_train,callbacks=[lr_scheduler])

LearningRateScheduler将在每个轮次开始时更新优化器的learning_rate属性。通常每轮次更新一次学习率

调度函数可以选择将当前学习率作为第二个参数。例如,以下调度调度函数将以前的学习率乘以$0.1^{1/20}$,这将导致相同的指数衰减(但现在衰减从轮次0开始而不是1开始):

def exponential_decay_fn(epoch,lr):
    return lr*0.1**(1/20)

此实现依赖于优化器的出是学习率。

保存模型时,优化器及其学习率也会随之保存。有了新的调度函数,只需加载经过训练的模型,从中断的地方开始训练。但是如果调度函数使用了epoch参数,就会有所区别:epoch不会被保存,并且每次调用fit()方法都会将其重置为0。如果要继续训练一个中断的模型,则可能会导致一个很高的学习率,这可能会损害模型的权重。一种解决方法是手动设置fit()方法的initial_epoch参数,使epoch从正确的值开始。

对于分段恒定调度,可以使用以下调度函数,然后创建带有此函数的LearningRateScheduler回调函数,并将其传递给fit()方法

def piecewise_constant_fn(epoch):
    if epoch<5:
        return 0.01
    elif epoch<15:
        return 0.005
    else:
        return 0.001 

对于性能调度,需要使用ReduceLROnPlateau回调函数。例如,如果连续5个轮次的最好验证损失都没有改善时,它将使学习率乘以0.5

lr_scheduler=keras.callbacks.ReduceLROnPlateau(factor=.5,patiences=5)

最后,tf.keras提供了另一种实现学习率调度的方法:使用keras.optimizers.schedule中可以使用的调度之一来定义学习率,然后将该学习率传递给任意优化器。这种方法在每个步骤更新学习率而不是每个轮次。例如,以下是实现与之前定义的exponential_decay_fn()函数相同的指数调度的方法:

s=20*len(X_train)//32 #batch_size=32,每轮X_train//32步,共20轮
learning_rate=keras.optimizers.schedules.ExponentialDecay(0.01,s,0.1)
optimizer=kersa.optimizers.SGD(leraning_rate)

保存模型时,学习率及其调度(包括其状态)也将被保存。

用Keras进行迁移学习

假如Fashion MNIST数据集包含了8个类别,例如,除凉鞋和衬衫之外的所有类别。有人在该数据集上建立并训练的Keras模型,并获得了相当不错的性能(精度>90%)。将此模型称为模型A。现在要处理另一项任务:有凉鞋和衬衫的图像,想要训练一个二元分类器(正=衬衫,负=凉鞋)。数据集非常小,只有200张带标签的图像。当使用与模型A相同的架构训练一个新模型(称为模型B)时,它的的性能相当好(97.2%)

首先,需要加载模型A并基于该模型的层创建一个新模型。来重用输出层之外的所有层:

import tensorflow as tf
from tensorflow import keras

model_A=keras.models.load_model('my_model_A.h5')
model_B_on_A=keras.models.Sequential(model_A.layers[:-1])
model_B_on_A.add(keras.layers.Dense(1,activation='sigmoid'))

model_A和model_B_on_A现在共享一些层。当训练model_B_on_A时,也会影响model_A。如果想避免这种情况,需要在重用model_A的层之前对其进行克隆。为此,需要使用clone_model()来克隆模型A的架构,然后复制其权重(因为clone_model()不会克隆权重):

model_A_clone=kersa.models.clone_model(model_A)
model_A_clone.set_weights(model_A.get_weights())

现在开始可以为任务B训练model_B_on_A,但是由于新的输出层是随机初始化的,它会产生较大的错误(至少在前几个轮次内),因为将存在较大的错误梯度,这可能会破坏重用的权重。为了避免这种情况,一种方法是在前几个轮次冻结重用的层,给新层一些时间来学习合理的权重。为此,可以将每一层的可训练属性设置为False并编译模型

for layer in model_B_on_A.layers[:-1]:
    layers.trainable=False

model_B_on_A.compile(loss='binary_crossentropy',optimizer='sgd',metrics=['accuracy'])
# 冻结或解冻层之后,必须总是要编译模型

冻结后,可以在训练模型几个轮次后,然后解冻重用的层(这需要再次编译模型),并继续进行训练来微调任务B的重用层。解冻重用层后,降低学习率通常是个好主意,可以再次避免损坏重用的权重:

history=model_B_on_A.fit(X_train_B,y_train_B,epochs=4,validation_data=(X_valid_B,y_valid_B))

for layer in model_B_on_A.layers[:-1]:
    layer.trainable=True
optimizer=keras.optimizers.SGD(lr=1e-4)
model_B_on_A.compile(loss='binary_crossentropy',optimizer=optimizer,metrics=['accuracy'])

history=model_B_on_A.fit(X_train_B,y_train_B,epochs=16,validation_data=(X_valid_B,y_valid_B))

IMDB数据集

它包含来自互联网电影数据库(IMDB)的50000条严重两极分化的评论

数据集被分为用于训练的25000条评论与用于测试的25000条评论

训练集和测试集都包含50%的正面评论和50%的负面评论

加载IMDB数据集

import tensorflow as tf
from tensorflow import keras
from keras.datasets import imdb
import warnings
warnings.filterwarnings('ignore')
(train_data,train_labels),(test_data,test_labels)=imdb.load_data(num_words=10000)
# num_words=10000是仅保留训练数据中前10000最常出现的单词
train_data[0]
[1,
 14,
 22,
 16,
 43,
 530,
 973,
 1622,
 1385,
 65,
 458,
 4468,
 66,
 3941,
 4,
 173,
 36,
 256,
 5,
 25,
 100,
 43,
 838,
 112,
 50,
 670,
 2,
 9,
 35,
 480,
 284,
 5,
 150,
 4,
 172,
 112,
 167,
 2,
 336,
 385,
 39,
 4,
 172,
 4536,
 1111,
 17,
 546,
 38,
 13,
 447,
 4,
 192,
 50,
 16,
 6,
 147,
 2025,
 19,
 14,
 22,
 4,
 1920,
 4613,
 469,
 4,
 22,
 71,
 87,
 12,
 16,
 43,
 530,
 38,
 76,
 15,
 13,
 1247,
 4,
 22,
 17,
 515,
 17,
 12,
 16,
 626,
 18,
 2,
 5,
 62,
 386,
 12,
 8,
 316,
 8,
 106,
 5,
 4,
 2223,
 5244,
 16,
 480,
 66,
 3785,
 33,
 4,
 130,
 12,
 16,
 38,
 619,
 5,
 25,
 124,
 51,
 36,
 135,
 48,
 25,
 1415,
 33,
 6,
 22,
 12,
 215,
 28,
 77,
 52,
 5,
 14,
 407,
 16,
 82,
 2,
 8,
 4,
 107,
 117,
 5952,
 15,
 256,
 4,
 2,
 7,
 3766,
 5,
 723,
 36,
 71,
 43,
 530,
 476,
 26,
 400,
 317,
 46,
 7,
 4,
 2,
 1029,
 13,
 104,
 88,
 4,
 381,
 15,
 297,
 98,
 32,
 2071,
 56,
 26,
 141,
 6,
 194,
 7486,
 18,
 4,
 226,
 22,
 21,
 134,
 476,
 26,
 480,
 5,
 144,
 30,
 5535,
 18,
 51,
 36,
 28,
 224,
 92,
 25,
 104,
 4,
 226,
 65,
 16,
 38,
 1334,
 88,
 12,
 16,
 283,
 5,
 16,
 4472,
 113,
 103,
 32,
 15,
 16,
 5345,
 19,
 178,
 32]



train_labels[0]
1



[max([max(sequence)for sequence in train_data])] # 前10000个最常见的单词,单词索引不会超过10000
[9999]



word_index=imdb.get_word_index() # word_index是一个将单词映射为整数的字典
reverse_word_index=dict(
[(value,key)for (key,value) in word_index.items()]) # 键值颠倒,将整数索引映射为单词
decoded_review=' '.join([
    reverse_word_index.get(i-3,'?')for i in train_data[0]
]) # 将评论解码,索引减去了3,因为0、1、2是为padding(填充)、start of sequence(序列开始)、unknown(未知词)分别保留的索引

准备数据

import numpy as np

def vectorize_sequences(sequences,dimension=10000):
    results=np.zeros((len(sequences),dimension)) # 创建一个形状为(len(sequences),dimension)的零矩阵
    for i,sequence in enumerate(sequences):
        results[i,sequence]=1. # 将results[i]的索引设置为1
    return results
X_train=vectorize_sequences(train_data)
X_test=vectorize_sequences(test_data) # 将数据向量化 
X_train[0]# 向量化后数据
array([0., 1., 1., ..., 0., 0., 0.])
y_train=np.array(train_labels).astype('float32') # 将标签向量化
y_test=np.array(test_labels).astype('float32')
y_train
array([1., 0., 0., ..., 0., 1., 0.], dtype=float32)

构建网络

模型定义

from keras import models
from keras import layers

model=models.Sequential()
model.add(layers.Dense(16,activation='relu',input_shape=[10000,]))
model.add(layers.Dense(16,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))
2021-09-27 21:09:53.396263: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcuda.so.1
2021-09-27 21:09:53.472257: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:53.473082: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1733] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: NVIDIA GeForce RTX 3060 Laptop GPU computeCapability: 8.6
coreClock: 1.702GHz coreCount: 30 deviceMemorySize: 5.81GiB deviceMemoryBandwidth: 312.97GiB/s
2021-09-27 21:09:53.473156: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
2021-09-27 21:09:53.481835: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublas.so.11
2021-09-27 21:09:53.481910: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublasLt.so.11
2021-09-27 21:09:53.485108: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcufft.so.10
2021-09-27 21:09:53.486878: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcurand.so.10
2021-09-27 21:09:53.489126: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcusolver.so.11
2021-09-27 21:09:53.491392: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcusparse.so.11
2021-09-27 21:09:53.491984: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudnn.so.8
2021-09-27 21:09:53.492082: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:53.492386: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:53.492756: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
2021-09-27 21:09:53.493101: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-09-27 21:09:53.493983: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:53.494378: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1733] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: NVIDIA GeForce RTX 3060 Laptop GPU computeCapability: 8.6
coreClock: 1.702GHz coreCount: 30 deviceMemorySize: 5.81GiB deviceMemoryBandwidth: 312.97GiB/s
2021-09-27 21:09:53.494501: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:53.494754: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:53.494977: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
2021-09-27 21:09:53.495238: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
2021-09-27 21:09:54.053755: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1258] Device interconnect StreamExecutor with strength 1 edge matrix:
2021-09-27 21:09:54.053791: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1264]      0 
2021-09-27 21:09:54.053797: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1277] 0:   N 
2021-09-27 21:09:54.053989: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:54.054601: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:54.055029: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-09-27 21:09:54.055310: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1418] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 3784 MB memory) -> physical GPU (device: 0, name: NVIDIA GeForce RTX 3060 Laptop GPU, pci bus id: 0000:01:00.0, compute capability: 8.6)

编译模型

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])
# 使用rmsprop优化器,binary_crossentropy损失函数来配置模型

验证方法

X_val=X_train[:10000] # 留出10000个样本作为验证集
partial_X_train=X_train[10000:]
y_val=y_train[:10000]
partial_y_train=y_train[10000:]

训练模型

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['acc'])
history=model.fit(partial_X_train,
                  partial_y_train,
                  epochs=20,
                  batch_size=512,
                  validation_data=(X_val,y_val))
2021-09-27 21:09:55.068570: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)
2021-09-27 21:09:55.069321: I tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3293895000 Hz
Epoch 1/20
2021-09-27 21:10:06.815229: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublas.so.11
19/30 [==================>...........] - ETA: 0s - loss: 0.6202 - acc: 0.6564
2021-09-27 21:10:07.491653: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublasLt.so.11
2021-09-27 21:10:07.491710: I tensorflow/stream_executor/cuda/cuda_blas.cc:1838] TensorFloat-32 will be used for the matrix multiplication. This will only be logged once.
30/30 [==============================] - 13s 34ms/step - loss: 0.5807 - acc: 0.7005 - val_loss: 0.3785 - val_acc: 0.8654
Epoch 2/20
30/30 [==============================] - 0s 16ms/step - loss: 0.3154 - acc: 0.9049 - val_loss: 0.3115 - val_acc: 0.8781
Epoch 3/20
30/30 [==============================] - 0s 16ms/step - loss: 0.2223 - acc: 0.9327 - val_loss: 0.3158 - val_acc: 0.8698
Epoch 4/20
30/30 [==============================] - 0s 16ms/step - loss: 0.1757 - acc: 0.9443 - val_loss: 0.3511 - val_acc: 0.8573
Epoch 5/20
30/30 [==============================] - 0s 15ms/step - loss: 0.1445 - acc: 0.9561 - val_loss: 0.2798 - val_acc: 0.8891
Epoch 6/20
30/30 [==============================] - 0s 16ms/step - loss: 0.1179 - acc: 0.9666 - val_loss: 0.3308 - val_acc: 0.8743
Epoch 7/20
30/30 [==============================] - 0s 15ms/step - loss: 0.0939 - acc: 0.9742 - val_loss: 0.3078 - val_acc: 0.8843
Epoch 8/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0778 - acc: 0.9797 - val_loss: 0.3289 - val_acc: 0.8810
Epoch 9/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0643 - acc: 0.9830 - val_loss: 0.3504 - val_acc: 0.8795
Epoch 10/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0546 - acc: 0.9880 - val_loss: 0.3877 - val_acc: 0.8752
Epoch 11/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0445 - acc: 0.9906 - val_loss: 0.4087 - val_acc: 0.8758
Epoch 12/20
30/30 [==============================] - 1s 17ms/step - loss: 0.0334 - acc: 0.9944 - val_loss: 0.4304 - val_acc: 0.8748
Epoch 13/20
30/30 [==============================] - 0s 15ms/step - loss: 0.0297 - acc: 0.9940 - val_loss: 0.4595 - val_acc: 0.8733
Epoch 14/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0213 - acc: 0.9968 - val_loss: 0.5709 - val_acc: 0.8600
Epoch 15/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0198 - acc: 0.9976 - val_loss: 0.5357 - val_acc: 0.8664
Epoch 16/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0158 - acc: 0.9981 - val_loss: 0.5745 - val_acc: 0.8682
Epoch 17/20
30/30 [==============================] - 0s 15ms/step - loss: 0.0112 - acc: 0.9989 - val_loss: 0.6031 - val_acc: 0.8665
Epoch 18/20
30/30 [==============================] - 0s 15ms/step - loss: 0.0083 - acc: 0.9992 - val_loss: 0.6395 - val_acc: 0.8670
Epoch 19/20
30/30 [==============================] - 0s 16ms/step - loss: 0.0056 - acc: 0.9997 - val_loss: 0.6643 - val_acc: 0.8653
Epoch 20/20
30/30 [==============================] - 0s 14ms/step - loss: 0.0038 - acc: 0.9999 - val_loss: 0.6918 - val_acc: 0.8646

调用moodel.fit()返回了一个History对象。这个对象有一个成员history是一个字典,包含训练过程中的所有数据

history_dict=history.history
history_dict.keys()
dict_keys(['loss', 'acc', 'val_loss', 'val_acc'])

绘制训练损失和验证损失

import matplotlib.pyplot as plt

history_dict=history.history
loss_values=history_dict['loss']
val_loss_values=history_dict['val_loss']

epochs=range(1,len(loss_values)+1)

plt.plot(epochs,loss_values,'bo',label='Training loss')
plt.plot(epochs,val_loss_values,'b',label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
<matplotlib.legend.Legend at 0x7ff3bf5f18e0>




绘制训练精度和验证精度

plt.clf()
acc=history_dict['acc']
val_acc=history_dict['val_acc']

plt.plot(epochs,acc,'bo',label='Training acc')
plt.plot(epochs,val_acc,'b',label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

为了防止拟合,可以在第三轮之后停止训练

从头开始从新训练一个模型

model=models.Sequential()
model.add(layers.Dense(16,activation='relu',input_shape=[10000,]))
model.add(layers.Dense(16,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

model.compile(optimizer='rmsprop',
             loss='binary_crossentropy',
             metrics=['accuracy'])

model.fit(X_train,y_train,epochs=4,batch_size=512)
results=model.evaluate(X_test,y_test)
2021-09-27 21:10:18.351156: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 1000000000 exceeds 10% of free system memory.


Epoch 1/4
49/49 [==============================] - 1s 10ms/step - loss: 0.5550 - accuracy: 0.7375
Epoch 2/4
49/49 [==============================] - 0s 10ms/step - loss: 0.2718 - accuracy: 0.9129
Epoch 3/4
49/49 [==============================] - 0s 9ms/step - loss: 0.2014 - accuracy: 0.9307
Epoch 4/4
49/49 [==============================] - 0s 9ms/step - loss: 0.1621 - accuracy: 0.9434


2021-09-27 21:10:21.517788: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 1000000000 exceeds 10% of free system memory.


782/782 [==============================] - 2s 2ms/step - loss: 0.2919 - accuracy: 0.8845
results
[0.29189127683639526, 0.8844799995422363]

使用训练好的网络在新数据上生成预测结果

model.predict(X_test)
2021-09-27 21:10:24.312851: W tensorflow/core/framework/cpu_allocator_impl.cc:80] Allocation of 1000000000 exceeds 10% of free system memory.
array([[0.19328508],
       [0.99986005],
       [0.9216948 ],
       ...,
       [0.20816731],
       [0.09685578],
       [0.6103842 ]], dtype=float32)

进一步的实验

  • 前面使用了两个隐藏层,可以尝试使用一个或三个隐藏层,然后观察对验证精度和测试精度的影响
  • 尝试使用更多或更少的隐藏单元
  • 尝试使用mse损失函数代替binary_crossentropy
  • 尝试使用tanh激活函数代替relu

    小结

  • 通常需要对原始数据进行大量预处理,以便于将其转换为张量输入到神经网络中。单词序列可以编码为二进制向量,但也有其他编码方式
  • 带有relu激活的Dense层叠加,可以解决很多种问题(包括情感分类)
  • 对于二分类问题(两个输出类别),网络的最后一层应该是只有一个单元并使用sigmoid激活的Dense层,网络输出应该是0-1范围内的标量,表示概率值
  • 对于二分类的sigmoid标量输出,应该使用binary_crossentropy损失函数
  • 无论什么问题,rmsprop优化器通常都是足够好的选择
  • 随着神经网络在训练数据上的表现越来越好,模型最终会过拟合,并在前所未见的数据上得到越来越差的结果。一定要一直监控模型在训练集之外的数据上的性能。

梯度消失与梯度爆炸问题

反向传播算法的工作原理是从输出层到输入层次,并在此过程中传播误差梯度。一旦算法计算出损失函数相对于每个参数的梯度,可以使用这些梯度以梯度下降步骤来更新每个参数。

随着算法向下传播到较低层,梯度通常会越来越小。结果梯度下降更新使较低层的连接权重保持不变,训练不能收敛到一个良好的解。这就是梯度消失问题。

在某种情况下,可能会出现相反的情况:梯度可能会越来越大,各层需要更新很大的权重直到算法发散。这就是梯度爆炸问题。

深度神经网络很受梯度不稳定的影响,不同的层可能以不同的速度学习

Glorot 和 He 初始化

Glorot和Bengio在他们的论文中提出一种能显著缓解不稳定梯问题的方法。他们指出,我们需要信号在两个方向上正确流动:进行预测时,信号为正向;在反向传播梯度时,信号为反向。我们既不希望信号小时,也不希望它爆炸饱和。为了信号正确流动,作者认为,我们需要每层输出的方差等于其输入的方差,并且我们需要在反方向时流过某层之前和之后的梯度具有相同的方差。除非该层具有相等数量的输入和神经元,否则实际上不能同时保证两者,但是Glorot和Bengio提出了一个很好的折中方案,在实践中证明很好地发挥作用,按照下面的公式随机初始化每层的连接权重,其中$fan_{avg}=(fan_{in}+fan_{out})/2$。这种初始化策略称为Xavier初始化或者Glorot初始化。

$$\mbox{正态分布,其均值为0,方差为}\,\sigma^2=\frac{1}{fan_{avg}}$$
$$\mbox{或-r和+r之间的均匀分布,其中}\, r=\frac{3}{fan_{avg}}$$

非饱和激活函数

ReLU激活函数并不完美。它有一个被称为“濒死的ReLU”的问题:在训练过程,某些神经元实际“死亡”了,这意味着它们停止输出除0以外的任何值。在某些情况下,你可能发现网络中一半的神经元都死了,特别是如果你使用较大的学习率。当神经元的权重进行调整时,其输入的加权和对于训练集中所有实例均为负数,神经元会死亡。发生这种情况,它只会继续输出零,梯度下降不会再影响它,因为ReLU函数的输入为负时其梯度为零。

批量归一化

尽管将He初始化与ELU(或ReLU的任何变体)一起使用可以显著减少训练开始时的梯度消失/梯度爆炸问题的危险,但这并不能保证它们在训练期间不会再出现。

2015年的一篇论文中,Sergey Ioffe和Christian Szegedy提出了一种称为批量归一化(BN)的技术来解决这些问题。该技术包括在模型中的每个隐藏层的激活函数之前或之后添加一个操作。该操作对每个输入零中心并归一化,然后每层使用两个新的参数向量缩放和偏移其结果:一个用于缩放,另一个用于偏移。该操作可以使模型学习各层输入的最佳缩放和均值。在许多情况下,如果将BN层添加为神经网络的第一层,则无需归一化训练集(例如,使用StandardScaler);BN层会完成此操作。

为了使输入零中心并归一化,该算法需要估计每个输入的均值和标准差。通过评估当前小批次上的输入的均值和标准差(因此成为“批量归一化“)来做到这以下

下列公式总结了整个操作

$$1.\ \mu_B=\frac{1}{m_B}\sum_{i=1}^{m_B}x^{(i)}$$
$$2.\ \sigma_B^2=\frac{1}{m_B}\sum_{i=1}^{m_B}({x^{(i)}-\mu_B})^2$$
$$3.\ \hat{x}^{(i)}=\frac{x^{(i)}-\mu_B}{\sqrt{\sigma_B^2+\varepsilon}}$$
$$4.\ z^{(i)}=\gamma\otimes\hat{x}^{(i)}+\beta$$

  • $\mu_B$是输入均值的向量,在整个小批量B上评估(每个输入包含一个均值)
  • $\sigma_B$是输入标准差的向量,也在整个小批量中进行评估(每个输入包括一个标准差)
  • $m_B$是小批量中的实例数量
  • $x^{(i)}$是实例i的零中心和归一化输入的向量
  • $\gamma$是该层的输出缩放参数变量(每个输入包含一个缩放参数)
  • $\otimes$表示逐元素乘法(每个输入乘以其相应的输出缩放参数)
  • $\beta$是层的输出移动(偏移)参数向量(每个输入包含一个偏移参数)。每个输入都通过其相应的移动参数进行偏移
  • $\varepsilon$是一个很小的数字以避免被零(通常为$10^{-5}$)。这称为平滑项
  • $z^{(i)}$是BN操作的输出。它是输入的缩放和偏移版本

批量归一化应用于最先进的图像分类模型,以少14倍的训练步骤即可达到相同的精度。

批量归一化的作用就像正则化一样,减少了对其他正则化技术的需求

用Keras实现批量化

实现批量归一化的方法只需在每个隐藏层的激活函数之前或之后添加一个BatchNormalization层,然后可选地在模型的第一层后添加一个BN层

import warnings
warnings.filterwarnings('ignore')
import tensorflow as tf
from tensorflow import keras

model=keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28,28]),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(300,activation='elu',kernel_initializer='he_normal'),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(100,activation='elu',kernel_initializer='he_normal'),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(10,activation='softmax')
])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
batch_normalization (BatchNo (None, 784)               3136      
_________________________________________________________________
dense (Dense)                (None, 300)               235500    
_________________________________________________________________
batch_normalization_1 (Batch (None, 300)               1200      
_________________________________________________________________
dense_1 (Dense)              (None, 100)               30100     
_________________________________________________________________
batch_normalization_2 (Batch (None, 100)               400       
_________________________________________________________________
dense_2 (Dense)              (None, 10)                1010      
=================================================================
Total params: 271,346
Trainable params: 268,978
Non-trainable params: 2,368
_________________________________________________________________

第一个BN层的参数,两个是可训练的(通过反向传播),两个不是

[(var.name,var.trainable)for var in model.layers[1].variables]
[('batch_normalization/gamma:0', True),
 ('batch_normalization/beta:0', True),
 ('batch_normalization/moving_mean:0', False),
 ('batch_normalization/moving_variance:0', False)]


在Keras创建BN层时,还会创建两个操作,在训练期间的迭代中,Keras都会调用这两个参数。这些操作会更新移动平均值。由于使用的是TensorFlow后端,所以这些操作都是TensorFlow操作

model.layers[1]
<tensorflow.python.keras.layers.normalization_v2.BatchNormalization at 0x7efbfda216d0>


BN论文的作者主张在激活函数之前(而不是之后)添加BN层。哪个更好取决于任务。

要在激活函数之前添加BN层,必须从隐藏层中删除激活函数,并将其作为单独的层添加到BN层之后

由于批量归一化层的每一个输入都包含一个偏移参数,所以可以从上一层中删除该偏置项(创建时只需要传递use_bias=False即可)

model=keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28,28]),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(300,kernel_initializer='he_normal',use_bias=False),
    keras.layers.BatchNormalization(),
    keras.layers.Activation('elu'),
    keras.layers.Dense(100,kernel_initializer='he_normal',use_bias=False),
    keras.layers.BatchNormalization(),
    keras.layers.Activation('elu'),
    keras.layers.Dense(10,activation='softmax')
    
])

梯度裁减

缓解梯度爆炸问题的另一种流行技术是在反向传播期间裁减梯度,使它们永远不会超过某个阈值。这称为梯度裁减。最常用于循环神经网络,因为在RNN中难以使用批量归一化

在Keras中,实现梯度裁减仅仅是在一个创建优化器时设置clipvalue或clipnorm参数的问题

optimizer=keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss='mse',optimizer=optimizer)

使用Keras加载数据集

import tensorflow as tf
from tensorflow import keras
fashion_mnist=keras.datasets.fashion_mnist
(X_train_full,y_train_full),(X_test,y_test)=fashion_mnist.load_data()
X_train_full.shape#查看训练集的形状
(60000, 28, 28)
X_train_full.dtype#查看训练集的数据类型
dtype('uint8')

将数据集划分为训练集,验证集和测试集

像素强度表示为整数0-255,由于我们要使用梯度下降神经网络,因此必须缩放输入特征。
因为将像素强度除以255.0将之转化为0-1之间的浮点数

X_valid,X_train=X_train_full[:5000]/255.0,X_train_full[5000:]/255.0
y_valid,y_train=y_train_full[:5000],y_train_full[5000:]

对于MNIST当标签等于5时,说明图像代表手写数字5。但是对于Fashion MNIST,我们需要一个类名列表来知道我们要处理的内容

class_names=['T-shirt/top','Trouser','Pullover','Dress','Coat','Sandal','Shirt','Sneaker','Bag','Ankle boot']

例如训练集中的第一幅图像代表一件外套

class_names[y_train[0]]
'Coat'

使用顺序API创建模型,建立一个具有两个隐藏层的分类MLP

典型的回归MLP架构

超参数典型值
输入神经元的数量每个输入特征一个(例如,MNIST为28*28=784)
隐藏层数量取决于问题,但通常为1到5
每个隐藏层的神经元数量取决于问题,但通常为10到100
输出神经元数量每个预测维度输出1个神经元
隐藏的激活ReLU(或SELU)
输出激活无,或ReLU/softplus(如果为正输出)或逻辑/tanh(如果为有界输出)
损失函数MSE或MAE/Huber(如果存在离群值)

典型的MLP架构

超参数二进制分类多标签二进制分类多类分类
输入层和隐藏层与回归相同与回归相同与回归相同
输出神经元数量1每个标签1每个类1
输出层激活逻辑逻辑softmax
损失函数交叉熵交叉熵交叉熵
model=keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28,28]))
model.add(keras.layers.Dense(300,activation='relu'))
model.add(keras.layers.Dense(100,activation='relu'))
model.add(keras.layers.Dense(10,activation='softmax'))

可以不用像前面那样逐层添加层,可以在创建顺序模型时传递一个层列表

model=keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28,28]),
    keras.layers.Dense(300,activation='relu'),
    keras.layers.Dense(100,activation='relu'),
    keras.layers.Dense(10,activation='softmax')
])
model.summary()#summary()方法显示模型的所有层
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten_1 (Flatten)          (None, 784)               0         
_________________________________________________________________
dense_3 (Dense)              (None, 300)               235500    
_________________________________________________________________
dense_4 (Dense)              (None, 100)               30100     
_________________________________________________________________
dense_5 (Dense)              (None, 10)                1010      
=================================================================
Total params: 266,610
Trainable params: 266,610
Non-trainable params: 0
_________________________________________________________________
model.layers#可以通过layers来获取模型的层列表,按其索引获取层,也可以按名称获取
[<tensorflow.python.keras.layers.core.Flatten at 0x1a8aa53b9d0>,
 <tensorflow.python.keras.layers.core.Dense at 0x1a8aa53b340>,
 <tensorflow.python.keras.layers.core.Dense at 0x1a8aa53b670>,
 <tensorflow.python.keras.layers.core.Dense at 0x1a8aa533c40>]
hidden1=model.layers[1]
hidden1.name
'dense_3'
model.get_layer('dense_3') is hidden1
True

可以用get_weights()和set_weights()方法访问层的所有参数。对于密集层,这包括连接权重和偏置项

weights,biases=hidden1.get_weights()
weights
array([[-0.00048143, -0.02221986, -0.03760444, ..., -0.01870283,
         0.02787271, -0.0562621 ],
       [ 0.03667355, -0.02800197, -0.02043942, ..., -0.05027718,
         0.04881267, -0.03488968],
       [-0.01286711,  0.0676775 ,  0.01634417, ...,  0.05434407,
        -0.02889663,  0.06560428],
       ...,
       [ 0.0035713 ,  0.03277423,  0.01696016, ..., -0.06722524,
         0.05769408, -0.05496902],
       [ 0.04029207,  0.05693065, -0.03199599, ...,  0.02694139,
         0.02356034, -0.01617898],
       [-0.03333716, -0.03082271,  0.00634206, ...,  0.04961272,
        -0.0399887 ,  0.0423771 ]], dtype=float32)



weights.shape
(784, 300)
biases
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)
biases.shape
(300,)

编译模型

使用loss='sparse_categorical_crossentropy'等同于使用loss=keras.losses.sparse_categorical_crossentropy。同样,指定optimizer='sgd'等同于指定optimizer=keras.optimizers.SGD(),而metrics=['accuracy']等同于metrics=[keras.metrics.sparse_categorical_accuracy]

model.compile(loss='sparse_categorical_crossentropy',
             optimizer='sgd',
             metrics=['accuracy'])

如果要使用稀疏标签(即类索引)转换为独热向量标签,使用keras.utils.to_categorical()函数。反之则使用np.argmax函数和axis=1
现在模型已准备好进行训练,只需要调用fit方法
将输入特征(X_train)和目标类(y_train)以及要训练的轮次数传给它,否则默认为1.在这里还传递了一个验证集。Keras将在每轮次结束时测量此集合上的损失和其他指标,这对查看模型的实际效果非常有用

history=model.fit(X_train,y_train,epochs=30,validation_data=(X_valid,y_valid))
Epoch 1/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.7114 - accuracy: 0.7640 - val_loss: 0.5329 - val_accuracy: 0.8156
Epoch 2/30
1719/1719 [==============================] - 3s 2ms/step - loss: 0.4915 - accuracy: 0.8293 - val_loss: 0.4504 - val_accuracy: 0.8464
Epoch 3/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.4486 - accuracy: 0.8427 - val_loss: 0.4134 - val_accuracy: 0.8562
Epoch 4/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.4186 - accuracy: 0.8516 - val_loss: 0.4002 - val_accuracy: 0.8618
Epoch 5/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.3975 - accuracy: 0.8590 - val_loss: 0.3786 - val_accuracy: 0.8692
Epoch 6/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.3805 - accuracy: 0.8649 - val_loss: 0.3897 - val_accuracy: 0.8648
Epoch 7/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.3668 - accuracy: 0.8697 - val_loss: 0.3785 - val_accuracy: 0.8686
Epoch 8/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.3546 - accuracy: 0.8732 - val_loss: 0.3493 - val_accuracy: 0.8762
Epoch 9/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.3442 - accuracy: 0.8784 - val_loss: 0.3476 - val_accuracy: 0.8776
Epoch 10/30
1719/1719 [==============================] - 3s 2ms/step - loss: 0.3352 - accuracy: 0.8808 - val_loss: 0.3419 - val_accuracy: 0.8796
Epoch 11/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.3250 - accuracy: 0.8839 - val_loss: 0.3328 - val_accuracy: 0.8812
Epoch 12/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.3175 - accuracy: 0.8866 - val_loss: 0.3247 - val_accuracy: 0.8860
Epoch 13/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.3099 - accuracy: 0.8887 - val_loss: 0.3309 - val_accuracy: 0.8794
Epoch 14/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.3027 - accuracy: 0.8914 - val_loss: 0.3240 - val_accuracy: 0.8810
Epoch 15/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2961 - accuracy: 0.8932 - val_loss: 0.3189 - val_accuracy: 0.8834
Epoch 16/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2905 - accuracy: 0.8954 - val_loss: 0.3242 - val_accuracy: 0.8852
Epoch 17/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2843 - accuracy: 0.8980 - val_loss: 0.3172 - val_accuracy: 0.8884
Epoch 18/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2795 - accuracy: 0.8997 - val_loss: 0.3126 - val_accuracy: 0.8856
Epoch 19/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2737 - accuracy: 0.9015 - val_loss: 0.3143 - val_accuracy: 0.8874
Epoch 20/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2696 - accuracy: 0.9024 - val_loss: 0.3120 - val_accuracy: 0.8884
Epoch 21/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2639 - accuracy: 0.9051 - val_loss: 0.3041 - val_accuracy: 0.8930
Epoch 22/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2596 - accuracy: 0.9062 - val_loss: 0.3035 - val_accuracy: 0.8926
Epoch 23/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2535 - accuracy: 0.9087 - val_loss: 0.2970 - val_accuracy: 0.8916
Epoch 24/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2497 - accuracy: 0.9101 - val_loss: 0.3263 - val_accuracy: 0.8864
Epoch 25/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2459 - accuracy: 0.9120 - val_loss: 0.2964 - val_accuracy: 0.8908
Epoch 26/30
1719/1719 [==============================] - 2s 1ms/step - loss: 0.2414 - accuracy: 0.9132 - val_loss: 0.2981 - val_accuracy: 0.8902
Epoch 27/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2377 - accuracy: 0.9144 - val_loss: 0.3021 - val_accuracy: 0.8920
Epoch 28/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2337 - accuracy: 0.9157 - val_loss: 0.3247 - val_accuracy: 0.8864
Epoch 29/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2301 - accuracy: 0.9178 - val_loss: 0.3469 - val_accuracy: 0.8764
Epoch 30/30
1719/1719 [==============================] - 3s 1ms/step - loss: 0.2265 - accuracy: 0.9179 - val_loss: 0.2957 - val_accuracy: 0.8920

fit()方法返回一个History对象,其中包含训练参数(history.params)、经历的轮次列表(history.epoch),最重要的是包含在训练集和验证集上的每个轮
次结束时测得的损失和额外指标的字典(history.history)如果用此字典创建pandas DataFrame并调用plot()方法,可以绘制出学习曲线

import pandas as pd
import matplotlib.pyplot as plt
pd.DataFrame(history.history).plot(figsize=(8,5))
plt.grid(True)
plt.gca().set_ylim(0,1)
plt.show()

对模型的验证精度感到满意后,应在测试集上对其进行评估泛化误差。这时可以使用evaluate()方法完成此操作

model.evaluate(X_test,y_test)
313/313 [==============================] - 0s 1ms/step - loss: 63.2170 - accuracy: 0.8478
[63.216957092285156, 0.8478000164031982]

使用模型进行预测

X_new=X_test[:3]
y_proba=model.predict(X_new)
y_proba.round(2)
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)
y_pred=model.predict_classes(X_new)
y_pred
C:\ProgramData\Miniconda3\lib\site-packages\tensorflow\python\keras\engine\sequential.py:455: UserWarning: `model.predict_classes()` is deprecated and will be removed after 2021-01-01. Please use instead:* `np.argmax(model.predict(x), axis=-1)`,   if your model does multi-class classification   (e.g. if it uses a `softmax` last-layer activation).* `(model.predict(x) > 0.5).astype("int32")`,   if your model does binary classification   (e.g. if it uses a `sigmoid` last-layer activation).
  warnings.warn('`model.predict_classes()` is deprecated and '
array([9, 2, 1], dtype=int64)
y_pred==y_test[:3]
array([ True,  True,  True])

可见分类器对所有三个图像进行了正确分类