在第九篇博客中咱们介绍了神经网络,它是一种机器学习方法,基于经验风险最小化策略,凭借这神经网络的拟合任意函数的能力,咱们可使用神经网络来表示任意的模式。然而,在前几十年,神经网络并无受到人们的重视,直到深度学习的出现,人们利用深度学习解决了很多实际问题(即一些落地性质的商业应用),神经网络才成为学界和工业界关注的一个焦点。本文以尽量直白,简单的方式介绍深度学习中三种典型的神经网络以及深度学习中的正则化方法。为后面在无人驾驶中的应用作铺垫。python
创做不易,转载请注明出处:http://blog.csdn.net/adamshan/article/details/79127573web
近期在学术领域存在这许多批判深度学习的言论(参考Gary Marcus的文章:https://arxiv.org/ftp/arxiv/papers/1801/1801.00631.pdf ),深度学习在一些学者看来并非通往通用人工智能的道路。可是,做为关注行业应用的研究者和工程师,咱们并不须要关注深度学习究竟是不是通往最终的通用人工智能的道路,咱们只须要知道,深度学习到底能不可以解决咱们行业的一些问题(经过传统的软件工程很难解决的问题)?答案是能,正是由于深度学习有着这样的能力,它才成为人工智能研究领域历史上第一次为各大商业公司追逐的技术(以往的人工智能大可能是实验室产品,从未吸引巨头的大量资本投入)。商业公司追逐利益,追逐商业化,一项技术可以为业内大量公司热捧,说明其已经具有在某些应用行业商业化,产品化的能力,那么咱们首先来了解一下深度学习如今已经具有的“产品力”:算法
传统的计算机视觉技术在处理图像识别问题时每每须要人为设计特征,要识别不一样的类别,就要设计不一样的特征,要识别猫和狗,就要分别为猫和狗设计特征,而这个过程是很是麻烦的,咱们以猫和狗为例:json
上图是猫和狗的照片,能够说,光是为猫和狗两个类别的识别设计特征就须要耗费大量的精力,而且还得是“猫狗专家”才能作这件事情。那么,当要识别的类别上升到1000类的时候呢?传统的视觉算法的识别精度将会更低。网络
深度学习取得的第一个重大的突破就在ImageNet的识别挑战上。app
ImageNet是一个拥有1400万张图片的巨大数据集,基于ImageNet数据集,ILSVRC(ImageNet Large Scale Visual Recognition Challenge)挑战赛每一年举办一次。dom
自2012年AlexNet在ILSVRC上以远超第二名的识别率打破当年的记录之后,深度学习在ImageNet数据集上的识别率在近两年取得了一个又一个的突破。到2015年,ResNet的top-5识别率正式超过人类:机器学习
深度学习有着极强的表示能力,可以处理复杂的任务,天然,咱们须要使用深度学习来解决无人驾驶中一直以来的问题(基于深度学习的计算机视觉,基于深度学习的决策,基于强化学习的决策等等)。下面咱们就开始深度学习的相关基础。svg
在第九篇博客的末尾其实咱们已经接触了深度前馈神经网络,咱们使用一个规模很大的深度前馈网络来解决MNIST手写字识别问题,咱们的这个网络取得了
其中的层被咱们称为全链接层。那么根据前面的神经网络的介绍咱们知道,即便仅仅使用3层的神经网络,咱们就可以拟合任意函数了,神经网络在设计的时候每每遵循奥卡姆剃刀原则,即咱们每每使用最简单的结构来建模,那么为何要加深网络的层数呢?
这个问题要从两个方面来看:一是大数据下的模型训练效率,一是表示学习。
有人把深度学习突破的原由归结于三个要素:
其中的大量数据是深度神经网络可以在性能上取得成功的一个重要因素,传统机器学习算法在数据量增大到必定的数量级之后彷佛会陷入一个性能的瓶颈(即便是基于结构风险最小化的支持向量机,其性能也会在数据量到达必定程度之后饱和),可是神经网路彷佛是能够不断扩容的机器学习算法,数据量越大,能够经过增长神经元的个数以及隐含层的层数,训练更增强大的神经网络,其变化趋势大体以下:
可是咱们以前的文章也提到,当前已经能够证实,仅仅是简单的三层神经网络,经过增长隐含层神经元数量,理论上也能够拟合任意函数。那么咱们为何不直接使用单纯的三层网络结构,只增长隐含层神经元数量来提升模型容量,从而拟合复杂问题呢?
单纯的增长单层神经元数量可以是的模型具备更增强的表示能力,可是,相比于增长层数,每层使用相对少的神经元的策略,前者在实际训练中训练成功的难度更大,包含大量隐含层神经元的三层网络的过拟合问题难以控制,而且要达到相同的性能,深层神经网络的结构每每要比三层网络须要的神经元更少。
另外一种对深度学习前若干层做用的解释就是表示学习。深度学习=深度表示学习(特征学习) ,下图是一个多层卷积网络在输入图像之后,神经网络隐含层神经元激活的可视化结果:
如图,神经网络的前若干层实际上发挥这特征提取和表示创建的做用,区别与传统机器学习方法的人为设计特征,神经网络的特征设计是伴随着神经网络的训练而进行的,是一个自动的表示创建过程。从图中咱们还能发现,越靠近输入端的层(越底层)提取的特征越简单,层数越高创建的特征越复杂。好比说图中,第一层提取了“边缘”特征,第二层则提取了轮廓特征,那么到了第三个隐含层,经过组合简单的底层特征,综合出了更加高级的表示,提取的是识别目标的局部特征。经过对特征的逐层抽象化,神经网络的层数越多,其可以创建的特征表示也就越丰富。
当神经网络的隐含层数和神经元数量增大时,随之而来的是参数数量的大幅度增大。这是的咱们的神经网络更加容易过拟合,即模型在训练集上表现好,可是泛化能力差。在机器学习中,许多策略显式地被设计为减小测试偏差(可能会以增大训练偏差为代价)。这些策略被统称为正则化。
下面咱们介绍四种常见的正则化技术,它们分别是:
* 数据集加强(Data Agumentation)
* 提早终止(Early Stopping)
* 参数范数惩罚(Parameter Norm Penalties)
* Dropout
加强机器学习鲁棒性的最直观的一个策略就是使用更多的数据来训练模型,即数据集加强。然而,在现实状况下,咱们的数据是有限的,因此咱们每每经过建立假数据来增长咱们的数据集合,而对于某些机器学习任务而言(如图像分类),建立假数据是很是简单的,下面咱们以MNIST手写字为例来讲明:
上图中的三个字符是MNIST数据集的训练集中的三个数字,对于图像这样的高维而且包含巨大的可变性的数据,咱们能够对数据进行简单的平移,旋转,缩放等等来产生新的数据。
def expend_training_data(train_x, train_y):
""" Augment training data """
expanded_images = np.zeros([train_x.shape[0] * 5, train_x.shape[1], train_x.shape[2]])
expanded_labels = np.zeros([train_x.shape[0] * 5])
counter = 0
for x, y in zip(train_x, train_y):
# register original data
expanded_images[counter, :, :] = x
expanded_labels[counter] = y
counter = counter + 1
# get a value for the background
# zero is the expected value, but median() is used to estimate background's value
bg_value = np.median(x) # this is regarded as background's value
for i in range(4):
# rotate the image with random degree
angle = np.random.randint(-15, 15, 1)
new_img = ndimage.rotate(x, angle, reshape=False, cval=bg_value)
# shift the image with random distance
shift = np.random.randint(-2, 2, 2)
new_img_ = ndimage.shift(new_img, shift, cval=bg_value)
# register new training data
expanded_images[counter, :, :] = new_img_
expanded_labels[counter] = y
counter = counter + 1
return expanded_images, expanded_labels
agument_x, agument_y = expend_training_data(x_train[:3], y_train[:3])
获得5倍于原数据集的新数据集:
后面的四个图像是咱们经过必定的变换获得的,咱们并无去采集新的数据,经过创造假数据,咱们的数据集变成了原来的若干倍,这种处理方法可以显著提升神经网络的泛化能力,即便是具备平移不变性的卷积神经网络(咱们后面会详述),使用这种简单处理方式获得的新数据也能大大改善泛化。
当训练参数数量大的神经网络时,即模型容量大于实际需求时,神经网络最终老是会过拟合,可是咱们老是可以观察到,训练偏差会随着训练时间的推移逐渐下降,可是验证集的偏差却会先下降后升高,以下图所示:
那么基于这个现象,咱们能够在每次观察到验证集偏差有所改善之后保存一份模型的副本,若是偏差恶化,则将 耐心值 +1,当耐心值到达一个事先设定的阈值的时候,终止训练,返回保存的最后一个副本。这样,咱们可以获得整个偏差曲线中最低的点的模型。
许多正则化方法会向神经网络的损失函数
其中的
以L2正则化为例,咱们在损失函数的后面添加了的正则项为:
那么最小化权重的平方会带来什么结果呢?
L2惩罚一方面下降了权重的学习自由度,削弱了网络的学习能力,另外一方面相对均匀的权重又能使模型光滑化,使模型对输入的细微变化不敏感,从而加强模型的鲁棒性。
参数范数惩罚经过改变神经网络的损失函数来实现正则化,而Dropout则经过改变神经网络的结构来加强网络的泛化能力,如图是一个神经网络训练时的结构:
咱们在第一个隐含层后面添加了一个Dropout层,Dropout 就是指 随机地删除掉网络中某层的节点,包括该节点的输入输出的边 ,以下图所示:
这也等价与以必定的概率保留节点。在本例中,
那么等到训练结束的时候,咱们的咱们的网络能够看做是不少个半数网络的集成模型,到应用网络的阶段,咱们就再也不使用Dropout,即
咱们使用BelgiumTS(Belgium Traffic Sign Dataset)来作一个简单的识别实例,BelgiumTS是一个交通讯号的数据集,包含62中交通讯号。
训练集的下载链接:http://btsd.ethz.ch/shareddata/BelgiumTSC/BelgiumTSC_Training.zip
测试集的下载连接:
http://btsd.ethz.ch/shareddata/BelgiumTSC/BelgiumTSC_Testing.zip
此数据集在不采用科学上网的方式时下载速度偏慢。
下载好数据之后,解压,使用以下目录结构存放数据:
data/Training/
data/Testing/
该数据集的训练集和测试集均包含了62个目录,表示62种交通讯号。使用以下函数读取数据:
def load_data(data_dir):
"""Loads a data set and returns two lists: images: a list of Numpy arrays, each representing an image. labels: a list of numbers that represent the images labels. """
# Get all subdirectories of data_dir. Each represents a label.
directories = [d for d in os.listdir(data_dir)
if os.path.isdir(os.path.join(data_dir, d))]
# Loop through the label directories and collect the data in
# two lists, labels and images.
labels = []
images = []
for d in directories:
label_dir = os.path.join(data_dir, d)
file_names = [os.path.join(label_dir, f)
for f in os.listdir(label_dir) if f.endswith(".ppm")]
# For each label, load it's images and add them to the images list.
# And add the label number (i.e. directory name) to the labels list.
for f in file_names:
images.append(skimage.data.imread(f))
labels.append(int(d))
return images, labels
# Load training and testing datasets.
ROOT_PATH = "data"
train_data_dir = os.path.join(ROOT_PATH, "Training")
test_data_dir = os.path.join(ROOT_PATH, "Testing")
images, labels = load_data(train_data_dir)
输出训练集的类别数和总的图片数量:
print("Unique Labels: {0}\nTotal Images: {1}".format(len(set(labels)), len(images)))
Unique Labels: 62
Total Images: 4575
咱们显示每个类别的第一张图片看看。。。
def display_images_and_labels(images, labels):
"""Display the first image of each label."""
unique_labels = set(labels)
plt.figure(figsize=(15, 15))
i = 1
for label in unique_labels:
# Pick the first image for each label.
image = images[labels.index(label)]
plt.subplot(8, 8, i) # A grid of 8 rows x 8 columns
plt.axis('off')
plt.title("Label {0} ({1})".format(label, labels.count(label)))
i += 1
_ = plt.imshow(image)
plt.show()
display_images_and_labels(images, labels)
显然,数据集的图片并非统一的尺寸的,为了训练神经网络,咱们须要将全部图片resize到一个相同的尺寸,在本文中,咱们将图片resize到(32,32):
# Resize images
images32 = [skimage.transform.resize(image, (32, 32))
for image in images]
display_images_and_labels(images32, labels)
输出resize之后的图片信息:
for image in images32[:5]:
print("shape: {0}, min: {1}, max: {2}".format(image.shape, image.min(), image.max()))
shape: (32, 32, 3), min: 0.0, max: 1.0
shape: (32, 32, 3), min: 0.13088235294117614, max: 1.0
shape: (32, 32, 3), min: 0.057059972426470276, max: 0.9011967677696078
shape: (32, 32, 3), min: 0.023820465686273988, max: 1.0
shape: (32, 32, 3), min: 0.023690257352941196, max: 1.0
图像的取值范围已经归一化好了,下面咱们使用TensorFlow构造神经网络来训练一个深度前馈网络识别这个交通讯号。
咱们对数据进行预处理,首先将三通道的RGB转成灰度图:
images_a = color.rgb2gray(images_a) display_images_and_labels(images_a, labels)
注意,这里现实的并非灰度图,缘由在于咱们使用了以前的display_images_and_labels
函数,只须要在该函数的imshow
部分添加cmap='gray'
便可显示灰度图。
咱们使用前面的方法对数据进行扩充(将数据扩充为原来的5倍),咱们现实其中的三张图片:
而后咱们对数据进行shuffle,而且把数据切分为训练集和验证集,并对标签进行one-hot编码:
from sklearn.utils import shuffle
indx = np.arange(0, len(labels_a))
indx = shuffle(indx)
images_a = images_a[indx]
labels_a = labels_a[indx]
print(images_a.shape, labels_a.shape)
train_x, val_x = images_a[:20000], images_a[20000:]
train_y, val_y = labels_a[:20000], labels_a[20000:]
train_y = keras.utils.to_categorical(train_y, 62)
val_y = keras.utils.to_categorical(val_y, 62)
print(train_x.shape, train_y.shape)
咱们仍然使用上一篇文章中用到的深度前馈网络,看看在这类复杂问题中的性能如何:
model = Sequential()
model.add(Flatten(input_shape=(32, 32)))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(62, activation='softmax'))
model.summary()
model.compile(loss='categorical_crossentropy',
optimizer=RMSprop(),
metrics=['accuracy'])
history = model.fit(train_x, train_y,
batch_size=128,
epochs=20,
verbose=1,
validation_data=(val_x, val_y))
### print the keys contained in the history object
print(history.history.keys())
model.save('model.json')
现实训练loss和验证loss为:
加载测试数据集,查看精度:
('Test loss:', 0.8060373229994661)
('Test accuracy:', 0.7932539684431893)
咱们的这个简单神经网络在测试集合上取得了79%的精度,咱们现实几个测试样本的预测结果:
虽然精度不高,但效果彷佛还行。。。固然,这个例子只是一个入门的网络,首先,它抛弃了3通道的图像,因此信息会有必定的损失,其次,全链接网络的第一步就是把图像向量化了,咱们能不能使用更加深,更加符合图片二维特征的网络呢?咱们在后面的文章中继续探讨!