可以对Fashion Images进行分类的4种卷积神经网络模型
在本教程中,我们将逐步构建一个机器学习模型,用于使用Fashion-MNIST数据集识别时尚对象的图像。我们将介绍如何训练模型,设计类别分类的输入和输出,最后显示每个模型的准确度结果。
图像分类
图像分类的问题是这样的:给定一组全部用单一类别标记的图像,我们被要求为一组新的测试图像预测这些类别并测量预测的准确性。这项任务涉及各种挑战,包括视角变化,尺度变化,类内变化,图像变形,图像遮挡,光照条件,背景杂波等。
我们如何编写可以将图像分类为不同类别的算法?计算机视觉研究人员已经提出了一种数据驱动方法来解决这个问题。它们不是试图直接在代码中指定每个感兴趣的图像类别,而是为计算机提供每个图像类的许多示例,然后开发学习算法,查看这些示例并了解每个类的视觉外观。换句话说,他们首先累积标记图像的训练数据集,然后将其提供给计算机,以使其熟悉数据。
完整的图像分类管道可以定义如下:
- 我们的输入是一个训练数据集,由N个图像组成,每个图像都标有K个不同类别中的一个。
- 然后,我们使用此训练集来训练分类器,以了解每个类的外观。
- 最后,我们通过要求分类器预测从未见过的一组新图像的标签来评估分类器的质量。然后,我们将比较这些图像的真实标签与分类器预测的标签。
卷积神经网络
卷积神经网络(tional Neural network, CNN)是目前用于图像分类问题中最常用的神经网络模型。CNN背后的重要思想是,对图像的局部理解足够好。实际的好处是,拥有更少的参数大大提高了学习的时间,并减少了训练模型所需的数据量。CNN并不是一个由每个像素的权重组成的全连接网络,而是有足够的权重来查看图像的一个小块。就像用放大镜读一本书;最终,您会阅读整个页面,但是您在任何时候只查看页面的一小部分。
考虑一个256 x 256的图像。CNN可以通过大块有效地扫描它 - 例如,一个5×5的窗口。5×5窗口沿图像滑动(通常从左到右,从上到下),如图所示。它如何“快速”滑动称为其步幅。例如,步幅长度2表示5×5滑动窗口一次移动2个像素,直到它跨越整个图像。
当窗口在整个图像滑动时,卷积是图像的像素值的加权和。事实证明,整个带有权重矩阵的图像的卷积过程会产生另一个图像(大小相同,具体取决于约定)。卷积是应用卷积的过程。
典型的CNN具有多个卷积层。每个卷积层通常产生许多交替卷积,因此权重矩阵是5×5×n的张量,其中n是卷积数。
例如,假设图像通过5×5×64的权重矩阵上的卷积层。它通过滑动5×5窗口生成64个卷积。因此,该模型具有5×5×64(= 1,600)个参数,其参数明显少于全连接网络,256×256(= 65,536)。
CNN的优点在于参数的数量与原始图像的大小无关。您可以在300×300图像上运行相同的CNN,并且卷积层中的参数数量不会改变。
数据增强
图像分类研究数据集通常非常大。尽管如此,通常使用数据增强来改进泛化特征。通常情况下,使用随机裁剪进行调整图像 ,其他如随机水平翻转和随机RGB颜色和亮度变化也被使用。对于图像的缩放和裁剪存在不同的方案(即single scale vs. multi scale training)。在测试期间也经常使用 Multi-crop评估,尽管计算成本更高,性能改进有限。注意,随机缩放和裁剪的目标是了解每个对象在不同尺度和位置上的重要特征。Keras没有开箱即用地实现所有这些数据增强技术,但是它们可以通过ImageDataGenerator模块的预处理函数轻松实现。
Fashion MNIST数据集
最近,Zalando研究发布了一个新的数据集,它与众所周知的MNIST手写数字数据库非常相似。该数据集专为机器学习分类任务而设计,包含总共60 000个训练和10 000个测试图像(灰度),每个28x28像素。每个培训和测试用例与十个标签之一(0-9)相关联。直到这里,Zalando的数据集基本上与原始手写数字数据相同。然而,Zalando的数据不包含数字0-9的图像,而是包含10种不同时尚产品的图像(并不令人惊讶)。因此,数据集称为Fashion-MNIST数据集,这些数据在Kaggle上(https://www.kaggle.com/zalando-research/fashionmnist)。下图中显示了一些示例,其中每行包含一个时尚项。
10个不同的类别标签是:
- 0 T-shirt/top
- 1 Trouser
- 2 Pullover
- 3 Dress
- 4 Coat
- 5 Sandal
- 6 Shirt
- 7 Sneaker
- 8 Bag
- 9 Ankle boot
Fashion-MNIST数据旨在成为旧MNIST手写数字数据的直接替代品,因为手写数字存在若干问题。例如,通过简单地查看几个像素,可以正确地区分几个数字。即使使用线性分类器,也可以实现高分类精度。Fashion-MNIST数据有望更加多样化,因此机器学习(ML)算法必须学习更多高级特征,以便能够可靠地分离各个类。
Fashion MNIST的可视化
嵌入是一种将离散对象(图像,单词等)映射到高维向量的方法。这些向量中的单个维度通常没有内在意义。相反,它是机器学习利用的向量之间的位置和距离的整体模式。因此,嵌入对于机器学习的输入很重要; 因为分类器和神经网络更普遍地用于实数矢量。它们在密集向量上训练最好,其中所有值都有助于定义对象。
TensorBoard有一个内置的可视化器,称为Embedding Projector,用于交互式可视化和分析嵌入等高维数据。Embedding Projector将从机器学习模型检查点文件中读取嵌入。虽然它对嵌入最有用,但它会加载任何2D张量,包括训练权重。
在这里,我将尝试使用TensorBoard表示高维Fashion MNIST数据。在读取数据并创建测试标签后,我使用此Python代码构建TensorBoard的 Embedding Projector:
from tensorflow.contrib.tensorboard.plugins import projector logdir = 'fashionMNIST-logs' # Creating the embedding variable with all the images defined above under X_test embedding_var = tf.Variable(X_test, name='fmnist_embedding') # Format: tensorflow/contrib/tensorboard/plugins/projector/projector_config.proto config = projector.ProjectorConfig() # You can add multiple embeddings. Here I add only one. embedding = config.embeddings.add() embedding.tensor_name = embedding_var.name # Link this tensor to its metadata file (e.g. labels). embedding.metadata_path = os.path.join(logdir, 'metadata.tsv') # Use this logdir to create a summary writer summary_writer = tf.summary.FileWriter(logdir) # The next line writes a projector_config.pbtxt in the logdir. TensorBoard will read this file during startup. projector.visualize_embeddings(summary_writer,config) # Periodically save the model variables in a checkpoint in logdir. with tf.Session() as sesh: sesh.run(tf.global_variables_initializer()) saver = tf.train.Saver() saver.save(sesh, os.path.join(logdir, 'model.ckpt')) # Create the sprite image rows = 28 cols = 28 label = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'] sprite_dim = int(np.sqrt(X_test.shape[0])) sprite_image = np.ones((cols * sprite_dim, rows * sprite_dim)) index = 0 labels = [] for i in range(sprite_dim): for j in range(sprite_dim): labels.append(label[int(Y_test[index])]) sprite_image[ i * cols: (i + 1) * cols, j * rows: (j + 1) * rows ] = X_test[index].reshape(28, 28) * -1 + 1 index += 1 # After constructing the sprite, I need to tell the Embedding Projector where to find it embedding.sprite.image_path = os.path.join(logdir, 'sprite.png') embedding.sprite.single_image_dim.extend([28, 28]) # Create the metadata (labels) file with open(embedding.metadata_path, 'w') as meta: meta.write('Index Label ') for index, label in enumerate(labels): meta.write('{} {} '.format(index, label))
Embedding Projector有三种减少数据集维数的方法:两个线性和一个非线性。每种方法都可用于创建二维或三维视图。
主成分分析:主成分分析(PCA)是一种简单的降维技术。嵌入投影仪计算出前10个主要成分。菜单允许我将这些组件投射到任何两个或三个的组合上。PCA是一种线性投影,经常有效地检查全局几何。
t-SNE:一种流行的非线性降维技术是t-SNE。Embedding Projector提供二维和三维t-SNE视图。对算法的每个步骤进行客户端动画化。因为t-SNE通常保留一些局部结构,所以它对于探索局部邻域和寻找聚类是有用的。
自定义:我还可以根据文本搜索构建专门的线性投影,以便在空间中找到有意义的方向。要定义投影轴,请输入两个搜索字符串或正则表达式。程序计算其标签与这些搜索匹配的点集的质心,并使用质心之间的差矢量作为投影轴。
在Fashion MNIST上训练CNN模型
现在让我们转到有趣的部分:我将创建各种不同的基于CNN的分类模型来评估Fashion MNIST的表现。我将使用Keras框架构建我们的深度学习模型。以下是我将尝试的模型列表,并比较它们的结果:
- CNN具有1个卷积层
- CNN具有3个卷积层
- CNN有4个卷积层
- VGG-19预训练模型
对于所有深度学习模型(预训练模型除外),方法是:
- 将原始训练数据(60,000张图像)分成80%训练(48,000张图像)和20%验证(12000张图像),优化分类器,同时保留测试数据(10,000张图像),最终评估模型对其从未见过的数据的准确性。这有助于了解我是否对训练数据进行了过度拟合,如果验证精度高于训练精度,我是否应该降低学习率并进行更大的训练,或者如果训练精度高于验证,我是否应该停止过度训练。
- 训练模型10个epochs,批量大小为256,使用categorical_crossentropy损失函数和Adam优化器编译。
- 然后,添加数据增强,通过旋转,shifting 和缩放训练样本生成新的训练样本,并在更新的数据上训练模型另外50个epochs。
这是加载和拆分数据的Python代码:
# Import libraries from keras.utils import to_categorical from sklearn.model_selection import train_test_split # Load training and test data into dataframes data_train = pd.read_csv('data/fashion-mnist_train.csv') data_test = pd.read_csv('data/fashion-mnist_test.csv') # X forms the training images, and y forms the training labels X = np.array(data_train.iloc[:, 1:]) y = to_categorical(np.array(data_train.iloc[:, 0])) # Here I split original training data to sub-training (80%) and validation data (20%) X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=13) # X_test forms the test images, and y_test forms the test labels X_test = np.array(data_test.iloc[:, 1:]) y_test = to_categorical(np.array(data_test.iloc[:, 0]))
在加载和拆分数据之后,我通过将它们reshaping为网络所需的形状,并对它们进行缩放,使所有值都位于[0,1]区间。例如,以前,训练数据存储在uint8类型的形状数组(60000,28,28)中,值在[0,255]区间。我将它转换为一个float32 的形状数组(60000,28 * 28),值介于0和1之间。Python代码如下:
# Each image's dimension is 28 x 28 img_rows, img_cols = 28, 28 input_shape = (img_rows, img_cols, 1) # Prepare the training images X_train = X_train.reshape(X_train.shape[0], img_rows, img_cols, 1) X_train = X_train.astype('float32') X_train /= 255 # Prepare the test images X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 1) X_test = X_test.astype('float32') X_test /= 255 # Prepare the validation images X_val = X_val.reshape(X_val.shape[0], img_rows, img_cols, 1) X_val = X_val.astype('float32') X_val /= 255
1 - 1-Conv CNN
以下是具有1个卷积层的CNN的Python代码:
from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D cnn1 = Sequential() cnn1.add(Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) cnn1.add(MaxPooling2D(pool_size=(2, 2))) cnn1.add(Dropout(0.2)) cnn1.add(Flatten()) cnn1.add(Dense(128, activation='relu')) cnn1.add(Dense(10, activation='softmax')) cnn1.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
训练模型后,这里是测试损失和测试精度:
history1 = cnn1.fit(X_train, y_train, batch_size=256, epochs=10, verbose=1, validation_data=(X_val, y_val)) score1 = cnn1.evaluate(X_test, y_test, verbose=0) print('Test loss:', score1[0]) print('Test accuracy:', score1[1])
应用数据增强后,这里是测试损失和测试准确度:
from keras.preprocessing.image import ImageDataGenerator gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3, height_shift_range=0.08, zoom_range=0.08) batches = gen.flow(X_train, y_train, batch_size=256) val_batches = gen.flow(X_val, y_val, batch_size=256) history1 = cnn1.fit_generator(batches, steps_per_epoch=48000//256, epochs=50, validation_data=val_batches, validation_steps=12000//256, use_multiprocessing=True) score1 = cnn1.evaluate(X_test, y_test, verbose=0) print('Test loss:', score1[0]) print('Test accuracy:', score1[1])
出于视觉目的,我绘制了训练和验证的准确性和损失:
import matplotlib.pyplot as plt %matplotlib inline accuracy = history1.history['acc'] val_accuracy = history1.history['val_acc'] loss = history1.history['loss'] val_loss = history1.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()
2 - 3-Conv CNN
以下是CNN与3卷积层的代码:
from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D cnn3 = Sequential() cnn3.add(Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) cnn3.add(MaxPooling2D((2, 2))) cnn3.add(Dropout(0.25)) cnn3.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) cnn3.add(MaxPooling2D(pool_size=(2, 2))) cnn3.add(Dropout(0.25)) cnn3.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) cnn3.add(Dropout(0.4)) cnn3.add(Flatten()) cnn3.add(Dense(128, activation='relu')) cnn3.add(Dropout(0.3)) cnn3.add(Dense(10, activation='softmax')) cnn3.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
训练模型后,这里是测试损失和测试精度:
history3 = cnn3.fit(X_train, y_train, batch_size=256, epochs=10, verbose=1, validation_data=(X_val, y_val)) score3 = cnn3.evaluate(X_test, y_test, verbose=0) print('Test loss:', score3[0]) print('Test accuracy:', score3[1])
应用数据增强后,这里是测试损失和测试准确度:
from keras.preprocessing.image import ImageDataGenerator gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3, height_shift_range=0.08, zoom_range=0.08) batches = gen.flow(X_train, y_train, batch_size=256) val_batches = gen.flow(X_val, y_val, batch_size=256) history3 = cnn3.fit_generator(batches, steps_per_epoch=48000//256, epochs=50, validation_data=val_batches, validation_steps=12000//256, use_multiprocessing=True) score3 = cnn3.evaluate(X_test, y_test, verbose=0) print('Test loss:', score3[0]) print('Test accuracy:', score3[1])
出于视觉目的,我绘制了训练和验证的准确性和损失:
import matplotlib.pyplot as plt %matplotlib inline accuracy = history3.history['acc'] val_accuracy = history3.history['val_acc'] loss = history3.history['loss'] val_loss = history3.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()
3 - 4-Conv CNN
以下是具有4个卷积层的CNN的代码:
from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D, BatchNormalization cnn4 = Sequential() cnn4.add(Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) cnn4.add(BatchNormalization()) cnn4.add(Conv2D(32, kernel_size=(3, 3), activation='relu')) cnn4.add(BatchNormalization()) cnn4.add(MaxPooling2D(pool_size=(2, 2))) cnn4.add(Dropout(0.25)) cnn4.add(Conv2D(64, kernel_size=(3, 3), activation='relu')) cnn4.add(BatchNormalization()) cnn4.add(Dropout(0.25)) cnn4.add(Conv2D(128, kernel_size=(3, 3), activation='relu')) cnn4.add(BatchNormalization()) cnn4.add(MaxPooling2D(pool_size=(2, 2))) cnn4.add(Dropout(0.25)) cnn4.add(Flatten()) cnn4.add(Dense(512, activation='relu')) cnn4.add(BatchNormalization()) cnn4.add(Dropout(0.5)) cnn4.add(Dense(128, activation='relu')) cnn4.add(BatchNormalization()) cnn4.add(Dropout(0.5)) cnn4.add(Dense(10, activation='softmax')) cnn4.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
训练模型后,这里是测试损失和测试精度:
history4 = cnn4.fit(X_train, y_train, batch_size=256, epochs=10, verbose=1, validation_data=(X_val, y_val)) score4 = cnn4.evaluate(X_test, y_test, verbose=0) print('Test loss:', score4[0]) print('Test accuracy:', score4[1])
应用数据增强后,这里是测试损失和测试准确度:
from keras.preprocessing.image import ImageDataGenerator gen = ImageDataGenerator(rotation_range=8, width_shift_range=0.08, shear_range=0.3, height_shift_range=0.08, zoom_range=0.08) batches = gen.flow(X_train, y_train, batch_size=256) val_batches = gen.flow(X_val, y_val, batch_size=256) history4 = cnn4.fit_generator(batches, steps_per_epoch=48000//256, epochs=50, validation_data=val_batches, validation_steps=12000//256, use_multiprocessing=True) score4 = cnn4.evaluate(X_test, y_test, verbose=0) print('Test loss:', score4[0]) print('Test accuracy:', score4[1])
出于视觉目的,我绘制了训练和验证的准确性和损失:
import matplotlib.pyplot as plt %matplotlib inline accuracy = history4.history['acc'] val_accuracy = history4.history['val_acc'] loss = history4.history['loss'] val_loss = history4.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()
4 - 迁移学习
对小图像数据集进行深度学习的常用且高效的方法是使用预训练网络。一个预训练网络是以前训练的大型数据集,通常在大型图像分类任务保存的网络。如果这个原始数据集足够大且足够通用,则预训练网络所学习的特征的空间层次结构可以有效地充当视觉世界的通用模型,因此其特征可以证明对于许多不同的计算机视觉问题是有用的。即使这些新问题可能涉及与原始任务完全不同的类。
我试图实现VGG19预训练模型,这是ImageNet广泛使用的ConvNets架构。这是您可以遵循的代码:
import keras from keras.applications import VGG19 from keras.applications.vgg19 import preprocess_input from keras.layers import Dense, Dropout from keras.models import Model from keras import models from keras import layers from keras import optimizers # Create the base model of VGG19 vgg19 = VGG19(weights='imagenet', include_top=False, input_shape = (150, 150, 3), classes = 10) # Preprocessing the input X_train = preprocess_input(X_train) X_val = preprocess_input(X_val) X_test = preprocess_input(X_test) # Extracting features train_features = vgg19.predict(np.array(X_train), batch_size=256, verbose=1) test_features = vgg19.predict(np.array(X_test), batch_size=256, verbose=1) val_features = vgg19.predict(np.array(X_val), batch_size=256, verbose=1) # Flatten extracted features train_features = np.reshape(train_features, (48000, 4*4*512)) test_features = np.reshape(test_features, (10000, 4*4*512)) val_features = np.reshape(val_features, (12000, 4*4*512)) # Add Dense and Dropout layers on top of VGG19 pre-trained model = models.Sequential() model.add(layers.Dense(512, activation='relu', input_dim=4 * 4 * 512)) model.add(layers.Dropout(0.5)) model.add(layers.Dense(10, activation="softmax")) # Compile the model model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
训练模型后,这里是测试损失和测试精度:
# Train the the model history = model.fit(train_features, y_train, batch_size=256, epochs=50, verbose=1, validation_data=(val_features, y_val)) score = model.evaluate(test_features, y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1])
出于视觉目的,我绘制了培训和验证的准确性和损失:
# plot the loss and accuracy import matplotlib.pyplot as plt %matplotlib inline acc = history.history['acc'] val_acc = history.history['val_acc'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(acc) + 1) plt.title('Training and validation accuracy') plt.plot(epochs, acc, 'red', label='Training acc') plt.plot(epochs, val_acc, 'blue', label='Validation acc') plt.legend() plt.figure() plt.title('Training and validation loss') plt.plot(epochs, loss, 'red', label='Training loss') plt.plot(epochs, val_loss, 'blue', label='Validation loss') plt.legend() plt.show()
最后
时尚领域是一个非常受欢迎的机器学习和计算机视觉应用的场景。由于高度的主观性和所涉及的特征的语义复杂性,该领域中的问题具有挑战性。我希望这篇文章有助于您了解构建自己的卷积神经网络以对时尚图像进行分类的4种不同方法。