logo

Xarxes residuals (ResNet) – Aprenentatge profund

Després de la primera arquitectura basada en CNN (AlexNet) que va guanyar el concurs ImageNet 2012, cada arquitectura guanyadora posterior utilitza més capes en una xarxa neuronal profunda per reduir la taxa d'error. Això funciona per a menys nombre de capes, però quan augmentem el nombre de capes, hi ha un problema comú en l'aprenentatge profund associat amb el que s'anomena gradient de desaparició/explosió. Això fa que el gradient sigui 0 o massa gran. Així, quan augmentem el nombre de capes, la taxa d'error d'entrenament i prova també augmenta.

Comparació de l'arquitectura de 20 capes i de 56 capes



A la trama anterior, podem observar que una CNN de 56 capes ofereix més taxa d'error tant en el conjunt de dades d'entrenament com de prova que una arquitectura CNN de 20 capes. Després d'analitzar més sobre la taxa d'error, els autors van poder arribar a la conclusió que és causat per un gradient de desaparició/explosió.
ResNet, que va ser proposat el 2015 per investigadors de Microsoft Research, va introduir una nova arquitectura anomenada Residual Network.

Xarxa residual: Per tal de resoldre el problema del gradient de desaparició/explosió, aquesta arquitectura va introduir el concepte anomenat blocs residuals. En aquesta xarxa, fem servir una tècnica anomenada saltar connexions . La connexió de salt connecta les activacions d'una capa amb capes addicionals saltant algunes capes entremig. Això forma un bloc residual. Els resnets es fan apilant aquests blocs residuals.
L'enfocament que hi ha darrere d'aquesta xarxa és, en comptes de capes que aprenen el mapeig subjacent, permetem que la xarxa s'ajusti al mapeig residual. Per tant, en comptes de dir H(x), mapeig inicial , deixar cabre la xarxa,

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

Omet la connexió (drecera).



L'avantatge d'afegir aquest tipus de connexió de salt és que si alguna capa perjudica el rendiment de l'arquitectura, es saltarà per regularització. Per tant, això resulta en l'entrenament d'una xarxa neuronal molt profunda sense els problemes causats pel gradient de desaparició/explosió. Els autors del document van experimentar amb 100-1000 capes del conjunt de dades CIFAR-10.
Hi ha un enfocament similar anomenat xarxes d'autopistes, aquestes xarxes també utilitzen connexió de salt. De manera similar a LSTM, aquestes connexions de salt també utilitzen portes paramètriques. Aquestes portes determinen quanta informació passa per la connexió de salt. Tanmateix, aquesta arquitectura no ha proporcionat una precisió millor que l'arquitectura ResNet.

Arquitectura de xarxa: Aquesta xarxa utilitza una arquitectura de xarxa senzilla de 34 capes inspirada en VGG-19 a la qual després s'afegeix la connexió de drecera. Aquestes connexions de drecera converteixen llavors l'arquitectura en una xarxa residual.

Arquitectura ResNet -34



Implementació: Utilitzant l'API Tensorflow i Keras, podem dissenyar l'arquitectura ResNet (inclosos els blocs residuals) des de zero. A continuació es mostra la implementació de diferents arquitectures ResNet. Per a aquesta implementació, utilitzem el conjunt de dades CIFAR-10. Aquest conjunt de dades conté 60.000 imatges en color de 32×32 en 10 classes diferents (avions, cotxes, ocells, gats, cérvols, gossos, granotes, cavalls, vaixells i camions), etc. Aquest conjunt de dades es pot avaluar a partir de k eras.datasets Funció API.

Pas 1: Primer, importem el mòdul keras i les seves API. Aquestes API ajuden a construir l'arquitectura del model ResNet.

Codi: Importació de biblioteques

# 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>

Pas 2: Ara, establim diferents hiperparàmetres necessaris per a l'arquitectura ResNet. També hem fet un preprocessament al nostre conjunt de dades per preparar-lo per a la formació.

Codi: Establiment d'hiperparàmetres d'entrenament

mètodes abstractes

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)>

>

>

Pas 3: En aquest pas, establim la taxa d'aprenentatge segons el nombre d'èpoques. A mesura que el nombre d'èpoques s'ha de reduir la taxa d'aprenentatge per garantir un millor aprenentatge.

Codi: Configuració de LR per a diferents nombres d'èpoques

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>

>

>

importació formiga

Pas 4: Definiu el bloc bàsic ResNet que es pot utilitzar per definir l'arquitectura ResNet V1 i V2.

Codi: Bloc bàsic ResNet

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>

>

>

Pas 5: Definiu l'arquitectura ResNet V1 que es basa en el bloc de construcció ResNet que hem definit anteriorment:

Codi: Arquitectura ResNet V1

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>

>

>

Pas 6: Definiu l'arquitectura ResNet V2 que es basa en el bloc de construcció ResNet que hem definit anteriorment:

Codi: Arquitectura ResNet V2

python3




què són els selectors en css

# 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>

>

>

Pas 7: El codi següent s'utilitza per entrenar i provar l'arquitectura ResNet v1 i v2 que hem definit anteriorment:

Codi: funció principal

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>])>

>

>

Resultats i conclusions:
Al conjunt de dades ImageNet, els autors utilitzen un ResNet de 152 capes, que és 8 vegades més profund que VGG19, però encara té menys paràmetres. Un conjunt d'aquests ResNets va generar un error de només un 3,7% al conjunt de proves ImageNet, el resultat que va guanyar la competició ILSVRC 2015. En el conjunt de dades de detecció d'objectes COCO, també genera una millora relativa del 28% a causa de la seva representació molt profunda.

Taxa d'error a l'arquitectura ResNet

  • El resultat anterior mostra que les connexions de drecera podrien resoldre el problema causat per augmentar les capes perquè a mesura que augmentem les capes de 18 a 34, la taxa d'error al conjunt de validació d'ImageNet també disminueix a diferència de la xarxa normal.

Top-1 i top-5 Taxa d'error al conjunt de validació d'ImageNet.

  • A continuació es mostren els resultats a ImageNet Test Set. El 3.57% La taxa d'error 5 de ResNet va ser la més baixa i, per tant, l'arquitectura ResNet va ser la primera al repte de classificació d'ImageNet el 2015.