粗读《Python 深度学习》(2)

第三章 神经网络入门

3.4 电源评论分类:二分类问题

3.4.1 IMDB 数据集

与 MNIST 数据集一样,IMDB 数据集也内置与 Keras 库。

from keras.datasets import imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words=10000)

参数 num_words=10000 的意思是仅保留训练数据中前 10 000 个最常出现的单词。低频单词将被舍弃。这样得到的向量数据不会太大,便于处理。

>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1

train_datatest_data 两个变量都是评论组成的列表,每条评论是由单词索引组成的列表。train_labelstest_labels 都是 0 和 1 组成的列表,0 代表负面评价,1 代表正面评价。

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]]) # 翻译文本

利用上述代码将评论翻译为文本。其中,i-3 是因为 0、1、2 是为“padding”(填充)、“start of sequence”(序列开始)、“unknown”(未知词)分别保留的索引。

3.4.2 准备数据

将整数序列输入到神经网络之前,需要进行 预处理,将列表转换为 张量。该书在此处介绍了两种:
1、填充列表,使其具有相同的长度,再将列表转换成形状为(samples, word_indices)的整数张量,然后网络第一层使用能处理这种整数张量的层(即 Embedding 层)。
2、使用 one-hot 编码,将其转换为 0 和 1 组成的向量。例如,[3, 5, 3] 会被转换为 10 000 维的向量,其中索引为 3 和 5 的元素为 1,其他位置为 0。
下列为 one-hot 编码

import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))  # 创建零矩阵
    for i, sequence in enumerate(sequences):         # 遍历列表,sequence 为一句评论的列表
        results[i, sequence] = 1.                    # 将包含元素的位置设为 1,为浮点数
     return results
    
x_train = vectorize_sequences(train_data)            # 将数据向量化
x_test = vectorize_sequences(test_data)


y_train = np.asarray(train_labels).astype('float32') # 将标签向量化,浮点数
y_test = np.asarray(test_labels).astype('float32')

3.4.3 构建网络

输入数据为简单向量时,选用带有 relu 激活 的全连接层(Dense)最为简单有效:Dense(16, activation='relu' )16是指该 Dense 层包含 16 个 隐藏单元(hidden unit)隐藏单元越多(即更高维的表示空间),网络越能够学到更加复杂的表示,但网络的计算代价也变得更大,而且可能会导致学到不好的模式(挖掘出训练数据集包含的内在特征联系,过拟合)。

对于这种 Dense 层的堆叠,需要确定以下两个关键架构:
1、网络有多少层;
2、每层的隐藏单元数。

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

Relu(rectified linear unit,整流线性单元)函数将所有负值归零,而 Sigmoid 函数则将任意值“压缩”到 [0, 1] 区间内,其输出值可以看作概率值。

Dense 层只包含两个线性运算——点积加法。这使得 Dense 层只能学习输入数据的 线性变换(仿射变换)。该层的假设空间是从输入数据到 16 位空间所有可能的 线性变换集合为此,通过引入激活函数为模型输入——输出映射添加非线性变化。

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

但对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。 交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离

可通过向 optimizer 参数传入一个优化器类实例来实现;后者可通过向 lossmetrics 参数传入函数对象来实现。

from keras import losses
from keras import metrics

model.compile(optimizer=optimizers.RMSprop(lr=0.001),
              loss=losses.binary_crossentropy,
              metrics=[metrics.binary_accuracy])

3.4.4 验证你的方法

为了在训练过程中监控模型在前所未见的数据上的精度,你需要将原始训练数据留出 10 000 个样本作为 验证集

x_val = x_train[:10000]             # 验证集
partial_x_train = x_train[10000:]   # 模型训练用数据
y_val = y_train[:10000]
partial_y_train = y_train[10000:]

现在使用 512 个样本组成的小批量,将模型训练 20 个轮次(即对 x_train 和 y_train 两
个张量中的所有样本进行 20 次迭代;每一次迭代中,随机选取 512 个样本为一批次进行训练并梯度下降,直到提取完所有数据)。

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))

