皇上,还记得我吗?我就是1999年那个Linux伊甸园啊-----24小时滚动更新开源资讯,全年无休!

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

作者 马卓奇

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)

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

(点击放大图像)

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

训练过程

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的网络结构图:

(点击放大图像)

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

网络共有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)样例:

(点击放大图像)

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

测试部分代码:

#预测任意交通图标
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

转自 http://www.infoq.com/cn/articles/an-introduction-to-customizing-a-neural-network