11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

基于 MXNet 的交通标志识别深度神经网络构建

  • 2017-09-12
  • 本文字数:6188 字

    阅读完需:约 20 分钟

MXNet 是一个轻量级、可移植、灵活的分布式深度学习框架,2017 年 1 月 23 日,该项目进入 Apache 基金会,成为 Apache 的孵化器项目。

尽管现在已经有很多深度学习框架,包括 TensorFlow、Keras、Torch、以及 Caffe,但 Apache MXNet 因其对多 GPU 的分布式支持而越来越受欢迎。

在这篇文章中,我们将介绍如何使用 MXNet 来解决一个经典计算机视觉问题:使用卷积神经网络对交通标志进行分类。网络输入为一张包含交通标志的彩色照片,输出为该标志的类别。

环境准备

  1. 安装 Anaconda。Anaconda 是一个用于科学计算的 Python 发行版,提供了包管理与环境管理的功能。Anaconda 利用 conda 来进行 package 和 environment 的管理,并且已经包含了 Python 和相关的配套工具。
  2. 在 conda 下安装 pip,安装命令为“conda install pip”。
  3. 安装 OpenCV-python 库。OpenCV-python 是一个很强大的计算机视觉库,在这个项目中可以用于处理图像。使用“pip install openvc-python”在 Anaconda 环境下安装 OpenCV。也可以从源文件进行编译(注意:conda 安装 opencv3.0 不能运行)。
  4. 安装 scikit learn,一个开源的 python 机器学习科学计算库,它将用于对数据进行预处理。安装命令为“conda install scikit-learn”。
  5. 安装 Jupyter Notebook,安装命令为“conda install jupyter notebook”。
  6. 安装 MXNet,安装命令为“pip install mxnet”。

数据库

作者使用的数据库是德国交通标志识别基准,来自论文《德国交通标志识别基准:多类别分类竞赛》(J. Stallkamp, M. Schlipsing, J. Salmen, and C. Igel. “The German Traffic Sign Recognition Benchmark: A multi-class classification competition.”),发表在 IEEE International Joint Conference on Neural Networks,2011。该数据集包含 39209 张训练样例和 12630 张测试样例,有 43 种不同的交通标志——停车标志、限速标志、各种警示标志以及其他标志。

数据库中的每张图像大小为 32*32 像素,均为三通道彩色图。每幅图属于一种交通标志。图像种类标签使用 0 到 42 的整数表示。

下载数据集的代码如下:

复制代码
import pickle
# 存储训练数据和测试数据的位置
training_file = "traffic-data/train.p"
validation_file = "traffic-data/valid.p"
with open(training_file, mode='rb') as f:
train = pickle.load(f)
with open(validation_file, mode='rb') as f:
valid = pickle.load(f)
X_train, y_train = train['features'], train['labels']
X_valid, y_valid = valid['features'], valid['labels']

作者从一个 NumPy 阵列中下载数据,数据分为训练、验证和测试集。训练集包含 39209 张大小为 32*32 像素,通道数为 3 的图像,所以 NumPy 阵列的维度为 39209*32*32*3。该项目中作者仅使用了训练集和验证集。作者将使用网上的真实图像来测试所构建的模型。X_train 存储图像,维度为 39209*32*32*3。Y_train 存储图像对应的类标,维度为 39209,包含 0-42 的整数,对应每张图的类标。

下一步,导入包含图片类标到自然语言名称的映射关系的文件“signnames.csv”:

复制代码
# 类别的实际名称在另一个文件中给出。这里我们载入 csv 文件,包含从类别到标签的映射。
import csv
def read_csv_and_parse():
traffic_labels_dict ={}
with open('signnames.csv') as f:
reader = csv.reader(f)
count = -1;
for row in reader:
count = count + 1
if(count == 0):
continue
label_index = int(row[0])
traffic_labels_dict[label_index] = row[1]
return traffic_labels_dict
traffic_labels_dict = read_csv_and_parse()
print(traffic_labels_dict)

运行结果如下:

