Преобразование grid_sample PyTorch в CoreML (через coremltools)

torch.nn.functional.grid_sample (источник здесь, щелкните документы для документации ) в настоящее время не поддерживается CoreML (и их библиотекой утилит преобразования: coremltools).

Я ищу способ экспортировать слой, показанный ниже, из torchscript PyTorch (документы здесь) в CoreML (либо с использованием пользовательского op, созданного с помощью Swift, либо с помощью эффективной переписывания grid_sample PyTorch).

Подробные сведения и советы по началу работы см. в разделе «Советы».

Минимальный проверяемый пример

import coremltools as ct
import torch


class GridSample(torch.nn.Module):
    def forward(self, inputs, grid):
        # Rest could be the default behaviour, e.g. bilinear
        return torch.nn.functional.grid_sample(inputs, grid, align_corners=True)


# Image could also have more in_channels, different dimension etc.,
# for example (2, 32, 64, 64)
image = torch.randn(2, 3, 32, 32)  # (batch, in_channels, width, height)
grid = torch.randint(low=-1, high=2, size=(2, 64, 64, 2)).float()

layer = GridSample()
# You could use `torch.jit.script` if preferable
scripted = torch.jit.trace(layer, (image, grid))

# Sanity check
print(scripted(image, grid).shape)


# Error during conversion
coreml_layer = ct.converters.convert(
    scripted,
    source="pytorch",
    inputs=[
        ct.TensorType(name="image", shape=image.shape),
        ct.TensorType(name="grid", shape=grid.shape),
    ],
)

что вызывает следующую ошибку:

Traceback (most recent call last):
  File "/home/REDACTED/Downloads/sample.py", line 23, in <module>
    coreml_layer = ct.converters.convert(
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/_converters_entry.py", line 175, in convert
    mlmodel = mil_convert(
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/converter.py", line 128, in mil_convert
    proto = mil_convert_to_proto(, convert_from, convert_to,
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/converter.py", line 171, in mil_convert_to_proto
    prog = frontend_converter(, **kwargs)
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/converter.py", line 85, in __call__
    return load(*args, **kwargs)
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/frontend/torch/load.py", line 81, in load
    raise e
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/frontend/torch/load.py", line 73, in load
    prog = converter.convert()
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/frontend/torch/converter.py", line 227, in convert
    convert_nodes(self.context, self.graph)
  File "/home/REDACTED/.conda/envs/REDACTED/lib/python3.9/site-packages/coremltools/converters/mil/frontend/torch/ops.py", line 54, in convert_nodes
    raise RuntimeError(
RuntimeError: PyTorch convert function for op 'grid_sampler' not implemented.

Зависимости

Python (conda):

  • coremltools==4.1
  • torch==1.8.0

Вы также можете использовать _10 _ / _ 11_ билдов (по крайней мере, на день написания: 2021-03-20)

подсказки

Они были разделены на два возможных решения, которые я сейчас вижу:

Только PyTorch

Перепишите torch.nn.functional.grid_sample с нуля.

  • Для этого потребуется придерживаться только операций PyTorch с тензорами, поскольку циклы (например, тройная вложенность) могут повесить преобразователь и будут слишком неэффективными.
  • Вы не можете использовать __getitem__ для list или связанных типов - похоже, работает с torch.Tensor, но были проблемы с этим, поэтому вам следует помнить об этом, если вы получите RuntimeError: PyTorch convert function for op '__getitem__' not implemented

Плюсы:

  • Нет необходимости в двух языках и использовании единой технологии

Минусы:

  • Ограничено циклами и потребует использования векторизованных операций (большую часть времени).

Swift и CoreML

Зарегистрируйте пользовательский уровень, который отвечает за работу grid_sample. Подойдет реализация только CPU (хотя было бы здорово использовать Apple Metal для ускорения графического процессора).

Поскольку мне не нравится Swift, я собрал несколько ресурсов, которые могут вам помочь:

Плюсы:

  • Возможность использовать циклы и более тонкий контроль над алгоритмом
  • Может быть проще, поскольку мы не ограничены операциями, которые CoreML в настоящее время может читать.

Минусы:

  • Два языка
  • Скудная документация

person Szymon Maszke    schedule 20.03.2021    source источник
comment
Здравствуйте, у вас есть прогресс с grid_sample конверсией?   -  person serg_zhd    schedule 29.03.2021


Ответы (1)


Что ж, это не точный ответ, скорее, какое-то исследование. grid_sample по своей природе является разреженной матричной операцией, идея состоит в том, чтобы попытаться сделать ее плотной. Приведенный ниже код демонстрирует, как это можно сделать. Это может быть медленным и требует, чтобы grid был статическим, чтобы исключить grid_sample из модели, которую нужно преобразовать, но вроде работает.

Цель состоит в том, чтобы преобразовать наше преобразование в линейную форму. Здесь, чтобы получить плотную матрицу, мы передаем единичную диагональ в grid_sample, и в результате получаем преобразование удержания матрицы, которое мы ищем. Чтобы выполнить именованное преобразование, умножьте плоское изображение на эту матрицу. Как вы можете видеть здесь batch = 1, преобразование должно выполняться для каждого grid независимо.

Ваш код:

in_sz  = 2;    out_sz = 4;    batch  = 1;    ch     = 3

class GridSample(torch.nn.Module):
    def forward(self, inputs, grid):
        # Rest could be the default behaviour, e.g. bilinear
        return torch.nn.functional.grid_sample(inputs, grid, align_corners=True)

image = torch.randn( batch, ch, in_sz, in_sz)  # (batch, in_channels, width, height)
grid = torch.randint(low=-1, high=2, size=( batch, out_sz, out_sz, 2)).float()

layer = GridSample()
scripted = torch.jit.trace(layer, (image, grid))
print(scripted(image, grid))

из:

tensor([[[[-0.8226, -0.4457, -0.3382, -0.0795],
          [-0.4457, -0.0052, -0.8226, -0.6341],
          [-0.4457, -0.8226, -0.4457, -0.6341],
          [-0.4510, -0.3382, -0.4457, -0.0424]],

         [[-1.0090, -1.6029, -1.3813, -0.1212],
          [-1.6029, -2.7920, -1.0090, -1.3060],
          [-1.6029, -1.0090, -1.6029, -1.3060],
          [-0.5651, -1.3813, -1.6029, -1.4566]],

         [[ 0.1482,  0.7313,  0.8916,  1.8723],
          [ 0.7313,  0.8144,  0.1482,  0.4398],
          [ 0.7313,  0.1482,  0.7313,  0.4398],
          [ 1.0103,  0.8916,  0.7313,  1.3434]]]])

Конверсия:

oness  = torch.ones( in_sz*in_sz )
diagg  = torch.diag( oness ).reshape( 1, in_sz*in_sz, in_sz, in_sz )
denser = torch.nn.functional.grid_sample( diagg, grid, align_corners=True).reshape( in_sz*in_sz, out_sz*out_sz ).transpose(0,1)
print (denser.shape)
print (image.shape)
image_flat = image.reshape( batch, ch, in_sz*in_sz )
print (image_flat.shape)
print( torch.nn.functional.linear( image_flat, denser ).reshape( batch, ch, out_sz, out_sz ) )

Из:

torch.Size([16, 4])
torch.Size([1, 3, 2, 2])
torch.Size([1, 3, 4])
tensor([[[[-0.8226, -0.4457, -0.3382, -0.0795],
          [-0.4457, -0.0052, -0.8226, -0.6341],
          [-0.4457, -0.8226, -0.4457, -0.6341],
          [-0.4510, -0.3382, -0.4457, -0.0424]],

         [[-1.0090, -1.6029, -1.3813, -0.1212],
          [-1.6029, -2.7920, -1.0090, -1.3060],
          [-1.6029, -1.0090, -1.6029, -1.3060],
          [-0.5651, -1.3813, -1.6029, -1.4566]],

         [[ 0.1482,  0.7313,  0.8916,  1.8723],
          [ 0.7313,  0.8144,  0.1482,  0.4398],
          [ 0.7313,  0.1482,  0.7313,  0.4398],
          [ 1.0103,  0.8916,  0.7313,  1.3434]]]])
         

Что ж, может быть, не очень эффективно, надеюсь, хоть это развлечет.

person Alexey Birukov    schedule 21.03.2021
comment
Это действительно довольно творческий подход, спасибо за вклад. - person Szymon Maszke; 21.03.2021