logo

Restnetzwerke (ResNet) – Deep Learning

Nach der ersten CNN-basierten Architektur (AlexNet), die den ImageNet-Wettbewerb 2012 gewann, nutzt jede weitere siegreiche Architektur mehr Schichten in einem tiefen neuronalen Netzwerk, um die Fehlerrate zu reduzieren. Dies funktioniert für eine geringere Anzahl von Ebenen, aber wenn wir die Anzahl der Ebenen erhöhen, gibt es beim Deep Learning ein häufiges Problem, das mit dem sogenannten „Verschwinden/Explodieren“-Gradienten verbunden ist. Dadurch wird der Gradient 0 oder zu groß. Wenn wir also die Anzahl der Schichten erhöhen, steigt auch die Trainings- und Testfehlerrate.

Vergleich der 20-Schicht- und der 56-Schicht-Architektur



Im obigen Diagramm können wir beobachten, dass ein 56-schichtiges CNN sowohl bei Trainings- als auch bei Testdatensätzen eine höhere Fehlerrate aufweist als eine 20-schichtige CNN-Architektur. Nach einer weiteren Analyse der Fehlerrate kamen die Autoren zu dem Schluss, dass sie durch einen verschwindenden/explodierenden Gradienten verursacht wird.
ResNet, das 2015 von Forschern bei Microsoft Research vorgeschlagen wurde, führte eine neue Architektur namens Residual Network ein.

Restnetzwerk: Um das Problem des verschwindenden/explodierenden Gradienten zu lösen, führte diese Architektur das Konzept „Residual Blocks“ ein. In diesem Netzwerk verwenden wir eine Technik namens Verbindungen überspringen . Die Sprungverbindung verbindet Aktivierungen einer Ebene mit weiteren Ebenen, indem einige dazwischen liegende Ebenen übersprungen werden. Dies bildet einen Restblock. Resnets werden durch Stapeln dieser Restblöcke hergestellt.
Der Ansatz hinter diesem Netzwerk besteht darin, dass wir es dem Netzwerk ermöglichen, sich an die Restabbildung anzupassen, anstatt dass Schichten die zugrunde liegende Zuordnung lernen. Anstelle von H(x) also eine anfängliche Zuordnung , Lass das Netzwerk passen,

 F(x) := H(x) - x  which gives H(x) := F(x) + x .>

Verbindung überspringen (Verknüpfung).



Der Vorteil des Hinzufügens dieser Art von Sprungverbindung besteht darin, dass eine Schicht, die die Leistung der Architektur beeinträchtigt, durch die Regularisierung übersprungen wird. Dies führt also zum Training eines sehr tiefen neuronalen Netzwerks ohne die Probleme, die durch verschwindende/explodierende Gradienten verursacht werden. Die Autoren des Artikels experimentierten mit 100–1000 Schichten des CIFAR-10-Datensatzes.
Es gibt einen ähnlichen Ansatz, der als Autobahnnetze bezeichnet wird. Diese Netze verwenden ebenfalls Skip-Verbindungen. Ähnlich wie LSTM verwenden auch diese Sprungverbindungen parametrische Gates. Diese Gates bestimmen, wie viele Informationen durch die Skip-Verbindung geleitet werden. Diese Architektur bietet jedoch keine bessere Genauigkeit als die ResNet-Architektur.

Netzwerkarchitektur: Dieses Netzwerk verwendet eine 34-schichtige einfache Netzwerkarchitektur, die von VGG-19 inspiriert ist und in der dann die Verknüpfungsverbindung hinzugefügt wird. Diese Shortcut-Verbindungen wandeln die Architektur dann in ein Restnetzwerk um.

ResNet-34-Architektur



