机器学习:车辆场景分类

湖南大学2018-2019秋季学期专业任选《机器学习》课程项目(20190104)

 

任务说明

提供2000张标注了的车辆场景分类信息的高分辨率图片,请使用这些数据,建立并训练模型,并将此模型运用于测试数据集的图像分类标注。

 

提示

该题目主要考核各位同学使用较小的模型和小规模的训练数据下的模型设计和训练的能力。需要同学充分利用迁移学习等方法,以解决训练数据少的问题,同时需要思考和解决深度学习中过拟合的问题。所提供的场景定义为比较单一的车辆场景,一定程度上平衡了难度系数。

 

标签信息

0,巴士,bus

1,出租车,taxi

2,货车,truck

3,家用轿车,family sedan

4,面包车,minibus

5,吉普车,jeep

6,运动型多功能车,SUV

7,重型货车,heavy truck

8,赛车,racing car

9,消防车,fire engine

 

数据集下载

链接:https://pan.baidu.com/s/1COLohBbj1Z8VFANMqLYoQg

密码:653r

 

训练模型构建过程(基于Keras库)

 

本次实验中采用的深度学习模型主要是卷积神经网络,卷积神经网络的原理如下:

 

卷积层:

图中不同颜色的神经元对不同区域进行取样,但其权值矩阵是共享的,即使用相同的一组权值在图像的不同区域进行采样,以提取特定的特征,通过增加卷积核的个数,能够增加提取的特征种类,利用这种方法可以大量减少需要训练的参数,并且也能对图像的特征进行有效的提取。

机器学习:车辆场景分类

池化层:(上图为最大值池化的示意图)

池化操作实际上是对数据规模的一个压缩的过程,经过卷积后得到的特征图包括很多特征值,由于卷积的范围一般会有交叉覆盖,实际上一些相邻的特征值是可以舍去的,池化层就是在一个较小的范围内将数据进行压缩,常见的有最大值池化,均值池化等,本次实验中使用的池化均为最大值池化。

 

为了避免梯度弥散,本次实验使用的**函数均为Relu函数,在每层卷积层之后还添加了规范化层和dropout层,规范化层的作用是将输入作规整化处理,使输出的均值趋向1,方差趋向0。Dropout层的作用是令输入神经元以给定的概率失效(变为0),这样每次训练的时候可以随机屏蔽掉一部分神经元,从而防止过拟合现象的产生

本次实验的网络结构图如下:

先通过数次卷积提取特征,再经过全连接层到输出层,做softmax处理。转化为one-hot向量输出。

机器学习:车辆场景分类

 

模型构建代码

# In[6]:


