resnet 又叫深度残差网络

图像识别准确率很高,主要作者是国人哦

深度网络的退化问题

深度网络难以训练,梯度消失,梯度爆炸,老生常谈,不多说

resnet 解决了这个问题,并且将网络深度扩展到了最多152层。怎么解决的呢?

残差学习

结构如图

在普通的卷积过程中加入了一个x的恒等映射(identity mapping)

专家把这称作 skip connections  或者 shortcut connections

残差结构的理解

为什么要这样呢?下面我从多个角度阐述这个问题。

生活角度

每学习一个模型,我都希望能用日常的生活去解释为什么模型要这样,一是加深对模型的理解,二是给自己搭建模型寻找灵感,三是给优化模型寻找灵感。

resnet 无疑是解决很难识别的问题的,那我举一个日常生活中人类也难以识别的问题,看看这个模型跟人类的识别方法是否一致。

比如人类识别杯子里的水烫不烫

一杯水,我摸了一下,烫,好,我的神经开始运转,最后形成理论杯子里的水烫,这显然不对

又一杯水,我一摸,不烫,好嘛,这咋办,认知混乱了,也就是无法得到有效的参数,

那人类是怎么办呢?

我们不止是摸一摸,而且在摸过之后还要把杯子拿起来仔细看看,有什么细节可以帮助我们更好的识别,这就是在神经经过运转后,又把x整体输入,

当然即使我们拿起杯子看半天,也可能看不出任何规律来帮助我们识别,那人类的作法是什么呢?我记住吧,这种情况要小心,这就是梯度消失了,学习不到任何规律,记住就是恒等映射,

这个过程和resnet是一致的。

网络结构角度

当梯度消失时,f(x)=0,y=g(x)=relu(x)=x,怎么理解呢?

1. 当梯度消失时,模型就是记住,长这样的就是该类别,是一个大型的过滤器

2. 在网络上堆叠这样的结构,就算梯度消失,我什么也学不到,我至少把原来的样子恒等映射了过去,相当于在浅层网络上堆叠了“复制层”,这样至少不会比浅层网络差。

3. 万一我不小心学到了什么,那就赚大了,由于我经常恒等映射,所以我学习到东西的概率很大。

数学角度

可以看到 有1 的存在,导数基本不可能为0

那为什么叫残差学习呢

可以看到 F(x) 通过训练参数 得到了 H(x)-x,也就是残差,所以叫残差学习,这比学习H(x)要简单的多。

等效映射 identity mapping

上面提到残差学习中需要进行 F(x)+x,在resnet中,卷积都是 same padding 的,当通道数相同时,直接相加即可,

但是通道数不一样时需要寻求一种方法使得  y=f(x)+wx

实现w有两种方式

1. 直接补0

2. 通过使用多个 1x1 的卷积来增加通道数。

网络结构

block

block为一个残差单元,resnet 网络由多个block 构成,resnet 提出了两种残差单元

左边针对的是ResNet34浅层网络,右边针对的是ResNet50/101/152深层网络,右边这个又被叫做 bottleneck

bottleneck 很好地减少了参数数量,第一个1x1的卷积把256维channel降到64维,第三个又升到256维,总共用参数:1x1x256x64+3x3x64x64+1x1x64x256=69632,

如果不使用 bottleneck,参数将是 3x3x256x256x2=1179648,差了16.94倍

这里的输出通道数是根据输入通道数确定的,因为要与x相加。

整体结构

1. 与vgg相比,其参数少得多,因为vgg有3个全连接层,这需要大量的参数,而resnet用 avg pool 代替全连接,节省大量参数。

2. 参数少,残差学习,所以训练效率高

结构参数

Resnet50和Resnet101是其中最常用的网络结构。

我们看到所有的网络都分成5部分,分别是:conv1,conv2_x,conv3_x,conv4_x,conv5_x

其结构是相对固定的,只是通道数根据输入确定。

注意,Resnet 最后的 avg_pool 是把每个 feature map 转换成 1 个特征,故池化野 size 为 feature map size,如 最后输出位 512x7x7,那么池化野size 为 7

pytorch-resnet34