复制代码
{0: 'Speed limit (20km/h)', 1: 'Speed limit (30km/h)',
2: 'Speed limit (50km/h)', 3: 'Speed limit (60km/h)', 4: 'Speed limit (70km/h)',
5: 'Speed limit (80km/h)', 6: 'End of speed limit (80km/h)', 7: 'Speed limit (100km/h)',
8: 'Speed limit (120km/h)', 9: 'No passing', 10: 'No passing for vehicles over 3.5 metric tons',
11: 'Right-of-way at the next intersection', 12: 'Priority road', 13: 'Yield',
14: 'Stop', 15: 'No vehicles', 16: 'Vehicles over 3.5 metric tons prohibited',
17: 'No entry', 18: 'General caution', 19: 'Dangerous curve to the left',
20: 'Dangerous curve to the right', 21: 'Double curve', 22: 'Bumpy road',
23: 'Slippery road', 24: 'Road narrows on the right', 25: 'Road work',
26: 'Traffic signals', 27: 'Pedestrians', 28: 'Children crossing',
29: 'Bicycles crossing', 30: 'Beware of ice/snow', 31: 'Wild animals crossing',
32: 'End of all speed and passing limits', 33: 'Turn right ahead', 34: 'Turn left ahead',
35: 'Ahead only', 36: 'Go straight or right', 37: 'Go straight or left', 38: 'Keep right',
39: 'Keep left', 40: 'Roundabout mandatory', 41: 'End of no passing',
42: 'End of no passing by vehicles over 3.5 metric tons'}

可以看出 43 个数字对应 43 个图片种类。例如,0 号图片代表 20km/h 的限速标志。

可视化

下面的代码可以帮助我们可视化图像以及类标(标志种类):

复制代码
# 数据可视化可以让我们对数据有一个更好更直观的理解
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
%matplotlib inline
#该函数为每种交通标志选择一张图并画出该图
def get_images_to_plot(images, labels):
selected_image = []
idx = []
for i in range(n_classes):
selected = np.where(labels == i)[0][0]
selected_image.append(images[selected])
idx.append(selected)
return selected_image,idx
# 在栅格中画图
def plot_images(selected_image,y_val,row=5,col=10,idx = None):
count =0;
f, axarr = plt.subplots(row, col,figsize=(50, 50))
for i in range(row):
for j in range(col):
if(count < len(selected_image)):
axarr[i,j].imshow(selected_image[count])
if(idx != None):
axarr[i,j].set_title(traffic_labels_dict[y_val[idx[count]]], fontsize=20)
axarr[i,j].axis('off')
count = count + 1
selected_image,idx = get_images_to_plot(X_train,y_train)
plot_images(selected_image,row=10,col=4,idx=idx,y_val=y_train)

下图是可视化的交通图像以及标志:

(点击放大图像)

训练过程

1. 准备数据集

X_train 和 Y_train 组成了训练数据集。可以使用 scikit-learn 对训练数据集进行分割得到验证集,这样可以避免使用出现过的图片测试模型。代码如下:

复制代码
from sklearn.model_selection import train_test_split
X_train_set,X_validation_set,Y_train_set,Y_validation_set = train_test_split
( X_train, Y_train, test_size=0.02, random_state=42)

2. 训练数据预处理

批训练

神经网络训练需要花费大量时间和内存。所以作者将数据分批训练,一批大小为 64。不仅是为了让数据适应内存,而且它可以让 MXNet 尽量利用 GPU 的计算效率。

归一化

除此之外,图像的像素值也进行了归一化,可以使学习算法更快收敛。

下面是对训练数据进行预处理的代码:

复制代码
batch_size = 64
X_train_set_as_float = X_train_reshape.astype('float32')
X_train_set_norm = X_train_set_as_float[:] / 255.0;
X_validation_set_as_float = X_valid_reshape.astype('float32')
X_validation_set_norm = X_validation_set_as_float[:] / 255.0 ;
train_iter =mx.io.NDArrayIter(X_train_set_as_float, y_train_extra, batch_size, shuffle=True)
val_iter = mx.io.NDArrayIter(X_validation_set_as_float, y_valid, batch_size,shuffle=True)
print("train set : ", X_train_set_norm.shape)
print("validation set : ", X_validation_set_norm.shape)
print("y train set : ", y_train.shape)
print("y validation set :", y_valid.shape)