通过将验证数据传入到 validation_data 参数,可以监控在验证集 10 000 个样本上的损失和精度。调用 model.fit() 返回了一个 History 对象。这个对象有一个成员 history,它是一个字典,包含训练过程中的所有数据。

>>> history_dict = history.history
>>> history_dict.keys()
dict_keys(['val_acc', 'acc', 'val_loss', 'loss']) # 验证精度、训练精度、验证损失、训练损失

利用 Matplotlib 绘制训练损失和验证损失,以及训练精度和验证精度。

1、训练损失和验证损失

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)   # 避开 x 轴零点

plt.plot(epochs, loss_values, 'bo', label='Training loss')    # 'bo' 表示蓝色圆点
plt.plot(epochs, val_loss_values, 'b', label='Validation loss')  # 'b' 表示蓝色实线
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

在这里插入图片描述

2、训练精度和验证精度


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

在这里插入图片描述

由此可见,模型在第 4 轮达到最佳,之后开始过拟合

3、从头开始训练一个新的网络,训练 4 轮,然后在测试数据上评估模型。

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)

训练结果为:

>>> results
[0.2929924130630493, 0.88327999999999995]   # [损失,精度]

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

利用训练好的网络模型对新数据进行预测。

>>> model.predict(x_test)
array([[ 0.98006207]
       [ 0.99758697]
       [ 0.99975556]
       ...,
       [ 0.82167041]
       [ 0.02885115]
       [ 0.65371346]], dtype=float32)
       

3.4.6 进一步的实验

1、改变隐藏层数量;
2、改变隐藏单元数量;
3、尝试使用均方误差 mse 损失函数代替 binary_crossentropy
3、尝试使用 tanh 激活函数代替 relu

3.4.7 小结

1、对原始数据进行 预处理,将其转换为 张量
2、使用带有 Relu 激活的全连接层堆叠成模型;
3、输出层使用 Sigmoid 激活函数 输出 0 ~ 1 的标量表示 概率值
4、对于二分类问题选用 二元交叉熵 作为损失函数;
5、随着神经网络在训练数据上的表现越来越好,模型最终会过拟合,并在前所未见的数据上得到越来越差的结果。一定要一直监控模型在训练集之外的数据上的性能。

3.5 新闻分类:多分类问题

本节将路透社新闻划分为 46 个互斥的主题。因为有多个类别,所以这是 多分类(multiclass classification)问题 的一个例子。因为每个数据点只能划分到一个类别,所以更具体地说,这是 单标签、多分类(single-label, multiclass classification)问题 的一个例子。如果每个数据点可以划分到多个类别(主题),那它就是一个 多标签、多分类(multilabel, multiclass classification)问题

3.5.1 路透社数据集

IMDB 和 MNIST 类似,路透社数据集也内置为 Keras 的一部分。

from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)

3.5.2 准备数据

需要对数据进行预处理,将其转换为张量。与 3.4 节相似,使用 one-hot 编码 将训练数据和测试数据转换为张量。

该书在此处提出两种标签向量化的方法:将标签列表转换为 整数张量,或者使用 one-hot 编码。Keras 内置方法可以实现 one-hot 编码,这在 MNIST 例子中已经使用过。

from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)

3.5.3 构建网络

与二分类问题相似,多分类问题一样可以采用全连接层堆叠来形成模型。在这个例子中,两者的区别在于多分类问题的输出类别远远多于二分类问题。而全连接层只能访问上一层输出的信息。如果某一层丢失了与分类问题相关的一些信息,那么这些信息无法被后面的层找回,也就是说,每一层都可能成为 信息瓶颈。所以需要更多隐藏单元来保留尽可能多的信息。

from keras import models
from keras import layers

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

输出层使用了 softmax 激活。网络将输出在 46 个不同输出类别上的概率分布——对于每一个输入样本,网络都会输出一个 46 维向量,其中 output[i] 是样本属于第 i 个类别的概率。46 个概率的总和为 1。

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