from torch import nn
import torch as t
from torch.nn import functional as F class ResidualBlock(nn.Module):
### 残差单元
def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
### 卷积
super(ResidualBlock, self).__init__()
self.left = nn.Sequential(
nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False),
nn.BatchNorm2d(outchannel),
nn.ReLU(inplace=True),
nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),
nn.BatchNorm2d(outchannel)
)
self.right = shortcut def forward(self, x):
### 先恒等映射,然后加上卷积后的out再relu
out = self.left(x)
residual = x if self.right is None else self.right(x)
out += residual
return F.relu(out) class ResNet34(nn.Module):
def __init__(self, num_classes=1000):
super(ResNet34, self).__init__()
### 先做 7x7 卷积
self.pre = nn.Sequential(
nn.Conv2d(3, 64, 7, 2 ,3, bias=False), ### 输入 3 通道,输出 64 通道,卷积核7x7,步长2,padding 3
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(3, 1, 1) ### inchannel,outchannel,padding
)
### 共32层
self.layer1 = self._make_layer(64, 128, 3) ### 3 个 64 通道的残差单元,输出 128通道,共6层
self.layer2 = self._make_layer(128, 256, 4, stride=2) ### 4 个 128通道的残差单元,输出 256通道,共8层
self.layer3 = self._make_layer(256, 512, 6, stride=2) ### 6 个 256通道的残差单元,输出 512通道,共12层
self.layer4 = self._make_layer(512, 512, 3, stride=2) ### 3 个 512通道的残差单元,输出 512通道,共6层
### fc,1层
self.fc = nn.Linear(512, num_classes) def _make_layer(self, inchannel, outchannel, block_num, stride=1):
### 1x1 卷积 改变通道数
shortcut = nn.Sequential(
nn.Conv2d(inchannel, outchannel, 1, stride, bias=False),
nn.BatchNorm2d(outchannel)
)
layers = []
layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut)) ### 先来一个残差单元,主要是改变通道数
### 再接几个同样的残差单元,通道数不变
for i in range(1, block_num+1): ### block_num
layers.append(ResidualBlock(outchannel, outchannel))
return nn.Sequential(*layers) def forward(self, x):
### 第1层
x = self.pre(x) x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x) ### 注意 resnet 最后的池化是把一个 feature map 变成一个特征,故池化野大小等于最后 x 的大小
x = F.avg_pool2d(x, 2) ### 这里用的 cifar10 数据集,此时的 x size 为 512x2x2,所以池化野为2 x = x.view(x.size(0), -1)
return self.fc(x)

tensorflow-resnet50

注意我标注的层数

class ResNet50(object):
def __init__(self, inputs, num_classes=1000, is_training=True,
scope="resnet50"):
self.inputs =inputs
self.is_training = is_training
self.num_classes = num_classes with tf.variable_scope(scope):
# construct the model
### 1层
net = conv2d(inputs, 64, 7, 2, scope="conv1") # -> [batch, 112, 112, 64]
net = tf.nn.relu(batch_norm(net, is_training=self.is_training, scope="bn1"))
net = max_pool(net, 3, 2, scope="maxpool1") # -> [batch, 56, 56, 64]
### 每个bottleneck3层, 16 * 3 = 48层
net = self._block(net, 256, 3, init_stride=1, is_training=self.is_training, scope="block2") # -> [batch, 56, 56, 256]
net = self._block(net, 512, 4, is_training=self.is_training, scope="block3") # -> [batch, 28, 28, 512]
net = self._block(net, 1024, 6, is_training=self.is_training, scope="block4") # -> [batch, 14, 14, 1024]
net = self._block(net, 2048, 3, is_training=self.is_training, scope="block5") # -> [batch, 7, 7, 2048]
net = avg_pool(net, 7, scope="avgpool5") # -> [batch, 1, 1, 2048]
net = tf.squeeze(net, [1, 2], name="SpatialSqueeze") # -> [batch, 2048] # 1层
self.logits = fc(net, self.num_classes, "fc6") # -> [batch, num_classes]
self.predictions = tf.nn.softmax(self.logits) def _block(self, x, n_out, n, init_stride=2, is_training=True, scope="block"):
with tf.variable_scope(scope):
h_out = n_out // 4
out = self._bottleneck(x, h_out, n_out, stride=init_stride, is_training=is_training, scope="bottlencek1")
for i in range(1, n):
out = self._bottleneck(out, h_out, n_out, is_training=is_training, scope=("bottlencek%s" % (i + 1)))
return out def _bottleneck(self, x, h_out, n_out, stride=None, is_training=True, scope="bottleneck"):
""" A residual bottleneck unit"""
n_in = x.get_shape()[-1]
if stride is None:
stride = 1 if n_in == n_out else 2 with tf.variable_scope(scope):
# 3层
h = conv2d(x, h_out, 1, stride=stride, scope="conv_1")
h = batch_norm(h, is_training=is_training, scope="bn_1")
h = tf.nn.relu(h)
h = conv2d(h, h_out, 3, stride=1, scope="conv_2")
h = batch_norm(h, is_training=is_training, scope="bn_2")
h = tf.nn.relu(h) h = conv2d(h, n_out, 1, stride=1, scope="conv_3")
h = batch_norm(h, is_training=is_training, scope="bn_3") if n_in != n_out:
shortcut = conv2d(x, n_out, 1, stride=stride, scope="conv_4")
shortcut = batch_norm(shortcut, is_training=is_training, scope="bn_4")
else:
shortcut = x
return tf.nn.relu(shortcut + h)

最新进展

残差单元被进一步更新

个人经验

1. 卷积层包含大量的卷积计算,如果想降低时间复杂度,减少卷积层

