python2.7交通标志识别图_(四)深度学习初探:基于LeNet-5卷积神经网络的交通标志识别...

1.项目任务

在常见深度学习模型的基础上(本文为LeNet-5),调整模型结构和参数,使用Tensorflow进行部署。利用公开的德国交通标志数据集进行训练,得到模型,并利用该模型对新的图片进行预测。全文运行环境Linux, 使用语言Python3。需要说明的是,本项目来源于Udacity,在Udacity无人驾驶工程师课程作业的基础上演化而来。

无人驾驶工程师课程_无人车培训_无人驾驶_自动驾驶基础技术-优达学城(Udacity)官网​cn.udacity.compython2.7交通标志识别图_(四)深度学习初探:基于LeNet-5卷积神经网络的交通标志识别..._第1张图片

2. 整体思路整体流程

总体而言,本文注重梳理卷积神经网络实现的基本逻辑,希望通过这个小项目打开深度学习的大门,拨开深度学习这颗“大洋葱”最外面的一层表皮,至于里面的果实及本文中未理解清楚的一部分,希望能有机会在后面的学习中解决。

3. 项目实施

3.1 环境搭建

本项目在Linux环境中运行,使用python3搭建Tensorflow模型进行运算。因此第一步先在Linux下搭建Tensorflow运行环境。可以采用anaconda作为python的环境管理仓库,在anaconda中直接通过命令行安装tensorflow。

3.1.1 Anaconda 的安装

Anaconda 为我们提供了很好的python环境管理机制,使同一台电脑上能够允许多个版本的python共存,且相互不干扰;它还能创建独立的环境,相当于在一台主机中分割出一个个独立的空间,存放不同的工作环境。

# 首先到官网上下载anaconda

wget https://repo.continuum.io/archive/Anaconda3-5.0.1-Linux-x86_64.sh

# 安装anaconda

bash Anaconda3-5.0.1-Linux-x86_64.sh

# 将anaconda添加到环境变量

echo 'export PATH="~/anaconda3/bin:$PATH"' >> ~/.bashrc

# 更新环境变量

source .bashrc

# 更新anaconda

conda upgrade --al

具体可以参考:linux命令 安装anaconda​blog.csdn.net

3.1.2 Tensorflow的安装

Tensorflow目前最常使用的是版本1.1.1, 它除了正常迭代的版本号之外,还有CPU和GPU之分。本文中的项目模型复杂度不高,需要的算力CPU就能解决,因此在这里选择安装CPU版本的tensorflow。

# 创建一个单独运行的环境,取名py37,采用python3

conda create --name = py37 python=3

# 激活刚刚创建的环境

source acitvate py37

# 安装CPU版本的Tensorflow(此过程需联网)

conda install -c conda-forge tensorflow

安装完成后,我们需要检查tensorflow是否安装好,进入pyhon环境:

import tensorflow as tf

如果没有报错,证明该环境中已经有了tensorflow,安装正确。

激活环境后,进入jupyter notebook工作环境:

#直接键入 jupyter notebook

PS-1:在调试过程中需要掌握一些anaconda常用命令,列举如下,已经会的可直接跳过:

1)查看版本:conda --version

2)创建环境:conda create -n py37 python=3

3)删除环境:conda remove -n py37 --all

4)查看本机所有环境:conda env list

5)激活环境:activate py37

6)安装第三方软件包:conda install 包名 或 pip install 包名

7)卸载第三方软件包: conda remove 包名 或 pip uninstall 包名

8)切换到base环境:activate

PS-2: GPU版本Tensorflow的安装

1)pip install tensorflow-gpu

需要注意的是,不要在同一个环境中共存CPU和GPU版本的Tensorflow, 容易出现环境混乱。

3.2 数据集处理

3.2.1 数据集导入

本文使用的数据集是公开的德国交通标志数据集, 下载地址:German Traffic Sign Benchmarks​benchmark.ini.rub.depython2.7交通标志识别图_(四)深度学习初探:基于LeNet-5卷积神经网络的交通标志识别..._第2张图片

已经被分为了训练集,验证集和测试集三个部分,分别是三个.p 文件,将三个载入。

# 导入pickle模块,用于加载数据,如果该句报错,需要使用anaconda 安装pickle包。在导入其他包

# 的过程中,如果出现没有该模块的情况,也需要使用anaconda安装。

# pickle 用来将python的对象序列化和解序列化

import pickle

# 找到三个数据文件的路径

training_file = '/home/project_our/project2/traffic-signs-data/train.p'

