Keras - Transfer learning - modifica della forma del tensore di input


15

Questo post sembra indicare che ciò che voglio realizzare non è possibile. Tuttavia, non ne sono convinto - dato quello che ho già fatto, non vedo perché ciò che voglio fare non possa essere raggiunto ...

Ho due set di dati di immagini in cui uno ha immagini di forma (480, 720, 3) mentre l'altro ha immagini di forma (540, 960, 3).

Ho inizializzato un modello utilizzando il seguente codice:

input = Input(shape=(480, 720, 3), name='image_input')

initial_model = VGG16(weights='imagenet', include_top=False)

for layer in initial_model.layers:
    layer.trainable = False

x = Flatten()(initial_model(input))
x = Dense(1000, activation='relu')(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
x = Dense(1000, activation='relu')(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
x = Dense(14, activation='linear')(x)

model = Model(inputs=input, outputs=x)
model.compile(loss='mse', optimizer='adam', metrics=['mae'])

Ora che ho addestrato questo modello sul set di dati precedente, vorrei rimuovere il livello del tensore di input e anteporre il modello con un nuovo tensore di input con una forma che corrisponda alle dimensioni dell'immagine di quest'ultimo set di dati.

model = load_model('path/to/my/trained/model.h5')
old_input = model.pop(0)
new_input = Input(shape=(540, 960, 3), name='image_input')
x = model(new_input)
m = Model(inputs=new_input, outputs=x)
m.save('transfer_model.h5')

che produce questo errore:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/aicg2/.local/lib/python2.7/site-packages/keras/engine/topology.py", line 2506, in save
    save_model(self, filepath, overwrite, include_optimizer)
  File "/home/aicg2/.local/lib/python2.7/site-packages/keras/models.py", line 106, in save_model
    'config': model.get_config()
  File "/home/aicg2/.local/lib/python2.7/site-packages/keras/engine/topology.py", line 2322, in get_config
    layer_config = layer.get_config()
  File "/home/aicg2/.local/lib/python2.7/site-packages/keras/engine/topology.py", line 2370, in get_config
    new_node_index = node_conversion_map[node_key]
KeyError: u'image_input_ib-0'

Nel post che ho collegato, maz afferma che esiste una mancata corrispondenza dimensionale che impedisce di modificare il livello di input di un modello - in questo caso, come posso mettere un livello di input (480, 720, 3) in primo piano del modello VGG16 che prevede (224, 224, 3) immagini?

Penso che un problema più probabile sia che l'output del mio ex modello si aspetti qualcosa di diverso da quello che sto dando in base a ciò che dice Fchollet in questo post . Sono sintatticamente confuso, ma credo che l'intero x = Layer()(x)segmento stia costruendo il layer pezzo per pezzo a partire da input-> output e semplicemente lanciare un input diverso davanti lo sta distruggendo.

Non ho davvero idea però ...

Qualcuno può illuminarmi come realizzare ciò che sto cercando di fare o, se non è possibile, spiegarmi perché no?


l'hai risolto?
tktktk0711,

Risposte:


4

Puoi farlo creando una nuova istanza del modello VGG16 con la nuova forma di input new_shapee copiando su tutti i pesi del livello. Il codice è approssimativamente

new_model = VGG16(weights=None, input_shape=new_shape, include_top=False)
for new_layer, layer in zip(new_model.layers[1:], model.layers[1:]):
    new_layer.set_weights(layer.get_weights())

Ho provato questo con InceptionV3, e diventa sempre più lento man mano che il ciclo continua
BachT

@ r-zip Ottengo un errore: Traceback (most recent call last): File "predict_video11.py", line 67, in <module> new_layer.set_weights(layer.get_weights()) File "/usr/local/lib/python2.7/dist-packages/keras/engine/base_layer.py", line 1057, in set_weights 'provided weight shape ' + str(w.shape)) ValueError: Layer weight shape (3, 3, 33, 64) not compatible with provided weight shape (3, 3, 9, 64) e questo è il livello di input, quindi usa [2:]?
mLstudent33,

1

La larghezza e l'altezza di output delle dimensioni di output di VGGnet sono una porzione fissa della larghezza e dell'altezza di input perché gli unici layer che modificano tali dimensioni sono i layer di pooling. Il numero di canali nell'uscita è fissato al numero di filtri nell'ultimo livello convoluzionale. Lo strato appiattito rimodellerà questo per ottenere una dimensione con la forma:

((input_width * x) * (input_height * x) * channels)

dove x è un decimale <1.

Il punto principale è che la forma dell'input per i layer Dense dipende dalla larghezza e dall'altezza dell'input per l'intero modello. L'input della forma nel layer denso non può cambiare poiché ciò significherebbe aggiungere o rimuovere nodi dalla rete neurale.

Un modo per evitarlo è utilizzare un livello di pooling globale anziché un livello appiattito (di solito GlobalAveragePooling2D), in questo modo la media per canale causa la forma dell'input nei layer Dense (channels,)che non dipende dalla forma di input per l'intero modello.

Una volta fatto questo, nessuno dei livelli nella rete dipende dalla larghezza e dall'altezza dell'input, quindi il livello di input può essere modificato con qualcosa di simile

input_layer = InputLayer(input_shape=(480, 720, 3), name="input_1")
model.layers[0] = input_layer

model.layers[0] = input_layernon funziona per me in TF 2.1. Non ci sono errori, ma il layer non è effettivamente sostituito. Sembra che la copia dei pesi possa essere più solida (vedi altre risposte).
z0r

0

Ecco un'altra soluzione, non specifica per il modello VGG.

Si noti che i pesi del layer denso non possono essere copiati (e saranno quindi inizializzati di nuovo). Questo ha senso, perché la forma dei pesi differisce dal vecchio e dal nuovo modello.

import keras
import numpy as np

def get_model():
    old_input_shape = (20, 20, 3)
    model = keras.models.Sequential()
    model.add(keras.layers.Conv2D(9, (3, 3), padding="same", input_shape=old_input_shape))
    model.add(keras.layers.MaxPooling2D((2, 2)))
    model.add(keras.layers.Flatten())
    model.add(keras.layers.Dense(1, activation="sigmoid"))
    model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.Adam(lr=0.0001), metrics=['acc'], )
    model.summary()
    return model

