分类 TensorFlow & Keras 下的文章

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])

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