validation_file='/home/project_our/project2/traffic-signs-data/valid.p'

testing_file = '/home/project_our/project2/traffic-signs-data/test.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)

with open(testing_file, mode='rb') as f:

test = pickle.load(f)

# 将数据分为图片和标签两个部分,X部分为图片,y为对应图片的标签值

X_train, y_train = train['features'], train['labels']

X_valid, y_valid = valid['features'], valid['labels']

X_test, y_test = test['features'], test['labels']

3.2.2 数据集情况总结

### 查看图片的尺寸,图片的数量验证数据集中图片数量和标签数量是否匹配

import numpy as np

# 训练数据数量

n_train = len(X_train)

# 验证数据数量

n_validation = len(X_valid)

# 测试数据数量

n_test = len(X_test)

# 图片尺寸

image_shape = X_train[1].shape

# 图片种类数

n_classes = len(np.unique(y_train))

print("Number of training examples =", n_train)

print("Number of testing examples =", n_test)

print("Image data shape =", image_shape)

print("Number of classes =", n_classes)

print("Number of classes =", n_validation)

输出数据集中共有训练数据34799张,验证数据4410张,测试数据12630张。训练数据标签数34799, 验证数据标签数4410。标签数量与图片数量一致。共有43种不同类型的交通标志牌。每张图片的大小为32x32x3。数据集样本量统计

### 数据可视化

# 导入随机函数

import random

# 导入python 画图功能包

import matplotlib.pyplot as plt

# 该语句称为”魔术公式” 能够让jupyter notebook 显示图片

%matplotlib inline

# 随机选取一个索引

index = random.randint(0, len(X_train))

# 从训练集中选取对应索引的图片

image = X_train[index]#不懂;

# 打印显示图片

plt.figure(figsize = (1,1))

plt.imshow(image, cmap="gray")

print(y_train[index])数据可视化

随机抽取的图片为直行的标志,对应的标签为35。

# 导入matplotlib下的cm模块

import matplotlib.cm as cm

# 以图表方式显示样本中各个标签样本的数量

bin = [x for x in range(0,43)]

plt.ylabel('Count of Examples')

plt.xlabel('Traffic Sign Number')

plt.hist(y_train, bin, color = 'r', rwidth = 0.8, label = 'train')

plt.hist(y_test, bin, color = 'c', rwidth = 0.8, label = 'test')

plt.hist(y_valid, bin, color = 'b', rwidth = 0.8, label = 'valid')

plt.legend()

plt.show()数据集样本分布

3.2.3 数据前处理

有很多方法对数据集中的图片进行处理,比如正则化,灰度化, 锐度处理,旋转等。对数据的前处理能够增强网络的鲁棒性,避免因为数据同质化或局部同质化造成模型局部收敛。

本文这里只进行正则化处理,对于图片,采用128进行正则化,性能较优。

# 定义正则化函数

def normalized(image):

return (image-128.)/128.

# 处理训练数据

X_train_normalized = []

for i in range(0,n_train):

X_train_normalized.append(normalized(X_train[i]))

# 处理验证数据

X_valid_normalized = []

for j in range(0, n_validation):

X_valid_normalized.append(normalized(X_valid[j]))

# 处理测试数据

X_test_normalized = []

for s in range(0, n_test):

X_test_normalized.append(normalized(X_test[s]))原始图像与正则化后的图像

3.3 模型搭建

3.3.1 卷积神经网络的基础知识

1) 一个卷积神经网络的基本组成输入层

卷积层

非线性变化(Relu)

池化(子采样)

分类(完全链接层)

2)各层的作用

(1)卷积层的作用:相当于滤波器,可以用来提取图片中的特征(点,线,图形等),不同的卷积核就相当于不同的滤波器,能够提取到不同的特征。

# 常用卷积函数,以Tensorflow 为例

tf.nn.convolution()

tf.nn.conv2d()

tf.nn.conv3d_transpose

# 以上只列举了3种卷积函数,没有列全,具体三个函数之间有什么区别还没有仔细研究

(2) 非线性变化:在卷积神经网络中引入非线性因数,因为实际生活中我们用神经网络解决的问题都是非线性的。目前最常用的非线性变化方法为Relu(修正线性单元),

output = Max(zeros, Input)ReLu 修正线性单元

