从零开始学keras(四)

2022-08-24 15:48:07 浏览数 (1)

导读】Keras是一个由Python编写的开源人工神经网络库,可以作为Tensorflow、和Theano的高阶应用程序接口,进行深度学习模型的设计、调试、评估、应用和可视化。本系列将教你如何从零开始学Keras,从搭建神经网络到项目实战,手把手教你精通Keras。相关内容参考《Python深度学习》这本书。

前面讲的都是分类问题,其目标是预测输入数据点所对应的单一离散的标签。另一种常见的机器学习问题是回归问题,它预测一个连续值而不是离散的标签,例如,根据气象数据预测明天的气温,或者根据软件说明书预测完成软件项目所需要的时间。

  注意:不要将回归问题与 logistic 回归算法混为一谈。令人困惑的是,logistic 回归不是回归算法,而是分类算法。

波士顿房价预测

  本节将要预测 20 世纪 70 年代中期波士顿郊区房屋价格的中位数,已知当时郊区的一些数据点,比如犯罪率、当地房产税率等。本节用到的数据集与前面两个例子有一个有趣的区别。它包含的数据点相对较少,只有 506 个,分为 404 个训练样本和 102 个测试样本。输入数据的每个特征(比如犯罪率)都有不同的取值范围。例如,有些特性是比例,取值范围为 0~1;有的取值范围为 1~12;还有的取值范围为 0~100,等等。

代码语言:javascript复制
import keras
from keras.datasets import boston_housing
 
(train_data, train_targets), (test_data, test_targets) =  boston_housing.load_data()#这里下载有时候会失败,多试几次,不需要访问国外网站
 
print(train_data.shape)
输出(404, 13)
 
print(test_data.shape)
输出为:(102, 13)

  如你所见,我们有 404 个训练样本和 102 个测试样本,每个样本都有 13 个数值特征,比如人均犯罪率、每个住宅的平均房间数、高速公路可达性等。

  目标是房屋价格的中位数,单位是千美元。

