2.1 为什么要进行实力探究(why look case studies)
这周我们来看看卷积神经网络的实例分析。
为什么要看这些案例分析呢?上周我们讲了基本构建,比如卷积层,池化层以及全连接层这些组件,事实上,过去几年,计算机视觉中的大量研究都集中在如何把这些基本构建组合起来,形成有效的卷积神经网络,找感觉最好的方法之一是看一些案例。
实际上,在计算机视觉中表现良好的神经网络框架往往也适用于其他任务。也就是说如果有人已经训练或者计算出擅长识别猫,狗,人的神经网络,或者框架。而我们的任务是构建一个自动驾驶汽车,那么我们完全可以借鉴别人的神经网络框架解决自己的问题。
下面来看几个经典的网络:
LeNet-5, AlexNet,VGG以及ResNet。其中ResNet又称为残差网络,他高达152层,并且在如何有效训练方面总结出了一些有趣的想法和窍门,课程最后还会讲一个Inception神经网络的实例分析。
2.2 经典网络(Classic networks)
这节将介绍几个经典的神经网络结构,分别为LeNet-5,AlexNet和VGG net。
LeNet-5
我们首先来看看leNet-5的网络结构:
由上图可知LeNet-5结构为:卷积——池化——卷积——池化——全连接——全连接。
注:(1)在这篇论文写成的年代里,人们更喜欢使用平均池化,而现在我们可能使用最大池化更多一点。(2)因为论文是在1998年撰写的,当时人们并不使用padding或者有效卷积,所以每进行一次卷积,图像的高度和宽度都会缩小。(3)在现在的版本中输出层使用的是softmax函数输出十种分类结果,而在当时,LeNet-5网络在输出层使用另外一种,现在很少用。
不管如何,如果我们从左往右看,随着网络越来越深,图像的高度和宽度都在缩小,信道数量一直在增加。这个网络中还有一种模式至今仍然使用,那就是一个或者多个卷积层后面跟着一个池化层,然后又是若干个卷积,池化,然后是全连接层,最后是输出。
AlexNet
AlexNet要比LeNet-5大得多,LeNet-5只有大约6万个参数,而AlexNet大约有6000万个参数,当用于训练图像和数据集时,AlexNet能够处理非常相似的基本构造模块,这些模块往往包含大量的隐藏单元或数据,这一点另AlexNet表现出色,其次,AlexNet比LeNet-5更为出色的原因是它使用了Relu激活函数。最后经典AlexNet结构还有另一种类型的层叫做“局部响应归一化层”即LRN(Local Response Normalization)。
VGG-16
VGG-16是一种只专注于构建圈基层的简单网络。VGG-16总共包含约1.38亿个参数,但是VGG-16的结构并不复杂,都是几个卷积层后面跟着池化层,可以压缩图像大小的池化层,同时卷积层的过滤器数量存在一定的规律。
VGG-16揭示了随着网络的加深,图像的高度和宽度都在以一定的规律不断缩小,每次迭代后刚好缩小一半,而信道数量在不断增加,而且刚好也是在每组卷积操作后增加一倍,也就是说,图像缩小的比例和信道增加的比例是有规律的。
2.3 残差网络(ResNets)
非常深的网络是很难训练的,因为存在梯度消失和梯度爆炸问题,这一节,我们将学习跳远连接,他可以从某一网络层获取激活,然后迅速反馈给另外一层,甚至是神经网络的更深层,ResNets是由残差块构造的。残差块如下:
与普通块不同,残差块的信息流直接从
注意:
接下来我们来看看残差网络的结构。如下所示:
残差网络是将一个普通网络加上跳远连接形成的。
凭经验,我们会觉得随着网络深度的加深,训练错误会减少,然后加深,而理论上,随着网络的加深,应该训练得越好才对,但实际上,如果没有残差网络,对于一个普通网络来说,深度越深,用优化算法越难训练,实际上随着网络加深,训练错误会越来越多,但是有了ResNets就不一样了,即使网络再深,训练的表现也不错。
以下是普通网络和残差网络的损失函数图
2.4 残差网络为什么有用?(why resnets work)
上一节,我们了解到,一个网络深度越深,它在训练集上训练网络的效率会有所减弱,这也是有时候我们不希望加深网络的原因,而事实并非如此,至少在训练ResNet网络时,并不完全如此。举个例子:假设有一个大型神经网络,用bigNN表示,输出激活值为
可以将后两层看作一个残差块,为了方便说明,假设我们在整个网络中使用Relu激活函数,所有激活值都大于等于0,包括输入x的非零异常值,因为relu激活函数输出的数字要么是0,要么是正数,我们看一下
残差网络起作用的主要原因在于残差块学习恒等式非常容易,此时网络性能不受影响,甚至可以提高效率。除此之外,另一个值得探讨的问题是假设
最后,我们来看看ResNets的图片识别,把普通网络转化为ResNets只需要添加跳远连接,此外ResNets使用1*1过滤器,这个想法很有意思,为什么呢?
普通网络:
ResNets:
2.5 网络中的网络以及1*1卷积(network in network and 1*1 convolutions)
1*1卷积所实现的功能是用来压缩信道,1*1卷积只是添加了非线性函数,从而减少或保持输入层中信道数量不变,通过1*1卷积的简单操作可以压缩或保持甚至增加信道数量。
如图所示:
2.6 谷歌Inception网络简介
构建卷积层时,我们要决定过滤器的大小,以及要不要添加池化层,而Inception网络的作用就是代替我们来做决定,虽然因此网络架构会变得更复杂,但网络表现却非常好。
Inception网络的基本思想是不需要人为决定使用哪个过滤器,或是否需要池化,而是由网络自行决定这些参数,我们可以给网络添加这些参数的所有可能值,然后把这些输出连接起来,让网络自己学习它需要什么样的参数。
如下所示:
2.7 Inception 网络(Inception network)
我们首先来看看Inception 模块的结构,如下所示:
Inception模块会将之前的激活或者输出作为它的输入。在这里我们主要讲一些重点的细节,为了能在最后,将所有的输出连接起来,我们会使用same类型的padding来池化。其次,我们需要加上一个1*1卷积,将通道的数量缩小到28*28*32,也就是使用了32个1*1*192的过滤器,这样就避免最后输出时,池化层占据所有的通道,最后讲这些方块连接起来,在这个过程中,把得到的各个层的通道都加起来,最后得到28*28*256的输出,这就是一个Inception模块,而Inception网络就是将这些模块都组合在一起,如下为一个Inception网络。
在该网络的论文中其实还存在这样的细节,即存在一些分支,这些分支通过隐藏层做出预测,其实就是一个sofamax输出,他确保了隐藏单元和中间层也参与了特征计算,他们能预测图片的分类,它在Inception网络中起到一种调整的效果,并且能防止过拟合。如下所示:
2.8 数据增强(Data augmentation)
大部分的计算机视觉任务都需要使用很多的数据,所以数据增强是一种经常使用的技巧来提高计算机视觉系统的表现。
常用的数据增强方法有,垂直镜像对称,随机裁剪,以及旋转,扭曲变换,还有色彩转换等。
垂直镜像对称:
随机裁剪:
色彩转换:给定一张图片,然后给 R,G,B三通道加上不同的失真值。
最后,我们举一个例子,下面是一个基于tensorflow框架,加载VGG-19网络进行特征提取的实例代码,在这里需要先加载VGG模型,模型下载链接如下:[链接: https://pan.baidu.com/s/1hsQtXVQ 密码: tvbb]
代码如下:
import scipy.io
import numpy as np
import os
import scipy.misc
import matplotlib.pyplot as plt
import tensorflow as tf
def _conv_layer(input, weights, bias):
conv = tf.nn.conv2d(input, tf.constant(weights), strides=(1, 1, 1, 1),
padding='SAME')
return tf.nn.bias_add(conv, bias)
def _pool_layer(input):
return tf.nn.max_pool(input, ksize=(1, 2, 2, 1), strides=(1, 2, 2, 1),
padding='SAME')
def preprocess(image, mean_pixel):
return image - mean_pixel
def unprocess(image, mean_pixel):
return image + mean_pixel
def imread(path):
return scipy.misc.imread(path).astype(np.float)
def imsave(path, img):
img = np.clip(img, 0, 255).astype(np.uint8)
scipy.misc.imsave(path, img)
print ("Functions for VGG ready")
def net(data_path, input_image):
layers = (
'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1',
'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2',
'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3',
'relu3_3', 'conv3_4', 'relu3_4', 'pool3',
'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'conv4_3',
'relu4_3', 'conv4_4', 'relu4_4', 'pool4',
'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'conv5_3',
'relu5_3', 'conv5_4', 'relu5_4'
)
data = scipy.io.loadmat(data_path)
mean = data['normalization'][0][0][0]
mean_pixel = np.mean(mean, axis=(0, 1))
weights = data['layers'][0]
net = {}
current = input_image
for i, name in enumerate(layers):
kind = name[:4]
if kind == 'conv':
kernels, bias = weights[i][0][0][0][0]
# matconvnet: weights are [width, height, in_channels, out_channels]
# tensorflow: weights are [height, width, in_channels, out_channels]
kernels = np.transpose(kernels, (1, 0, 2, 3))
bias = bias.reshape(-1)
current = _conv_layer(current, kernels, bias)
elif kind == 'relu':
current = tf.nn.relu(current)
elif kind == 'pool':
current = _pool_layer(current)
net[name] = current
assert len(net) == len(layers)
return net, mean_pixel, layers
print ("Network for VGG ready")
cwd = os.getcwd()
VGG_PATH = cwd + "/data/imagenet-vgg-verydeep-19.mat"
IMG_PATH = cwd + "/data/resistance.jpg"
input_image = imread(IMG_PATH)
shape = (1, input_image.shape[0], input_image.shape[1], input_image.shape[2])
with tf.Session() as sess:
image = tf.placeholder('float', shape=shape)
nets, mean_pixel, all_layers = net(VGG_PATH, image)
input_image_pre = np.array([preprocess(input_image, mean_pixel)])
layers = all_layers # For all layers
# layers = ('relu2_1', 'relu3_1', 'relu4_1')
for i, layer in enumerate(layers):
print("[%d/%d] %s" % (i + 1, len(layers), layer))
features = nets[layer].eval(feed_dict={image: input_image_pre})
print(" Type of 'features' is ", type(features))
print(" Shape of 'features' is %s" % (features.shape,))
# Plot response
if 1:
plt.figure(i + 1, figsize=(10, 5))
plt.matshow(features[0, :, :, 0], cmap=plt.cm.gray, fignum=i + 1)
plt.title("" + layer)
plt.colorbar()
plt.show()