Keras深度学习实战(3)——神经网络性能优化技术详解

0. 前言

我们已经学习了神经网络的基础概念,并了解了如何利用 Keras 库构建实用神经网络模型。同时我们还提到了,有多种超参数可以影响神经网络的准确性。在本节中,我们将详细介绍神经网络中各种超参数的作用,通过使用不同的超参数来优化神经网络性能。[0][1]

1. 缩放输入数据集

缩放数据集是在训练网络之前对数据进行预处理的过程,在此过程中,限制数据集中的数据范围以确保它们不会分布在较大的间隔上。实现此目的的一种方法是将数据集中的每个数据除以数据集中的最大数据。通常,缩放输入数据集可以提高神经网络的性能,是常用的数据预处理方法之一。

1.1 数据集缩放的合理性解释

在本节中,我们将了解为什么缩放数据集可以使神经网络表现更好。为了了解缩放输入对输出的影响,我们将输入数据集未缩放时的模型性能与输入数据集缩放时的性能进行比较。
当输入数据未缩放时,在不同权重值的作用下 sigmoid 函数值如下表所示:

输入权重偏置sigmoid 值
2550.0100.93
2550.101.00
2550.201.00
2550.401.00
2550.801.00
2551.601.00
2553.201.00
2556.401.00

在上表中,即使权重值在 0.01 到 6.4 之间变化,在经过函数 Sigmoid 后输出变化也不大,为了解释这一现象,我们首先回忆下 Sigmoid 函数的计算方法:

output = 1/(1+np.exp(-(w*x + b))

其中 w 是权重,x 是输入,b 是偏置值。

sigmoid 输出不变的原因是由于 w * x 的乘积很大(主要是因为 x 较大),导致 sigmoid 值始终落在 sigmoid 曲线的饱和部分中( sigmoid 曲线的右上角或左下角的值称为饱和部分)。而如果我们将不同的权重值乘以一个较小的输入数字,则结果如下所示:

输入权重偏置sigmoid 值
10.0100.50
10.100.52
10.200.55
10.400.60
10.800.69
11.600.83
13.200.96
16.401.00

由于输入值较小,因此上表中的 Sigmoid 输出值会随权重的变化发生改变。
通过此示例,我们了解了缩放输入对数据集的影响,当权重(假设权重不具有较大范围)乘以较小输入值时,使输入数据能够对输出产生足够重要的影响。同样当权重值也很大时,输入值对输出的影响将变得不太重要。因此,我们一般将权重值初始化为更接近零的较小数值。同时,为了获得最佳的权重值,通常设置初始化权重的范围变化不大,比如权重在初始化期间采样介于 -1 和 +1 之间的随机值。
接下来,我们对使用的数据集MNIST进行缩放,并比较使用和不使用数据缩放对性能的影响。

1.2 使用缩放后的数据集训练模型

  1. 导入相关的包和 MNIST 数据集:
from keras.datasets import mnist
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.utils import np_utils
import matplotlib.pyplot as plt

(x_train, y_train), (x_test, y_test) = mnist.load_data()
  1. 缩放数据集有多种方法。一种方法是将所有数据点转换为 0 到 1 之间的值(通过将每个数据点除以数据集中的最大值,在本例中最大值为 255),展平输入数据集并对其进行缩放,如下所示:
num_pixels = x_train.shape[1] * x_train.shape[2]
x_train = x_train.reshape(-1, num_pixels).astype('float32')
x_test = x_test.reshape(-1, num_pixels).astype('float32')
x_train = x_train / 255.
x_test = x_test / 255.

另一种流行的数据缩放方法是对数据集进行归一化,以使值转换到 -1 和 +1 之间,方法是用数据平均值减去数据点,然后将得到的结果除以原始数据集的标准差:

Keras深度学习实战(3)——神经网络性能优化技术详解
3. 将训练和测试输入的值缩放至 [0, 1] 后,我们将数据集的标签转换为独热编码格式:

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
  1. 构建模型并编译它:
model = Sequential()
model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

上述模型与我们在《使用Keras构建神经网络》中构建的模型完全相同,唯一的区别是本节模型将在缩放后的数据集上进行训练。[0]

  1. 拟合模型如下:
history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=64,
                    verbose=1)