Implementierung: Mit der Tensorflow- und Keras-API können wir die ResNet-Architektur (einschließlich Restblöcke) von Grund auf entwerfen. Nachfolgend finden Sie die Implementierung verschiedener ResNet-Architekturen. Für diese Implementierung verwenden wir den CIFAR-10-Datensatz. Dieser Datensatz enthält 60.000 32×32-Farbbilder in 10 verschiedenen Klassen (Flugzeuge, Autos, Vögel, Katzen, Hirsche, Hunde, Frösche, Pferde, Schiffe und Lastwagen) usw. Dieser Datensatz kann aus k bewertet werden eras.datasets API-Funktion.

Schritt 1: Zuerst importieren wir das Keras-Modul und seine APIs. Diese APIs helfen beim Aufbau der Architektur des ResNet-Modells.

Code: Bibliotheken importieren

# Import Keras modules and its important APIs import keras from keras.layers import Dense, Conv2D, BatchNormalization, Activation from keras.layers import AveragePooling2D, Input, Flatten from keras.optimizers import Adam from keras.callbacks import ModelCheckpoint, LearningRateScheduler from keras.callbacks import ReduceLROnPlateau from keras.preprocessing.image import ImageDataGenerator from keras.regularizers import l2 from keras import backend as K from keras.models import Model from keras.datasets import cifar10 import numpy as np import os>

Schritt 2: Jetzt legen wir verschiedene Hyperparameter fest, die für die ResNet-Architektur erforderlich sind. Wir haben unseren Datensatz auch vorverarbeitet, um ihn für das Training vorzubereiten.

Code: Einstellen von Trainingshyperparametern

Python3




# Setting Training Hyperparameters> batch_size>=> 32> # original ResNet paper uses batch_size = 128 for training> epochs>=> 200> data_augmentation>=> True> num_classes>=> 10> > # Data Preprocessing> subtract_pixel_mean>=> True> n>=> 3> > # Select ResNet Version> version>=> 1> > # Computed depth of> if> version>=>=> 1>:> >depth>=> n>*> 6> +> 2> elif> version>=>=> 2>:> >depth>=> n>*> 9> +> 2> > # Model name, depth and version> model_type>=> 'ResNet % dv % d'> %> (depth, version)> > # Load the CIFAR-10 data.> (x_train, y_train), (x_test, y_test)>=> cifar10.load_data()> > # Input image dimensions.> input_shape>=> x_train.shape[>1>:]> > # Normalize data.> x_train>=> x_train.astype(>'float32'>)>/> 255> x_test>=> x_test.astype(>'float32'>)>/> 255> > # If subtract pixel mean is enabled> if> subtract_pixel_mean:> >x_train_mean>=> np.mean(x_train, axis>=> 0>)> >x_train>->=> x_train_mean> >x_test>->=> x_train_mean> > # Print Training and Test Samples> print>(>'x_train shape:'>, x_train.shape)> print>(x_train.shape[>0>],>'train samples'>)> print>(x_test.shape[>0>],>'test samples'>)> print>(>'y_train shape:'>, y_train.shape)> > # Convert class vectors to binary class matrices.> y_train>=> keras.utils.to_categorical(y_train, num_classes)> y_test>=> keras.utils.to_categorical(y_test, num_classes)>

>

>

Schritt 3: In diesem Schritt legen wir die Lernrate entsprechend der Anzahl der Epochen fest. Mit zunehmender Anzahl der Epochen muss die Lernrate verringert werden, um ein besseres Lernen zu gewährleisten.

Code: Einstellen von LR für unterschiedliche Anzahlen von Epochen

Python3




# Setting LR for different number of Epochs> def> lr_schedule(epoch):> >lr>=> 1e>->3> >if> epoch>>180>:> >lr>*>=> 0.5e>->3> >elif> epoch>>160>:> >lr>*>=> 1e>->3> >elif> epoch>>120>:> >lr>*>=> 1e>->2> >elif> epoch>>80>:> >lr>*>=> 1e>->1> >print>(>'Learning rate: '>, lr)> >return> lr>

>

>

Schritt 4: Definieren Sie den grundlegenden ResNet-Baustein, der zum Definieren der ResNet V1- und V2-Architektur verwendet werden kann.