代码语言:javascript复制
  1. print(train_targets)
  2. 输出为:
  3. array([15.2, 42.3, 50. , 21.1, 17.7, 18.5, 11.3, 15.6, 15.6, 14.4, 12.1,
  4. 17.9, 23.1, 19.9, 15.7, 8.8, 50. , 22.5, 24.1, 27.5, 10.9, 30.8,
  5. 32.9, 24. , 18.5, 13.3, 22.9, 34.7, 16.6, 17.5, 22.3, 16.1, 14.9,
  6. 23.1, 34.9, 25. , 13.9, 13.1, 20.4, 20. , 15.2, 24.7, 22.2, 16.7,
  7. 12.7, 15.6, 18.4, 21. , 30.1, 15.1, 18.7, 9.6, 31.5, 24.8, 19.1,
  8. 22. , 14.5, 11. , 32. , 29.4, 20.3, 24.4, 14.6, 19.5, 14.1, 14.3,
  9. 15.6, 10.5, 6.3, 19.3, 19.3, 13.4, 36.4, 17.8, 13.5, 16.5, 8.3,
  10. 14.3, 16. , 13.4, 28.6, 43.5, 20.2, 22. , 23. , 20.7, 12.5, 48.5,
  11. 14.6, 13.4, 23.7, 50. , 21.7, 39.8, 38.7, 22.2, 34.9, 22.5, 31.1,
  12. 28.7, 46. , 41.7, 21. , 26.6, 15. , 24.4, 13.3, 21.2, 11.7, 21.7,
  13. 19.4, 50. , 22.8, 19.7, 24.7, 36.2, 14.2, 18.9, 18.3, 20.6, 24.6,
  14. 18.2, 8.7, 44. , 10.4, 13.2, 21.2, 37. , 30.7, 22.9, 20. , 19.3,
  15. 31.7, 32. , 23.1, 18.8, 10.9, 50. , 19.6, 5. , 14.4, 19.8, 13.8,
  16. 19.6, 23.9, 24.5, 25. , 19.9, 17.2, 24.6, 13.5, 26.6, 21.4, 11.9,
  17. 22.6, 19.6, 8.5, 23.7, 23.1, 22.4, 20.5, 23.6, 18.4, 35.2, 23.1,
  18. 27.9, 20.6, 23.7, 28. , 13.6, 27.1, 23.6, 20.6, 18.2, 21.7, 17.1,
  19. 8.4, 25.3, 13.8, 22.2, 18.4, 20.7, 31.6, 30.5, 20.3, 8.8, 19.2,
  20. 19.4, 23.1, 23. , 14.8, 48.8, 22.6, 33.4, 21.1, 13.6, 32.2, 13.1,
  21. 23.4, 18.9, 23.9, 11.8, 23.3, 22.8, 19.6, 16.7, 13.4, 22.2, 20.4,
  22. 21.8, 26.4, 14.9, 24.1, 23.8, 12.3, 29.1, 21. , 19.5, 23.3, 23.8,
  23. 17.8, 11.5, 21.7, 19.9, 25. , 33.4, 28.5, 21.4, 24.3, 27.5, 33.1,
  24. 16.2, 23.3, 48.3, 22.9, 22.8, 13.1, 12.7, 22.6, 15. , 15.3, 10.5,
  25. 24. , 18.5, 21.7, 19.5, 33.2, 23.2, 5. , 19.1, 12.7, 22.3, 10.2,
  26. 13.9, 16.3, 17. , 20.1, 29.9, 17.2, 37.3, 45.4, 17.8, 23.2, 29. ,
  27. 22. , 18. , 17.4, 34.6, 20.1, 25. , 15.6, 24.8, 28.2, 21.2, 21.4,
  28. 23.8, 31. , 26.2, 17.4, 37.9, 17.5, 20. , 8.3, 23.9, 8.4, 13.8,
  29. 7.2, 11.7, 17.1, 21.6, 50. , 16.1, 20.4, 20.6, 21.4, 20.6, 36.5,
  30. 8.5, 24.8, 10.8, 21.9, 17.3, 18.9, 36.2, 14.9, 18.2, 33.3, 21.8,
  31. 19.7, 31.6, 24.8, 19.4, 22.8, 7.5, 44.8, 16.8, 18.7, 50. , 50. ,
  32. 19.5, 20.1, 50. , 17.2, 20.8, 19.3, 41.3, 20.4, 20.5, 13.8, 16.5,
  33. 23.9, 20.6, 31.5, 23.3, 16.8, 14. , 33.8, 36.1, 12.8, 18.3, 18.7,
  34. 19.1, 29. , 30.1, 50. , 50. , 22. , 11.9, 37.6, 50. , 22.7, 20.8,
  35. 23.5, 27.9, 50. , 19.3, 23.9, 22.6, 15.2, 21.7, 19.2, 43.8, 20.3,
  36. 33.2, 19.9, 22.5, 32.7, 22. , 17.1, 19. , 15. , 16.1, 25.1, 23.7,
  37. 28.7, 37.2, 22.6, 16.4, 25. , 29.8, 22.1, 17.4, 18.1, 30.3, 17.5,
  38. 24.7, 12.6, 26.5, 28.7, 13.3, 10.4, 24.4, 23. , 20. , 17.8, 7. ,
  39. 11.8, 24.4, 13.8, 19.4, 25.2, 19.4, 19.4, 29.1])

  房价大都在 10 000~50 000 美元。如果你觉得这很便宜,不要忘记当时是 20 世纪 70 年代中 期,而且这些价格没有根据通货膨胀进行调整。

准备数据

  将取值范围差异很大的数据输入到神经网络中,这是有问题的。网络可能会自动适应这种取值范围不同的数据,但学习肯定变得更加困难。对于这种数据,普遍采用的最佳实践是对每个特征做标准化,即对于输入数据的每个特征(输入数据矩阵中的列),减去特征平均值,再除以标准差,这样得到的特征平均值为 0,标准差为 1。用 Numpy 可以很容易实现标准化。

代码语言:javascript复制
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
 
test_data -= mean
test_data /= std

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

构建网络

  由于样本数量很少,我们将使用一个非常小的网络,其中包含两个隐藏层,每层有 64 个单元。一般来说,训练数据越少,过拟合会越严重,而较小的网络可以降低过拟合。

代码语言:javascript复制
from keras import models
from keras import layers
 
def build_model():
    # Because we will need to instantiate the same model multiple times,(因为需要将同一个模型多次实例化,)
    # we use a function to construct it.(所以用一个函数来构建模型)
    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

  网络的最后一层只有一个单元,没有激活,是一个线性层。这是标量回归(标量回归是预测单一连续值的回归)的典型设置。添加激活函数将会限制输出范围。例如,如果向最后一层添加 sigmoid 激活函数,网络只能学会预测 0~1 范围内的值。这里最后一层是纯线性的,所以网络可以学会预测任意范围内的值。

  注意,编译网络用的是 mse 损失函数,即均方误差(MSE,mean squared error),预测值与目标值之差的平方。这是回归问题常用的损失函数。

  在训练过程中还监控一个新指标:平均绝对误差(MAE,mean absolute error)。它是预测值与目标值之差的绝对值。比如,如果这个问题的 MAE 等于 0.5,就表示你预测的房价与实际价格平均相差 500 美元。