2. 全连接层包含大量的参数,如果想降低空间复杂度,减少全连接层

参考资料:

https://blog.csdn.net/lanran2/article/details/79057994

https://zhuanlan.zhihu.com/p/31852747

ResNet 简介的更多相关文章

  1. 十分钟一起学会ResNet残差网络

    作者 | 荔枝boy 目录 深层次网络训练瓶颈:梯度消失,网络退化 ResNet简介 ResNet解决深度网络瓶颈的魔力 ResNet使用的小技巧 总结 深层次网络训练瓶颈:梯度消失,网络退化 深度卷 ...

  2. ResNet学习笔记

    ResNet学习笔记 前言 这篇文章实在看完很多博客之后写的,需要读者至少拥有一定的CNN知识,当然我也不知道需要读者有什么水平,所以可能对一些很入门的基本的术语进行部分的解释,也有可能很多复杂的术语 ...

  3. 比较 VGG, resnet和inception的图像分类效果

    简介 VGG, resnet和inception是3种典型的卷积神经网络结构. VGG采用了3*3的卷积核,逐步扩大通道数量 resnet中,每两层卷积增加一个旁路 inception实现了卷积核的并 ...

  4. 官网实例详解-目录和实例简介-keras学习笔记四

    官网实例详解-目录和实例简介-keras学习笔记四 2018-06-11 10:36:18 wyx100 阅读数 4193更多 分类专栏: 人工智能 python 深度学习 keras   版权声明: ...

  5. CNN基础框架简介

    卷积神经网络简介 卷积神经网络是多层感知机的变种,由生物学家休博尔和维瑟尔在早期关于猫视觉皮层的研究发展而来.视觉皮层的细胞存在一个复杂的构造,这些细胞对视觉输入空间的子区域非常敏感,我们称之为感受野 ...

  6. tensorflow学习笔记——ResNet

    自2012年AlexNet提出以来,图像分类.目标检测等一系列领域都被卷积神经网络CNN统治着.接下来的时间里,人们不断设计新的深度学习网络模型来获得更好的训练效果.一般而言,许多网络结构的改进(例如 ...

  7. ResNet网络的训练和预测

    ResNet网络的训练和预测 简介 Introduction 图像分类与CNN 图像分类 是指将图像信息中所反映的不同特征,把不同类别的目标区分开来的图像处理方法,是计算机视觉中其他任务,比如目标检测 ...

  8. ASP.NET Core 1.1 简介

    ASP.NET Core 1.1 于2016年11月16日发布.这个版本包括许多伟大的新功能以及许多错误修复和一般的增强.这个版本包含了多个新的中间件组件.针对Windows的WebListener服 ...

  9. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

随机推荐

  1. 2.2 调试 HelloWorld.exe 程序

  2. lanmp环境一键安装

    yum install -y wgetwget http://dl.wdlinux.cn/files/lanmp_v3.2.tar.gztar zxvf lanmp_v3.2.tar.gzsh lan ...

  3. SPL之Iterator(迭代器)接口

    前言:SPL是用于解决典型问题(standard problems)的一组接口与类的集合. <?php /** * Class MyIterator * 在 PHP 中,通常情况下遍历数组使用 ...

  4. Known Notation ZOJ - 3829 (后缀表达式,贪心)

    大意:给定后缀表达式, 每次操作可以添加一个字符, 可以交换两个字符的位置, 相邻数字可以看做一个整体也可以分开看, 求合法所需最少操作数. 数字个数一定为星号个数+1, 添加星号一定不会更优. 先判 ...

  5. MyBatis中mybatis-generator代码生成的一般过程

    MyBatis框架的使用,可以参考我的文章: https://blog.csdn.net/JayInnn/article/details/81746571(基于Mybatis实现一个查库的接口) ht ...

  6. php面向对象比较

    在PHP中有 = 赋值符号.== 等于符号 和 === 全等于符号, 这些符号代表什么意思? (1).=是赋值符 (2).使用 == 符号比较两个对象 ,比较的仅仅是两个对象的内容是否一致. (3). ...

  7. URL与URI的含义及区别

    1.1 什么是URI? 简单点说:URI就是通用资源标志符,不理解是吧,我第一次听说也是不理解. 进一步说:网络上的一些资源(文档.图片.音频.视频.程序等)都是有一些通用资源标识(Universal ...

  8. Leetcode 117

    if(root == NULL) return; queue<TreeLinkNode *> que; que.push(root); while(!empty(que)){ int le ...

  9. ajax实现给JavaScript中全局变量赋值(转)

    原文地址:ajax实现给JavaScript中全局变量赋值 问题简化: <script type="text/javascript"> var a=1 ; functi ...

  10. Spring注解之@Lazy注解

    @Lazy用于指定该Bean是否取消预初始化.主要用于修饰Spring Bean类,用于指定该Bean的预初始化行为, 使用该Annotation时可以指定一个boolean型的value属性,该属性 ...