Code: Grundlegender ResNet-Baustein

Python3




# Basic ResNet Building Block> > > def> resnet_layer(inputs,> >num_filters>=>16>,> >kernel_size>=>3>,> >strides>=>1>,> >activation>=>'relu'>,> >batch_normalization>=>True>,> >conv>=>Conv2D(num_filters,> >kernel_size>=>kernel_size,> >strides>=>strides,> >padding>=>'same'>,> >kernel_initializer>=>'he_normal'>,> >kernel_regularizer>=>l2(>1e>->4>))> > >x>=>inputs> >if> conv_first:> >x>=> conv(x)> >if> batch_normalization:> >x>=> BatchNormalization()(x)> >if> activation>is> not> None>:> >x>=> Activation(activation)(x)> >else>:> >if> batch_normalization:> >x>=> BatchNormalization()(x)> >if> activation>is> not> None>:> >x>=> Activation(activation)(x)> >x>=> conv(x)> >return> x>

>

bash if-Anweisung

>

Schritt 5: Definieren Sie die ResNet V1-Architektur, die auf dem oben definierten ResNet-Baustein basiert:

Code: ResNet V1-Architektur

Python3




def> resnet_v1(input_shape, depth, num_classes>=>10>):> > >if> (depth>-> 2>)>%> 6> !>=> 0>:> >raise> ValueError(>'depth should be 6n + 2 (eg 20, 32, 44 in [a])'>)> ># Start model definition.> >num_filters>=> 16> >num_res_blocks>=> int>((depth>-> 2>)>/> 6>)> > >inputs>=> Input>(shape>=>input_shape)> >x>=> resnet_layer(inputs>=>inputs)> ># Instantiate the stack of residual units> >for> stack>in> range>(>3>):> >for> res_block>in> range>(num_res_blocks):> >strides>=> 1> >if> stack & gt> >0> and> res_block>=>=> 0>:># first layer but not first stack> >strides>=> 2> # downsample> >y>=> resnet_layer(inputs>=>x,> >num_filters>=>num_filters,> >strides>=>strides)> >y>=> resnet_layer(inputs>=>y,> >num_filters>=>num_filters,> >activation>=>None>)> >if> stack & gt> >0> and> res_block>=>=> 0>:># first layer but not first stack> ># linear projection residual shortcut connection to match> ># changed dims> >x>=> resnet_layer(inputs>=>x,> >num_filters>=>num_filters,> >kernel_size>=>1>,> >strides>=>strides,> >activation>=>None>,> >batch_normalization>=>False>)> >x>=> keras.layers.add([x, y])> >x>=> Activation(>'relu'>)(x)> >num_filters>*>=> 2> > ># Add classifier on top.> ># v1 does not use BN after last shortcut connection-ReLU> >x>=> AveragePooling2D(pool_size>=>8>)(x)> >y>=> Flatten()(x)> >outputs>=> Dense(num_classes,> >activation>=>'softmax'>,> >kernel_initializer>=>'he_normal'>)(y)> > ># Instantiate model.> >model>=> Model(inputs>=>inputs, outputs>=>outputs)> >return> model>

>

>

Schritt 6: Definieren Sie die ResNet V2-Architektur, die auf dem oben definierten ResNet-Baustein basiert:

Code: ResNet V2-Architektur

Python3