3. 构建深度网络

目前,对于图像识别这类处在探索研究热点的问题,学界已经设计了很多效果良好的网络结构。所以最好的方法是实现一个已经发表出来的网络结构,然后对其进行改进。作者基于 AlexNet 的结构,构建了一个简化版的卷积神经网络。

AlexNet 是 2012 年发表的一个经典网络,在当年取得了 ImageNet 的最好成绩。

这是 AlexNet 的网络结构图:

(点击放大图像)

网络共有 8 层,其中前 5 层是卷积层,后边 3 层是全连接层,在每一个卷积层中包含了激励函数 RELU 以及局部响应归一化(LRN)处理,然后再经过池化(max pooling),最后的一个全连接层的输出是具有 1000 个输出的 softmax 层,最后的优化目标是最大化平均的多元逻辑回归。

在此之后也有很多更优秀的网络结构被提出,例如 VGGNet 和 ResNet,大家可以选择更好的网络结构去实现。

由于 MXNet 的符号计算构架,该神经网络的代码十分简洁明了:

首先导入数据

复制代码
data = mx.symbol.Variable('data')

conv 卷积层对图像进行卷积操作,relu 层与 conv 层相连,对输入进行非线性激活,最大池化层(pool)对前一层的输出进行池化操作,也就是随机 drop 掉一些像素,然后减少图像尺寸。这里作者一共设计了三个卷积层。

复制代码
conv1 = mx.sym.Convolution(data=data, pad=(1,1), kernel=(3,3), num_filter=24, name="conv1")
relu1 = mx.sym.Activation(data=conv1, act_type="relu", name= "relu1")
pool1 = mx.sym.Pooling(data=relu1, pool_type="max", kernel=(2,2), stride=(2,2),name="max_pool1")
# 第二卷积层
conv2 = mx.sym.Convolution(data=pool1, kernel=(3,3), num_filter=48, name="conv2", pad=(1,1))
relu2 = mx.sym.Activation(data=conv2, act_type="relu", name="relu2")
pool2 = mx.sym.Pooling(data=relu2, pool_type="max", kernel=(2,2), stride=(2,2),name="max_pool2")
#第三卷积层
conv3 = mx.sym.Convolution(data=pool2, kernel=(5,5), num_filter=64, name="conv3")
relu3 = mx.sym.Activation(data=conv3, act_type="relu", name="relu3")
pool3 = mx.sym.Pooling(data=relu3, pool_type="max", kernel=(2,2), stride=(2,2),name="max_pool3")

在网络的最后是全连接层,全连接层的每一个神经元都和之前的神经元连接在一起。该层后面是有 43 个神经元的全连接层。每一个神经元代表一个图片种类。但是由于神经元的输出是实值,但是分类要求输出为类标,所以作者使用另一个激活函数,使 43 个神经元中的某个值为 1,其他的为 0。最后的一个全连接层的输出是具有 43 个输出的 softmax 层,最后的优化目标是最大化平均的多元逻辑回归。

复制代码
# 第一层全连接层
flatten = mx.sym.Flatten(data=pool3)
fc1 = mx.symbol.FullyConnected(data=flatten, num_hidden=500, name="fc1")
relu3 = mx.sym.Activation(data=fc1, act_type="relu" , name="relu3")
# 第二层全连接层
fc2 = mx.sym.FullyConnected(data=relu3, num_hidden=43,name="final_fc")
# softmax 层
mynet = mx.sym.SoftmaxOutput(data=fc2, name='softmax')

4. 训练网络

训练 epoch 为 10,训练好的模型存在 JSON 文件中,并且可以通过测量训练和验证准确率来观测网络“学习”的情况,代码如下:

