Keras - Transfer learning - zmiana kształtu tensora wejściowego


15

Ten post wydaje się wskazywać, że to, co chcę osiągnąć, nie jest możliwe. Nie jestem jednak do tego przekonany - biorąc pod uwagę to, co już zrobiłem, nie rozumiem, dlaczego nie mogę osiągnąć tego, co chcę zrobić ...

Mam dwa zestawy danych obrazów, w których jeden ma obrazy kształtu (480, 720, 3), a drugi ma obrazy kształtu (540, 960, 3).

Zainicjowałem model przy użyciu następującego kodu:

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

Teraz, kiedy wyszkoliłem ten model na poprzednim zestawie danych, chciałbym usunąć warstwę tensora wejściowego i przygotować model z nowym tensorem wejściowym o kształcie pasującym do wymiarów obrazu tego drugiego zestawu danych.

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

co powoduje ten błąd:

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'

W poście, który podłączyłem, maz stwierdza, że ​​istnieje niedopasowanie wymiarów, które uniemożliwia zmianę warstwy wejściowej modelu - jeśli tak było, to w jaki sposób umieszczam warstwę wejściową (480, 720, 3) z przodu modelu VGG16, który oczekuje (224, 224, 3) obrazów?

Wydaje mi się, że bardziej prawdopodobnym problemem jest to, że wyniki mojego poprzedniego modelu oczekują czegoś innego niż to, co daję na podstawie tego, co fchollet mówi w tym poście . Jestem zagubiony pod względem składniowym, ale wierzę, że cały x = Layer()(x)segment konstruuje warstwę kawałek po kawałku z input-> output i po prostu rzucanie innym wejściem z przodu go psuje.

Naprawdę nie mam pojęcia ...

Czy ktoś może mi oświecić, jak osiągnąć to, co próbuję zrobić, a jeśli nie jest to możliwe, wytłumacz mi, dlaczego nie?


rozwiązałeś to?
tktktk0711

Odpowiedzi:


4

Możesz to zrobić, tworząc nową instancję modelu VGG16 z nowym kształtem wejściowym new_shapei kopiując wszystkie grubości warstw. Kod jest z grubsza

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

Wypróbowałem to z
incepcją V3

@ r-zip Pojawia się błąd: 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) i to jest warstwa wejściowa, więc użyj [2:]?
mLstudent33,

1

Szerokość wyjściowa i wysokość wymiarów wyjściowych sieci VGGnet stanowią stałą część szerokości i wysokości wejściowej, ponieważ jedynymi warstwami, które zmieniają te wymiary, są warstwy puli. Liczba kanałów na wyjściu jest ustalona na liczbę filtrów w ostatniej warstwie splotowej. Spłaszczona warstwa przekształci to, aby uzyskać jeden wymiar kształtu:

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

gdzie x to część dziesiętna <1.

Głównym punktem jest to, że kształt danych wejściowych do warstw gęstych zależy od szerokości i wysokości danych wejściowych do całego modelu. Wejście kształtu do gęstej warstwy nie może się zmienić, ponieważ oznaczałoby to dodanie lub usunięcie węzłów z sieci neuronowej.

Jednym ze sposobów na uniknięcie tego jest użycie globalnej warstwy puli zamiast spłaszczania (zwykle GlobalAveragePooling2D). To pozwoli znaleźć średnią na kanał, powodując, że kształt danych wejściowych do warstw gęstych będzie taki, (channels,)który nie zależy od kształtu wejściowego cały model.

Po wykonaniu tej czynności żadna z warstw w sieci nie jest zależna od szerokości i wysokości wejścia, więc warstwę wejściową można zmienić za pomocą czegoś takiego

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

model.layers[0] = input_layernie działa dla mnie w TF 2.1. Nie ma błędu, ale warstwa nie jest w rzeczywistości zastępowana. Wygląda na to, że kopiowanie wag może być bardziej niezawodne (zobacz inne odpowiedzi).
z0r

0

Oto inne rozwiązanie, nie specyficzne dla modelu VGG.

Zauważ, że ciężarów gęstej warstwy nie można skopiować (i dlatego zostaną one nowo zainicjowane). Ma to sens, ponieważ kształt obciążników różni się w starym i nowym modelu.

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

To powinno być dość łatwe kerassurgeon. Najpierw musisz zainstalować bibliotekę; w zależności od tego, czy korzystasz z Keras za pośrednictwem TensorFlow (z tf 2.0 i nowszymi) lub Keras jako osobnej biblioteki, należy go zainstalować na różne sposoby.

W przypadku Keras w TF: pip install tfkerassurgeon( https://github.com/Raukk/tf-keras-surgeon ). W przypadku samodzielnego Keras: pip install kerassurgeon( https://github.com/BenWhetton/keras-surgeon )

Aby zamienić dane wejściowe (przykład na TF 2.0; obecnie nieprzetestowany kod):

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

Odpowiedź @gebbissimo działała dla mnie w TF2 z niewielkimi adaptacjami, które udostępniam poniżej w jednej funkcji:

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

W ten sposób zmieniam rozmiar wejściowy w modelu Keras. Mam dwa modele CNN, jeden z rozmiarem wejściowym [Brak, Brak, 3], a drugi ma rozmiar wejściowy [512,512,3]. Oba modele mają te same ciężary. Za pomocą set_weights (model.get_weights ()), wagi modelu 1 można przenieść do modelu 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())
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.