1.图像分类数据的标准化
本篇内容是【TensorFlow2 Fashion-MNIST 图像分类(一)】的第二部分,请先阅读第一部分中的内容。
上一部分内容最后提到,模型训练结果出现不拟合现象,主要原因就在于特征数据没有进行标准化处理,因此本部分针对数据进行标准化处理。
关于归一化和标准化的定义和区别,可以参考下面这篇文章:
标准化和归一化,请勿混为一谈,透彻理解数据变换
标准化和归一化的用途,之前简要进行过说明,此处再补充一下:
- 统计建模中,如回归模型,自变量 X XX 的量纲不一致导致了回归系数无法直接解读或者错误解读;需要将 X XX 都处理到统一量纲下,这样才可比; 
- 机器学习任务和统计学任务中有很多地方要用到“距离”的计算,比如 PCA,比如 KNN,比如 kmeans 等等,假使算欧式距离,不同维度量纲不同可能会导致距离的计算依赖于量纲较大的那些特征而得到不合理的结果; 
- 参数估计时使用梯度下降,在使用梯度下降的方法求解最优化问题时, 归一化/标准化后可以加快梯度下降的求解速度,即提升模型的收敛速度。 
数据加载部分如下:
 def load_data():  """Loads the Fashion-MNIST dataset.
  This is a dataset of 60,000 28x28 grayscale images of 10 fashion categories,  along with a test set of 10,000 images. This dataset can be used as  a drop-in replacement for MNIST. The class labels are:
  | Label | Description |  |:-----:|-------------|  |   0   | T-shirt/top |  |   1   | Trouser     |  |   2   | Pullover    |  |   3   | Dress       |  |   4   | Coat        |  |   5   | Sandal      |  |   6   | Shirt       |  |   7   | Sneaker     |  |   8   | Bag         |  |   9   | Ankle boot  |
  Returns:      Tuple of Numpy arrays: `(x_train, y_train), (x_test, y_test)`.
      **x_train, x_test**: uint8 arrays of grayscale image data with shape        (num_samples, 28, 28).
      **y_train, y_test**: uint8 arrays of labels (integers in range 0-9)        with shape (num_samples,).
  License:      The copyright for Fashion-MNIST is held by Zalando SE.      Fashion-MNIST is licensed under the [MIT license](      https://github.com/zalandoresearch/fashion-mnist/blob/master/LICENSE).
  """  dirname = os.path.join('datasets', 'fashion-mnist')  # 数据下载到本地  base = 'data/'  # base = 'https://storage.googleapis.com/tensorflow/tf-keras-datasets/'  files = [      'train-labels-idx1-ubyte.gz', 'train-images-idx3-ubyte.gz',      't10k-labels-idx1-ubyte.gz', 't10k-images-idx3-ubyte.gz'  ]
  paths = [base + f_name for f_name in files]  # for fname in files:  #   paths.append(get_file(fname, origin=base + fname, cache_subdir=dirname))
  with gzip.open(paths[0], 'rb') as lbpath:    y_train = np.frombuffer(lbpath.read(), np.uint8, offset=8)
  with gzip.open(paths[1], 'rb') as imgpath:    x_train = np.frombuffer(        imgpath.read(), np.uint8, offset=16).reshape(len(y_train), 28, 28)
  with gzip.open(paths[2], 'rb') as lbpath:    y_test = np.frombuffer(lbpath.read(), np.uint8, offset=8)
  with gzip.open(paths[3], 'rb') as imgpath:    x_test = np.frombuffer(        imgpath.read(), np.uint8, offset=16).reshape(len(y_test), 28, 28)
  return (x_train, y_train), (x_test, y_test)
# fashion_mnist = keras.datasets.fashion_mnist(x_train_all, y_train_all), (x_test, y_test) = load_data()x_valid, x_train = x_train_all[:5000], x_train_all[5000:]y_valid, y_train = y_train_all[:5000], y_train_all[5000:]
print(x_valid.shape, y_valid.shape)print(x_train.shape, y_train.shape)print(x_test.shape, y_test.shape)
   复制代码
 查看训练数据中的特征数据分布:
 print(np.max(x_train), np.min(x_train))# 结果如下:255 0
   复制代码
 