def change_model(model, new_input_shape=(None, 40, 40, 3)):
    # replace input shape of first layer
    model._layers[1].batch_input_shape = new_input_shape

    # feel free to modify additional parameters of other layers, for example...
    model._layers[2].pool_size = (8, 8)
    model._layers[2].strides = (8, 8)

    # rebuild model architecture by exporting and importing via json
    new_model = keras.models.model_from_json(model.to_json())
    new_model.summary()

    # copy weights from old model to new one
    for layer in new_model.layers:
        try:
            layer.set_weights(model.get_layer(name=layer.name).get_weights())
        except:
            print("Could not transfer weights for layer {}".format(layer.name))

    # test new model on a random input image
    X = np.random.rand(10, 40, 40, 3)
    y_pred = new_model.predict(X)
    print(y_pred)

    return new_model

if __name__ == '__main__':
    model = get_model()
    new_model = change_model(model)

0

Questo dovrebbe essere abbastanza facile con kerassurgeon. Per prima cosa devi installare la libreria; a seconda se si utilizza Keras tramite TensorFlow (con tf 2.0 e versioni successive) o Keras come libreria separata, è necessario installarlo in diversi modi.

Per Keras in TF: pip install tfkerassurgeon( https://github.com/Raukk/tf-keras-surgeon ). Per telecamere standalone: pip install kerassurgeon( https://github.com/BenWhetton/keras-surgeon )

Per sostituire l'input (esempio con TF 2.0; codice attualmente non testato):

from tensorflow import keras  # or import keras for standalone version
from tensorflow.keras.layers import Input

model = load_model('path/to/my/trained/model.h5')
new_input = Input(shape=(540, 960, 3), name='image_input')

# or kerassurgeon for standalone Keras
from tfkerassurgeon import delete_layer, insert_layer

model = delete_layer(model.layers[0])
# inserts before layer 0
model = insert_layer(model.layers[0], new_input)

0

La risposta di @gebbissimo ha funzionato per me in TF2 con solo piccoli adattamenti che condivido di seguito in un'unica funzione:

def change_input_size(model,h,w,ch=3):
   model._layers[0]._batch_input_shape = (None,h,w,ch)
   new_model = keras.models.model_from_json(model.to_json())
   new_model.summary()
   for layer,new_layer in zip(model.layers,new_model.layers):
      new_layer.set_weights(layer.get_weights())
   return new_model

0

In questo modo cambio la dimensione di input nel modello di Keras. Ho due modelli CNN, uno con dimensioni input [None, None, 3] mentre l'altro ha dimensioni input [512.512,3]. Entrambi i modelli hanno gli stessi pesi. Usando set_weights (model.get_weights ()), i pesi del modello 1 possono essere trasferiti al modello 2

inputs = Input((None, None, 3))
.....
model = Model(inputs=[inputs], outputs=[outputs])
model.compile(optimizer='adam', loss='mean_squared_error')
model.load_weights('my_model_name.h5')

inputs2 = Input((512, 512, 3))
....
model2 = Model(inputs=[inputs2], outputs=[outputs])
model2.compile(optimizer='adam', loss='mean_squared_error')
model2.set_weights(model.get_weights())
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.