# ResNet V2 architecture> def> resnet_v2(input_shape, depth, num_classes>=>10>):> >if> (depth>-> 2>)>%> 9> !>=> 0>:> >raise> ValueError(>'depth should be 9n + 2 (eg 56 or 110 in [b])'>)> ># Start model definition.> >num_filters_in>=> 16> >num_res_blocks>=> int>((depth>-> 2>)>/> 9>)> > >inputs>=> Input>(shape>=>input_shape)> ># v2 performs Conv2D with BN-ReLU on input before splitting into 2 paths> >x>=> resnet_layer(inputs>=>inputs,> >num_filters>=>num_filters_in,> >conv_first>=>True>)> > ># Instantiate the stack of residual units> >for> stage>in> range>(>3>):> >for> res_block>in> range>(num_res_blocks):> >activation>=> 'relu'> >batch_normalization>=> True> >strides>=> 1> >if> stage>=>=> 0>:> >num_filters_out>=> num_filters_in>*> 4> >if> res_block>=>=> 0>:># first layer and first stage> >activation>=> None> >batch_normalization>=> False> >else>:> >num_filters_out>=> num_filters_in>*> 2> >if> res_block>=>=> 0>:># first layer but not first stage> >strides>=> 2> # downsample> > ># bottleneck residual unit> >y>=> resnet_layer(inputs>=>x,> >num_filters>=>num_filters_in,> >kernel_size>=>1>,> >strides>=>strides,> >activation>=>activation,> >batch_normalization>=>batch_normalization,> >conv_first>=>False>)> >y>=> resnet_layer(inputs>=>y,> >num_filters>=>num_filters_in,> >conv_first>=>False>)> >y>=> resnet_layer(inputs>=>y,> >num_filters>=>num_filters_out,> >kernel_size>=>1>,> >conv_first>=>False>)> >if> res_block>=>=> 0>:> ># linear projection residual shortcut connection to match> ># changed dims> >x>=> resnet_layer(inputs>=>x,> >num_filters>=>num_filters_out,> >kernel_size>=>1>,> >strides>=>strides,> >activation>=>None>,> >batch_normalization>=>False>)> >x>=> keras.layers.add([x, y])> > >num_filters_in>=> num_filters_out> > ># Add classifier on top.> ># v2 has BN-ReLU before Pooling> >x>=> BatchNormalization()(x)> >x>=> Activation(>'relu'>)(x)> >x>=> AveragePooling2D(pool_size>=>8>)(x)> >y>=> Flatten()(x)> >outputs>=> Dense(num_classes,> >activation>=>'softmax'>,> >kernel_initializer>=>'he_normal'>)(y)> > ># Instantiate model.> >model>=> Model(inputs>=>inputs, outputs>=>outputs)> >return> model>

>

>

Schritt 7: Der folgende Code wird zum Trainieren und Testen der oben definierten ResNet v1- und v2-Architektur verwendet:

Code: Hauptfunktion

Python3