该模型的准确率约为 98.41%,而未缩放数据时训练的模型准确率在 97% 左右。绘制不同 epoch 的训练和测试的准确性以及损失值(绘制曲线图的代码与训练原始神经网络方法中使用的代码完全相同):[0]

训练与测试监测

从上图可以看出,与在未缩放数据集上训练的模型相比,训练和测试损失的变化更加平缓。尽管网络能够平滑地降低损失值,但我们看到训练和测试准确率之间存在很大差距,这表明训练数据集可能存在过度拟合。过度拟合是由于模型对训练数据的过度拟合,导致在测试数据集上的性能比在训练数据集上的性能差,泛化性能差。
除了通过将值除以最大值来缩放数据集外,其他常用的缩放方法如下:

  • min-max normalization
  • mean normalization
  • Standard deviation normalization

2. 输入值分布对模型性能的影响

到目前为止,我们还没有查看 MNIST 数据集中值的分布情况,而输入值的不同分布可以改变训练速度。在本节中,我们将了解如何通过修改输入值以缩短训练时间,更快地训练权重。
在本节中,我们将构建与上一节完全相同的模型架构。但是,将对输入数据集进行一些小的更改:

  • 反转背景和前景色。本质上,将背景涂成白色,将数字涂成黑色。

我们首先从理论上分析像素值对模型性能的影响。由于黑色像素值为零,因此当此输入乘以任何权重值时,输出为零。这将导致连接到隐藏层的黑色像素的权重值发生变化,而不会影响损失值。但是,如果有一个白色的像素,那么它会贡献一些隐藏节点的值,需要调整权重。

  1. 加载和缩放输入数据集:
from keras.datasets import mnist
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.utils import np_utils
import matplotlib.pyplot as plt

(x_train, y_train), (x_test, y_test) = mnist.load_data()

num_pixels = x_train.shape[1] * x_train.shape[2]
x_train = x_train.reshape(-1, num_pixels).astype('float32')
x_test = x_test.reshape(-1, num_pixels).astype('float32')
x_train = x_train / 255.
x_test = x_test / 255.

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
  1. 查看输入值的分布:
x_train.flatten()

前面的代码将所有输入扁平化为一个形状列表 (Keras深度学习实战(3)——神经网络性能优化技术详解)。绘制所有输入值的分布:

plt.hist(x_train.flatten())
plt.title('Histogram of input values')
plt.xlabel('Input values')
plt.ylabel('Frequency of input values')
plt.show()

由于输入图像的背景是黑色的,所以大部分输入为零(黑色像素值)。

数据值分布

  1. 使用以下代码反转颜色,使背景为白色,数字为黑色。
x_train = 1-x_train
x_test = 1-x_test

Draw the image:

plt.subplot(221)
plt.imshow(x_train[0].reshape(28,28), cmap='gray')
plt.subplot(222)
plt.imshow(x_train[1].reshape(28,28), cmap='gray')
plt.subplot(223)
plt.imshow(x_test[0].reshape(28,28), cmap='gray')
plt.subplot(224)
plt.imshow(x_test[1].reshape(28,28), cmap='gray')
plt.show()

As follows:
反转后的图片

反转颜色后生成的图像的直方图如下所示:

数据值分布

可以看到,现在大多数输入值的值为 1。

  1. 使用与以前完全相同的模型架构:
model = Sequential()
model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=64,
                    verbose=1)

绘制不同 epoch 的训练和测试的准确率以及损失值:

准确率和损失值的变化

