【深度学习系列】用PaddlePaddle和Tensorflow实现经典CNN网络GoogLeNet
前面讲了LeNet、AlexNet和Vgg,这周来讲讲GoogLeNet。GoogLeNet是由google的Christian Szegedy等人在2014年的论文《Going Deeper with Convolutions》提出,其最大的亮点是提出一种叫Inception的结构,以此为基础构建GoogLeNet,并在当年的ImageNet分类和检测任务中获得第一,ps:GoogLeNet的取名是为了向YannLeCun的LeNet系列致敬。
(本系列所有代码均在github:https://github.com/huxiaoman7/PaddlePaddle_code)
关于深度网络的一些思考
在本系列最开始的几篇文章我们讲到了卷积神经网络,设计的网络结构也非常简单,属于浅层神经网络,如三层的卷积神经网络等,但是在层数比较少的时候,有时候效果往往并没有那么好,在实验过程中发现,当我们尝试增加网络的层数,或者增加每一层网络的神经元个数的时候,对准确率有一定的提升,简单的说就是增加网络的深度与宽度,但这样做有两个明显的缺点:
- 更深更宽的网络意味着更多的参数,提高了模型的复杂度,从而大大增加过拟合的风险,尤其在训练数据不是那么多或者某个label训练数据不足的情况下更容易发生;
- 增加计算资源的消耗,实际情况下,不管是因为数据稀疏还是扩充的网络结构利用不充分(比如很多权重接近0),都会导致大量计算的浪费。
解决以上两个问题的基本方法是将全连接或卷积连接改为稀疏连接。不管从生物的角度还是机器学习的角度,稀疏性都有良好的表现,回想一下在讲AlexNet这一节提出的Dropout网络以及ReLU激活函数,其本质就是利用稀疏性提高模型泛化性(但需要计算的参数没变少)。
简单解释下稀疏性,当整个特征空间是非线性甚至不连续时:
- 学好局部空间的特征集更能提升性能,类似于Maxout网络中使用多个局部线性函数的组合来拟合非线性函数的思想;
- 假设整个特征空间由N个不连续局部特征空间集合组成,任意一个样本会被映射到这N个空间中并激活/不激活相应特征维度,如果用C1表示某类样本被激活的特征维度集合,用C2表示另一类样本的特征维度集合,当数据量不够大时,要想增加特征区分度并很好的区分两类样本,就要降低C1和C2的重合度(比如可用Jaccard距离衡量),即缩小C1和C2的大小,意味着相应的特征维度集会变稀疏。
不过尴尬的是,现在的计算机体系结构更善于稠密数据的计算,而在非均匀分布的稀疏数据上的计算效率极差,比如稀疏性会导致的缓存miss率极高,于是需要一种方法既能发挥稀疏网络的优势又能保证计算效率。好在前人做了大量实验(如《On Two-Dimensional Sparse Matrix Partitioning: Models, Methods, and a Recipe》),发现对稀疏矩阵做聚类得到相对稠密的子矩阵可以大幅提高稀疏矩阵乘法性能,借鉴这个思想,作者提出Inception的结构。
图1 Inception结构
- 把不同大小卷积核抽象得到的特征空间看做子特征空间,每个子特征空间都是稀疏的,把这些不同尺度特征做融合,相当于得到一个相对稠密的空间;
- 采用1×1、3×3、5×5卷积核(不是必须的,也可以是其他大小),stride取1,利用padding可以方便的做输出特征维度对齐;
- 大量事实表明pooling层能有效提高卷积网络的效果,所以加了一条max pooling路径;
- 这个结构符合直观理解,视觉信息通过不同尺度的变换被聚合起来作为下一阶段的特征,比如:人的高矮、胖瘦、青老信息被聚合后做下一步判断。
这个网络的最大问题是5×5卷积带来了巨大计算负担,例如,假设上层输入为:28×28×192:
- 直接经过96个5×5卷积层(stride=1,padding=2)后,输出为:28×28×96,卷积层参数量为:192×5×5×96=460800;
- 借鉴NIN网络(Network in Network,后续会讲),在5×5卷积前使用32个1×1卷积核做维度缩减,变成28×28×32,之后经过96个5×5卷积层(stride=1,padding=2)后,输出为:28×28×96,但所有卷积层的参数量为:192×1×1×32+32×5×5×96=82944,可见整个参数量是原来的1/5.5,且效果上没有多少损失。
新网络结构为
图2 新Inception结构
GoogLeNet网络结构
利用上述Inception模块构建GoogLeNet,实验表明Inception模块出现在高层特征抽象时会更加有效(我理解由于其结构特点,更适合提取高阶特征,让它提取低阶特征会导致特征信息丢失),所以在低层依然使用传统卷积层。整个网路结构如下:
图3 GoogLeNet网络结构
图4 GoogLeNet详细网络结构示意图
网络说明:
- 所有卷积层均使用ReLU激活函数,包括做了1×1卷积降维后的激活;
- 移除全连接层,像NIN一样使用Global Average Pooling,使得Top 1准确率提高0.6%,但由于GAP与类别数目有关系,为了方便大家做模型fine-tuning,最后加了一个全连接层;
- 与前面的ResNet类似,实验观察到,相对浅层的神经网络层对模型效果有较大的贡献,训练阶段通过对Inception(4a、4d)增加两个额外的分类器来增强反向传播时的梯度信号,但最重要的还是正则化作用,这一点在GoogLeNet v3中得到实验证实,并间接证实了GoogLeNet V2中BN的正则化作用,这两个分类器的loss会以0.3的权重加在整体loss上,在模型inference阶段,这两个分类器会被去掉;
- 用于降维的1×1卷积核个数为128个;
- 全连接层使用1024个神经元;
- 使用丢弃概率为0.7的Dropout层;
网络结构详细说明:
输入数据为224×224×3的RGB图像,图中"S"代表做same-padding,"V"代表不做。
- C1卷积层:64个7×7卷积核(stride=2,padding=3),输出为:112×112×64;
- P1抽样层:64个3×3卷积核(stride=2),输出为56×56×64,其中:56=(112-3+1)/2+1
- C2卷积层:192个3×3卷积核(stride=1,padding=1),输出为:56×56×192;
- P2抽样层:192个3×3卷积核(stride=2),输出为28×28×192,其中:28=(56-3+1)/2+1,接着数据被分出4个分支,进入Inception (3a)
- Inception (3a):由4部分组成
- 64个1×1的卷积核,输出为28×28×64;
- 96个1×1的卷积核做降维,输出为28×28×96,之后128个3×3卷积核(stride=1,padding=1),输出为:28×28×128
- 16个1×1的卷积核做降维,输出为28×28×16,之后32个5×5卷积核(stride=1,padding=2),输出为:28×28×32
- 192个3×3卷积核(stride=1,padding=1),输出为28×28×192,进行32个1×1卷积核,输出为:28×28×32
最后对4个分支的输出做“深度”方向组合,得到输出28×28×256,接着数据被分出4个分支,进入Inception (3b);- Inception (3b):由4部分组成
- 128个1×1的卷积核,输出为28×28×128;
- 128个1×1的卷积核做降维,输出为28×28×128,进行192个3×3卷积核(stride=1,padding=1),输出为:28×28×192
- 32个1×1的卷积核做降维,输出为28×28×32,进行96个5×5卷积核(stride=1,padding=2),输出为:28×28×96
- 256个3×3卷积核(stride=1,padding=1),输出为28×28×256,进行64个1×1卷积核,输出为:28×28×64
最后对4个分支的输出做“深度”方向组合,得到输出28×28×480;
后面结构以此类推。
用PaddlePaddle实现GoogLeNet
1.网络结构 googlenet.py
在PaddlePaddle的models下面,有关于GoogLeNet的实现代码,大家可以直接学习拿来跑一下:
import paddle.v2 as paddle __all__ = ['googlenet'] def inception(name, input, channels, filter1, filter3R, filter3, filter5R,
filter5, proj):
cov1 = paddle.layer.img_conv(
name=name + '_1',
input=input,
filter_size=1,
num_channels=channels,
num_filters=filter1,
stride=1,
padding=0) cov3r = paddle.layer.img_conv(
name=name + '_3r',
input=input,
filter_size=1,
num_channels=channels,
num_filters=filter3R,
stride=1,
padding=0)
cov3 = paddle.layer.img_conv(
name=name + '_3',
input=cov3r,
filter_size=3,
num_filters=filter3,
stride=1,
padding=1) cov5r = paddle.layer.img_conv(
name=name + '_5r',
input=input,
filter_size=1,
num_channels=channels,
num_filters=filter5R,
stride=1,
padding=0)
cov5 = paddle.layer.img_conv(
name=name + '_5',
input=cov5r,
filter_size=5,
num_filters=filter5,
stride=1,
padding=2) pool1 = paddle.layer.img_pool(
name=name + '_max',
input=input,
pool_size=3,
num_channels=channels,
stride=1,
padding=1)
covprj = paddle.layer.img_conv(
name=name + '_proj',
input=pool1,
filter_size=1,
num_filters=proj,
stride=1,
padding=0) cat = paddle.layer.concat(name=name, input=[cov1, cov3, cov5, covprj])
return cat def googlenet(input, class_dim):
# stage 1
conv1 = paddle.layer.img_conv(
name="conv1",
input=input,
filter_size=7,
num_channels=3,
num_filters=64,
stride=2,
padding=3)
pool1 = paddle.layer.img_pool(
name="pool1", input=conv1, pool_size=3, num_channels=64, stride=2) # stage 2
conv2_1 = paddle.layer.img_conv(
name="conv2_1",
input=pool1,
filter_size=1,
num_filters=64,
stride=1,
padding=0)
conv2_2 = paddle.layer.img_conv(
name="conv2_2",
input=conv2_1,
filter_size=3,
num_filters=192,
stride=1,
padding=1)
pool2 = paddle.layer.img_pool(
name="pool2", input=conv2_2, pool_size=3, num_channels=192, stride=2) # stage 3
ince3a = inception("ince3a", pool2, 192, 64, 96, 128, 16, 32, 32)
ince3b = inception("ince3b", ince3a, 256, 128, 128, 192, 32, 96, 64)
pool3 = paddle.layer.img_pool(
name="pool3", input=ince3b, num_channels=480, pool_size=3, stride=2) # stage 4
ince4a = inception("ince4a", pool3, 480, 192, 96, 208, 16, 48, 64)
ince4b = inception("ince4b", ince4a, 512, 160, 112, 224, 24, 64, 64)
ince4c = inception("ince4c", ince4b, 512, 128, 128, 256, 24, 64, 64)
ince4d = inception("ince4d", ince4c, 512, 112, 144, 288, 32, 64, 64)
ince4e = inception("ince4e", ince4d, 528, 256, 160, 320, 32, 128, 128)
pool4 = paddle.layer.img_pool(
name="pool4", input=ince4e, num_channels=832, pool_size=3, stride=2) # stage 5
ince5a = inception("ince5a", pool4, 832, 256, 160, 320, 32, 128, 128)
ince5b = inception("ince5b", ince5a, 832, 384, 192, 384, 48, 128, 128)
pool5 = paddle.layer.img_pool(
name="pool5",
input=ince5b,
num_channels=1024,
pool_size=7,
stride=7,
pool_type=paddle.pooling.Avg())
dropout = paddle.layer.addto(
input=pool5,
layer_attr=paddle.attr.Extra(drop_rate=0.4),
act=paddle.activation.Linear()) out = paddle.layer.fc(
input=dropout, size=class_dim, act=paddle.activation.Softmax()) # fc for output 1
pool_o1 = paddle.layer.img_pool(
name="pool_o1",
input=ince4a,
num_channels=512,
pool_size=5,
stride=3,
pool_type=paddle.pooling.Avg())
conv_o1 = paddle.layer.img_conv(
name="conv_o1",
input=pool_o1,
filter_size=1,
num_filters=128,
stride=1,
padding=0)
fc_o1 = paddle.layer.fc(
name="fc_o1",
input=conv_o1,
size=1024,
layer_attr=paddle.attr.Extra(drop_rate=0.7),
act=paddle.activation.Relu())
out1 = paddle.layer.fc(
input=fc_o1, size=class_dim, act=paddle.activation.Softmax()) # fc for output 2
pool_o2 = paddle.layer.img_pool(
name="pool_o2",
input=ince4d,
num_channels=528,
pool_size=5,
stride=3,
pool_type=paddle.pooling.Avg())
conv_o2 = paddle.layer.img_conv(
name="conv_o2",
input=pool_o2,
filter_size=1,
num_filters=128,
stride=1,
padding=0)
fc_o2 = paddle.layer.fc(
name="fc_o2",
input=conv_o2,
size=1024,
layer_attr=paddle.attr.Extra(drop_rate=0.7),
act=paddle.activation.Relu())
out2 = paddle.layer.fc(
input=fc_o2, size=class_dim, act=paddle.activation.Softmax()) return out, out1, out2
2.训练模型
import gzip
import paddle.v2.dataset.flowers as flowers
import paddle.v2 as paddle
import reader
import vgg
import resnet
import alexnet
import googlenet
import argparse DATA_DIM = 3 * 224 * 224
CLASS_DIM = 102
BATCH_SIZE = 128 def main():
# parse the argument
parser = argparse.ArgumentParser()
parser.add_argument(
'model',
help='The model for image classification',
choices=['alexnet', 'vgg13', 'vgg16', 'vgg19', 'resnet', 'googlenet'])
args = parser.parse_args() # PaddlePaddle init
paddle.init(use_gpu=True, trainer_count=7) image = paddle.layer.data(
name="image", type=paddle.data_type.dense_vector(DATA_DIM))
lbl = paddle.layer.data(
name="label", type=paddle.data_type.integer_value(CLASS_DIM)) extra_layers = None
learning_rate = 0.01
if args.model == 'alexnet':
out = alexnet.alexnet(image, class_dim=CLASS_DIM)
elif args.model == 'vgg13':
out = vgg.vgg13(image, class_dim=CLASS_DIM)
elif args.model == 'vgg16':
out = vgg.vgg16(image, class_dim=CLASS_DIM)
elif args.model == 'vgg19':
out = vgg.vgg19(image, class_dim=CLASS_DIM)
elif args.model == 'resnet':
out = resnet.resnet_imagenet(image, class_dim=CLASS_DIM)
learning_rate = 0.1
elif args.model == 'googlenet':
out, out1, out2 = googlenet.googlenet(image, class_dim=CLASS_DIM)
loss1 = paddle.layer.cross_entropy_cost(
input=out1, label=lbl, coeff=0.3)
paddle.evaluator.classification_error(input=out1, label=lbl)
loss2 = paddle.layer.cross_entropy_cost(
input=out2, label=lbl, coeff=0.3)
paddle.evaluator.classification_error(input=out2, label=lbl)
extra_layers = [loss1, loss2] cost = paddle.layer.classification_cost(input=out, label=lbl) # Create parameters
parameters = paddle.parameters.create(cost) # Create optimizer
optimizer = paddle.optimizer.Momentum(
momentum=0.9,
regularization=paddle.optimizer.L2Regularization(rate=0.0005 *
BATCH_SIZE),
learning_rate=learning_rate / BATCH_SIZE,
learning_rate_decay_a=0.1,
learning_rate_decay_b=128000 * 35,
learning_rate_schedule="discexp", ) train_reader = paddle.batch(
paddle.reader.shuffle(
flowers.train(),
# To use other data, replace the above line with:
# reader.train_reader('train.list'),
buf_size=1000),
batch_size=BATCH_SIZE)
test_reader = paddle.batch(
flowers.valid(),
# To use other data, replace the above line with:
# reader.test_reader('val.list'),
batch_size=BATCH_SIZE) # Create trainer
trainer = paddle.trainer.SGD(
cost=cost,
parameters=parameters,
update_equation=optimizer,
extra_layers=extra_layers) # End batch and end pass event handler
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 1 == 0:
print "\nPass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
if isinstance(event, paddle.event.EndPass):
with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f:
trainer.save_parameter_to_tar(f) result = trainer.test(reader=test_reader)
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) trainer.train(
reader=train_reader, num_passes=200, event_handler=event_handler) if __name__ == '__main__':
main()
3.运行方式
python train.py googlenet
其中最后的googlenet是可选的网络模型,输入其他的网络模型,如alexnet、vgg3、vgg6等就可以用不同的网络结构来训练了。
用Tensorflow实现GoogLeNet
tensorflow的实现在models里有非常详细的代码,这里就不全部贴出来了,大家可以在models/research/slim/nets/里详细看看,关于InceptionV1~InceptionV4的实现都有。
ps:这里的slim不是tensorflow的contrib下的slim,是models下的slim,别弄混了,slim可以理解为Tensorflow的一个高阶api,在构建这些复杂的网络结构时,可以直接调用slim封装好的网络结构就可以了,而不需要从头开始写整个网络结构。关于slim的详细大家可以在网上搜索,非常方便。
总结
其实GoogLeNet的最关键的一点就是提出了Inception结构,这有个什么好处呢,原来你想要提高准确率,需要堆叠更深的层,增加神经元个数等,堆叠到一定层可能结果的准确率就提不上去了,因为参数更多了啊,模型更复杂,更容易过拟合了,但是在实验中转向了更稀疏但是更精密的结构同样可以达到很好的效果,说明我们可以照着这个思路走,继续做,所以后面会有InceptionV2 ,V3,V4等,它表现的结果也非常好。给我们传统的通过堆叠层提高准确率的想法提供了一个新的思路。
【深度学习系列】用PaddlePaddle和Tensorflow实现经典CNN网络GoogLeNet的更多相关文章
- 【深度学习系列】用PaddlePaddle和Tensorflow实现经典CNN网络Vgg
上周我们讲了经典CNN网络AlexNet对图像分类的效果,2014年,在AlexNet出来的两年后,牛津大学提出了Vgg网络,并在ILSVRC 2014中的classification项目的比赛中取得 ...
- 【深度学习系列】用PaddlePaddle和Tensorflow实现经典CNN网络AlexNet
上周我们用PaddlePaddle和Tensorflow实现了图像分类,分别用自己手写的一个简单的CNN网络simple_cnn和LeNet-5的CNN网络识别cifar-10数据集.在上周的实验表现 ...
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(二)
PaddlePaddle垃圾邮件处理实战(二) 前文回顾 在上篇文章中我们讲了如何用支持向量机对垃圾邮件进行分类,auc为73.3%,本篇讲继续讲如何用PaddlePaddle实现邮件分类,将深度 ...
- 使用腾讯云 GPU 学习深度学习系列之二:Tensorflow 简明原理【转】
转自:https://www.qcloud.com/community/article/598765?fromSource=gwzcw.117333.117333.117333 这是<使用腾讯云 ...
- 【深度学习系列】PaddlePaddle之手写数字识别
上周在搜索关于深度学习分布式运行方式的资料时,无意间搜到了paddlepaddle,发现这个框架的分布式训练方案做的还挺不错的,想跟大家分享一下.不过呢,这块内容太复杂了,所以就简单的介绍一下padd ...
- 【深度学习系列】PaddlePaddle可视化之VisualDL
上篇文章我们讲了如何对模型进行可视化,用的keras手动绘图输出CNN训练的中途结果,本篇文章将讲述如何用PaddlePaddle新开源的VisualDL来进行可视化.在讲VisualDL之前,我们先 ...
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(一)
PaddlePaddle垃圾邮件处理实战(一) 背景介绍 在我们日常生活中,经常会受到各种垃圾邮件,譬如来自商家的广告.打折促销信息.澳门博彩邮件.理财推广信息等,一般来说邮件客户端都会设置一定的 ...
- 【深度学习系列】PaddlePaddle之数据预处理
上篇文章讲了卷积神经网络的基本知识,本来这篇文章准备继续深入讲CNN的相关知识和手写CNN,但是有很多同学跟我发邮件或私信问我关于PaddlePaddle如何读取数据.做数据预处理相关的内容.网上看的 ...
- 【深度学习系列】关于PaddlePaddle的一些避“坑”技巧
最近除了工作以外,业余在参加Paddle的AI比赛,在用Paddle训练的过程中遇到了一些问题,并找到了解决方法,跟大家分享一下: PaddlePaddle的Anaconda的兼容问题 之前我是在服务 ...
随机推荐
- 使用Identity Server 4建立Authorization Server (2)
第一部分: http://www.cnblogs.com/cgzl/p/7780559.html 第一部分主要是建立了一个简单的Identity Server. 接下来继续: 建立Web Api项目 ...
- 对比jquery获取属性的方法props、attr、data
1.attr,prop 对于HTML元素本身就带有的固有属性,在处理时,使用prop方法.对于自定义的属性是取不到的: 对于HTML元素我们自己自定义的DOM属性,在处理时,使用attr方法. 2.a ...
- ueditor插入百度音乐无法播放-403 问题
简单记录一下,其实403是因为百度音乐设置了禁止外部连接引用,因此 几乎所有的百度音乐播放都会提示403. 注意:预览页面(dialog/music/music.html)和实际插入页面(uedito ...
- JavaScript系列----事件机制
1.事件流 1.1.标准事件流 所谓的标准事件流指的的:EMCAScript标准规定事件流包含三个阶段,分别为事件捕获阶段,处于目标阶段,事件冒泡阶段. 下面是一段html代码,根据代码来说明标准事件 ...
- maven构建SSM--pox.mxl
最近在做项目,把maven构建SSM的pom.xml文件内容小结一下: 在console中输入如下内容,使用maven创建java web项目: mvn archetype:generate -Dgr ...
- 根据矩阵变化实现基于 HTML5 的 WebGL 3D 自动布局
在数学中,矩阵是以行和列排列的数字,符号或表达式的矩形阵列,任何矩阵都可以通过相关字段的标量乘以元素.矩阵的主要应用是表示线性变换,即f(x)= 4 x等线性函数的推广.例如,旋转的载体在三维空间是一 ...
- C#动态获取鼠标坐标
.Net封装好的方法 int Control.MousePosition.X;int Control.MousePosition.Y; 用API方法 using System.Runtime.Inte ...
- java 压缩导出多个excel
简单介绍下我实现的功能,首先是我的excel上传,它是以blob字段存储在oracel数据库中的,我实现的是循环遍历blob字段并使用io流进行打包下载,如有需要可自行修改 使用技术有,springM ...
- Java多线程学习之wait、notify/notifyAll 详解
1.wait().notify/notifyAll() 方法是Object的本地final方法,无法被重写. 2.wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关 ...
- Xilinx FPGA LVDS应用
最近项目需要用到差分信号传输,于是看了一下FPGA上差分信号的使用.Xilinx FPGA中,主要通过原语实现差分信号的收发:OBUFDS(差分输出BUF),IBUFDS(差分输入BUF). 注意在分 ...