(3)池化:也称为子采样或下采样,可降低每个特征映射的维度,并保留最重要的信息。翻译成人话:池化就是提取特征映射中最重要的信息,降低特征的维度,减少计算量(在保留数据特征的前提下,最大程度的降低运算量)。选取特征映射中最重要信息的方法有很多种,例如最大池化,提取每个每个特征向量中的最大值,其他删掉。一般跟在卷积层的后面。

# 常用池化函数

tf.nn.avg_pool() 平均池化

tf.nn.max() 最大池化

(4)全链接层:利用卷积层和池化层在数据集的基础上得到的特征,将输入图像分为不同的类。也就是利用上述的特征判断输入图像属于哪个标签的概率。

# 常用的分类函数

sigmoid_cross_entropy_with_logits

softmax

log_softmax

softmax_cross_entropy_with_logits

总结以上:卷积层+池化层======= 从输入图像提取特征;

完全链接层========== 用来做分类器

3)图片在卷积神经网络中的“生命”流程

a)输入一张图像:猪;

b)输入标签:比如识别猫,狗,鸟,猪 四种动物,那我们输入的标签应该是[0,0,0,1]

c) 迭代:

(1)卷积核(滤波器)参数和权重初始化:一般采用符合高斯分布的随机值;

(2)对输入图像使用“前向传播”:包括一系列卷积,Relu,池化和全链接,得到每个类的输出概率

比如说是[0.2, 0.4, 0.1, 0.3](这是个随机概率,因为一次的权重是随机的)

(3)计算输出层的总误差(4个类总误差之和)

(4)使用反向传播,计算网络中所有权重的误差梯度,并使用梯度下降更新所有 滤波器的权重

和误差,更新的目标是最小化输出误差。

(5)对训练集中的所有图像重复(2)-(4)

d)利用模型进行预测

(1)对于一个新图像,模型利用之前训练的参数进行前向传播得到分类概率。

3.3.2 LeNet-5 框架

LeNet-5框架

LeNet-5原本用于手写英文字母的识别,如上图所示,主要又5层组成,分别是:输入层-卷积层1-池化层1-卷积层2-池化层2-卷积层3-全链接层1-全链接层2(输出层)。

对于原始LeNet-5的详细解析可以参考一下文章:网络解析(一):LeNet-5详解​cuijiahua.compython2.7交通标志识别图_(四)深度学习初探:基于LeNet-5卷积神经网络的交通标志识别..._第3张图片

3.3.3 搭建模型

# 导入tensorflow

import tensorflow as tf

# 设置迭代代数

EPOCHS = 20

# 设置没批处理图片的张数:数据集图片太多,受内存大小的限制,需要规定每次循环迭代处理的图片数量,

# 理论上批量越大,模型运转越快,但批量大小受内存大小的限制;

BATCH_SIZE = 128

定义模型

from tensorflow.contrib.layers import flatten

def LeNet(x, keep_prob):

# 每一层初始化weight和bias时的均值和方差

mu = 0

sigma = 0.1

# 第一层卷积,输入32x32x1, 输出 28x28x6。卷积滤波器大小5x5,输入深度为3,输出深度为6

conv1_w = tf.Variable(tf.truncated_normal(shape=(5, 5, 3, 6),mean = mu, stddev=sigma))

# 参数的维度,均值,方差

conv1_b = tf.Variable(tf.zeros(6))

# 使用conv2d函数对滤波器和图像进行卷积运算,然后再加上偏差

conv1 = tf.nn.conv2d(x,conv1_w, strides=[1,1,1,1],padding='VALID') + conv1_b

# 非线性变换(激活函数)

conv1 = tf.nn.relu(conv1)

# 池化或下采样 输入28x28x6,输出14x14x6: 使用2x2的卷积核,并以2x2的步长对输出进行池化

conv1 = tf.nn.max_pool(conv1,ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1], padding='VALID')

# 第二层卷积,输入14x14x6, 输出10x10x16

conv2_w = tf.Variable(tf.truncated_normal(shape=(5, 5, 6, 16), mean = mu, stddev=sigma))

conv2_b = tf.Variable(tf.zeros(16))

conv2 = tf.nn.conv2d(conv1, conv2_w, strides = [1,1,1,1], padding='VALID') + conv2_b

# 非线性变换(激活函数)

conv2 = tf.nn.relu(conv2)

# 池化(下采样),输入10x10x16, 输出是5x5x16

conv2 = tf.nn.max_pool(conv2,ksize=[1, 2, 2, 1],strides=[1, 2, 2,1], padding='VALID')

# 压平,把输出排成一列,得到一个向量,输入5x5x16,输出 400

fc0 = flatten(conv2)

