首页 科技正文

allbet:经典卷积神经网络算法(5):ResNet

admin 科技 2020-06-22 52 0

 

1 弁言¶

神经网络算法有时刻又被称为深度学习,究其缘故原由就是由于神经网络模子可以通过添加网络层数来扩展网络的深度以获得更优越的性能。以CNN网络为例,卷积层数越多,模子越能够提取到更高条理的特征,信息加倍厚实。以是,我们不禁要料想,是不是网络的深度越深,模子的性能越好。若是真是这样,那神经网络就是近乎无所不能的算法,没有什么是添加一层网络不能解决的,若是有,那就添加两层。

在前面几篇博客中,我们实现了LeNet、AlexNet、VGG、GoogLeNet等几个景点的卷积神经网络算法,这些算法层数对比如下所示:

 

 

开端一看,可以发现网络的层数确实是在增添的,但若是这就以为网络层数越多,模子性能越高,那就太无邪了(若是真这么简朴,那时的这些学者直接将网络层数提升到成千上万层,性能不比上表中一二十次高得多?另有这几个算法什么事)。事实确实也是云云,经由频频验证,人们发现,当网络层数到达一定数目时,继续添加网络层数,模子性能不只得不到提升,反而下降。

这是为什么呢?

针对这一问题举行探索、优化,最终就有了本文的主角——残差网络(ResNet)的降生。

 

2 退化征象¶

 

ResNet是我国华人学者何凯明团队在其论文《Deep Residual Learning for Image Recognition》中提出。在改论文中,作者先对网络深度与模子性能的关系举行探索,如下所示:

 

(注:图来自论文)</cneter>  

对比上面两图红黄两线,可以发现,网络层数多的(56层)训练效果远比网络层数少(20层)模子差。实在,在其他诸多实验也发现,随着网络层级的不停增添,模子精度不停获得提升,而当网络层级增添到一定的数目以后,训练精度和测试精度迅速下降,这说明当网络变得很深以后,深度网络就变得加倍难以训练了。

为什么会泛起这种征象呢?

VGGNet和GoogLeNet等网络都解释有足够的深度是模子显示优越的条件,然则在网络深度增添到一定水平时,更深的网络意味着更高的训练误差。 神经网络在反向流传历程中通过链式法则不停地流传梯度,而当网络层数加深时,梯度在流传历程中会逐渐消逝(如果接纳Sigmoid函数,对于幅度为1的信号,每向后通报一层,梯度就衰减为原来的0.25,层数越多,衰减越厉害),导致无法对前面网络层的权重举行有用的调整,导致训练和测试效果变差,这一征象称为退化。

那么,该若何解决退化问题,使得在加深网络层数的同时,又能解决梯度消逝问题,从而提升模子精度呢?

 

3 残差网络(ResNet)¶

 

通过前文剖析我们知道了,网络模子的深度到达一定量时,模子的精度就不再随着深度的提升而增添,更有可能会随着降低。这里,我们有一个设想,假设模子总共有100层,然则模子在第10层的时刻精度就到达了巅峰,那么,后续的90层都是多余的,我们不需要它对输出效果举行任何改变,这90层只需要保证第10层网络的输出能够原模原样映射到最后一层即可——这就是本文的主角——残差网络(ResNet)算法灵感的泉源。在论文中,作者将后续90层这种将输入原样输出的映射层为恒等映射

我们回到ResNet算法来看看恒等映射详细是若何发挥作用的。如下图所示,假设$x$为浅层网络的输出,在原始的网络中,$x$会继续前向流传,经由至少一层网络后输出为$F(x)$,在ResNet网络中,从浅层玩谁人罗的输出$x$到$F(x)$之间添加了一条捷径,这条捷径不会对$x$做任何改变,保留原样输出,以是,在图中捷径与原始网络的交汇处将获得的输出变为$H(x)=F(x)+x$此时,$F(x)=H(x)-x$,浅层网络后续的更深条理网络只需要对残差$H(x)-x$举行拟合,这也是为什么作者将这一模子称为残差网络的缘故原由。若是浅层网络的输出,也就是$x$的进度已经到达最优,那么,$H(x)=x$将是最优的输出,$F(x)$只需要对$0$举行拟合,这样的拟合会比没有残差时的拟合简朴得多。另外,也由于有恒等映射的捷径存在,因此在反向流传历程中梯度的传导也多了更简捷的路径,仅仅经由一个relu就可以把梯度转达给上一个模块。

 

 

这种残差跳跃式的结构,打破了传统的神经网络n-1层的输出只能给n层作为输入的老例,使某一层的输出可以直接跨过几层作为后面某一层的输入,其意义在于为叠加多层网络而使得整个学习模子的错误率不降反升的难题提供了新的偏向。

 

固然,在卷积网络的残差块设计中,并不都是设计成恒等映射,这也是允许的,如下图所示。

  • 实线毗邻部门示意通道数稳定,如图中的第二个矩形和第三个矩形,都是3x3x64的特征图,由于通道相同,以是接纳盘算方式为$H(x)=F(x)+x$;
  • 虚线毗邻部门示意通道发生变化,如上图的第一个绿色矩形和第三个绿色矩形,分别是3x3x64和3x3x128的特征图,通道差别,接纳的盘算方式为$H(x)=F(x)+Wx$,其中$W$是卷积操作,用来调整$x$的通道数。
 

allbet:经典卷积神经网络算法(5):ResNet 第1张

 

在论文中,作者进一步证明了深度残差网络简直解决了退化问题,如下图所示,左图为平原网络(plain network)网络条理越深(34层)比网络条理浅的(18层)的误差率更高;右图为残差网络ResNet的网络条理越深(34层)比网络条理浅的(18层)的误差率更低。

 

allbet:经典卷积神经网络算法(5):ResNet 第2张

 

