简体   繁体   English

CoreML:为ONNX RandomNormal创建自定义图层

[英]CoreML: creating a custom layer for ONNX RandomNormal

I've trainined a VAE that in PyTorch that I need to convert to CoreML. 我已经在PyTorch中培训了一个VAE,我需要将其转换为CoreML。 From this thread PyTorch VAE fails conversion to onnx I was able to get the ONNX model to export, however, this just pushed the problem one step further to the ONNX-CoreML stage. 从此线程PyTorch VAE无法转换为onnx,我能够导出ONNX模型,但是,这只是将问题推到了ONNX-CoreML阶段。

The original function that contains the torch.randn() call is the reparametrize func: 包含torch.randn()调用的原始函数是重新参数化功能:

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)

The solution is, of course, to create a custom layer, but I'm having problems creating a layer with no inputs (ie, it's just a randn() call). 解决方案当然是创建自定义图层,但是在创建没有输入的图层时遇到问题(即,这只是randn()调用)。

I can get the CoreML conversion to complete with this def: 我可以通过以下定义完成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

I do the conversion with: 我使用以下方法进行转换:

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

I should also note that, at the completion of the mlmodel export, the following is printed: 我还应注意,在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')]

Bringing the .mlmodel into Xcode complains that Layer '62' of type 500 has 0 inputs but expects at least 1. So I'm wondering how to specify a kind of "dummy" input to the layer, since it doesn't actually have an input -- it's just a wrapper around torch.randn() (or, more specifically, the onnx RandonNormal op). .mlmodel带入Xcode抱怨Layer '62' of type 500 has 0 inputs but expects at least 1.所以我想知道如何为该层指定一种“虚拟”输入,因为它实际上没有输入-只是torch.randn()的包装(或更具体地说,是onnx RandonNormal op)。 I should clarify that I do need the whole VAE, not just the decoder, as I'm actually using the entire process to "error correct" my inputs (ie, the encoder estimates my z vector, based on an input, then the decoder generates the closest generalizable prediction of the input). 我应该澄清一下,我确实需要整个VAE,而不仅仅是解码器,因为我实际上是在使用整个过程来“纠错”我的输入(即,编码器根据输入估算我的z矢量,然后解码器生成最接近的输入通用化预测)。

Any help greatly appreciated. 任何帮助,不胜感激。

UPDATE: Okay, I finally got a version to load in Xcode (thanks to @MattijsHollemans and his book!). 更新:好的,我终于在Xcode中加载了一个版本(感谢@MattijsHollemans和他的书!)。 The originalConversion.mlmodel is the initial output of converting my model from ONNX to CoreML. originalConversion.mlmodel是将模型从ONNX转换为CoreML的初始输出。 To this, I had to manually insert the input for the RandomNormal layer. 为此,我必须手动为RandomNormal层插入输入。 I made it (64, 28, 28) for no great reason — I know my batch size is 64, and my inputs are 28 x 28 (but presumably it could also be (1, 1, 1), since it's a "dummy"): 我无缘无故地做到了(64,28,28)—我知道我的批处理大小是64,我的输入是28 x 28(但也可能是(1、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") 

This loads in Xcode, but I have yet to test the functioning of the model in my app. 这会加载到Xcode中,但是我尚未在我的应用程序中测试模型的功能。 Since the additional layer is simple, and the input is literally a bogus, non-functional input (just to keep Xcode happy), I don't imagine it will be a problem, but I'll post again if it doesn't run properly. 由于附加层很简单,并且输入实际上是伪造的,非功能性的输入(只是为了让Xcode开心),我不认为这会成为问题,但是如果它运行,我会再次发布正常。

UPDATE 2: Unfortunately, the model doesn't load at runtime. 更新2:不幸的是,该模型无法在运行时加载。 It fails with [espresso] [Espresso::handle_ex_plan] exception=Failed in 2nd reshape after missing custom layer info. 它失败,并显示[espresso] [Espresso::handle_ex_plan] exception=Failed in 2nd reshape after missing custom layer info. What I find very strange and confusing is that, inspecting model.espresso.shape , I see that almost every node has a shape like: 我发现非常奇怪和令人困惑的是,检查model.espresso.shape ,我看到几乎每个节点都具有如下形状:

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

I have two question/concerns: 1) Most obviously, why are all the values zero (this is the case with all but the input nodes), and 2) Why does it appear to be a sequential model, when it's just a fairly conventional VAE? 我有两个问题/担忧:1)最明显的是,为什么所有值都为零(除了输入节点以外的所有情况都如此),以及2)当它只是一个相当传统的方法时,为什么它似乎是一个顺序模型? VAE? Opening model.espresso.shape for a fully-functioning GAN in the same app, I see that the nodes are of the format: 在同一应用程序中为全功能GAN打开model.espresso.shape ,我看到节点的格式为:

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

That is, they contain reasonable shape info, and they don't have seq fields. 也就是说,它们包含合理的形状信息,并且没有 seq字段。

Very, very confused... 非常非常困惑

UPDATE 3: I've also just noticed in the compiler report the error: 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. 更新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. 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.

Here's the original PyTorch model: 这是原始的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

To add an input to your Core ML model, you can do the following from Python: 要将输入添加到您的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")

Here, "your_custom_layer" is the name of the layer you want to add the dummy input to. 在这里, "your_custom_layer"是要将虚拟输入添加到的图层的名称。 In your model it looks like it's called 62 . 在您的模型中,它看起来像是62 You can look at the layers dictionary to see the names of all the layers in the model. 您可以查看layers字典以查看模型中所有图层的名称。

Notes: 笔记:

  • If your model is not a classifier, use nn = spec.neuralNetwork instead of neuralNetworkClassifier . 如果您的模型不是分类器,请使用nn = spec.neuralNetwork而不是neuralNetworkClassifier
  • I made the new dummy input have the type "double". 我使新的虚拟输入的类型为“ double”。 That means your custom layer gets a double value as input. 这意味着您的自定义图层将获得一个双精度值作为输入。
  • You need to specify a value for this dummy input when using the model. 使用模型时,需要为此虚拟输入指定一个值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM