CoreML: создание пользовательского слоя для ONNX RandomNormal - PullRequest
0 голосов
/ 16 февраля 2019

Я обучил VAE, что в PyTorch мне нужно конвертировать в CoreML.Из этого потока PyTorch VAE не удается преобразовать в onnx Мне удалось получить модель ONNX для экспорта, однако это только подтолкнуло проблему на один шаг дальше к этапу ONNX-CoreML.

исходная функция, которая содержит вызов torch.randn(), - это функция reparametrize:

def reparametrize(self, mu, logvar):
    std = logvar.mul(0.5).exp_()
    if self.have_cuda:
        eps = torch.randn(self.bs, self.nz, device='cuda')
    else:
        eps = torch.randn(self.bs, self.nz)
    return eps.mul(std).add_(mu)

Решение, конечно, состоит в создании пользовательского слоя, но у меня возникают проблемы при создании слоя без входных данных (т.е., это просто randn() вызов).

Я могу завершить преобразование CoreML с помощью этого определения:

def convert_randn(node):
    params = NeuralNetwork_pb2.CustomLayerParams()
    params.className = "RandomNormal"
    params.description = "Random normal distribution generator"
    params.parameters["dtype"].intValue = node.attrs.get('dtype', 1)
    params.parameters["bs"].intValue = node.attrs.get("shape")[0]
    params.parameters["nz"].intValue = node.attrs.get("shape")[1]
    return params

Я выполняю преобразование с помощью:

coreml_model = convert(onnx_model, add_custom_layers=True, 
    image_input_names = ['input'], 
    custom_conversion_functions={"RandomNormal": convert_randn})

Следует также отметить, что по завершении экспорта mlmodel выводится следующее:

Custom layers have been added to the CoreML model corresponding to the 
following ops in the onnx model: 
1/1: op type: RandomNormal, op input names and shapes: [], op output     
names and shapes: [('62', 'Shape not available')]

При вводе .mlmodel в Xcode жалуется, что Layer '62' of type 500 has 0 inputs but expects at least 1. Так что мне интересно, какуказать тип «фиктивного» ввода для слоя, поскольку на самом деле он не имеет ввода - это просто оболочка вокруг torch.randn() (или, более конкретно, onnx RandonNormal op).Я должен уточнить, что мне нужен весь VAE, а не только декодер, так как я на самом деле использую весь процесс для «исправления ошибок» моих входных данных (т. Е. Кодер оценивает мой z вектор на основе входных данных, затемдекодер генерирует наиболее близкий обобщенный прогноз ввода).

Любая помощь очень ценится.

ОБНОВЛЕНИЕ: Хорошо, я наконец-то получил версию для загрузки в XCode (благодаря @MattijsHollemans и его книге!).originalConversion.mlmodel - это начальный результат преобразования моей модели из ONNX в CoreML.Для этого мне пришлось вручную вставить вход для слоя RandomNormal.Я сделал это (64, 28, 28) без особой причины - я знаю, что мой размер пакета равен 64, и мои входные данные равны 28 x 28 (но, вероятно, это также может быть (1, 1, 1), так как это «пустышка»"):

spec = coremltools.utils.load_spec('originalConversion.mlmodel')
nn = spec.neuralNetwork
layers = {l.name:i for i,l in enumerate(nn.layers)}
layer_idx = layers["62"] # '62' is the name of the layer -- see above
layer = nn.layers[layer_idx]
layer.input.extend(["dummy_input"])

inp = spec.description.input.add()
inp.name = "dummy_input"
inp.type.multiArrayType.SetInParent()
spec.description.input[1].type.multiArrayType.shape.append(64)
spec.description.input[1].type.multiArrayType.shape.append(28)
spec.description.input[1].type.multiArrayType.shape.append(28)
spec.description.input[1].type.multiArrayType.dataType = ft.ArrayFeatureType.DOUBLE

coremltools.utils.save_spec(spec, "modelWithInsertedInput.mlmodel") 

Это загружается в XCode, но мне еще предстоит проверить работу модели в моем приложении.Поскольку дополнительный слой прост, а ввод буквально является фиктивным, нефункциональным вводом (просто для того, чтобы Xcode был доволен), я не думаю, что это будет проблемой, но я опубликую еще раз, если он не't работает правильно.

ОБНОВЛЕНИЕ 2: К сожалению, модель не загружается во время выполнения.Это не с [espresso] [Espresso::handle_ex_plan] exception=Failed in 2nd reshape after missing custom layer info. То, что я нахожу очень странным и сбивающим с толку, заключается в том, что, осматривая model.espresso.shape, я вижу, что почти каждый узел имеет форму, такую ​​как:

"62" : {
  "k" : 0,
  "w" : 0,
  "n" : 0,
  "seq" : 0,
  "h" : 0
}

У меня есть двавопрос / беспокойство: 1) наиболее очевидно, почему все значения равны нулю (это имеет место для всех, кроме входных узлов), и 2) почему это выглядит как последовательная модель, когда это просто довольно обычный VAE?Открывая model.espresso.shape для полностью работающей GAN в том же приложении, я вижу, что узлы имеют формат:

"54" : {
  "k" : 256,
  "w" : 16,
  "n" : 1,
  "h" : 16
}

То есть они содержат разумную информацию о форме, и они не делаютt имеют seq поля.

Очень, очень запутано ...

ОБНОВЛЕНИЕ 3: Я также только что заметил в отчете компилятора ошибку: IMPORTANT: new sequence length computation failed, falling back to old path. Your compilation was sucessful, but please file a radar on Core ML | Neural Networks and attach the model that generated this message.

Вот оригинальная модель PyTorch:

class VAE(nn.Module):
def __init__(self, bs, nz):
    super(VAE, self).__init__()

    self.nz = nz
    self.bs = bs

    self.encoder = nn.Sequential(
        # input is (nc) x 28 x 28
        nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
        nn.LeakyReLU(0.2, inplace=True),
        # size = (ndf) x 14 x 14
        nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
        nn.BatchNorm2d(ndf * 2),
        nn.LeakyReLU(0.2, inplace=True),
        # size = (ndf*2) x 7 x 7
        nn.Conv2d(ndf * 2, ndf * 4, 3, 2, 1, bias=False),
        nn.BatchNorm2d(ndf * 4),
        nn.LeakyReLU(0.2, inplace=True),
        # size = (ndf*4) x 4 x 4
        nn.Conv2d(ndf * 4, 1024, 4, 1, 0, bias=False),
        nn.LeakyReLU(0.2, inplace=True),
    )

    self.decoder = nn.Sequential(
        # input is Z, going into a convolution
        nn.ConvTranspose2d(     1024, ngf * 8, 4, 1, 0, bias=False),
        nn.BatchNorm2d(ngf * 8),
        nn.ReLU(True),
        # size = (ngf*8) x 4 x 4
        nn.ConvTranspose2d(ngf * 8, ngf * 4, 3, 2, 1, bias=False),
        nn.BatchNorm2d(ngf * 4),
        nn.ReLU(True),
        # size = (ngf*4) x 8 x 8
        nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
        nn.BatchNorm2d(ngf * 2),
        nn.ReLU(True),
        # size = (ngf*2) x 16 x 16
        nn.ConvTranspose2d(ngf * 2,     nc, 4, 2, 1, bias=False),
        nn.Sigmoid()
    )

    self.fc1 = nn.Linear(1024, 512)
    self.fc21 = nn.Linear(512, nz)
    self.fc22 = nn.Linear(512, nz)

    self.fc3 = nn.Linear(nz, 512)
    self.fc4 = nn.Linear(512, 1024)

    self.lrelu = nn.LeakyReLU()
    self.relu = nn.ReLU()

def encode(self, x):
    conv = self.encoder(x);
    h1 = self.fc1(conv.view(-1, 1024))
    return self.fc21(h1), self.fc22(h1)

def decode(self, z):
    h3 = self.relu(self.fc3(z))
    deconv_input = self.fc4(h3)
    deconv_input = deconv_input.view(-1,1024,1,1)
    return self.decoder(deconv_input)

def reparametrize(self, mu, logvar):
    std = logvar.mul(0.5).exp_()
    eps = torch.randn(self.bs, self.nz, device='cuda') # needs custom layer!
    return eps.mul(std).add_(mu)

def forward(self, x):
    # print("x", x.size())
    mu, logvar = self.encode(x)
    z = self.reparametrize(mu, logvar)
    decoded = self.decode(z)
    return decoded, mu, logvar

1 Ответ

0 голосов
/ 16 февраля 2019

Чтобы добавить вход в вашу модель Core ML, вы можете сделать из Python следующее:

import coremltools
spec = coremltools.utils.load_spec("YourModel.mlmodel")

nn = spec.neuralNetworkClassifier  # or just spec.neuralNetwork

layers = {l.name:i for i,l in enumerate(nn.layers)}
layer_idx = layers["your_custom_layer"]
layer = nn.layers[layer_idx]
layer.input.extend(["dummy_input"])

inp = spec.description.input.add()
inp.name = "dummy_input"
inp.type.doubleType.SetInParent()

coremltools.utils.save_spec(spec, "NewModel.mlmodel")

Здесь "your_custom_layer" - это имя слоя, к которому вы хотите добавить фиктивный вход.В вашей модели это выглядит так, как будто оно называется 62.Вы можете посмотреть словарь layers, чтобы увидеть имена всех слоев в модели.

Примечания:

  • Если ваша модель не является классификатором, используйте nn = spec.neuralNetworkвместо neuralNetworkClassifier.
  • я сделал новый фиктивный ввод типа «двойной».Это означает, что ваш пользовательский слой получает в качестве входных данных двойное значение.
  • При использовании модели необходимо указать значение для этого фиктивного ввода.
...