木瓜子 2019-12-11
计算机视觉是一个跨学科的科学领域,研究如何使计算机从数字图像或视频中获得高级理解。
在过去的几年里,计算机视觉已经取得了长足的进步,当CNN的AlexNet在ImageNet挑战中获得了最先进的性能标签图片时,它实现了一个重大的飞跃。
如今,随着Tensorflow和PyTorch等深度学习框架的日益流行,只需几行代码就可以轻松实现各种深度学习算法。
在这里,我将使用不同数量的VGG Blocks以及一些众所周知的正则化技术来对来自CIFAR-10数据集的对象进行分类并比较结果。
CIFAR-10是用于计算机视觉和深度学习的标准数据集。该机器学习数据集主要用于计算机视觉研究。数据集由60000张来自10个类别的对象的32*32像素的彩色照片组成,如飞机、汽车、鸟等。类标签及其标准的相关整数值如下:
CIFAR-10是一个易于理解的机器学习数据集,广泛用于计算机视觉算法。通过使用深度学习卷积神经网络,我们可以在测试集上获得90%以上的分类准确率。
CIFAR-10数据集
下面的示例从Keras API加载CIFAR-10数据集,并绘制了一个示例的Python代码。
我们可以看到训练集中有50,000个示例,测试集中有10,000个示例。稍后,我们将测试集分为两个不同的子集:验证集和测试集,我们将在连续训练期间和之后将其用于验证模型。
样本图像
很明显,与普通图像相比,这幅图像非常小,分辨率也很低,因此很难用肉眼看到图像所包含的内容。低分辨率很可能是顶级算法在数据集上的性能有限的原因。
加载数据集之后,我们可以进入数据预处理步骤。
我们知道CIFAR-10数据集中有10个类,并且这些类表示为唯一的整数。因此,我们可以对每个样本的类元素使用one hot编码,将整数转换为10个元素的binary向量,其中类值的索引为1。我们可以使用Keras提供的to_categorical实用程序函数进行one hot编码。
load_dataset()函数用于加载数据并one hot编码类元素。
我们知道数据集中图像的像素值是无符号整数,范围在0到255之间。我们需要对像素值进行归一化,例如将它们重新缩放到[0,1]范围。这涉及将像素值除以最大值。
normalize()函数实现了这一点:
我们还需要定义一个验证集,以在训练期间验证我们的模型。validate_split()函数实现了这一点:
训练期间将使用训练集和验证集,而训练完成后将使用测试集评估机器学习模型。好的方法是将数据分成单独的训练集,测试集和验证集。
经过预处理后,我们需要一种方法来定义我们的神经网络模型。在这里,我们将尝试不同版本的VGG模型来测试其在CIFAR-10数据集上的准确性。可以调用define_model()函数来获得一个CNN模型。
定义了机器学习模型之后,我们需要对模型进行拟合和评估。我们将使用训练集来训练模型,使用验证集来计算训练过程中的损失和准确性。稍后,在单独的测试集上进行训练后,将对模型进行评估。
# fit model history = model.fit(trainX,trainY, epochs = 50, batch_size = 64, validation_data = (testX, testY), verbose = 1)
模型拟合后,我们可以直接在验证集上对其进行评估。
# evaluate model _, acc = model.evaluate(validX, validY, verbose=0)
一旦模型被评估,我们就可以展示结果。
这里有两个关键的方面:模型在训练期间的学习行为的诊断和模型性能的估计。
首先,诊断涉及创建图像,以显示训练期间模型在训练和测试集上的性能。这些图对于了解模型是否过度拟合、欠拟合或对数据集有良好的拟合性是很有价值的。
我们将创建一个包含两个图形,一个用于损失,另一个用于准确性。蓝色线表示训练数据集上的模型性能,橙色线表示测试数据集上的性能。下面的summary_diagnostics()函数根据收集的训练历史创建并显示此图。
我们还可以将模型的分类准确度度进行打印:
print(‘> %.3f’ % (acc * 100.0))
现在,我们需要一个函数来驱动训练和测试过程。下面定义的test_model()函数执行此操作,并可用于启动给定模型的评估:
基准模型将建立可与我们所有其他模型进行比较的最低模型性能。
我们将使用不同版本的VGG模型。该体系结构使用3 * 3 filters堆叠卷积层,然后是最大池化层。这些层一起形成一个块,并且可以重复这些块,其中每个块中的filters数量随网络深度的增加而增加,例如对于模型的前四个块而言为32、64、128、256。在卷积层上使用相同的padding,以确保输出特征的高度和宽度与输入相匹配。
我们可以在CIFAR-10问题中探索这种架构,并将具有这种架构的模型与1、2、3和4个块进行比较。
每层将使用ReLU激活函数和“ he_uniform”权重初始化,这通常是最佳做法。例如,可以在Keras中定义2-block VGG样式的体系结构,如下所示:
这定义了模型的特征检测器部分。这将与模型的分类器部分相结合,该部分将解释特征并预测给定照片所属的类别。
该模型将使用随机梯度下降进行优化。我们将使用0.001的学习率和0.9的momentum。
1 VGG Block
运行模型会在测试数据集上打印分类准确度。
在这种情况下,我们可以看到该模型实现了约67%的分类准确度。
我们可以清楚地看到,模型在训练数据集上过度拟合。第一个图将训练集上的交叉熵损失与测试集进行了对比,显示出训练集上的损失在不断减少,而在测试集上,损失首先减小,之后继续增大。第二幅图中训练集的分类准确度与测试集的分类准确度也有相似的趋势。
2 VGG Blocks
准确度略好于我们的1-VGG block,几乎达到71.5%。
与1-VGG block一样,该模型在训练数据集上过拟合。测试数据集上的交叉熵损失不断减少,直到大约15个epochs,然后开始增加,而训练集上的损失则不断减少。
3 VGG Blocks
同样,这个模型也在改进,但不是很明显。验证集的分类准确度仅略高于74%,而交叉熵损失和分类精度图与1 和2 VGG blocks的分类准确度趋势相似。
4 VGG Blocks
当我们实现4 VGG blocks模型时,与3 VGG blocks模型相比,模型准确性没有明显变化。但是,与3 VGG blocks块模型相比,该模型显然过度拟合,如训练和测试数据集的交叉熵损失的差异所示。因此,我们将使用3 VGG blocks模型作为基线模型。
现在,让我们使用不同的正则化技术来尽可能地改善模型。
在这里,我们将在每个VGG块之后使用0.2的固定Dropout概率,这意味着20%的节点将被忽略,只有80%的节点将被保留。
可以通过向神经网络添加一个新的Dropout层(其中节点被删除的概率作为参数)来将Dropout添加到模型中,其中删除节点的概率作为参数传递。
3 VGG blocks的define_model()函数:
当我们对模型进行100个epochs的训练时,我们获得了大约81.5%的分类准确度,这明显大于我们的基线3 blocks VGG 模型。通过对交叉熵和分类准确度图的检验,我们可以看出,该模型是从后期对训练数据集的拟合开始的。验证集的分类准确度基本保持在80%左右,而训练集的分类准确度不断下降。
当我们分别在连续的VGG区块上使用0.2、0.3、0.4的变化dropout,在dense层上使用0.5的dropout时,测试集上的分类精度提高到约83.5%。
图像数据增强是一种可通过在机器学习数据集中创建图像的修改版本来人工扩展训练数据集大小的技术。
可以使用ImageDataGenerator类在Keras中实现数据增强。
我们可以在定义单个epoch中的batches数量之后,将迭代器传递给model.fit_generator()函数。
经过200个epochs的训练后,我们可以看到测试数据集的准确性略微下降到83%,但是该模型的泛化效果很好,这可以从两个图上的训练准确度和验证准确度之间的差异看出。即使到了200个epochs,交叉熵损失仍在不断减小。进一步训练模型,肯定会改进模型。
接下来,我们添加批归一化,以稳定网络并缩短训练时间。
当我们训练模型400个epochs时,测试数据集的分类准确度为88%。
神经网络模型的最终Python实现:
import tensorflow as tf from tensorflow import keras import numpy as np import matplotlib.pyplot as plt import sys from keras.datasets import cifar10 from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D from keras.optimizers import SGD from keras.regularizers import l2 from keras.preprocessing.image import ImageDataGenerator from keras.layers import BatchNormalization from time import time def load_dataset(): #load dataset (trainX, trainY),(testX, testY) = cifar10.load_data() #one hot encode the target trainY = keras.utils.to_categorical(trainY) testY = keras.utils.to_categorical(testY) return trainX, trainY, testX, testY def validation_split(testX, testY, valid_X, valid_Y, v_split): index_of_validation = int(v_split * len(testX)) valid_X.extend(testX[-index_of_validation:]) valid_Y.extend(testY[-index_of_validation:]) testX = testX[:-index_of_validation] testY = testY[:-index_of_validation] return testX, testY, np.asarray(valid_X), np.asarray(valid_Y) def normalize(train,test,valid): # convert from integers to float train_norm = train.astype('float32') test_norm = test.astype('float32') valid_norm = valid.astype('float32') #normalize to range 0-1 train_norm = train_norm / 255.0 test_norm = test_norm / 255.0 valid_norm = valid_norm / 255.0 return train_norm, test_norm,valid_norm def define_model(): model = Sequential() model.add(Conv2D(32,(3,3), activation = 'relu', kernel_initializer = 'he_uniform', padding = 'same', input_shape = (32,32,3))) model.add(BatchNormalization()) model.add(Conv2D(32,(3,3), activation = 'relu', kernel_initializer = 'he_uniform', padding = 'same')) model.add(BatchNormalization()) model.add(MaxPooling2D((2,2))) model.add(Dropout(0.2)) model.add(Conv2D(64,(3,3), activation = 'relu', kernel_initializer = 'he_uniform', padding = 'same')) model.add(BatchNormalization()) model.add(Conv2D(64,(3,3), activation = 'relu', kernel_initializer = 'he_uniform', padding = 'same')) model.add(BatchNormalization()) model.add(MaxPooling2D((2,2))) model.add(Dropout(0.3)) model.add(Conv2D(128,(3,3), activation = 'relu', kernel_initializer = 'he_uniform', padding = 'same')) model.add(BatchNormalization()) model.add(Conv2D(128,(3,3), activation = 'relu', kernel_initializer = 'he_uniform', padding = 'same')) model.add(BatchNormalization()) model.add(MaxPooling2D((2,2))) model.add(Dropout(0.4)) model.add(Flatten()) model.add(Dense(128, activation = 'relu', kernel_initializer = 'he_uniform')) model.add(BatchNormalization()) model.add(Dropout(0.5)) model.add(Dense(10, activation = 'softmax')) #compile model opt = SGD(lr = 0.001, momentum = 0.9) model.compile(optimizer = opt, loss = 'categorical_crossentropy', metrics = ['accuracy']) return model # plot diagnostic learning curves def summarize_diagnostics(history): plt.subplots(figsize = (7,7)) # plot loss plt.subplot(211) plt.title('Cross Entropy Loss') plt.plot(history.history['loss'], color='blue', label='train') plt.plot(history.history['val_loss'], color='orange', label='test') # plot accuracy plt.subplot(212) plt.title('Classification Accuracy') plt.plot(history.history['accuracy'], color='blue', label='train') plt.plot(history.history['val_accuracy'], color='orange', label='test') plt.show() # save plot to file filename = sys.argv[0].split('/')[-1] plt.savefig(filename + '_plot.png') plt.close() # run all the defined functions for evaluating a model def test_model(): # load dataset trainX, trainY, testX, testY = load_dataset() #get validation set valid_X = [] valid_Y = [] testX, testY, validX, validY = validation_split(testX, testY, valid_X, valid_Y,v_split=0.5) # normalize the data trainX, testX,validX = normalize(trainX, testX,validX) # define model model = define_model() #create data generator datagen = ImageDataGenerator(width_shift_range = 0.1, height_shift_range = 0.1, horizontal_flip = True) #iterator train = datagen.flow(trainX, trainY, batch_size = 64) # fit model steps = int(trainX.shape[0]/ 64) history = model.fit_generator(train, steps_per_epoch = steps, epochs=400, validation_data=(validX, validY), verbose=0) # evaluate model _, acc = model.evaluate(testX, testY, verbose=0) print('> %.3f' % (acc * 100.0)) # learning curves summarize_diagnostics(history) return history def main(): test_model() if __name__ == "__main__": main()
我们探索了各种不同的方法来扩展用于图像分类的VGG块。最终模型学习得很好,即使经过了400个epochs,交叉熵损失似乎仍在减少。我们仍然可以通过更改不同的超参数(例如学习率,epochs数等)来进一步优化模型。通过将优化器更改为Adam,RMSprop或Adagrad之类的内容,我们也许能够获得更好的模型。