利用 K 折验证来验证你的方法

  为了在调节网络参数(比如训练的轮数)的同时对网络进行评估,你可以将数据划分为训 练集和验证集,正如前面例子中所做的那样。但由于数据点很少,验证集会非常小(比如大约100 个样本)。因此,验证分数可能会有很大波动,这取决于你所选择的验证集和训练集。也就是说,验证集的划分方式可能会造成验证分数上有很大的方差,这样就无法对模型进行可靠的评估。在这种情况下,最佳做法是使用 K 折交叉验证。这种方法将可用数据划分为 K 个分区(K 通常取 4 或 5),实例化 K 个相同的模型,将每个模型在 K-1 个分区上训练,并在剩下的一个分区上进行评估。模型的验证分数等于 K 个验证分数的平均值。

这种代码的实现很简单。

代码语言:javascript复制
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)
    # Prepare the validation data: data from partition # k(准备验证数据:第 k 个分区的数据)
    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]
 
    # Prepare the training data: data from all other partitions(准备训练数据:其他所有分区的数据)
    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)
 
    # Build the Keras model (already compiled)(构建 Keras 模型(已编译))
    model = build_model()
    # Train the model (in silent mode, verbose=0)(训练模型(静默模式,)
    model.fit(partial_train_data, partial_train_targets,
              epochs=num_epochs, batch_size=1, verbose=0)
    # Evaluate the model on the validation data(在验证数据上评估模型)
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    all_scores.append(val_mae)
 
输出为:
processing fold # 0
processing fold # 1
processing fold # 2
processing fold # 3

  每次运行模型得到的验证分数有很大差异,从 2.1 到 2.9 不等。平均分数(2.4)是比单一分数更可靠的指标——这就是 K 折交叉验证的关键。在这个例子中,预测的房价与实际价格平均相差 2400 美元,考虑到实际价格范围在 10 000~50 000 美元,这一差别还是很大的。

  我们让训练时间更长一点,达到 500 个轮次。为了记录模型在每轮的表现,我们需要修改训练循环,以保存每轮的验证分数记录。

代码语言:javascript复制
from keras import backend as K
 
# Some memory clean-up
K.clear_session()
num_epochs = 500
all_mae_histories = []
for i in range(k):
    print('processing fold #', i)
    # Prepare the validation data: data from partition # k(准备验证数据:第 k 个分区的数据)
    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]
 
    # Prepare the training data: data from all other partitions(准备训练数据:其他所有分区的数据)
    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)
 
    # Build the Keras model (already compiled)(构建 Keras 模型(已编译))
    model = build_model()
    # Train the model (in silent mode, verbose=0)(训练模型(静默模式,verbose=0))
    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)

输出为:

代码语言:javascript复制
processing fold # 0
processing fold # 1
processing fold # 2
processing fold # 3

  然后你可以计算每个轮次中所有折 MAE 的平均值。

代码语言:javascript复制
average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
 
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()

  因为纵轴的范围较大,且数据方差相对较大,所以难以看清这张图的规律。我们来重新绘制一张图。

删除前 10 个数据点,因为它们的取值范围与曲线上的其他点不同。 将每个数据点替换为前面数据点的指数移动平均值,以得到光滑的曲线。

代码语言:javascript复制
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 轮后不再显著降低,之后就开始过拟合。

  完成模型调参之后(除了轮数,还可以调节隐藏层大小),你可以使用最佳参数在所有训练数据上训练最终的生产模型,然后观察模型在测试集上的性能。

代码语言:javascript复制
# Get a fresh, compiled model.
model = build_model()
# Train it on the entirety of the data.
model.fit(train_data, train_targets,
          epochs=80, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
 
print(test_mae_score)
 
输出为:
2.686123801212685

你预测的房价还是和实际价格相差约 2550 美元。

Reference

[1]《Python深度学习》François Chollet[美]著[2]https://github.com/fengdu78/machine_learning_beginner/tree/master/deep-learning-with-python-notebooks

未完待续,如果有什么不懂的欢迎随时交流!

0 人点赞