# 第三层:全链接层,输入400,输出120

fc1_w = tf.Variable(tf.truncated_normal(shape=(400, 120),mean = mu, stddev = sigma))

fc1_b = tf.Variable(tf.zeros(120))

fc1 = tf.matmul(fc0, fc1_w)+ fc1_b

# 非线性变换(激活函数)

fc1 = tf.nn.relu(fc1)

# dropout

fc1 = tf.nn.dropout(fc1, keep_prob)

# 第四层:全链接层,输入120, 输出84

fc2_w = tf.Variable(tf.truncated_normal(shape=(120, 84),mean = mu, stddev = sigma))

fc2_b = tf.Variable(tf.zeros(84))

fc2 = tf.matmul(fc1, fc2_w)+ fc2_b

# 非线性变换(激活函数)

fc2 = tf.nn.relu(fc2)

# dropout2

fc2 = tf.nn.dropout(fc2, keep_prob)

# 第五层:全链接层,输入84, 输出10。层的宽度等于标签集中所包含的类别数

fc3_w = tf.Variable(tf.truncated_normal(shape=(84,43), mean = mu, stddev = sigma))

fc3_b = tf.Variable(tf.zeros(43))

#这些输出被称为对数几率(logits)

logits = tf.matmul(fc2, fc3_w)+ fc3_b

return logits

以上使用Tensorflow重新部署了LeNet模型,但原本的LeNet模型用于手势识别,需要针对我们当前的任务进行调整。具体的调整体现在:

(1)输入数据维度,原来的LeNet-5输入为32x32x1,但我们数据集中的尺寸为32x32x3

(2)各个卷积层对应的尺寸都进行了调整

(3)增加了两个池化层,防止过拟合

3.3.4 训练-验证-测试模型

Tensorflow 使用图计算,在开始训练模型之前,需要搭建图的结构。等把整个运算流程图搭建好之后,在运算的过程中通过feed_dict填充具体的数据。

参数占位:

# 占位符,为输入数据占位子,None 表示batch_size 批量的大小,可以接收任意批量大小的输入数据

x = tf.placeholder(tf.float32,(None, 32, 32, 3))

# 为标签占位子,标签维度为1,批量为任意值

y = tf.placeholder(tf.int32,(None))

# 为下采样概率占位子,以keep_prob的概率丢弃一部分的特征

keep_prob = tf.placeholder(tf.float32)

# 独热编码

one_hot_y = tf.one_hot(y, 43)

搭建训练框图,设定参数:优化器类型,优化目标

## 搭建训练框图(只是一个流程,还没有输入数据)

# 学习速率,决定模型中更新权重的速率,默认0.001是一个比较好的初始速度;

rate = 0.001

# 将图片输入的LeNet模型中计算对数几率

logits = LeNet(x,keep_prob)

# 分类函数,将对数几率结果和真实标签进行比较,并计算交叉熵

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels = one_hot_y,logits = logits)

# 求出所有训练图像的交叉熵的平均值,交叉熵越小,模型值越好

loss_operation = tf.reduce_mean(cross_entropy)

# 采用Adam优化方法优化参数(可以动态调整每个参数的学习速率)

optimizer = tf.train.AdamOptimizer(learning_rate = rate)

# 设定优化目标,在优化器上运行最小化函数

training_operation = optimizer.minimize(loss_operation)

搭建评估模型性能的框图:

# tf.argmax返回最大值的索引

# tf.equal返回True or false

correct_prediction = tf.equal(tf.argmax(logits,1), tf.argmax(one_hot_y,1))

# 计算模型的整体准确度:所有预测的准确度的平均值

accuracy_operation = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 建立保存模型的对象

saver = tf.train.Saver()

# 构建一个评估函数,输入是一整个数据集

def evaluate(x_data, y_data):

num_examples = len(x_data)

total_accuracy = 0

#获取当前的默认会话

sess = tf.get_default_session()

# 对数据进行分批评估

for offset in range(0, num_examples, BATCH_SIZE):

batch_x, batch_y = x_data[offset:offset+BATCH_SIZE], y_data[offset:offset+BATCH_SIZE]

accuracy = sess.run(accuracy_operation, feed_dict={x:batch_x, y:batch_y, keep_prob:1.0})

# 计算总精度

total_accuracy+=(accuracy* len(batch_x))

return total_accuracy / num_examples

开始训练,并保存模型:

## 创建一个函数,训练并验证模型

# 创建一个TensorFlow会话,并初始化变量

with tf.Session() as sess:

