本文概述
- Fashion-MNIST数据集
- 载入数据
- 卷积自动编码器!
- 训练模型
- 训练模型
- 测试集上的模型评估
- 预测标签
- 你想更深入地潜水吗?
更具体地说, 你将在本教程中解决以下主题:
在开始时, 将向你简要介绍Fashion-MNIST数据。你将使用Python的各种库来加载, 浏览和分析数据,
【使用Fashion-MNIST数据集将自动编码器用作分类器】之后, 你将预处理数据:将学习如何调整大小, 重新缩放数据, 验证图像的数据类型以及在训练和验证集中拆分数据。
完成所有这些操作后, 你可以构建卷积自动编码器模型:你将学习如何对数据建模并形成网络。接下来, 你将编译, 训练模型, 可视化精度和损耗图, 最后保存模型。
接下来, 你将分割时尚信息数据:首先将标签转换为一键编码矢量, 将训练图像和验证图像以及它们各自的标签分开。然后, 你将定义将在自动编码器体系结构中使用的编码器功能, 然后定义完全连接的层。
你将学习如何将经过训练的模型的权重加载到新模型的几层中, 验证经过训练的模型和新模型的权重矩阵, 使新模型的几层为假, 最后将编译新的分类建模, 训练模型并保存权重。
你将使用所有可训练的层重新训练模型, 评估模型, 可视化准确性和损失图, 对测试数据进行预测, 将概率转换为类别标签并绘制一些模型正确分类和分类错误的测试样本。
最后, 你将可视化分类报告, 这将使你更直观地了解模型已正确分类了哪个类。
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 osos.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 kerasfrom matplotlib import pyplot as pltimport numpy as npimport gzip%matplotlib inlinefrom keras.models import Modelfrom keras.optimizers import RMSpropfrom keras.layers import Input, Dense, Flatten, Dropout, merge, Reshape, Conv2D, MaxPooling2D, UpSampling2D, Conv2DTransposefrom keras.layers.normalization import BatchNormalizationfrom keras.models import Model, Sequentialfrom keras.callbacks import ModelCheckpointfrom keras.optimizers import Adadelta, RMSprop, SGD, Adamfrom keras import regularizersfrom keras import backend as Kfrom 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 = http://www.srcmini.com/np.frombuffer(buf, dtype=np.uint8).astype(np.float32)data = data.reshape(num_images, 28, 28)return data
现在, 你将传递训练和测试文件以及相应的图像数量, 从而调用函数extract_data()。
train_data = http://www.srcmini.com/extract_data('train-images-idx3-ubyte.gz', 60000)test_data = http://www.srcmini.com/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 setprint("Training set (images) shape: {shape}".format(shape=train_data.shape))# Shapes of test setprint("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 classeslabel_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 dataplt.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 dataplt.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 = http://www.srcmini.com/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 = http://www.srcmini.com/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_splittrain_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 = 64epochs = 200inChannel = 1x, y = 28, 28input_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的滤波器。
- 第一块包含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 32conv1 = 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 32conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64conv2 = 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 64conv3 = 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 conv4def decoder(conv4):#decoderconv5 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv4) #7 x 7 x 128conv5 = 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 64conv6 = BatchNormalization()(conv6)conv6 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv6)conv6 = BatchNormalization()(conv6)up1 = UpSampling2D((2, 2))(conv6) #14 x 14 x 64conv7 = Conv2D(32, (3, 3), activation='relu', padding='same')(up1) # 14 x 14 x 32conv7 = BatchNormalization()(conv7)conv7 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv7)conv7 = BatchNormalization()(conv7)up2 = UpSampling2D((2, 2))(conv7) # 28 x 28 x 32decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2) # 28 x 28 x 1return decoded
创建模型后, 必须使用优化器将其编译为RMSProp。
注意, 你还必须通过参数loss指定损失类型。在这种情况下, 这就是均方误差, 因为将使用逐像素均方误差来计算每批预测输出与地面实况之间的每批损失。
autoencoder = Model(input_img, decoder(encoder(input_img)))autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())
让我们使用摘要功能来可视化在上一步中创建的图层。这将在每个图层中显示许多参数(权重和偏差), 以及模型中的总参数。
autoencoder.summary()
_________________________________________________________________Layer (type)Output ShapeParam #=================================================================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, 657Trainable params: 1, 755, 841Non-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=http://www.srcmini.com/(valid_X, valid_ground))
Train on 48000 samples, validate on 12000 samplesEpoch 1/20048000/48000 [==============================] - 19s - loss: 0.0202 - val_loss: 0.0114s: 0.020Epoch 2/20048000/48000 [==============================] - 17s - loss: 0.0087 - val_loss: 0.0071...Epoch 199/20048000/48000 [==============================] - 18s - loss: 7.0886e-04 - val_loss: 8.9876e-04Epoch 200/20048000/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 x10。这里要注意的重要一点是, 该向量由全零组成, 除了其类别代表, 为此, 它是1。
因此, 让我们将标签转换为一键编码矢量:
# Change the labels from categorical to one-hot encodingtrain_Y_one_hot = to_categorical(train_labels)test_Y_one_hot = to_categorical(test_labels)# Display the change for category label using one-hot encodingprint('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 32conv1 = 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 32conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64conv2 = 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 64conv3 = 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 ShapeParam #=================================================================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, 570Trainable params: 1, 607, 050Non-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=http://www.srcmini.com/(valid_X, valid_label))
Train on 48000 samples, validate on 12000 samplesEpoch 1/10048000/48000 [==============================] - 6s - loss: 0.3747 - acc: 0.8732 - val_loss: 0.2888 - val_acc: 0.8935Epoch 2/10048000/48000 [==============================] - 6s - loss: 0.2216 - acc: 0.9178 - val_loss: 0.2942 - val_acc: 0.9010Epoch 3/10048000/48000 [==============================] - 5s - loss: 0.1762 - acc: 0.9340 - val_loss: 0.2868 - val_acc: 0.9078...Epoch 74/10048000/48000 [==============================] - 6s - loss: 0.0182 - acc: 0.9953 - val_loss: 0.8069 - val_acc: 0.9109Epoch 75/10048000/48000 [==============================] - 6s - loss: 0.0102 - acc: 0.9971 - val_loss: 0.7872 - val_acc: 0.9125Epoch 76/10011776/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=http://www.srcmini.com/(valid_X, valid_label))
Train on 48000 samples, validate on 12000 samplesEpoch 1/10048000/48000 [==============================] - 13s - loss: 0.1584 - acc: 0.9718 - val_loss: 0.7902 - val_acc: 0.8960Epoch 2/10048000/48000 [==============================] - 12s - loss: 0.1049 - acc: 0.9759 - val_loss: 0.8327 - val_acc: 0.8893Epoch 3/10048000/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.9217Epoch 98/10048000/48000 [==============================] - 13s - loss: 0.0097 - acc: 0.9975 - val_loss: 0.7074 - val_acc: 0.9211Epoch 99/10048000/48000 [==============================] - 13s - loss: 0.0081 - acc: 0.9984 - val_loss: 0.6846 - val_acc: 0.9205Epoch 100/10048000/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_reporttarget_names = ["Class {}".format(i) for i in range(num_classes)]print(classification_report(test_labels, predicted_classes, target_names=target_names))
precisionrecallf1-scoresupportClass 00.830.890.861000Class 10.990.990.991000Class 20.890.870.881000Class 30.920.930.921000Class 40.850.900.881000Class 50.990.990.991000Class 60.810.720.761000Class 70.960.980.971000Class 80.980.980.981000Class 90.980.960.971000avg / total0.920.920.9210000
你想更深入地潜水吗? 本教程是将自动编码器和完全连接的卷积神经网络与Python和Keras结合使用的良好起点。如果你能够轻松地进行工作, 甚至只需付出更多的努力, 那就做好了!尝试使用相同的模型架构但使用不同类型的可用公共数据集进行一些实验。
还有很多内容要讲, 为什么不参加srcmini的Python深度学习课程呢?同时, 请确保还查看Keras文档(如果你尚未这样做的话)。你将找到有关所有函数, 参数, 更多层等的更多示例和信息。当你学习如何在Python中使用神经网络时, 它无疑是必不可少的资源!
如果你想阅读一本解释深度学习基础知识(使用Keras)以及其在实践中的用法的书, 那么你绝对应该阅读Fran?oisChollet的Python深度学习书。
推荐阅读
- 常见数据科学陷阱以及如何避免它们!
- 使用机器学习检测真实和欺骗性的酒店评论
- 使用Python进行网页爬取
- Python列表index()用法
- R中的层次聚类
- 在Python中使用模块
- 如何在SQL中执行Python/R
- 图像超分辨率使用多解码器框架
- 使用Python和BeautifulSoup 4抓取Reddit