复制代码
#创建 adam optimiser 优化器
adam = mx.optimizer.create('adam')
#在 check point 存储模型。
model_prefix = 'models/chkpt'
checkpoint = mx.callback.do_checkpoint(model_prefix)
#加载模型的 API。
model = mx.mod.Module(
context = mx.gpu(0), # 如果没有 GPU,改为 mx.gpu()。
symbol = mynet,
data_names=['data']
)
#训练模型 10 个 epoch,大约花费 5 分钟。
model.fit(
train_iter,
eval_data=val_iter,
batch_end_callback = mx.callback.Speedometer(batch_size, 64),
num_epoch = 10,
eval_metric='acc', # evaluation metric is accuracy.
optimizer = adam,
epoch_end_callback=checkpoint
)

5. 载入预训练模型

下面给出了加载第 10 个 epoch 模型(最终模型)的代码。由于将在单张图片上进行测试,所以批尺寸由 64 减到 1,数据维度也变成了 1*3*32*32。

复制代码
#加载 checkpoint 的模型,这里我们加载的是第 10 个 epoch。
sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, 10)
# 将加载的参数分配给模型
mod = mx.mod.Module(symbol=sym, context=mx.cpu())
mod.bind(for_training=False, data_shapes=[('data', (1,3,32,32))])
mod.set_params(arg_params, aux_params)

测试过程

测试图像(32*32*3)样例:

(点击放大图像)

测试部分代码:

复制代码
#预测任意交通图标
from collections import namedtuple
Batch = namedtuple('Batch', ['data'])
#载入图像,将尺寸压缩到 32*32,然后将图像维度转换成 1*3*32*32
def get_image(url, show=False):
# 加载并显示图像
img =cv2.imread(url)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
if img is None:
return None
if show:
plt.imshow(img)
plt.axis('off')
# 转换维度 (批尺寸, RGB 通道数, 宽度, 高度)
img = cv2.resize(img, (32, 32))
img = np.swapaxes(img, 0, 2)
img = np.swapaxes(img, 1, 2) #交换图像维度,使其成为 3*32*32 大小
img = img[np.newaxis, :] # 添加新的图像维度,使其成为 1*3*32*32
return img
def predict(url):
img = get_image(url, show=True)
# compute the predict probabilities
mod.forward(Batch([mx.nd.array(img)]))
prob = mod.get_outputs()[0].asnumpy()
# print the top-5
prob = np.squeeze(prob)
prob = np.argsort(prob)[::-1]
for i in prob[0:5]:
print('class=%s' %(traffic_labels_dict[i]))
predict('traffic-data/Stop.jpg',)

测试结果(前五个预测结果):

复制代码
class=Stop
class=Speed limit (30km/h)
class=Speed limit (20km/h)
class=Speed limit (70km/h)
class=Bicycles crossing

从结果可以看出可能性最高的种类为停车标志,说明预测准确。如果需要对模型有一个更完整的衡量,还需要用测试数据库进行测试,得到最终的分类准确率。

总结

本文我们介绍了使用 MXNet 进行多目标分类任务的方法。作者使用 MXNet,在 AlexNet 的结构基础上构建了一个更为简单的卷积神经网络结构。网络由卷积层,激活函数层,池化层和全连接层组成,采用德国交通标志图像训练数据库对该网络进行训练,实验结果证明网络可以将交通标志进行正确的分类。作者介绍了如何使用 MXNet 对数据进行预处理,构建网络,以及如何加载预训练好的网络模型。可以看出,MXNet 因其在多 GPU 上进行并行训练的能力,以及网络模型构建简单灵活的特性,是一个十分优秀的深度学习框架。

查看英文原文: Classifying traffic signs with MXNet: An introduction to customizing a neural network


感谢薛命灯对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-09-12 17:302007
用户头像

发布了 52 篇内容, 共 25.8 次阅读, 收获喜欢 70 次。

关注

评论

发布
暂无评论
发现更多内容
基于MXNet的交通标志识别深度神经网络构建_语言 & 开发_马卓奇_InfoQ精选文章