这个例子中,选用 categorical_crossentropy(分类交叉熵) 作为损失函数。通过将网络输出的概率分布和标签的真实分布之间的距离最小化,使网络的输出结果尽可能接近真实标签。

3.5.4 验证你的方法

1、留出验证集

x_val = x_train[:1000]
partial_x_train = x_train[1000:]

y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]

2、训练模型

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))

3、绘制训练损失和验证损失

import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']

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

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

在这里插入图片描述

4、绘制训练精度和验证精度

plt.clf()

acc = history.history['acc']
val_acc = history.history['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()

在这里插入图片描述

可见,网络在训练 9 轮后开始过拟合。从新训练一个模型,共 9 个轮次,然后在测试集上评估模型。

5、从头开始重新训练一个模型

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(partial_x_train,
          partial_y_train,
          epochs=9,
          batch_size=512,
          validation_data=(x_val, y_val))
results = model.evaluate(x_test, one_hot_test_labels)

训练结果:

>>> results
[0.9565213431445807, 0.79697239536954589]

3.5.5 在新数据上生成预测结果

predictions = model.predict(x_test)

结果:

>>> np.argmax(predictions[0])
4

3.5.6 处理标签和损失的另一种方法

将标签转换为整数张量:

y_train = np.array(train_labels)
y_test = np.array(test_labels)

对于整数标签,应该使用 sparse_categorical_crossentropy (稀疏范畴交叉熵) 作为损失函数。这个损失函数在数学上与 categorical_crossentropy 完全相同,二者只是接口不同。

3.5.7 中间层维度足够大的重要性

model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))

将中间层的隐藏单元数减少为 4 个,训练好的模型精度大约为 71%,下降了 8%。

3.5.8 进一步的实验

1、改变隐藏单元数目;
2、改变隐藏层数目。

3.5.9 小结

1、单标签、多分类问题的输出层为大小为类别数目;
2、对于单标签、多分类问题,网络的最后一层应该使用 softmax 激活,这样可以输出在 N 个输出类别上的概率分布;
3、这类问题大多使用 分类交叉熵 作为损失函数;
4、将标签编码为整数时,应使用 sparse_categorical_crossentropy 作为损失函数
5、应该避免使用太小的中间层,以免在网络中造成信息瓶颈。

3.6 预测房价:回归问题

前面两个例子都是 分类问题,其目标是预测输入数据点所对应的单一离散的标签。另一种常见的机器学习问题是 回归问题,它预测一个连续值而不是离散的标签。

3.6.1 波士顿房价数据集

本节要预测的是 20 世纪 70 年代中期波士顿郊区房屋价格的中位数,已知当时郊区的一些数据点,比如犯罪率、当地房产税率等。这个数据集较小,且输入数据的每个特征(比如犯罪率)都有不同的取值范围

from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

3.6.2 准备数据

输入数据的特征取值范围差异较大,这会加大模型的训练难度。所以,需要对输入数据标准化,即对于输入数据的每个特征(输入数据矩阵中的列),减去特征平均值,再除以标准差,这样得到的特征平均值为 0,标准差为 1。

mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std

test_data -= mean
test_data /= std

上述代码中,测试数据标准化的均值和标准差都是在训练数据上计算得到的这是因为在工作流程中,不能使用在测试数据上计算得到的任何结果,即使是像数据标准化这么简单的事情也不行。

3.6.3 构建网络

由于样本数量很少,所以使用了一个非常小的网络。一般来说,训练数据越少,过拟合会越严重,而较小的网络可以降低过拟合。

from keras import models
from keras import layers
def build_model():
    model = models.Sequential() 
    model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    return model

上述代码搭建的网络最后一层只有一个单元,没有激活,是一个线性层。这是标量回归(标量回归是预测单一连续值的回归)的典型设置。添加激活函数将会限制输出范围。

编译网络用的是 mse 损失函数,即 均方误差(MSE,mean squared error),预测值与目标值之差的平方。这是回归问题常用的损失函数。在训练过程中监控的指标是 平均绝对误差(MAE,mean absolute error)。它是预测值与目标值之差的绝对值。

