[Python] Conversion of (old) Keras (version) model weights to Pytorch

Discussão em 'Python' iniciado por Stack, Março 30, 2025.

  1. Stack

    Stack Membro Participativo

    I'm currently want to transfer pretrained weights from an old project on github : https://github.com/ajgallego/staff-lines-removal

    The original Keras model code is:

    def get_keras_autoencoder(self, input_size=256, nb_filter=96, k_size=5):

    input_img = Input(shape=(1, input_size, input_size))

    conv1 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv1')(input_img)
    maxp1 = MaxPooling2D((2, 2), border_mode='same', name='maxp1')(conv1)

    conv2 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv2')(maxp1)
    maxp2 = MaxPooling2D((2, 2), border_mode='same', name='maxp2')(conv2)

    conv3 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv3')(maxp2)
    encoder = MaxPooling2D((2, 2), border_mode='same', name='encoder')(conv3)

    conv4 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv4')(encoder)
    upsa1 = UpSampling2D((2, 2), name='upsa1')(conv4)

    conv4 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv5')(upsa1)
    upsa2 = UpSampling2D((2, 2), name='upsa2')(conv4)

    conv5 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv6')(upsa2)
    upsa3 = UpSampling2D((2, 2), name='upsa3')(conv5)

    decoder = Convolution2D(1, k_size, k_size, activation='sigmoid', border_mode='same')(upsa3)

    autoencoder = Model(input_img, decoder)

    return autoencoder


    The ported PyTorch code I've done is:

    class SAEModel(nn.Module):
    def __init__(self):
    super(SAEModel, self).__init__()

    # encoder
    self.conv1 = nn.Conv2d( 1, 96, kernel_size=5, stride=1, padding=2)
    self.maxp1 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
    self.conv2 = nn.Conv2d(96, 96, kernel_size=5, stride=1, padding=2)
    self.maxp2 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
    self.conv3 = nn.Conv2d(96, 96, kernel_size=5, stride=1, padding=2)
    self.encod = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)

    # decoder
    self.conv4 = nn.Conv2d(96, 96, kernel_size=5, stride=1, padding=2)
    self.upsa1 = nn.Upsample(scale_factor=2)
    self.conv5 = nn.Conv2d(96, 96, kernel_size=5, stride=1, padding=2)
    self.upsa2 = nn.Upsample(scale_factor=2)
    self.conv6 = nn.Conv2d(96, 96, kernel_size=5, stride=1, padding=2)
    self.upsa3 = nn.Upsample(scale_factor=2)
    self.decod = nn.Conv2d(96, 1, kernel_size=5, stride=1, padding=2)

    def forward(self, x):

    # encoder activations
    x = F.relu(self.conv1(x))
    x = self.maxp1(x)
    x = F.relu(self.conv2(x))
    x = self.maxp2(x)
    x = F.relu(self.conv3(x))
    x = self.encod(x)

    # decoder activations
    x = F.relu(self.conv4(x))
    x = self.upsa1(x)
    x = F.relu(self.conv5(x))
    x = self.upsa2(x)
    x = F.relu(self.conv6(x))
    x = self.upsa3(x)
    x = F.sigmoid(self.decod(x))

    return x


    The summary dump of the Keras model :

    ____________________________________________________________________________________________________
    Layer (type) Output Shape Param # Connected to
    ====================================================================================================
    input_1 (InputLayer) (None, 1, 256, 256) 0
    ____________________________________________________________________________________________________
    conv1 (Convolution2D) (None, 1, 256, 96) 2496 input_1[0][0]
    ____________________________________________________________________________________________________
    maxp1 (MaxPooling2D) (None, 1, 128, 96) 0 conv1[0][0]
    ____________________________________________________________________________________________________
    conv2 (Convolution2D) (None, 1, 128, 96) 230496 maxp1[0][0]
    ____________________________________________________________________________________________________
    maxp2 (MaxPooling2D) (None, 1, 64, 96) 0 conv2[0][0]
    ____________________________________________________________________________________________________
    conv3 (Convolution2D) (None, 1, 64, 96) 230496 maxp2[0][0]
    ____________________________________________________________________________________________________
    encoder (MaxPooling2D) (None, 1, 32, 96) 0 conv3[0][0]
    ____________________________________________________________________________________________________
    conv4 (Convolution2D) (None, 1, 32, 96) 230496 encoder[0][0]
    ____________________________________________________________________________________________________
    upsa1 (UpSampling2D) (None, 2, 64, 96) 0 conv4[0][0]
    ____________________________________________________________________________________________________
    conv5 (Convolution2D) (None, 2, 64, 96) 230496 upsa1[0][0]
    ____________________________________________________________________________________________________
    upsa2 (UpSampling2D) (None, 4, 128, 96) 0 conv5[0][0]
    ____________________________________________________________________________________________________
    conv6 (Convolution2D) (None, 4, 128, 96) 230496 upsa2[0][0]
    ____________________________________________________________________________________________________
    upsa3 (UpSampling2D) (None, 8, 256, 96) 0 conv6[0][0]
    ____________________________________________________________________________________________________
    convolution2d_1 (Convolution2D) (None, 8, 256, 1) 2401 upsa3[0][0]
    ====================================================================================================
    Total params: 1,157,377
    Trainable params: 1,157,377
    Non-trainable params: 0


    And the one for the ported PyTorch mode :

    ----------------------------------------------------------------
    Layer (type) Output Shape Param #
    ================================================================
    Conv2d-1 [-1, 96, 256, 256] 2,496
    MaxPool2d-2 [-1, 96, 128, 128] 0
    Conv2d-3 [-1, 96, 128, 128] 230,496
    MaxPool2d-4 [-1, 96, 64, 64] 0
    Conv2d-5 [-1, 96, 64, 64] 230,496
    MaxPool2d-6 [-1, 96, 32, 32] 0
    Conv2d-7 [-1, 96, 32, 32] 230,496
    Upsample-8 [-1, 96, 64, 64] 0
    Conv2d-9 [-1, 96, 64, 64] 230,496
    Upsample-10 [-1, 96, 128, 128] 0
    Conv2d-11 [-1, 96, 128, 128] 230,496
    Upsample-12 [-1, 96, 256, 256] 0
    Conv2d-13 [-1, 1, 256, 256] 2,401
    ================================================================
    Total params: 1,157,377
    Trainable params: 1,157,377
    Non-trainable params: 0
    ----------------------------------------------------------------
    Input size (MB): 0.25
    Forward/backward pass size (MB): 158.00
    Params size (MB): 4.42
    Estimated Total Size (MB): 162.67


    And finally the code to convert the weights :

    import os
    import argparse

    from pathlib import Path

    from keras.layers import Input, Convolution2D, MaxPooling2D, UpSampling2D
    from keras.models import Model
    from keras.models import load_model

    import torch
    import torch.nn as nn
    import torch.nn.functional as F

    from torchsummary import summary

    class Musica:

    def decompose_path(self, path):
    return Path(path).parent, Path(path).stem, Path(path).suffix

    def convert_keras_weights(self, weights_path):
    autoencoder = self.get_keras_autoencoder()
    autoencoder.load_weights(weights_path)
    directory, filename, extension = self.decompose_path(weights_path)
    autoencoder.save(os.path.join(directory, filename + '_model_included' + extension))

    def convert_keras_to_torch(self, full_model_path, print_models: bool = False, print_weights: bool = False):

    keras_model = load_model(full_model_path)
    if print_models:
    print(keras_model.summary())

    weights = keras_model.get_weights()

    torch_model = SAEModel()
    if print_models:
    summary(torch_model.cuda(), (1, 256, 256))

    if print_weights:
    print(f'Keras Weight {weights[0].shape} and Pytorch Weight {torch_model.conv1.weight.shape}')
    print(f'Keras Weight {weights[1].shape} and Pytorch Weight {torch_model.conv1.bias.shape}')
    print(f'Keras Weight {weights[2].shape} and Pytorch Weight {torch_model.conv2.weight.shape}')
    print(f'Keras Weight {weights[3].shape} and Pytorch Weight {torch_model.conv2.bias.shape}')
    print(f'Keras Weight {weights[4].shape} and Pytorch Weight {torch_model.conv3.weight.shape}')
    print(f'Keras Weight {weights[5].shape} and Pytorch Weight {torch_model.conv3.bias.shape}')
    print(f'Keras Weight {weights[6].shape} and Pytorch Weight {torch_model.conv4.weight.shape}')
    print(f'Keras Weight {weights[7].shape} and Pytorch Weight {torch_model.conv4.bias.shape}')
    print(f'Keras Weight {weights[8].shape} and Pytorch Weight {torch_model.conv5.weight.shape}')
    print(f'Keras Weight {weights[9].shape} and Pytorch Weight {torch_model.conv5.bias.shape}')
    print(f'Keras Weight {weights[10].shape} and Pytorch Weight {torch_model.conv6.weight.shape}')
    print(f'Keras Weight {weights[11].shape} and Pytorch Weight {torch_model.conv6.bias.shape}')
    print(f'Keras Weight {weights[12].shape} and Pytorch Weight {torch_model.decod.weight.shape}')
    print(f'Keras Weight {weights[13].shape} and Pytorch Weight {torch_model.decod.bias.shape}')

    # load keras weights into torch model
    torch_model.conv1.weight.data = torch.from_numpy(weights[0])
    torch_model.conv1.bias.data = torch.from_numpy(weights[1])
    torch_model.conv2.weight.data = torch.from_numpy(weights[2])
    torch_model.conv2.bias.data = torch.from_numpy(weights[3])
    torch_model.conv3.weight.data = torch.from_numpy(weights[4])
    torch_model.conv3.bias.data = torch.from_numpy(weights[5])
    torch_model.conv4.weight.data = torch.from_numpy(weights[6])
    torch_model.conv4.bias.data = torch.from_numpy(weights[7])
    torch_model.conv5.weight.data = torch.from_numpy(weights[8])
    torch_model.conv5.bias.data = torch.from_numpy(weights[9])
    torch_model.conv6.weight.data = torch.from_numpy(weights[10])
    torch_model.conv6.bias.data = torch.from_numpy(weights[11])
    torch_model.decod.weight.data = torch.from_numpy(weights[12])
    torch_model.decod.bias.data = torch.from_numpy(weights[13])

    directory, filename, extension = self.decompose_path(full_model_path)
    export_path = os.path.join(directory, filename + '_torch' + '.pth')

    torch.save(torch_model.state_dict(), export_path)

    def get_keras_autoencoder(self, input_size=256, nb_filter=96, k_size=5):

    input_img = Input(shape=(1, input_size, input_size))

    conv1 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv1')(input_img)
    maxp1 = MaxPooling2D((2, 2), border_mode='same', name='maxp1')(conv1)

    conv2 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv2')(maxp1)
    maxp2 = MaxPooling2D((2, 2), border_mode='same', name='maxp2')(conv2)

    conv3 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv3')(maxp2)
    encoder = MaxPooling2D((2, 2), border_mode='same', name='encoder')(conv3)

    conv4 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv4')(encoder)
    upsa1 = UpSampling2D((2, 2), name='upsa1')(conv4)

    conv4 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv5')(upsa1)
    upsa2 = UpSampling2D((2, 2), name='upsa2')(conv4)

    conv5 = Convolution2D(nb_filter, k_size, k_size, activation='relu', border_mode='same', name='conv6')(upsa2)
    upsa3 = UpSampling2D((2, 2), name='upsa3')(conv5)

    decoder = Convolution2D(1, k_size, k_size, activation='sigmoid', border_mode='same')(upsa3)

    autoencoder = Model(input_img, decoder)

    return autoencoder


    The weights transpose dump:

    Keras Weight (96, 1, 5, 5) and Pytorch Weight torch.Size([96, 1, 5, 5])
    Keras Weight (96,) and Pytorch Weight torch.Size([96])
    Keras Weight (96, 96, 5, 5) and Pytorch Weight torch.Size([96, 96, 5, 5])
    Keras Weight (96,) and Pytorch Weight torch.Size([96])
    Keras Weight (96, 96, 5, 5) and Pytorch Weight torch.Size([96, 96, 5, 5])
    Keras Weight (96,) and Pytorch Weight torch.Size([96])
    Keras Weight (96, 96, 5, 5) and Pytorch Weight torch.Size([96, 96, 5, 5])
    Keras Weight (96,) and Pytorch Weight torch.Size([96])
    Keras Weight (96, 96, 5, 5) and Pytorch Weight torch.Size([96, 96, 5, 5])
    Keras Weight (96,) and Pytorch Weight torch.Size([96])
    Keras Weight (96, 96, 5, 5) and Pytorch Weight torch.Size([96, 96, 5, 5])
    Keras Weight (96,) and Pytorch Weight torch.Size([96])
    Keras Weight (1, 96, 5, 5) and Pytorch Weight torch.Size([1, 96, 5, 5])
    Keras Weight (1,) and Pytorch Weight torch.Size([1])


    All work well and I run the pytorch model using patched image (into 256x256 blocks). Unfortunatly, the model fail to do the job (sheet music staff-line remover) and got a slightly padded output (few pixels to right/bottom). I suspect a problem with the padding='same' that introduce some padding divergence.

    Here is the input image :

    [​IMG]

    Here is the same image binarized :

    [​IMG]

    Here is the output image :

    [​IMG]

    I precise that the code of the patching to process the image work well and well tested before, so the horizontal and vertical lines are not a patching problem but the result of the bad processing of the patch inside the model.

    Thank you very much for your help.

    Continue reading...

Compartilhe esta Página