可以看到,模型准确率下降到到 97%,相比之下,在未反转数据集的时(数据集中数据值多数为零),使用相同的 epoch 数、批大小和模型架构,训练得到的模型的准确率约为 98%。而像素值反转(数据集中数据值零较少)的情况下模型的精度为 97%,且训练过程比大多数输入像素为零的情况要慢得多。当大多数像素为零时,模型的训练更加容易,因为它只需要根据像素值大于零的少数像素值进行预测。但是,当大多数像素不为零时,需要微调更多的权重以减小损失值。

3. 批大小对模型准确率的影响

在之前的模型中,我们对于构建的所有模型使用的批大小 (batch size) 为均为 64。在本节中,我们将研究改变批大小对准确率的影响。 为了探究批大小对模型准确率,我们对比一下两种情况:

  • 批大小为 4096
  • 批大小为 64

与批大小较小的情况相比,批大小较大时,每个 epoch 中的权重更新次数较少。当批大小较小时,每个 epoch 会进行多次的权重更新,因为在每个 epoch 中,必须遍历数据集中的所有训练数据,因此如果每个 batch 使用较少的数据计算损失值,会导致每个 epoch 具有更多的 batch 才能遍历整个数据集。因此,batch 大小越小,相同 epoch 训练后的模型准确率越好。但是,还应确保批大小不能过小,以免导致过拟合。
在先前的模型中,我们使用了批大小为 64 的模型。本节中,我们继续使用相同的模型架构,仅修改模型训练的批大小,以比较不同批大小对模型性能的影响。预处理数据集并拟合模型:

(x_train, y_train), (x_test, y_test) = mnist.load_data()

num_pixels = x_train.shape[1] * x_train.shape[2]
x_train = x_train.reshape(-1, num_pixels).astype('float32')
x_test = x_test.reshape(-1, num_pixels).astype('float32')
x_train = x_train / 255.
x_test = x_test / 255.

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
model = Sequential()
model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=4096,
                    verbose=1)

代码的唯一更改是模型拟合过程中的 batch_size 参数。绘制训练和测试的准确率和损失值:

训练和测试的准确率和损失值

在上图中可以注意到,与批大小较小时的模型相比,批大小较大时模型需要训练更多的 epoch 准确率才能达到了 98%。在本节模型中,刚开始的训练阶段,模型准确率相对较低,并且只有在运行了相当多的 epoch 后模型的准确率才能达到较高水平。其原因是,批大小较小时,在每个 epoch 中权重更新的次数要少得多。
数据集总大小为 60000,当我们以批大小为 4096 运行模型 500 个 epoch 时,权重更新进行了 Keras深度学习实战(3)——神经网络性能优化技术详解 次。当批大小为 64 时,权重更新进行了 Keras深度学习实战(3)——神经网络性能优化技术详解 次。因此,批大小越小,权重更新的次数就越多,并且通常在 epoch 数相同的情况下,准确率越好。同时,应注意批大小也不能过小,这可能导致训练时间过长以及过拟合情况的出现。

4. 构建深度神经网络提高模型准确性

到目前为止,我们使用的神经网络在输入层和输出层之间只有一个隐藏层。在本节中,我们将学习在神经网络(因此称为深度神经网络)中使用多个隐藏层来探索网络深度对模型性能的影响。
深度神经网络意味着在输入层和输出层之间有多个隐藏层。多个隐藏层确保神经网络可以学习输入和输出之间复杂的非线性关系,这是简单的神经网络无法完成的要求。一个经典的深度神经网络架构如下所示:

经典深度神经网络架构

通过在输入和输出层之间添加多个隐藏层来构建深度神经网络架构,步骤如下。

  1. 加载和缩放数据集:
(x_train, y_train), (x_test, y_test) = mnist.load_data()