此处数据变变换的方式,采用标准化,这样处理之后的数据就符合均值是 0,标准差是 1,但有一点需要说明,均值是 0,标准差是 1 的分布不一定是正态分布。标准化直接使用 sklearn 中的已经封装好的处理函数,需要注意的问题已经在注释中进行说明和解释。
 # x = (x - u) / std
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()# x_train: [None, 28, 28] -> [None, 784]# 此处采用fit_transform是因为,该函数可以将训练集的均值和方差记录下来,这样在验证集和测试集可以保持一致,这样也就保证了数据是同分布的,模型的构建和训练才会有效x_train_scaled = scaler.fit_transform(    x_train.astype(np.float32).reshape(-1, 1)).reshape(-1, 28, 28)x_valid_scaled = scaler.transform(    x_valid.astype(np.float32).reshape(-1, 1)).reshape(-1, 28, 28)x_test_scaled = scaler.transform(    x_test.astype(np.float32).reshape(-1, 1)).reshape(-1, 28, 28)
   复制代码
 再次查看训练集中特征数据分布,
 print(np.max(x_train_scaled), np.min(x_train_scaled))# 结果如下:2.023144 -0.8105139
   复制代码
 可以看出,最大最小值分别为 2.023144,-0.8105139。与归一化之前差别较大。对于特征数据而言,不管是由于量纲的不同导致的模型拟合问题还是数据中的极值导致的模型效果较差问题,都与梯度求解相关,因为在优化目标函数值的时候必然会涉及到求导,那么在优化目标函数的时候,自然是沿着负梯度方向去减小函数值,以此达到我们的优化目标。如何沿着负梯度方向减小函数值呢?既然梯度是偏导数的集合,如下:
同时梯度和偏导数都是向量,那么参考向量运算法则,我们在每个变量轴上减小对应变量值即可,梯度下降法可以描述如下:
更直观的我们可以通过两个图来区别。
标准化之后,训练的速度会更快,因为求解梯度的过程是沿着法向量的方向。
2. 模型构建
模型构建的部分与之前相同,代码如下:
 # tf.keras.models.Sequential()