# 搭建神经网络模型
chanDim = -1
model = Sequential()
'''
添加一层卷积层
卷积核的数目:32
卷积核的宽度:3
卷积核的长度:3
**函数:relu(线性整流函数f(x) = max(0, x))
输入规模:之前给定的
'''
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(150, 150, 3)))
'''
添加一层二维池化层
pool_size = (3, 3)即池化窗口的大小
'''
model.add(MaxPool2D((3, 3)))
'''
添加一个规范化层,该层处理上一层传来的数据,使其均值接近0,标准差接近1
'''
model.add(BatchNormalization(axis=chanDim))
'''
添加一层损失层,随机损失0.25的边,避免过拟合
'''
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(MaxPool2D((2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(MaxPool2D((2, 2)))
model.add(Dropout(0.25))

'''
flatten将多维的层压平
'''
model.add(Flatten())
'''
添加一个普通的全连接层,输出的尺寸为(*, 1024),**函数为relu
'''
model.add(Dense(1024, activation='relu'))
'''
添加一个规范化层,该层处理上一层传来的数据,使其均值接近0,标准差接近1
'''
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
'''
编译模型,选择损失函数为categorical_crossentropy,优化器为adadelta(),评价标准为acc
'''
model.compile(loss=categorical_crossentropy, optimizer=Adadelta(), metrics=['acc'])

实际操作过程

 

1、基于google colab的环境搭建和google drive授权绑定,实现从云盘读取数据

(强烈建议使用服务器,比如科学上网的可以和我用同一个,用Jupiter也可以,在本机上运行实在太慢)

 

Colaboratory(简称colab) 是一个Google提供的免费GPU服务器,不需要进行任何设置就可以使用,并且完全在云端运行。利用Colaboratory ,可以方便的使用Keras,TensorFlow等框架进行深度学习应用的开发。但是,当我们运行.py程序需要读取google driver中的数据时,就需要进行相关的操作。

 

本次实验需要涉及到的主要问题在于对于图片的分析,因此我们使用“代码执行程序 --> 更改运行时类型 --> 选择python版本和加速器”中,勾选GPU加速。

机器学习:车辆场景分类

在colab上的操作实际就是在一个分配好的linux系统主机上操作,我们需要手动设置好和程序所中需路径一致的目录结构。最简单并且能够可视化的方法就是怪哉google drive,然后对于文件的上传、新建、删除等工作便可以在google drive中进行。

 

首先是对于谷歌账号的授权:(参考:https://blog.****.net/weixin_43792757/article/details/84523103

机器学习:车辆场景分类

在进行完绑定授权之后,执行指令将工作文件夹切换到云盘中存有图片的对应文件夹下。因为Google colab为我们提供的是一个linux的开发环境,这里直接使用linux的命令即可。

机器学习:车辆场景分类

2、从存放图片数据的路径,读取图片构造样本生成器。

 

关键代码如下:

机器学习:车辆场景分类

首先,我们在第一步骤中完成了对于Google colab的环境设置,也就是说,我们当前已经建立好了colab与谷歌云盘之间的联系,程序可以直接读取到云盘中文件夹的数据,且在上一步中,我们已经通过mkdir指令,将当前工作文件夹定位到了需要读取的数据出。因此,这里对于训练集和测试集的指定路径,作直接指定文件夹名称“train”、“val”处理,这样就能够获取到图片的信息。

接下来我们通过ImageDataGenerator函数,对于训练集和验证集中的所有数据做了第一步处理,这一步处理主要是对着图像指定的尺度因子,做放大或缩小操作。因为有些图片不仅大小不一致,而且尺寸(这里一般指长宽)比例还不一致,这一步的预处理首先是将比例进行一个统一化,至于图片大小一致的问题,在随机生成数据集的过程中在进行处理,减小了复杂度,也避免了一次性要将所有的图片读入程序的问题。

最后,我们定义了两个样本生成器。

这两个“样本生成器”,使用函数train_datagen.flow_from_directory进行定义,一个用于训练,一个用于测试;

相关参数解释:

directory:产生数据集的原始数据的路径;

target_size:处理更改图片比例之后的遗留问题“大小不一致”,统一将图片大小变为150*150,因为每次生成的数据集是随机的,因此只需要每次获取随机数据集的时候,对于有限的图片做一个尺度变换,减小了复杂度;

batch_size:每个样本生成器每次从路径下读取30张图片,用于训练和测试。

 

3、进行具体的训练过程

 

主要代码如下:

机器学习:车辆场景分类

使用model_fit_generator函数进行训练。

 

执行fit_generator时,由train_flow 数据流返回train_flow的batch_size的参数,也就是说选取batch_size个参数作为一个batch训练模型,而这个batch_size在上一步中的随机样本生成器中定义过;

重复这一过程100(fit_generator的steps_per_epoch参数)次,一个epoch结束。一个epoch所用样本batch_size乘以steps_per_epoch。

当epoch=50(fit_generator的epochs参数)时,模型训练结束。

 

其他相关参数如下:

 

train_generator:训练样本生成器,在第二步中已经构造;

steps_per_epoch:每一轮训练所调用的训练样本生成器次数;

epochs:训练轮数;

validation=validation_generator:验证样本生成器,每次训练后使用该生成器随机构造一定数量的样本,进行测试,验证准确性。

 

在本次实验中,我们的训练规模为:每一轮迭代训练50次(即调用50次随机样本生成器进行训练,每次调用样本生成器,随机返回30张图片构成训练集进行训练,也就是每一轮迭代训练1500张图),该过程重复100遍结束。

 

测试结果与分析

 

结果的分析使用了matplotlib(一个python的第三方2D绘图库),根据训练数据绘制图标。

关键代码如下:

机器学习:车辆场景分类

绘图的结果如下:

机器学习:车辆场景分类

蓝线为训练集的准确率,经过大约15轮训练,训练集的准确率基本能够达到100%;

绿线为训练损失,与我们的预期一样,随着训练的轮次增加和训练准确率的提高,训练的损失函数数值越来越小,最终接近于0;

黄线为测试集的准确率。最初的几次迭代准确率只有50%左右,随着训练轮次的增加,我们发现准确率在波动中,得到了一些提升,在最后五分之一的训练,即80——100轮迭代中,准确率稳定在大约78%——83%的范围内。

 

更为详细的参数输出:

 

每一轮训练使用50步,即调用样本生成器,每次随机选30张图片作为训练样本,重复50次为一轮,一共训练100轮,并边训练边计算其交叉熵。

初次训练loss值为2.2左右,训练集正确率43%、测试集准确率46%。

机器学习:车辆场景分类

经过100轮训练,最终的准确率大约为78%,训练loss值控制在10^-4数量级,测试loss在1.2左右。

机器学习:车辆场景分类

注意,acc为训练集准确率,而val_acc才是实验的结果,即验证集的准确率。

 

迭代训练过程中,平均每轮耗时38秒,总共100轮,用时约为64分钟,最终的训练准确率在78%——83%之间波动,平均在80%左右。

 

写在最后

 

实验过程中我们也和其他小组进行了交流,最后,推荐有条件的同学们还是使用Tensorflow,因为之前12月份第一次汇报课的第一个展示的组,也是使用Keras库,当时他们的正确率只有67%到69%,而所有使用Keras的组,正确率最多也就七八十;而且,博主技术菜鸡,临近期末考试,这个代码更像是随便写写的,神经网络模型显得非常简单,有条件的同学们还是可以尝试更多、更复杂的模型,正确率达到90以上应该比较轻松。

 

完整代码

#!/usr/bin/env python
# coding: utf-8

# In[5]:

import keras
from keras.layers import Conv2D, MaxPool2D, Flatten, Dense, Dropout, BatchNormalization
from keras.losses import categorical_crossentropy
from keras.models import Sequential
from keras.optimizers import Adadelta, Adam
from keras.preprocessing.image import ImageDataGenerator

import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')

# In[6]:

# 搭建神经网络模型
chanDim = -1
model = Sequential()
'''
添加一层卷积层
卷积核的数目:32
卷积核的宽度:3
卷积核的长度:3
**函数:relu(线性整流函数f(x) = max(0, x))
输入规模:之前给定的
'''
model.add(Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(150, 150, 3)))
'''
添加一层二维池化层
pool_size = (3, 3)即池化窗口的大小
'''
model.add(MaxPool2D((3, 3)))
'''
添加一个规范化层,该层处理上一层传来的数据,使其均值接近0,标准差接近1
'''
model.add(BatchNormalization(axis=chanDim))
'''
添加一层损失层,随机损失0.25的边,避免过拟合
'''
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(MaxPool2D((2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(BatchNormalization(axis=chanDim))
model.add(MaxPool2D((2, 2)))
model.add(Dropout(0.25))

'''
flatten将多维的层压平
'''
model.add(Flatten())
'''
添加一个普通的全连接层,输出的尺寸为(*, 1024),**函数为relu
'''
model.add(Dense(1024, activation='relu'))
'''
添加一个规范化层,该层处理上一层传来的数据,使其均值接近0,标准差接近1
'''
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
'''
编译模型,选择损失函数为categorical_crossentropy,优化器为adadelta(),评价标准为acc
'''
model.compile(loss=categorical_crossentropy, optimizer=Adadelta(), metrics=['acc'])

# In[7]:

'''
给定训练集、测试集路径
'''
train_dir = "train"
validation_dir = "val"

'''
生成数据集
尺度变换(scale): 对图像按照指定的尺度因子, 进行放大或缩小; 或者参照SIFT特征提取思想, 利用指定的尺度因子对图像滤波构造尺度空间. 改变图像内容的大小或模糊程度;
'''
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
    directory=train_dir,
    target_size=(150, 150),
    batch_size=30)

validation_generator = test_datagen.flow_from_directory(
    directory=validation_dir,
    target_size=(150, 150),
    batch_size=30)

# In[8]:

train_generator.class_indices, validation_generator.class_indices

# In[9]:

history = model.fit_generator(
    train_generator,
    steps_per_epoch=50,
    epochs=100,
    validation_data=validation_generator)

# In[10]:

model.save('car_cls2.h5')

# In[11]:

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.figure(figsize=(12, 8))
plt.plot(epochs, acc, label='Training acc')
plt.plot(epochs, val_acc, label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.plot(epochs, loss, label='Training loss')
plt.plot(epochs, val_loss, label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()