TensorFlow2 Fashion-MNIST图像分类(二)

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/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.5309 - accuracy: 0.8121 - val_loss: 0.4014 - val_accuracy: 0.8600
Epoch 2/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.3904 - accuracy: 0.8590 - val_loss: 0.3724 - val_accuracy: 0.8680
Epoch 3/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.3527 - accuracy: 0.8727 - val_loss: 0.3563 - val_accuracy: 0.8752
Epoch 4/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.3279 - accuracy: 0.8820 - val_loss: 0.3375 - val_accuracy: 0.8780
Epoch 5/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.3076 - accuracy: 0.8886 - val_loss: 0.3493 - val_accuracy: 0.8746
Epoch 6/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2938 - accuracy: 0.8935 - val_loss: 0.3172 - val_accuracy: 0.8846
Epoch 7/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2795 - accuracy: 0.8987 - val_loss: 0.3269 - val_accuracy: 0.8826
Epoch 8/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2681 - accuracy: 0.9017 - val_loss: 0.3198 - val_accuracy: 0.8830
Epoch 9/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2569 - accuracy: 0.9060 - val_loss: 0.3000 - val_accuracy: 0.8898
Epoch 10/10
1719/1719 [==============================] - 6s 3ms/step - loss: 0.2469 - accuracy: 0.9101 - val_loss: 0.3024 - val_accuracy: 0.8896

此处没有进行batch_size的设置,默认32,如果想按照自己的batch_size大小训练,可进行参数设置调整。

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这三个常用的参数进行设置。各个参数的解释如下:

  • tf.keras.callbacks.TensorBoard,tensorflow提供的可视化工具,主要用于以下方面的可视化展示:
    • 指标摘要图
    • 训练图可视化
    • 激活直方图
    • 采样分析
  • tf.keras.callbacks.EarlyStopping,对模型训练的关键指标进行监控,通常主要对以下三个指标进行设置:
    在这里插入图片描述

monitor 验证集上目标函数的值.

min_delta 目标函数的提升变化值的阈值,也即是两个epochs之间的目标函数提升值之差小于min_delta那么就停止不再训练。

patience 当出现两个epochs之间的目标函数提升值之差小于min_delta,如果连续出现patience次数,那么就会关闭停止训练。
tf.keras.callbacks.ModelCheckpoint,记录模型训练的中间结果状态。默认是保存最后一次训练的模型结果,可以设置save_best_only保存最优的模型训练结果。

下面是具体代码部分:

# Tensorboard, earlystopping, ModelCheckpoint
logdir = './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/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.5394 - accuracy: 0.8100 - val_loss: 0.4176 - val_accuracy: 0.8502
Epoch 2/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.3938 - accuracy: 0.8596 - val_loss: 0.3653 - val_accuracy: 0.8698
Epoch 3/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.3560 - accuracy: 0.8717 - val_loss: 0.3452 - val_accuracy: 0.8782
Epoch 4/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.3313 - accuracy: 0.8795 - val_loss: 0.3329 - val_accuracy: 0.8814
Epoch 5/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.3113 - accuracy: 0.8871 - val_loss: 0.3235 - val_accuracy: 0.8800
Epoch 6/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.2949 - accuracy: 0.8943 - val_loss: 0.3118 - val_accuracy: 0.8886
Epoch 7/10
1719/1719 [==============================] - 6s 4ms/step - loss: 0.2812 - accuracy: 0.8989 - val_loss: 0.3089 - val_accuracy: 0.8882
Epoch 8/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.2696 - accuracy: 0.9028 - val_loss: 0.3013 - val_accuracy: 0.8896
Epoch 9/10
1719/1719 [==============================] - 7s 4ms/step - loss: 0.2590 - accuracy: 0.9053 - val_loss: 0.2948 - val_accuracy: 0.8920
Epoch 10/10
1719/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的平滑处理。
模型结构图如下:
在这里插入图片描述

可以点击查看具体某个节点的详细情况。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值