Custom layers

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza il sorgente su GitHub Scarica il notebook

Raccomandiamo di usare tf.keras come API di alto livello per la costruzione di reti neurali. Detto ciò, molte delle API di TensorFLow sono utilizzabili tramite esecuzione eager.

import tensorflow as tf
print(tf.test.is_gpu_available())
WARNING:tensorflow:From <ipython-input-1-ae932be897c3>:1: is_gpu_available (from tensorflow.python.framework.test_util) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.list_physical_devices('GPU')` instead.
True

Layers: sets comuni di operazioni utili

Spesso scrivendo codice per modelli di machine learning vuoi operare a un livello di astrazione più alto delle singole operazioni e della manipolazione di singole variabili.

Molti modelli di machine learning sono esprimibili tramite composizione e impilamento di layer relativamente semplici e TensorFlow fornishe sia un set composto di molti layer comuni sia modalità semplici per te per scrivere layer specifici per la tua apllicazione da zero o come composizione di layer esistenti.

TensorFlow include l'API completa di Keras nel package tf.keras e i layer di Keras sono molto utili quando si creano i propri modelli.

# Nel package tf.keras.layers, i layers sono oggetti. Per costruire un layer,
# costruisci semplicemente l'oggetto. La maggior parte dei layer prendono come
# primo argomento il numero delle dimensioni / canali in output.
layer = tf.keras.layers.Dense(100)
# Il numero di dimensioni in input è spesso non necessario, dal momento che
# può essere inferita la prima volta che il layer è usato, ma può essere fornito
# se vuoi spcificarlo manualmente, il che è utile in alcuni modelli complessi.
layer = tf.keras.layers.Dense(10, input_shape=(None, 5))

La lista completa di layer preesistenti può essere vista nella documentazione. Questa include Dense (un layer completamente connesso), Conv2D, LSTM, BatchNormalization, Dropout e molti altri.

# Per usare un layer, è sufficente chiamarlo.
layer(tf.zeros([10, 5]))
<tf.Tensor: shape=(10, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
# I layers hanno molti metodi utili. Per esempio, puoi ispezionare tutte le
# variabili in un layer usando `layer.variables` e variabili allenabili usando
# `layer.trainable_variables`. In questo caso un layer completamente connesso
# avrà variabili per pesi e bias.
layer.variables
[<tf.Variable 'dense_1/kernel:0' shape=(5, 10) dtype=float32, numpy=
 array([[ 0.6060403 , -0.58118075,  0.5625821 ,  0.5742199 ,  0.5515261 ,
          0.1657598 , -0.06135309, -0.4850014 ,  0.17795801, -0.5731532 ],
        [-0.2838304 , -0.30454355,  0.08480155, -0.32337126,  0.34053242,
         -0.1909639 ,  0.36156857,  0.33472884, -0.5544799 , -0.39040756],
        [ 0.32538337,  0.1343522 , -0.18232489,  0.6212432 ,  0.16428417,
          0.5214228 , -0.3893617 , -0.53435075, -0.3268344 , -0.06557399],
        [-0.55410534,  0.15366578,  0.35757744, -0.09819478,  0.06112057,
         -0.03338379, -0.48052067, -0.13761154, -0.59619045,  0.4163794 ],
        [ 0.48326236, -0.4806478 , -0.1887767 , -0.6058129 , -0.2127184 ,
         -0.25420925,  0.47742623,  0.3853498 , -0.5200392 ,  0.26031548]],
       dtype=float32)>,
 <tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>]
# Le variabili sono  anche accessibili tramite accessori.
layer.kernel, layer.bias
(<tf.Variable 'dense_1/kernel:0' shape=(5, 10) dtype=float32, numpy=
 array([[ 0.6060403 , -0.58118075,  0.5625821 ,  0.5742199 ,  0.5515261 ,
          0.1657598 , -0.06135309, -0.4850014 ,  0.17795801, -0.5731532 ],
        [-0.2838304 , -0.30454355,  0.08480155, -0.32337126,  0.34053242,
         -0.1909639 ,  0.36156857,  0.33472884, -0.5544799 , -0.39040756],
        [ 0.32538337,  0.1343522 , -0.18232489,  0.6212432 ,  0.16428417,
          0.5214228 , -0.3893617 , -0.53435075, -0.3268344 , -0.06557399],
        [-0.55410534,  0.15366578,  0.35757744, -0.09819478,  0.06112057,
         -0.03338379, -0.48052067, -0.13761154, -0.59619045,  0.4163794 ],
        [ 0.48326236, -0.4806478 , -0.1887767 , -0.6058129 , -0.2127184 ,
         -0.25420925,  0.47742623,  0.3853498 , -0.5200392 ,  0.26031548]],
       dtype=float32)>,
 <tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>)

Implementare layer custom

Il modo migliore per implementare il tuo proprio layer è estendere la classe tf.keras.Layer e implementare:

  1. __init__ , dove puoi fare tutte le inizializzazioni indipendenti dall'input
  2. build, dove sai la forma dei tensori di input e puoi fare il resto dell'inizializzazione
  3. call, dove puoi fare il calcolo forward

Nota che non devi aspettare fino a che build sia chiamata per creare le tue variabili, puoi anche crearle in __init__. Tuttavia, il vantaggio di creare in build sta nella possibilità di creare le variabili dopo, basandosi sulla forma dell'input su cui il layer opererà. Dall'altro lato, creare le variabili in __init__ significherebbe che la forma richiesta per creare le variabili dovra' esspere esplicitamente specificata.

class MyDenseLayer(tf.keras.layers.Layer):
  def __init__(self, num_outputs):
    super(MyDenseLayer, self).__init__()
    self.num_outputs = num_outputs

  def build(self, input_shape):
    self.kernel = self.add_weight("kernel",
                                  shape=[int(input_shape[-1]),
                                         self.num_outputs])

  def call(self, input):
    return tf.matmul(input, self.kernel)

layer = MyDenseLayer(10)
_ = layer(tf.zeros([10, 5])) # Calling the layer `.builds` it.
print([var.name for var in layer.trainable_variables])
['my_dense_layer/kernel:0']

Il codice in generale è più facile da leggere e mantenere se usa layer standard ogniqualvolta possibile, dal momento che i lettori saranno più familari col comportamento di layer standard. Se vuoi utilizzare un layer che non è present in tf.keras.layers, considera di creare una issue github o, ancora meglio, di mandarci una pull request!

Modelli: Comporre layers

Molte cose interessanti sullo stile dei layer nei modelli di machine learning sono implementate componendo layer esistenti. Per esempio, ogni blocco residuale in una resnet è la composizione di convoluzioni, normalizzazioni di batch e una scorciatoia. I layers possono essere innestati dentro altri layer.

Tipicamente erediti da keras.Model quando necessiti di metodi per i modelli tra i quali: Model.fit,Model.evaluate e Model.save (vedi Layer Custom Keras e modelli per dettagli).

Un altra feature fornita da keras.Model (invece di keras.layers.Layer) è che in aggiunta al tenere traccia delle variabii, un keras.Model tiene traccia anche dei suoi layer interni, rendendoli più semplici per l'ispezione,

Per esempio ecco qui un blocco ResNet:

class ResnetIdentityBlock(tf.keras.Model):
  def __init__(self, kernel_size, filters):
    super(ResnetIdentityBlock, self).__init__(name='')
    filters1, filters2, filters3 = filters

    self.conv2a = tf.keras.layers.Conv2D(filters1, (1, 1))
    self.bn2a = tf.keras.layers.BatchNormalization()

    self.conv2b = tf.keras.layers.Conv2D(filters2, kernel_size, padding='same')
    self.bn2b = tf.keras.layers.BatchNormalization()

    self.conv2c = tf.keras.layers.Conv2D(filters3, (1, 1))
    self.bn2c = tf.keras.layers.BatchNormalization()

  def call(self, input_tensor, training=False):
    x = self.conv2a(input_tensor)
    x = self.bn2a(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2b(x)
    x = self.bn2b(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2c(x)
    x = self.bn2c(x, training=training)

    x += input_tensor
    return tf.nn.relu(x)


block = ResnetIdentityBlock(1, [1, 2, 3])
_ = block(tf.zeros([1, 2, 3, 3])) 
block.layers
[<tensorflow.python.keras.layers.convolutional.Conv2D at 0x7f7df1a08908>,
 <tensorflow.python.keras.layers.normalization_v2.BatchNormalization at 0x7f7df1a08be0>,
 <tensorflow.python.keras.layers.convolutional.Conv2D at 0x7f7df1a08e48>,
 <tensorflow.python.keras.layers.normalization_v2.BatchNormalization at 0x7f7df1a10128>,
 <tensorflow.python.keras.layers.convolutional.Conv2D at 0x7f7df1a10358>,
 <tensorflow.python.keras.layers.normalization_v2.BatchNormalization at 0x7f7df1a105f8>]
len(block.variables)
18
block.summary()
Model: "resnet_identity_block"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              multiple                  4         
_________________________________________________________________
batch_normalization (BatchNo multiple                  4         
_________________________________________________________________
conv2d_1 (Conv2D)            multiple                  4         
_________________________________________________________________
batch_normalization_1 (Batch multiple                  8         
_________________________________________________________________
conv2d_2 (Conv2D)            multiple                  9         
_________________________________________________________________
batch_normalization_2 (Batch multiple                  12        
=================================================================
Total params: 41
Trainable params: 29
Non-trainable params: 12
_________________________________________________________________

La maggior parte delle volte, tuttavia, i modelli che compongono molti layer sono semplicemente chiamati un layer alla volta. Questo può essere fatto con poco codice usando tf.keras.Sequential:

my_seq = tf.keras.Sequential([tf.keras.layers.Conv2D(1, (1, 1),
                                                    input_shape=(
                                                        None, None, 3)),
                             tf.keras.layers.BatchNormalization(),
                             tf.keras.layers.Conv2D(2, 1,
                                                    padding='same'),
                             tf.keras.layers.BatchNormalization(),
                             tf.keras.layers.Conv2D(3, (1, 1)),
                             tf.keras.layers.BatchNormalization()])
my_seq(tf.zeros([1, 2, 3, 3]))
<tf.Tensor: shape=(1, 2, 3, 3), dtype=float32, numpy=
array([[[[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]],

        [[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]]]], dtype=float32)>
my_seq.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_3 (Conv2D)            (None, None, None, 1)     4         
_________________________________________________________________
batch_normalization_3 (Batch (None, None, None, 1)     4         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, None, None, 2)     4         
_________________________________________________________________
batch_normalization_4 (Batch (None, None, None, 2)     8         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, None, None, 3)     9         
_________________________________________________________________
batch_normalization_5 (Batch (None, None, None, 3)     12        
=================================================================
Total params: 41
Trainable params: 29
Non-trainable params: 12
_________________________________________________________________

Passi successivi

Ora puoi tornare indietro al notebook precedente e addattare l'esempio della regressione lineare per usare layer e modelli affiché sia meglio strutturato.