至此,神经网络的层数可以逾越之前的约束,到达几十层、上百层甚至千层,为更高条理特征提取和分类提供了可行性。

 

4 代码实现¶

In [1]:
import os
import tensorflow as tf
from tensorflow import keras 
from tensorflow.keras import layers, Sequential, datasets, optimizers
In [2]:
os.environ["TF_CPP_MIN_LOG_LEVEL"]='2'
tf.random.set_seed(2345)
In [3]:
# 使用cifar100数据集
(x_train, y_train), (x_test, y_test) = datasets.cifar100.load_data()
In [4]:
def preprocess(x, y):
    x = tf.cast(x, dtype=tf.float32) / 255.  # 将每个像素值映射到[0, 1]内
    y = tf.cast(y, dtype=tf.float32)
    return x, y
In [5]:
y_train = tf.squeeze(y_train, axis=1)
y_test = tf.squeeze(y_test, axis=1)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_db = train_db.shuffle(1000).map(preprocess).batch(256)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_db = test_db.shuffle(1000).map(preprocess).batch(256)
In [6]:
class BasicBlock(layers.Layer):
    def __init__(self,filter_num,stride=1):
        super(BasicBlock, self).__init__()
        self.conv1=layers.Conv2D(filter_num,(3,3),strides=stride,padding='same')
        self.bn1=layers.BatchNormalization()
        self.relu=layers.Activation('relu')

        self.conv2=layers.Conv2D(filter_num,(3,3),strides=1,padding='same')
        self.bn2 = layers.BatchNormalization()

        if stride!=1:
            self.downsample=Sequential()
            self.downsample.add(layers.Conv2D(filter_num,(1,1),strides=stride))
        else:
            self.downsample=lambda x:x
    def call(self,input,training=None):
        out=self.conv1(input)
        out=self.bn1(out)
        out=self.relu(out)

        out=self.conv2(out)
        out=self.bn2(out)

        identity=self.downsample(input)
        output=layers.add([out,identity])
        output=tf.nn.relu(output)
        return output
In [7]:
class ResNet(keras.Model):
    def __init__(self,layer_dims,num_classes=100):
        super(ResNet, self).__init__()
        # 预处理层
        self.stem=Sequential([
            layers.Conv2D(64,(3,3),strides=(1,1)),
            layers.BatchNormalization(),
            layers.Activation('relu'),
            layers.MaxPool2D(pool_size=(2,2),strides=(1,1),padding='same')
        ])
        # resblock
        self.layer1 = self.build_resblock(64,layer_dims[0])
        self.layer2 = self.build_resblock(128, layer_dims[1], stride=2)
        self.layer3 = self.build_resblock(256, layer_dims[2], stride=2)
        self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)

        # there are [b,512,h,w]
        # 自适应
        self.avgpool=layers.GlobalAveragePooling2D()
        self.fc=layers.Dense(num_classes)



    def call(self,input,training=None):
        x=self.stem(input)
        x=self.layer1(x)
        x=self.layer2(x)
        x=self.layer3(x)
        x=self.layer4(x)
        # [b,c]
        x=self.avgpool(x)
        x=self.fc(x)
        return x

    def build_resblock(self,filter_num,blocks, stride=1):
        res_blocks= Sequential()
        # may down sample
        res_blocks.add(BasicBlock(filter_num, stride))
        # just down sample one time
        for pre in range(1, blocks):
            res_blocks.add(BasicBlock(filter_num, stride=1))
        return res_blocks
In [8]:
def resnet18():
    return ResNet([2, 2, 2, 2])
In [13]:
def main():
    model=resnet18()
    model.build(input_shape=(None,32,32,3))
    model.summary()
    optimizer=optimizers.Adam(lr=1e-3)
    for epoch in range(50):
        for step,(x,y) in enumerate(train_db):
            with tf.GradientTape() as tape:
                logits=model(x)
                y = tf.cast(y, tf.int32)
                y_onehot=tf.one_hot(y,depth=100)
                loss=tf.losses.categorical_crossentropy(y_onehot,logits,from_logits=True)
                loss=tf.reduce_mean(loss)
            grads=tape.gradient(loss,model.trainable_variables)
            optimizer.apply_gradients(zip(grads,model.trainable_variables))
            if step%100==0:
                print(epoch,step,'loss',float(loss))
        total_num=0
        total_correct=0
        for x,y in test_db:
            logits=model(x)
            prob=tf.nn.softmax(logits,axis=1)
            pred=tf.argmax(prob,axis=1)
            pred=tf.cast(pred,dtype=tf.int32)
            correct=tf.cast(tf.equal(pred,y),dtype=tf.int32)
            correct=tf.reduce_sum(correct)
            total_num+=x.shape[0]
            total_correct+=int(correct)
        acc=total_correct/total_num
        print(epoch,'acc:',acc)
In [ ]:
main()
 
Model: "res_net_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
sequential_16 (Sequential)   multiple                  2048      
_________________________________________________________________
sequential_17 (Sequential)   multiple                  148736    
_________________________________________________________________
sequential_18 (Sequential)   multiple                  526976    
_________________________________________________________________
sequential_20 (Sequential)   multiple                  2102528   
_________________________________________________________________
sequential_22 (Sequential)   multiple                  8399360   
_________________________________________________________________
global_average_pooling2d_2 ( multiple                  0         
_________________________________________________________________
dense_2 (Dense)              multiple                  51300     
=================================================================
Total params: 11,230,948
Trainable params: 11,223,140
Non-trainable params: 7,808
_________________________________________________________________
0 0 loss 4.609687805175781

,

联博统计

www.326681.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

版权声明

本文仅代表作者观点,
不代表本站Allbet的立场。
本文系作者授权发表,未经许可,不得转载。

评论