sess.run(tf.global_variables_initializer())

print("Training...")

print()

for i in range(EPOCHS):

# 在没轮训练开始前打乱数据的顺序,确保数据顺序不会影响模型

X_normal_image, y_train = shuffle(X_normal_image, y_train)

# 将数据集分成几批,分批训练模型

for offset in range(0, n_train, BATCH_SIZE):

end = offset + BATCH_SIZE

batch_x, batch_y = X_normal_image[offset:end], y_train[offset:end]

sess.run(training_operation, feed_dict={x:batch_x, y:batch_y, keep_prob:0.8})

# 每轮结束的时候评估模型的精度

train_accuracy = evaluate(X_normal_image, y_train)

# 每轮结束的时候利用验证数据集评估模型精度

validation_accuracy = evaluate(X_valid_normalized, y_valid)

print("EPOCH {}...".format(i+1))

print("Train accuracy = {:.3f}" .format(train_accuracy))

print("Validation accuracy = {: .3f}" .format(validation_accuracy))

print()

# 一但模型训练结束,保存模型

save_file = './model2'

saver = tf.train.Saver()

saver.save(sess, save_file)

print("Model saved")训练过程

模型训练完成后会输出4个文件:其中:checkpoint为文本文件,里面记录了保存的最新的checkpoint文件以及其它checkpoint文件列表; train_model.ckpt.meat 是图结构,meta文件是pb(protocol buffer)格式文件,包含变量、op、集合等; ckpt保存每个变量的取值,此处文件名的写入方式会因不同参数的设置而不同。是二进制文件,保存了所有的weights、biases、gradients等变量。在tensorflow 0.11之 前,保存在.ckpt文件中。0.11后,通过两个文件保存,如:.data-00000-of-00001和.index文件。

参考:Tensorflow模型保存与加载 - 追求沉默者 - 博客园​www.cnblogs.com

测试模型:

# 使用测试数据,评价模型

# 只有彻底完成训练,并且选定模型之后我们才可以用测试数据集评估模型

with tf.Session() as sess:

# 导入刚刚运行的模型,输入test数据和对应的标签

new_saver = tf.train.import_meta_graph('model2.meta')

new_saver.restore(sess, tf.train.latest_checkpoint('.'))

test_accuracy = evaluate(X_test_normalized, y_test)

print("Test Accuracy = {:.3f}".format(test_accuracy))测试数据精度

测试集的输出精度为0.939

3.3.5 利用模型对新图片进行预测

## 导入新图片,并进行处理

import numpy as np

import os

from scipy import ndimage, misc

import matplotlib.image as mpimg

# 解决ros的opencv和anaconda 中opencv的冲突问题

import sys

sys.path.remove('/opt/ros/kinetic/lib/python2.7/dist-packages')

# 导入opencv

import cv2

import matplotlib.pyplot as plt

# 导入图片

test_images = os.listdir("web_images/")

print("test_images: ok")

image1=[]

image2=[]

for images in test_images:

image1 = cv2.imread('web_images/' + images)

print(image1.shape)

image1 = cv2.resize(image1,(32,32))

plt.imshow(image1)

#确保每张图片都保存起来

image2.append(image1)

# 同时打印5张图片

for sk in range(0,5):

plt.imshow(image2[sk])

plt.show()待预测图片

# 正则化

import numpy as np

image3=[]

for i in range(5):

image3.append((image2[i] - 128.)/128.)

# 真实标签

image_labels = [4, 23, 25, 28, 31]

predicted_labels = tf.argmax(logits, axis=1)

with tf.Session() as sess:

# 初始化参数

init=tf.initialize_all_variables()

sess.run(init)

# 导入模型

new_saver2 = tf.train.import_meta_graph('model2.meta')

new_saver2.restore(sess, tf.train.latest_checkpoint('.'))

# 开始预测

predicted_logits = sess.run(predicted_labels,feed_dict={x:image3, keep_prob:1.0})

print("predicted labels: ", predicted_logits)

print("real labels:", image_labels)

输出结果 [4, 23, 28, 30, 31],准确率80%

4. 后续还可以调整参数提高预测准确率:步长, batch_size, 图像预处理等。由于篇幅限制,本文没有进行尝试。行文仓促,如有不正确的地方还望见谅,可以指出,一起讨论。

其他参考文献:

2) TensorFlow 技术解析与实战

你可能感兴趣的:(python2.7交通标志识别图_(四)深度学习初探:基于LeNet-5卷积神经网络的交通标志识别...)