# Main function> if> version>=>=> 2>:> >model>=> resnet_v2(input_shape>=> input_shape, depth>=> depth)> else>:> >model>=> resnet_v1(input_shape>=> input_shape, depth>=> depth)> > model.>compile>(loss>=>'categorical_crossentropy'>,> >optimizer>=> Adam(learning_rate>=> lr_schedule(>0>)),> >metrics>=>[>'accuracy'>])> model.summary()> print>(model_type)> > # Prepare model saving directory.> save_dir>=> os.path.join(os.getcwd(),>'saved_models'>)> model_name>=> 'cifar10_% s_model.{epoch:03d}.h5'> %> model_type> if> not> os.path.isdir(save_dir):> >os.makedirs(save_dir)> filepath>=> os.path.join(save_dir, model_name)> > # Prepare callbacks for model saving and for learning rate adjustment.> checkpoint>=> ModelCheckpoint(filepath>=> filepath,> >monitor>=>'val_acc'>,> >verbose>=> 1>,> >save_best_only>=> True>)> > lr_scheduler>=> LearningRateScheduler(lr_schedule)> > lr_reducer>=> ReduceLROnPlateau(factor>=> np.sqrt(>0.1>),> >cooldown>=> 0>,> >patience>=> 5>,> >min_lr>=> 0.5e>->6>)> > callbacks>=> [checkpoint, lr_reducer, lr_scheduler]> > # Run training, with or without data augmentation.> if> not> data_augmentation:> >print>(>'Not using data augmentation.'>)> >model.fit(x_train, y_train,> >batch_size>=> batch_size,> >epochs>=> epochs,> >validation_data>=>(x_test, y_test),> >shuffle>=> True>,> >callbacks>=> callbacks)> else>:> >print>(>'Using real-time data augmentation.'>)> ># This will do preprocessing and realtime data augmentation:> >datagen>=> ImageDataGenerator(> ># set input mean to 0 over the dataset> >featurewise_center>=> False>,> ># set each sample mean to 0> >samplewise_center>=> False>,> ># divide inputs by std of dataset> >featurewise_std_normalization>=> False>,> ># divide each input by its std> >samplewise_std_normalization>=> False>,> ># apply ZCA whitening> >zca_whitening>=> False>,> ># epsilon for ZCA whitening> >zca_epsilon>=> 1e>->06>,> ># randomly rotate images in the range (deg 0 to 180)> >rotation_range>=> 0>,> ># randomly shift images horizontally> >width_shift_range>=> 0.1>,> ># randomly shift images vertically> >height_shift_range>=> 0.1>,> ># set range for random shear> >shear_range>=> 0.>,> ># set range for random zoom> >zoom_range>=> 0.>,> ># set range for random channel shifts> >channel_shift_range>=> 0.>,> ># set mode for filling points outside the input boundaries> >fill_mode>=>'nearest'>,> ># value used for fill_mode = 'constant'> >cval>=> 0.>,> ># randomly flip images> >horizontal_flip>=> True>,> ># randomly flip images> >vertical_flip>=> False>,> ># set rescaling factor (applied before any other transformation)> >rescale>=> None>,> ># set function that will be applied on each input> >preprocessing_function>=> None>,> ># image data format, either 'channels_first' or 'channels_last'> >data_format>=> None>,> ># fraction of images reserved for validation (strictly between 0 and 1)> >validation_split>=> 0.0>)> > ># Compute quantities required for featurewise normalization> ># (std, mean, and principal components if ZCA whitening is applied).> >datagen.fit(x_train)> > ># Fit the model on the batches generated by datagen.flow().> >model.fit_generator(datagen.flow(x_train, y_train, batch_size>=> batch_size),> >validation_data>=>(x_test, y_test),> >epochs>=> epochs, verbose>=> 1>, workers>=> 4>,> >callbacks>=> callbacks)> > # Score trained model.> scores>=> model.evaluate(x_test, y_test, verbose>=> 1>)> print>(>'Test loss:'>, scores[>0>])> print>(>'Test accuracy:'>, scores[>1>])>

>

>

Ergebnisse & Schlussfolgerung:
Für den ImageNet-Datensatz verwenden die Autoren ein ResNet mit 152 Schichten, das achtmal tiefer als VGG19 ist, aber immer noch weniger Parameter aufweist. Ein Ensemble dieser ResNets erzeugte im ImageNet-Testsatz einen Fehler von nur 3,7 %, das Ergebnis, das den ILSVRC 2015-Wettbewerb gewann. Auch im COCO-Objekterkennungsdatensatz wird aufgrund seiner sehr tiefen Darstellung eine relative Verbesserung von 28 % erzielt.

Fehlerrate bei der ResNet-Architektur

  • Das obige Ergebnis zeigt, dass Verknüpfungsverbindungen das durch die Erhöhung der Schichten verursachte Problem lösen könnten, da mit der Erhöhung der Schichten von 18 auf 34 auch die Fehlerrate im ImageNet Validation Set im Gegensatz zum einfachen Netzwerk abnimmt.

Top-1- und Top-5-Fehlerrate im ImageNet Validation Set.

  • Nachfolgend finden Sie die Ergebnisse des ImageNet-Testsatzes. Der 3,57 % Die Top-5-Fehlerrate von ResNet war am niedrigsten und daher belegte die ResNet-Architektur 2015 den ersten Platz bei der ImageNet-Klassifizierungsherausforderung.