num_pixels = x_train.shape[1] * x_train.shape[2]
x_train = x_train.reshape(-1, num_pixels).astype('float32')
x_test = x_test.reshape(-1, num_pixels).astype('float32')
x_train = x_train / 255.
x_test = x_test / 255.

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
  1. 在输入层和输出层之间构建具有多个隐藏层的模型:
model = Sequential()
model.add(Dense(512, input_dim=num_pixels, activation='relu'))
model.add(Dense(1024, activation='relu'))
model.add(Dense(64, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))

模型架构的相关模型信息如下:

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               401920    
_________________________________________________________________
dense_1 (Dense)              (None, 1024)              525312    
_________________________________________________________________
dense_2 (Dense)              (None, 64)                65600     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                650       
=================================================================
Total params: 993,482
Trainable params: 993,482
Non-trainable params: 0
_________________________________________________________________

由于深度神经网络架构中有更多的隐藏层,因此模型中的参数也更多。

  1. 构建模型后,就该编译和拟合模型了:
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=64,
                    verbose=1)

训练完成的模型的准确度约为 98.9%,比之前所用模型架构所得到的精确度略好,这是由于 MNIST 数据集相对简单。训练和测试损失及准确率如下:

训练和测试损失及准确率

从上图中可以看出,训练数据集的准确率大大优于测试数据集的准确率,这表明深度神经网络对训练数据进行了过拟合。在以后的研究中,我们将学习避免过度拟合训练数据的方法。

5. 学习率对网络性能的影响

在先前的模型中,我们一直使用 Adam 优化器,默认学习率为 0.0001。在本节中,手动将学习率设置为更高的数值,并查看更改学习率对模型准确率的影响,使用与先前示例相同的 MNIST 数据集。
在神经网络基础中,我们了解到学习率可用于更新权重,权重的变化与减少的损失成正比。权重值的变化等于损失的减少乘以学习率。因此,学习率越低,权重值的变化就越小,反之亦然。
本质上,权重值可以被认为是一个连续的空间状态,其中权重是随机初始化的。当权重值变化较大时,空间中大量的权重值无法被充分搜索到。但是,当权重值变化较小时,权重可能会达到全局最小值,因为可以考虑更多可能的权重值:[0]

权重更新

5.1 基础示例

为了进一步理解这一点,我们拟合简单函数 Keras深度学习实战(3)——神经网络性能优化技术详解,其中初始权重值为 1.477,初始偏置值为 0。前向和后向传播过程与《神经网络基础》中的相同:[0]

def feed_forward(inputs, outputs, weights):
    hidden = np.dot(inputs, weights[0])
    out = hidden + weights[1]
    squared_error = (np.square(out - outputs))
    return squared_error

def update_weights(inputs, outputs, weights, epochs, lr):
    for epoch in range(epochs):
        org_loss = feed_forward(inputs, outputs, weights)
        wts_tmp = deepcopy(weights)
        wts_tmp2 = deepcopy(weights)
        for ix, wt in enumerate(weights):
            # print(ix, wt)
            wts_tmp[ix] += 0.0001
            loss = feed_forward(inputs, outputs, wts_tmp)
            del_loss = np.sum(org_loss - loss) / (0.0001 * len(inputs))
            wts_tmp2[ix] += del_loss * lr
            wts_tmp = deepcopy(weights)
        weights = deepcopy(wts_tmp2)
    return wts_tmp2

与在《神经网络基础》中的反向传播函数相比,唯一的变化是我们将学习率( learning rate, lr )作为参数传递给它,统计在不同 epoch 内学习率为 0.01 时的权重值的变化:[0]

x = np.array([[1], [2], [3], [4]])
y = np.array([[2], [4], [6], [8]])
w = np.array([[[1.477]], [[0]]])
w_val = []
b_val = []
for k in range(1000):
    w_new, b_new = update_weights(x, y, w, (k+1), 0.5)
    w_val.append(w_new[0])
    b_val.append(b_new[0])

可以使用以下代码绘制不同 epoch 的权重变化情况:

plt.plot(w_val)
plt.title('Weight value over different epochs when learning rate is 0.01')
plt.xlabel('epochs')
plt.ylabel('weight value')
plt.show()

绘图结果的输出如下:

学习率为0.01时权重变化

以类似的方式,绘制当学习率为 0.1 时,在不同 epoch 上的权重值如下:

学习率为0.1时权重变化

下图显示了学习率为 0.5 时不同 epoch 的权重值:

学习率为0.5时权重变化

观察以上情况,可以看到权重值最初都发生了巨大变化,并且学习率为 0.1 时模型仍可以收敛,而学习率为 0.5 时没有收敛到最优解,其陷入了局部最小值,因此无法达到最佳值 2。

5.2 MNIST 数据集上不同学习率对性能的影响

我们已经了解了学习率对模型的影响,接下来我们来看一下学习率对 MNIST 数据集上训练的模型影响,在该模型中,我们使用与之前相同的模型架构,仅更改学习率参数。

  1. 通过指定优化器来改变模型的学习率:
from keras import optimizers
# 以学习速率 `0.01` 初始化 `Adam` 优化器
adam = optimizers.Adam(lr=0.01)
  1. 构建、编译和拟合模型如下:
model = Sequential()
model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['acc'])

history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=64,
                    verbose=1)

在 50 个 epoch 结束时,网络的准确性约为 91%,观察损失值和准确率在不同 epoch 之间如何变化:

损失值和准确率的变化情况

学习率高时,损失下降不太平滑,学习率低的模型会缓慢更新权重,从而导致损失值平滑减少,准确率更高。或者可以说,在学习率高的情况下,损失值的跳跃式变化是由于损失值卡在了一个局部最小值上造成的。较低的学习率可以更好地实现最佳权重值,因为权重缓慢但稳定地朝着正确的方向变化。
我们可以以类似的方式探索学习率高达 0.1 时的网络准确率:

from keras import optimizers
adam=optimizers.Adam(lr=0.1)

model = Sequential()
model.add(Dense(1000, input_dim=784, activation='relu'))
model.add(Dense(10,  activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])
history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=64,
                    verbose=1)

由于学习率高,损失值无法进一步降低,权重可能保持在局部最小值:

损失值和准确率的变化情况

因此,一般来说,将学习率设置为较低的值并让网络训练大量时间是一种常见的策略。

6. 不同损失优化器对模型性能的影响

在前面的模型中,我们设定损失优化器为 Adam 优化器。但是,优化器还有其他多种类型,优化器中的更改可能会影响模型学习拟合输入和输出的速度。在本节中,将了解更改优化器对模型准确性的影响。
为了了解更改优化器对网络准确性的影响,我们将前几节中的 Adam 优化器与本节中使用随机梯度下降优化器进行对比:

model = Sequential()
model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['acc'])

history = model.fit(x_train, y_train,
                    validation_data=(x_test, y_test),
                    epochs=50,
                    batch_size=64,
                    verbose=1)

当我们使用随机梯度下降优化器时,在 50 个 epoch 之后的最终精度约为 98%:

使用随机梯度下降

但是,与使用 Adam 优化器的模型相比,该模型获得较高准确率的速度要慢得多。其他一些可用的损失优化器如下:

  • RMSprop
  • Adagrad
  • Adadelta
  • Adamax
  • Nadam

summary

在本文中,我们学习了数据预处理、批量大小、网络深度等各种因素在网络训练中的重要作用,并研究了如何通过使用不同的超参数来优化神经网络性能,以充分发挥神经网络模型的模拟作用。 .综合能力;并初步了解过拟合对模型的不利影响。

Series link

Keras深度学习实战(1)——神经网络基础与模型训练过程详解
Keras深度学习实战(2)——使用Keras构建神经网络[0][1]

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年4月25日
下一篇 2022年4月25日

相关推荐