"""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')])
# relu: y = max(0, x)# softmax: 将向量变成概率分布. x = [x1, x2, x3], #          y = [e^x1/sum, e^x2/sum, e^x3/sum], sum = e^x1 + e^x2 + e^x3
# reason for sparse: y->index. y->one_hot->[] model.compile(loss="sparse_categorical_crossentropy",              optimizer = "sgd",              metrics = ["accuracy"])
   复制代码
 进行模型训练
 history = model.fit(x_train_scaled, y_train, epochs=10,                    validation_data=(x_valid_scaled, y_valid))
   复制代码
 训练结果如下:
 Epoch 1/101719/1719 [==============================] - 6s 3ms/step - loss: 0.5309 - accuracy: 0.8121 - val_loss: 0.4014 - val_accuracy: 0.8600Epoch 2/101719/1719 [==============================] - 6s 3ms/step - loss: 0.3904 - accuracy: 0.8590 - val_loss: 0.3724 - val_accuracy: 0.8680Epoch 3/101719/1719 [==============================] - 6s 3ms/step - loss: 0.3527 - accuracy: 0.8727 - val_loss: 0.3563 - val_accuracy: 0.8752Epoch 4/101719/1719 [==============================] - 6s 3ms/step - loss: 0.3279 - accuracy: 0.8820 - val_loss: 0.3375 - val_accuracy: 0.8780Epoch 5/101719/1719 [==============================] - 6s 3ms/step - loss: 0.3076 - accuracy: 0.8886 - val_loss: 0.3493 - val_accuracy: 0.8746Epoch 6/101719/1719 [==============================] - 6s 3ms/step - loss: 0.2938 - accuracy: 0.8935 - val_loss: 0.3172 - val_accuracy: 0.8846Epoch 7/101719/1719 [==============================] - 6s 3ms/step - loss: 0.2795 - accuracy: 0.8987 - val_loss: 0.3269 - val_accuracy: 0.8826Epoch 8/101719/1719 [==============================] - 6s 3ms/step - loss: 0.2681 - accuracy: 0.9017 - val_loss: 0.3198 - val_accuracy: 0.8830Epoch 9/101719/1719 [==============================] - 6s 3ms/step - loss: 0.2569 - accuracy: 0.9060 - val_loss: 0.3000 - val_accuracy: 0.8898Epoch 10/101719/1719 [==============================] - 6s 3ms/step - loss: 0.2469 - accuracy: 0.9101 - val_loss: 0.3024 - val_accuracy: 0.8896
   复制代码
 此处没有进行 batchsize 的设置,默认 32,如果想按照自己的 batchsize 大小训练,可进行参数设置调整。
3. 结果分析
对返回结果评价指标绘图展示,代码如下:
 def plot_learning_curves(history):    pd.DataFrame(history.history).plot(figsize=(8, 5))    plt.grid(True)    plt.gca().set_ylim(0, 1)    plt.show()
plot_learning_curves(history)
   复制代码
 绘图结果如下:
从上面训练的过程和参数的趋势图可以看出,损失变量在不断减小,准确率在不断增加。
4. 回调函数设置
下面主要针对模型的回调函数进行设置,也即是参数 callbacks。回调函数设置在模型的训练过程中。
此处我们主要针对 Tensorboard, earlystopping, ModelCheckpoint 这三个常用的参数进行设置。各个参数的解释如下:
  - 指标摘要图
  - 训练图可视化
  - 激活直方图
  - 采样分析
monitor	验证集上目标函数的值.
min_delta  目标函数的提升变化值的阈值,也即是两个 epochs 之间的目标函数提升值之差小于 min_delta 那么就停止不再训练。
patience	当出现两个 epochs 之间的目标函数提升值之差小于 min_delta,如果连续出现 patience 次数,那么就会关闭停止训练。
tf.keras.callbacks.ModelCheckpoint,记录模型训练的中间结果状态。默认是保存最后一次训练的模型结果,可以设置 savebestonly 保存最优的模型训练结果。
下面是具体代码部分:
 # Tensorboard, earlystopping, ModelCheckpointlogdir = './callbacks'  # 保存文件目录if not os.path.exists(logdir):    os.mkdir(logdir)outout_model_file = os.path.join(logdir, "fashion_mnist_model.h5")  # 输出保存模型文件
callbacks = [    keras.callbacks.TensorBoard(logdir),    keras.callbacks.ModelCheckpoint(outout_model_file, save_best_only=True),    keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3),]
history = model.fit(x_train_scaled, y_train, epochs=10, validation_data=(x_valid_scaled, y_valid),                   callbacks=callbacks)
   复制代码
 
最终训练过程如下:
 Epoch 1/101719/1719 [==============================] - 7s 4ms/step - loss: 0.5394 - accuracy: 0.8100 - val_loss: 0.4176 - val_accuracy: 0.8502Epoch 2/101719/1719 [==============================] - 6s 4ms/step - loss: 0.3938 - accuracy: 0.8596 - val_loss: 0.3653 - val_accuracy: 0.8698Epoch 3/101719/1719 [==============================] - 7s 4ms/step - loss: 0.3560 - accuracy: 0.8717 - val_loss: 0.3452 - val_accuracy: 0.8782Epoch 4/101719/1719 [==============================] - 7s 4ms/step - loss: 0.3313 - accuracy: 0.8795 - val_loss: 0.3329 - val_accuracy: 0.8814Epoch 5/101719/1719 [==============================] - 7s 4ms/step - loss: 0.3113 - accuracy: 0.8871 - val_loss: 0.3235 - val_accuracy: 0.8800Epoch 6/101719/1719 [==============================] - 7s 4ms/step - loss: 0.2949 - accuracy: 0.8943 - val_loss: 0.3118 - val_accuracy: 0.8886Epoch 7/101719/1719 [==============================] - 6s 4ms/step - loss: 0.2812 - accuracy: 0.8989 - val_loss: 0.3089 - val_accuracy: 0.8882Epoch 8/101719/1719 [==============================] - 7s 4ms/step - loss: 0.2696 - accuracy: 0.9028 - val_loss: 0.3013 - val_accuracy: 0.8896Epoch 9/101719/1719 [==============================] - 7s 4ms/step - loss: 0.2590 - accuracy: 0.9053 - val_loss: 0.2948 - val_accuracy: 0.8920Epoch 10/101719/1719 [==============================] - 7s 4ms/step - loss: 0.2487 - accuracy: 0.9094 - val_loss: 0.3015 - val_accuracy: 0.8912
   复制代码
 最终 EarlyStopping 没有被触发,可以设置 epochs 的次数大一些,比如 100,1000,可以观察被触发的情况。
训练完成之后我们观察 callbacks 目录情况。
然后使用 TensorBoard 查看可视化结果,具体查看命令:
 tensorboard --logdir=callbacks --host 10.119.68.11 --port 8000
   复制代码
 可以设置 host 和 port,这样就可以远程查看,当然也可以不用设置,直接本地浏览器进行查看,默认 6006 端口。
趋势图结果如下:
可以对曲线趋势图是否平滑处理进行设置,这里默认使用的 0.6 的平滑处理。
模型结构图如下:
可以点击查看具体某个节点的详细情况。
评论