本文概述
注意:本教程将主要涵盖使用卷积神经网络和卷积自动编码器进行分类的实际实现。因此, 如果你尚不了解卷积神经网络(CNN)和自动编码器, 则可能需要查看CNN和自动编码器教程。
更具体地说, 你将在本教程中解决以下主题:
在开始时, 将向你简要介绍Fashion-MNIST数据。你将使用Python的各种库来加载, 浏览和分析数据,
之后, 你将预处理数据:将学习如何调整大小, 重新缩放数据, 验证图像的数据类型以及在训练和验证集中拆分数据。
完成所有这些操作后, 你可以构建卷积自动编码器模型:你将学习如何对数据建模并形成网络。接下来, 你将编译, 训练模型, 可视化精度和损耗图, 最后保存模型。
接下来, 你将分割时尚信息数据:首先将标签转换为一键编码矢量, 将训练图像和验证图像以及它们各自的标签分开。然后, 你将定义将在自动编码器体系结构中使用的编码器功能, 然后定义完全连接的层。
你将学习如何将经过训练的模型的权重加载到新模型的几层中, 验证经过训练的模型和新模型的权重矩阵, 使新模型的几层为假, 最后将编译新的分类建模, 训练模型并保存权重。
你将使用所有可训练的层重新训练模型, 评估模型, 可视化准确性和损失图, 对测试数据进行预测, 将概率转换为类别标签并绘制一些模型正确分类和分类错误的测试样本。
最后, 你将可视化分类报告, 这将使你更直观地了解模型已正确分类了哪个类。
Fashion-MNIST数据集
在开始加载数据集并开始对其进行处理之前, 最好先简要了解一下它是什么类型的数据集, 数据的维数是多少以及数据集中有多少个不同的类。
Fashion-MNIST数据集是来自10个类别的70, 000种时尚产品的28×28灰度图像, 每个类别7, 000张图像。训练集有60, 000张图像, 测试集有10, 000张图像。 Fashion-MNIST替代了原始MNIST数据集以产生更好的结果, 图像尺寸, 训练和测试分割与原始MNIST数据集相似。该数据集可在此URL上免费获得, 并且可以使用tensorflow和keras作为框架进行加载, 而无需在计算机上下载。
与MNIST相似, Fashion-MNIST也包含10个类别, 但是除了手写数字外, 我们还有10个不同类别的时尚配饰, 例如凉鞋, 衬衫, 裤子等。
当前的任务是训练卷积自动编码器, 并将自动编码器的编码器部分与完全连接的层结合使用, 以正确识别测试集中的新样本。
提示:如果你想学习如何使用MNIST数据集为分类任务实现多层感知器(MLP), 请查看本教程。
在下面的代码中, 你基本上使用os.environ在笔记本中设置了环境变量。在初始化Keras以限制Keras后端TensorFlow使用第一个GPU之前, 最好执行以下操作。如果你在其上训练的机器的GPU为0, 请确保使用0而不是1。你可以通过在终端上运行一个简单的命令来进行检查:例如nvidia-smi
import os
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="0" #model will be trained on GPU 0
载入数据
接下来, 导入所有必需的模块, 例如numpy, matplotlib以及最重要的是Keras, 因为在本教程中, 你将使用keras作为框架!
import keras
from matplotlib import pyplot as plt
import numpy as np
import gzip
%matplotlib inline
from keras.models import Model
from keras.optimizers import RMSprop
from keras.layers import Input, Dense, Flatten, Dropout, merge, Reshape, Conv2D, MaxPooling2D, UpSampling2D, Conv2DTranspose
from keras.layers.normalization import BatchNormalization
from keras.models import Model, Sequential
from keras.callbacks import ModelCheckpoint
from keras.optimizers import Adadelta, RMSprop, SGD, Adam
from keras import regularizers
from keras import backend as K
from keras.utils import to_categorical
Using TensorFlow backend.
/usr/local/lib/python2.7/dist-packages/h5py/__init__.py:34: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
from ._conv import register_converters as _register_converters
在这里, 你定义了一个打开gzip文件, 使用bytestream.read()读取文件的函数。你将图像尺寸和总共的图像数量传递给此功能。然后, 使用np.frombuffer()将存储在变量buf中的字符串转换为float32类型的NumPy数组。
将其转换为NumPy数组后, 可将其重塑为三维数组或张量, 其中第一维是许多图像, 第二维和第三维是图像的尺寸。最后, 返回NumPy数组数据。
def extract_data(filename, num_images):
with gzip.open(filename) as bytestream:
bytestream.read(16)
buf = bytestream.read(28 * 28 * num_images)
data = np.frombuffer(buf, dtype=np.uint8).astype(np.float32)
data = data.reshape(num_images, 28, 28)
return data
现在, 你将传递训练和测试文件以及相应的图像数量, 从而调用函数extract_data()。
train_data = extract_data('train-images-idx3-ubyte.gz', 60000)
test_data = extract_data('t10k-images-idx3-ubyte.gz', 10000)
同样, 你定义了一个提取标签函数, 该函数将打开gzip文件, 并使用bytestream.read()读取文件, 并向其传递标签尺寸(1)和总共的图像数量。然后, 使用np.frombuffer()将存储在变量buf中的字符串转换为int64类型的NumPy数组。
这次, 你不需要重新调整数组的形状, 因为变量标签将返回尺寸为60, 000 x 1的列向量。最后, 你将返回NumPy数组标签。
def extract_labels(filename, num_images):
with gzip.open(filename) as bytestream:
bytestream.read(8)
buf = bytestream.read(1 * num_images)
labels = np.frombuffer(buf, dtype=np.uint8).astype(np.int64)
return labels
现在, 你将通过传递训练和测试标签文件以及它们对应的图像数量来调用函数提取标签。
train_labels = extract_labels('train-labels-idx1-ubyte.gz', 60000)
test_labels = extract_labels('t10k-labels-idx1-ubyte.gz', 10000)
一旦加载了训练和测试数据, 就可以对数据进行分析, 以便获得有关本教程要使用的数据集的直觉!
数据探索
现在让我们分析数据集中的图像外观, 并借助NumPy数组属性.shape来查看图像的尺寸:
# Shapes of training set
print("Training set (images) shape: {shape}".format(shape=train_data.shape))
# Shapes of test set
print("Test set (images) shape: {shape}".format(shape=test_data.shape))
Training set (images) shape: (60000, 28, 28)
Test set (images) shape: (10000, 28, 28)
从上面的输出中, 你可以看到训练数据的形状为60000 x 28 x 28, 因为每个28 x 28维矩阵都有60, 000个训练样本。同样, 由于有10, 000个测试样本, 因此测试数据的形状为10000 x 28 x 28。
请注意, 在使用卷积自动编码器进行重构的任务中, 你不需要培训和测试标签。与分类任务中的标签类似, 你的训练图像将既是输入也是基础事实。
但是对于分类任务, 你还将需要标签以及图像, 这些将在本教程的后面部分进行。即使手头的任务只是处理训练和测试图像。但是, 出于探索目的, 这可能会使你对数据有更好的直觉, 你将使用标签。
让我们创建一个字典, 该字典将具有类名称及其相应的分类类标签:
# Create dictionary of target classes
label_dict = {
0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J', }
现在, 让我们看一下数据集中的几个图像:
plt.figure(figsize=[5, 5])
# Display the first image in training data
plt.subplot(121)
curr_img = np.reshape(train_data[10], (28, 28))
curr_lbl = train_labels[10]
plt.imshow(curr_img, cmap='gray')
plt.title("(Label: " + str(label_dict[curr_lbl]) + ")")
# Display the first image in testing data
plt.subplot(122)
curr_img = np.reshape(test_data[10], (28, 28))
curr_lbl = test_labels[10]
plt.imshow(curr_img, cmap='gray')
plt.title("(Label: " + str(label_dict[curr_lbl]) + ")")
Text(0.5, 1, '(Label: E)')
上面两个图的输出是来自训练和测试数据的样本图像之一, 这些图像一方面被分配为0或A, 另一方面被分配为4或E的类别标签。同样, 其他字母将具有不同的标签, 但相似的字母将具有相同的标签。这意味着所有6, 000个E类图像将具有4。
数据预处理
数据集的图像确实是灰度图像, 像素值为0到255, 尺寸为28 x 28, 因此在将数据输入模型之前, 对其进行预处理非常重要。首先, 将火车和测试集的每个28 x 28图像转换为大小为28 x 28 x 1的矩阵, 你可以将其输入网络:
train_data = train_data.reshape(-1, 28, 28, 1)
test_data = test_data.reshape(-1, 28, 28, 1)
train_data.shape, test_data.shape
((60000, 28, 28, 1), (10000, 28, 28, 1))
接下来, 你要确保检查训练和测试NumPy数组的数据类型, 该数据类型应为float32格式, 如果不是, 则需要将其转换为该格式, 但是由于在读取数据时已经对其进行了转换, 你不再需要再次执行此操作。你还必须在0-1(含)范围内重新缩放像素值。因此, 让我们开始吧!
不要忘记验证培训和测试数据类型:
train_data.dtype, test_data.dtype
(dtype('float32'), dtype('float32'))
接下来, 使用训练和测试数据的最大像素值重新缩放训练和测试数据:
np.max(train_data), np.max(test_data)
(255.0, 255.0)
train_data = train_data / np.max(train_data)
test_data = test_data / np.max(test_data)
重新缩放后, 让我们确认训练和测试数据的最大值应为1.0!
np.max(train_data), np.max(test_data)
(1.0, 1.0)
完成所有这些之后, 对数据进行分区很重要。为了使我们的模型更好地泛化, 你将训练数据分为两部分:训练和验证集。你将在80%的数据上训练模型, 并在剩余训练数据的20%上验证模型。
这也将帮助你减少过度拟合的机会, 因为你将根据训练阶段未看到的数据来验证模型。
你可以使用scikit-learn的train_test_split模块正确地划分数据:
from sklearn.model_selection import train_test_split
train_X, valid_X, train_ground, valid_ground = train_test_split(train_data, train_data, test_size=0.2, random_state=13)
注意:你将使用两次以上的数据拆分, 一次用于使用卷积自动编码器进行重构的任务, 而无需进行卷积和编码测试。这就是为什么你将两次通过训练图像。与分类任务中的标签类似, 你的训练图像将既是输入也是基础事实。
但是对于分类任务, 你还将传递标签以及图像, 稍后将在本教程中进行操作。
现在你已经准备好定义网络并将数据馈入网络。因此, 事不宜迟, 让我们跳到下一步!
卷积自动编码器!
图像的尺寸为28 x 28 x 1或30976维矢量。你将图像矩阵转换为数组, 在0到1之间重新缩放, 重新调整形状使其大小为28 x 28 x 1, 并将其作为输入馈送到网络。
同样, 你将使用128的批次大小, 最好使用256或512的较高批次大小, 这完全取决于你训练模型的系统。它在确定学习参数方面做出了巨大贡献, 并影响了预测准确性。
batch_size = 64
epochs = 200
inChannel = 1
x, y = 28, 28
input_img = Input(shape = (x, y, inChannel))
num_classes = 10
你可能已经知道, 自动编码器分为两个部分:有一个编码器和一个解码器。
编码器:它具有4个卷积块, 每个块都有一个卷积层, 后跟一个批处理归一化层。最大卷积层用于第一和第二卷积块之后。
- 第一个卷积块将包含32个大小为3 x 3的滤波器, 然后是下采样(最大合并)层,
- 第二块将具有64个尺寸为3 x 3的滤波器, 然后是另一个下采样层,
- 编码器的第三块将具有大小为3 x 3的128个滤波器,
- 编码器的第四个块将具有256个大小为3 x 3的滤波器。
解码器:它具有3个卷积块, 每个块都有一个卷积层, 后跟一个批处理归一化层。在第二和第三卷积块之后使用上采样层。
- 第一块包含128个尺寸为3 x 3的过滤器,
- 第二块将具有64个尺寸为3 x 3的滤波器, 然后是另一个上采样层,
- 第三块将包含32个大小为3 x 3的滤波器, 然后是另一个上采样层,
- 编码器的最后一层将具有1个大小为3 x 3的滤波器, 它将重建具有单个通道的输入。
每次使用时, 最大池化层将对输入进行两次下采样, 而每次使用时, 向上采样层将对输入进行两次下采样。
注意:滤波器的数量, 滤波器的大小, 层数, 训练模型的时期数都是超参数, 应根据自己的直觉来决定, 你可以通过调整这些超参数和衡量模型的性能。这就是你将如何慢慢学习深度学习的艺术!
让我们创建单独的编码器和解码器功能, 因为稍后将使用编码器权重进行分类!
def encoder(input_img):
#encoder
#input = 28 x 28 x 1 (wide and thin)
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img) #28 x 28 x 32
conv1 = BatchNormalization()(conv1)
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv1)
conv1 = BatchNormalization()(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1) #14 x 14 x 32
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64
conv2 = BatchNormalization()(conv2)
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv2)
conv2 = BatchNormalization()(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2) #7 x 7 x 64
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2) #7 x 7 x 128 (small and thick)
conv3 = BatchNormalization()(conv3)
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)
conv3 = BatchNormalization()(conv3)
conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv3) #7 x 7 x 256 (small and thick)
conv4 = BatchNormalization()(conv4)
conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv4)
conv4 = BatchNormalization()(conv4)
return conv4
def decoder(conv4):
#decoder
conv5 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv4) #7 x 7 x 128
conv5 = BatchNormalization()(conv5)
conv5 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv5)
conv5 = BatchNormalization()(conv5)
conv6 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv5) #7 x 7 x 64
conv6 = BatchNormalization()(conv6)
conv6 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv6)
conv6 = BatchNormalization()(conv6)
up1 = UpSampling2D((2, 2))(conv6) #14 x 14 x 64
conv7 = Conv2D(32, (3, 3), activation='relu', padding='same')(up1) # 14 x 14 x 32
conv7 = BatchNormalization()(conv7)
conv7 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv7)
conv7 = BatchNormalization()(conv7)
up2 = UpSampling2D((2, 2))(conv7) # 28 x 28 x 32
decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2) # 28 x 28 x 1
return decoded
创建模型后, 必须使用优化器将其编译为RMSProp。
注意, 你还必须通过参数loss指定损失类型。在这种情况下, 这就是均方误差, 因为将使用逐像素均方误差来计算每批预测输出与地面实况之间的每批损失。
autoencoder = Model(input_img, decoder(encoder(input_img)))
autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())
让我们使用摘要功能来可视化在上一步中创建的图层。这将在每个图层中显示许多参数(权重和偏差), 以及模型中的总参数。
autoencoder.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) (None, 28, 28, 1) 0
_________________________________________________________________
conv2d_16 (Conv2D) (None, 28, 28, 32) 320
_________________________________________________________________
batch_normalization_15 (Batc (None, 28, 28, 32) 128
_________________________________________________________________
...
batch_normalization_28 (Batc (None, 14, 14, 32) 128
_________________________________________________________________
up_sampling2d_4 (UpSampling2 (None, 28, 28, 32) 0
_________________________________________________________________
conv2d_30 (Conv2D) (None, 28, 28, 1) 289
=================================================================
Total params: 1, 758, 657
Trainable params: 1, 755, 841
Non-trainable params: 2, 816
_________________________________________________________________
终于是时候用Keras的fit()函数训练模型了!该模型训练200个纪元。 fit()函数将返回一个历史对象;通过将此函数的结果存储在autoencoder_train中, 以后可以使用它在训练和验证之间绘制损失函数图, 这将帮助你直观地分析模型的性能。
训练模型
autoencoder_train = autoencoder.fit(train_X, train_ground, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(valid_X, valid_ground))
Train on 48000 samples, validate on 12000 samples
Epoch 1/200
48000/48000 [==============================] - 19s - loss: 0.0202 - val_loss: 0.0114s: 0.020
Epoch 2/200
48000/48000 [==============================] - 17s - loss: 0.0087 - val_loss: 0.0071
...
Epoch 199/200
48000/48000 [==============================] - 18s - loss: 7.0886e-04 - val_loss: 8.9876e-04
Epoch 200/200
48000/48000 [==============================] - 18s - loss: 7.0929e-04 - val_loss: 0.0010
最后!你在fashion-mnist数据集上训练了100个时期的模型, 现在, 让我们在训练和验证数据之间绘制损失图, 以可视化模型的性能。
loss = autoencoder_train.history['loss']
val_loss = autoencoder_train.history['val_loss']
epochs = range(200)
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
最后, 你可以看到验证损失和训练损失都是同步的。它表明你的模型不是过拟合的:验证损失正在减少且没有增加, 并且在整个训练阶段, 培训与验证损失之间几乎没有任何差距。
因此, 可以说模型的泛化能力很好。
但是请记住, 当前的任务是使用上面训练的模型的编码器部分对时尚图片进行分类。因此, 让我们现在移至下一部分!
保存模型
由于在分类任务中将需要编码器权重, 因此首先让我们保存完整的自动编码器权重。你将学习如何尽快提取编码器权重。
autoencoder.save_weights('autoencoder.h5')
现在, 你将使用训练有素的自动编码器的头部, 即编码器部分, 并且将加载刚刚训练的自动编码器的权重, 但仅在模型的编码器部分中加载。
你将在编码器中添加一些密集或完全连接的层以对时尚mnist图像进行分类。
- 让我们首先将标签转换为单编码矢量。
对于那些不了解一键编码的人:
在单编码中, 你可以将分类数据转换为数字向量。之所以以一种热编码方式转换分类数据, 是因为机器学习算法无法直接使用分类数据。你为每个类别或类生成一个布尔列。对于每个样本, 这些列中只有一个可以取值为1。因此, 术语”一次热编码”。
对于你的问题陈述, 一个热编码将是一个行向量, 并且对于每个图像, 其尺寸将为1 x10。这里要注意的重要一点是, 该向量由全零组成, 除了其类别代表, 为此, 它是1。
因此, 让我们将标签转换为一键编码矢量:
# Change the labels from categorical to one-hot encoding
train_Y_one_hot = to_categorical(train_labels)
test_Y_one_hot = to_categorical(test_labels)
# Display the change for category label using one-hot encoding
print('Original label:', train_labels[0])
print('After conversion to one-hot:', train_Y_one_hot[0])
('Original label:', 9)
('After conversion to one-hot:', array([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]))
这很清楚, 对吧?
- 最后一步是你之前也已经完成的关键步骤, 即将自动编码器训练阶段中的数据分为训练和验证。因此, 让我们也快速进行分类步骤。请注意, 你将使用与以前相同的随机状态, 并且这次也将传递标签, 而你现在刚刚将其转换为一键编码矢量。
train_X, valid_X, train_label, valid_label = train_test_split(train_data, train_Y_one_hot, test_size=0.2, random_state=13)
最后, 让我们检查一下训练和验证集的形状。
train_X.shape, valid_X.shape, train_label.shape, valid_label.shape
((48000, 28, 28, 1), (12000, 28, 28, 1), (48000, 10), (12000, 10))
现在, 让我们定义分类模型。请记住, 你将使用与自动编码器体系结构中使用的完全相同的编码器部分。
def encoder(input_img):
#encoder
#input = 28 x 28 x 1 (wide and thin)
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img) #28 x 28 x 32
conv1 = BatchNormalization()(conv1)
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv1)
conv1 = BatchNormalization()(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1) #14 x 14 x 32
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64
conv2 = BatchNormalization()(conv2)
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv2)
conv2 = BatchNormalization()(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2) #7 x 7 x 64
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2) #7 x 7 x 128 (small and thick)
conv3 = BatchNormalization()(conv3)
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)
conv3 = BatchNormalization()(conv3)
conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv3) #7 x 7 x 256 (small and thick)
conv4 = BatchNormalization()(conv4)
conv4 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv4)
conv4 = BatchNormalization()(conv4)
return conv4
让我们定义将与编码器功能堆叠在一起的完全连接的层。
def fc(enco):
flat = Flatten()(enco)
den = Dense(128, activation='relu')(flat)
out = Dense(num_classes, activation='softmax')(den)
return out
encode = encoder(input_img)
full_model = Model(input_img, fc(encode))
for l1, l2 in zip(full_model.layers[:19], autoencoder.layers[0:19]):
l1.set_weights(l2.get_weights())
注意:下一步非常重要。为了确保自动编码器的编码器部分的权重是否与你加载到分类模型的编码器功能中的权重相似, 应始终打印两个模型中相同层的权重中的任何一个。如果它们不相似, 则使用自动编码器分类策略没有任何用处。
让我们打印两个模型的第一层权重。
autoencoder.get_weights()[0][1]
array([[[ 0.22935028, -0.800786 , 0.42421195, -0.6509941 , -0.82958347, -0.44448015, 0.04182598, -0.05483926, 0.44611776, 0.7123421 , -0.4499234 , 0.16125064, 0.1174996 , 0.12156075, 0.8391102 , -0.44067 , 0.02915774, -0.7223025 , 0.33398604, -0.69252896, 0.04369332, -0.3793029 , 0.37535954, 0.34269437, 0.8863593 , -0.2114254 , 0.21323568, -0.4076597 , 0.2965019 , 0.11617199, -0.22282824, -0.9501956 ]], [[ 0.23096658, 0.3701021 , 0.78717273, -0.5014979 , -1.3326751 , -0.73818666, 2.6434395 , -0.7560537 , -0.52561104, -0.67917436, 2.0205429 , 0.14013338, -0.9140436 , 0.169709 , 0.09063474, -0.20975377, -0.11247484, -0.09702996, 0.17846109, 0.40699893, -0.5722246 , -1.0119121 , 0.30877167, 0.6645408 , -0.68007207, -0.57144946, -0.68339616, 0.45407826, 1.0148963 , 0.88867754, -0.57179326, 0.01268557]], [[ 0.23020297, 0.14018346, -0.37600747, -0.6213855 , -0.4104492 , -0.2036299 , 0.12469969, 0.08351921, 0.20644444, -0.01170571, -0.07618313, 0.23164392, -0.38417578, 0.3481844 , -0.8055927 , 0.76824665, 0.06819476, 0.93830526, 0.31898668, 0.51119566, 0.4445658 , -0.4568496 , 0.1269397 , -0.34482956, -1.3285302 , -0.20479 , -0.17618039, -0.22546193, -0.35588196, 0.9971566 , -0.03546353, -0.7294457 ]]], dtype=float32)
full_model.get_weights()[0][1]
array([[[ 0.22935028, -0.800786 , 0.42421195, -0.6509941 , -0.82958347, -0.44448015, 0.04182598, -0.05483926, 0.44611776, 0.7123421 , -0.4499234 , 0.16125064, 0.1174996 , 0.12156075, 0.8391102 , -0.44067 , 0.02915774, -0.7223025 , 0.33398604, -0.69252896, 0.04369332, -0.3793029 , 0.37535954, 0.34269437, 0.8863593 , -0.2114254 , 0.21323568, -0.4076597 , 0.2965019 , 0.11617199, -0.22282824, -0.9501956 ]], [[ 0.23096658, 0.3701021 , 0.78717273, -0.5014979 , -1.3326751 , -0.73818666, 2.6434395 , -0.7560537 , -0.52561104, -0.67917436, 2.0205429 , 0.14013338, -0.9140436 , 0.169709 , 0.09063474, -0.20975377, -0.11247484, -0.09702996, 0.17846109, 0.40699893, -0.5722246 , -1.0119121 , 0.30877167, 0.6645408 , -0.68007207, -0.57144946, -0.68339616, 0.45407826, 1.0148963 , 0.88867754, -0.57179326, 0.01268557]], [[ 0.23020297, 0.14018346, -0.37600747, -0.6213855 , -0.4104492 , -0.2036299 , 0.12469969, 0.08351921, 0.20644444, -0.01170571, -0.07618313, 0.23164392, -0.38417578, 0.3481844 , -0.8055927 , 0.76824665, 0.06819476, 0.93830526, 0.31898668, 0.51119566, 0.4445658 , -0.4568496 , 0.1269397 , -0.34482956, -1.3285302 , -0.20479 , -0.17618039, -0.22546193, -0.35588196, 0.9971566 , -0.03546353, -0.7294457 ]]], dtype=float32)
瞧!两个阵列看起来完全相似。因此, 事不宜迟, 让我们编译模型并开始训练。
接下来, 你将使编码器部分即模型的前19层可训练为假。由于编码器零件已经过培训, 因此你无需对其进行培训。你将只训练”完全连接”部分。
for layer in full_model.layers[0:19]:
layer.trainable = False
让我们编译模型!
full_model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
让我们也打印模型的摘要。由于你已将模型的前十五层设为不可训练, 因此也应该有不可训练的参数。
full_model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) (None, 28, 28, 1) 0
_________________________________________________________________
conv2d_55 (Conv2D) (None, 28, 28, 32) 320
_________________________________________________________________
batch_normalization_53 (Batc (None, 28, 28, 32) 128
_________________________________________________________________
conv2d_56 (Conv2D) (None, 28, 28, 32) 9248
_________________________________________________________________
batch_normalization_54 (Batc (None, 28, 28, 32) 128
_________________________________________________________________
max_pooling2d_11 (MaxPooling (None, 14, 14, 32) 0
_________________________________________________________________
conv2d_57 (Conv2D) (None, 14, 14, 64) 18496
_________________________________________________________________
batch_normalization_55 (Batc (None, 14, 14, 64) 256
_________________________________________________________________
conv2d_58 (Conv2D) (None, 14, 14, 64) 36928
_________________________________________________________________
batch_normalization_56 (Batc (None, 14, 14, 64) 256
_________________________________________________________________
max_pooling2d_12 (MaxPooling (None, 7, 7, 64) 0
_________________________________________________________________
conv2d_59 (Conv2D) (None, 7, 7, 128) 73856
_________________________________________________________________
batch_normalization_57 (Batc (None, 7, 7, 128) 512
_________________________________________________________________
conv2d_60 (Conv2D) (None, 7, 7, 128) 147584
_________________________________________________________________
batch_normalization_58 (Batc (None, 7, 7, 128) 512
_________________________________________________________________
conv2d_61 (Conv2D) (None, 7, 7, 256) 295168
_________________________________________________________________
batch_normalization_59 (Batc (None, 7, 7, 256) 1024
_________________________________________________________________
conv2d_62 (Conv2D) (None, 7, 7, 256) 590080
_________________________________________________________________
batch_normalization_60 (Batc (None, 7, 7, 256) 1024
_________________________________________________________________
flatten_4 (Flatten) (None, 12544) 0
_________________________________________________________________
dense_7 (Dense) (None, 128) 1605760
_________________________________________________________________
dense_8 (Dense) (None, 10) 1290
=================================================================
Total params: 2, 782, 570
Trainable params: 1, 607, 050
Non-trainable params: 1, 175, 520
_________________________________________________________________
训练模型
终于是时候用Keras的fit()函数训练模型了!该模型训练10个纪元。 fit()函数将返回一个历史对象;通过将此函数的结果存储在fashion_train中, 你以后可以使用它来绘制训练和验证之间的准确性和损失函数图, 这将帮助你直观地分析模型的性能。
classify_train = full_model.fit(train_X, train_label, batch_size=64, epochs=100, verbose=1, validation_data=(valid_X, valid_label))
Train on 48000 samples, validate on 12000 samples
Epoch 1/100
48000/48000 [==============================] - 6s - loss: 0.3747 - acc: 0.8732 - val_loss: 0.2888 - val_acc: 0.8935
Epoch 2/100
48000/48000 [==============================] - 6s - loss: 0.2216 - acc: 0.9178 - val_loss: 0.2942 - val_acc: 0.9010
Epoch 3/100
48000/48000 [==============================] - 5s - loss: 0.1762 - acc: 0.9340 - val_loss: 0.2868 - val_acc: 0.9078
...
Epoch 74/100
48000/48000 [==============================] - 6s - loss: 0.0182 - acc: 0.9953 - val_loss: 0.8069 - val_acc: 0.9109
Epoch 75/100
48000/48000 [==============================] - 6s - loss: 0.0102 - acc: 0.9971 - val_loss: 0.7872 - val_acc: 0.9125
Epoch 76/100
11776/48000 [======>.......................] - ETA: 3s - loss: 0.0084 - acc: 0.9977
最后!你仅在时尚MNIST上对模型进行了10个训练, 通过观察训练的准确性和损失, 你可以说该模型的工作非常出色, 因为经过10个训练后, 训练准确性为99%, 验证损失为98%。
保存分类模型!
full_model.save_weights('autoencoder_classification.h5')
接下来, 你将通过使前十九层可训练为True而不是保持False来重新训练模型!因此, 让我们快速地做到这一点。
for layer in full_model.layers[0:19]:
layer.trainable = True
full_model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
现在, 让我们最后一次训练整个模型!
classify_train = full_model.fit(train_X, train_label, batch_size=64, epochs=100, verbose=1, validation_data=(valid_X, valid_label))
Train on 48000 samples, validate on 12000 samples
Epoch 1/100
48000/48000 [==============================] - 13s - loss: 0.1584 - acc: 0.9718 - val_loss: 0.7902 - val_acc: 0.8960
Epoch 2/100
48000/48000 [==============================] - 12s - loss: 0.1049 - acc: 0.9759 - val_loss: 0.8327 - val_acc: 0.8893
Epoch 3/100
48000/48000 [==============================] - 12s - loss: 0.0792 - acc: 0.9804 - val_loss: 0.6947 - val_acc: 0.9099
...
loss: 0.0123 - acc: 0.9971 - val_loss: 0.6827 - val_acc: 0.9217
Epoch 98/100
48000/48000 [==============================] - 13s - loss: 0.0097 - acc: 0.9975 - val_loss: 0.7074 - val_acc: 0.9211
Epoch 99/100
48000/48000 [==============================] - 13s - loss: 0.0081 - acc: 0.9984 - val_loss: 0.6846 - val_acc: 0.9205
Epoch 100/100
48000/48000 [==============================] - 13s - loss: 0.0090 - acc: 0.9977 - val_loss: 0.6739 - val_acc: 0.9226
让我们最后保存一次模型。
full_model.save_weights('classification_complete.h5')
让我们将模型评估放到透视图中, 并绘制训练和验证数据之间的准确性和损失图:
accuracy = classify_train.history['acc']
val_accuracy = classify_train.history['val_acc']
loss = classify_train.history['loss']
val_loss = classify_train.history['val_loss']
epochs = range(len(accuracy))
plt.plot(epochs, accuracy, 'bo', label='Training accuracy')
plt.plot(epochs, val_accuracy, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
从上面的两个图中, 你可以看到模型过度拟合, 因为训练和验证损失之间有很大的差距。为了解决过度拟合问题, 你可能必须使用某些规范化技术, 例如Dropout。你可以按照本教程进行操作。
测试集上的模型评估
最后, 让我们还根据测试数据评估模型, 并查看其性能!
test_eval = full_model.evaluate(test_data, test_Y_one_hot, verbose=0)
print('Test loss:', test_eval[0])
print('Test accuracy:', test_eval[1])
('Test loss:', 0.7068972043234281)
('Test accuracy:', 0.9205)
预测标签
predicted_classes = full_model.predict(test_data)
由于你得到的预测是浮点值, 因此将预测的标签与真实的测试标签进行比较是不可行的。因此, 你将舍入输出, 该输出会将float值转换为整数。此外, 你将使用np.argmax()选择行中具有较高值的索引号。
例如, 假设一个测试图像的预测为[0 1 0 0 0 0 0 0 0 0 0 0], 则其输出应为类标签1。
predicted_classes = np.argmax(np.round(predicted_classes), axis=1)
predicted_classes.shape, test_labels.shape
((10000, ), (10000, ))
correct = np.where(predicted_classes==test_labels)[0]
print "Found %d correct labels" % len(correct)
for i, correct in enumerate(correct[:9]):
plt.subplot(3, 3, i+1)
plt.imshow(test_data[correct].reshape(28, 28), cmap='gray', interpolation='none')
plt.title("Predicted {}, Class {}".format(predicted_classes[correct], test_labels[correct]))
plt.tight_layout()
Found 9204 correct labels
incorrect = np.where(predicted_classes!=test_labels)[0]
print "Found %d incorrect labels" % len(incorrect)
for i, incorrect in enumerate(incorrect[:9]):
plt.subplot(3, 3, i+1)
plt.imshow(test_data[incorrect].reshape(28, 28), cmap='gray', interpolation='none')
plt.title("Predicted {}, Class {}".format(predicted_classes[incorrect], test_labels[incorrect]))
plt.tight_layout()
Found 796 incorrect labels
分类报告
分类报告将帮助你更详细地识别错误分类的类。你将能够观察到模型在给定的十个类别中表现不佳的原因。
from sklearn.metrics import classification_report
target_names = ["Class {}".format(i) for i in range(num_classes)]
print(classification_report(test_labels, predicted_classes, target_names=target_names))
precision recall f1-score support
Class 0 0.83 0.89 0.86 1000
Class 1 0.99 0.99 0.99 1000
Class 2 0.89 0.87 0.88 1000
Class 3 0.92 0.93 0.92 1000
Class 4 0.85 0.90 0.88 1000
Class 5 0.99 0.99 0.99 1000
Class 6 0.81 0.72 0.76 1000
Class 7 0.96 0.98 0.97 1000
Class 8 0.98 0.98 0.98 1000
Class 9 0.98 0.96 0.97 1000
avg / total 0.92 0.92 0.92 10000
你想更深入地潜水吗?
本教程是将自动编码器和完全连接的卷积神经网络与Python和Keras结合使用的良好起点。如果你能够轻松地进行工作, 甚至只需付出更多的努力, 那就做好了!尝试使用相同的模型架构但使用不同类型的可用公共数据集进行一些实验。
还有很多内容要讲, 为什么不参加srcmini的Python深度学习课程呢?同时, 请确保还查看Keras文档(如果你尚未这样做的话)。你将找到有关所有函数, 参数, 更多层等的更多示例和信息。当你学习如何在Python中使用神经网络时, 它无疑是必不可少的资源!
如果你想阅读一本解释深度学习基础知识(使用Keras)以及其在实践中的用法的书, 那么你绝对应该阅读FrançoisChollet的Python深度学习书。
评论前必须登录!
注册