3.6.4 使用 K 折验证来验证你的方法

但由于数据点很少,验证集也会非常小。根据选取的验证集和训练集的不同,验证分数可能会有很大波动。也就是说,验证集的划分方式可能会造成验证分数上有很大的方差,这样就无法对模型进行可靠的评估。在这种情况下,最佳做法是使用 K 折交叉验证

在这里插入图片描述
1、K折验证

import numpy as np

k = 4
num_val_samples = len(train_data)//k    # 整除
num_epochs = 100
all_scores = []

for i in range(k):
    print('processing fold #', i)
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] 
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
    
    partial_train_data = np.concatenate(          # 将两段数据集拼接在一起
        [train_data[:i * num_val_samples],
        train_data[(i + 1) * num_val_samples:]], 
        axis=0)
    partial_train_targets = np.concatenate(       # 将两段数据集的标签拼接在一起
        [train_targets[:i * num_val_samples],
        train_targets[(i + 1) * num_val_samples:]], 
        axis=0)
        
    model = build_model() 
    model.fit(partial_train_data, partial_train_targets, 
              epochs=num_epochs, batch_size=1, verbose=0)  # 训练模型(静默模式,verbose=0)
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)  # 在验证数据上评估模型
    all_scores.append(val_mae)   # 记录每一折的验证分数

2、保存每折的验证结果

num_epochs = 500 
all_mae_histories = []

for i in range(k):
    print('processing fold #', i)
    ...
    
    build_model()
    history = model.fit(partial_train_data, partial_train_targets, 
                        validation_data=(val_data, val_targets), 
                        epochs=num_epochs, batch_size=1, verbose=0)
    mae_history = history.history['val_mean_absolute_error']
    all_mae_histories.append(mae_history)

3、计算所有轮次中的 K 折验证分数平均值

average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

4、绘制验证分数

import matplotlib.pyplot as plt

plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

在这里插入图片描述

因为纵轴的范围较大,且数据方差相对较大,所以难以看清这张图的规律。作出下列两点修改,并重新绘制:
1、删除前 10 个数据点,因为它们的取值范围与曲线上的其他点不同。
2、将每个数据点替换为前面数据点的 指数移动平均值,以得到光滑的曲线。

5、重新绘制

def smooth_curve(points, factor=0.9):
    smoothed_points = []
    for point in points:
      if smoothed_points:
        previous = smoothed_points[-1]
        smoothed_points.append(previous * factor + point * (1 - factor))
      else:
        smoothed_points.append(point)
    return smoothed_points

smooth_mae_history = smooth_curve(average_mae_history[10:])

plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

在这里插入图片描述

可以看出,验证 MAE 在 80 轮后不再显著降低,之后就开始过拟合。

6、训练最终模型

model = build_model() 
model.fit(train_data, train_targets, 
          epochs=80, batch_size=16, verbose=0)   # 之前模型批量数为 1
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)

3.6.5 小结

1、回归常用的损失函数是 均方误差(MSE)
2、常见的回归指标是平均 绝对误差(MAE)
3、如果输入数据的特征具有不同的取值范围,应该先进行预处理,对每个特征单独进行 标准化
4、如果可用的数据很少,使用 K 折验证 来评估模型。
5、如果可用的训练数据很少,最好使用隐藏层较少的小型网络,以避免严重的过拟合。

小结

1、二分类问题多分类问题回归问题 的基本思路;
2、数据预处理,本章只讲了:将数据转换为张量和标准化;
3、激活函数,使用带有 relu 激活的全连接层堆叠成模型,输出层根据实际需要选取;
4、损失函数,本章介绍了 二元交叉熵分类交叉熵稀疏范畴交叉熵均方误差
5、过拟合,本章讨论了如何降低过拟合,包括:监控模型在训练集外的数据上的性能,选择合适了迭代次数,选择合适的模型结构。
6、在可用数据较少时,使用 K 折验证 来评估回归模型。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猎猫骑巨兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值