Преобразование модели в модель wk, поддерживаемую инфраструктурой NNIE. Первый шаг: tensorflow->caffe

TensorFlow

Аннотация: Эта серия статей направлена ​​на то, чтобы поделиться процессом преобразования модели tensorflow-> onnx-> Caffe-> wk, в основном для HI3516CV500, Hi3519AV100, поддерживающих структуру вывода NNIE для инженерной посадки алгоритма чипа HiSilicon.

Эта статья опубликована в сообществе HUAWEI CLOUD.«Преобразование модели в модель wk, поддерживаемую инфраструктурой NNIE, на примере фреймворка tensorflow (1)», оригинальный автор: wwwyx_*^▽^*.

Учащиеся, которые использовали структуру NNIE, знают, что структура NNIE поддерживает только рассуждения модели wk.

В процессе фактического использования программный инструмент преобразования RuyiStudio, предоставленный HiSilicon, используется для преобразованияcaffe 1.0Модель конвертируется в wk. При обычных обстоятельствах, если чип куплен, HiSilicon напрямую отправит соответствующий пакет SDK клиенту, если нет, вы можете получить его по этой ссылке:RuyiStudio

Как видно из вышеизложенного, другие модели фреймворков необходимо преобразовать в caffe, чтобы использовать RuyiStudio.В настоящее время основные фреймворки включают pytorch,tensorflow, mxnet и т. д.pytorch->caffe, студенты, которым это нужно, могут пойти посмотреть на это. В этой статье в основном описываются проблемы и решения, с которыми можно столкнуться при преобразовании фреймворка tensorflow в caffe. mxnet имеет прямой интерфейс, который можно преобразовать в onnx, или вы можете обратиться к этой статье для преобразования caffe.

Введите тему ниже.

tensorflow->caffe

Это действительно большая яма (плачу), здесь я использую промежуточную модель onnx, то есть путь окончательного успешного преобразования pb->onnx->caffe->wk, поговорим о конкретной операции~

Первый шаг: tensorflow->onnx

Этот шаг является самым простым шагом ==, и здесь еще нечего встречать яму.

Используйте проекты с открытым исходным кодом на github:tensorflow->onnx, используйте pip install сразу после установки.

Обратите внимание на то, какие там параметры.Функция каждого параметра в основном заключается в том, использовать ли nchw или nhwc для ввода, вывода и рассуждений (фреймворк caffe — nchw, поэтому здесь используется nchw), opset (по умолчанию используется 9), многие параметры я не использовал. Здесь, если у вас есть какие-либо вопросы, вы можете перейти непосредственно к проблемам и посмотреть.

Вот команда преобразования для справки:

python -m tf2onnx.convert --input ./model.pb --inputs input_image:0[1,112,112,3] --inputs-as-nchw input_image:0 --outputs output_0:0,output_1:0,output_2:0,output_3:0,output_4:0 --output ./convert.onnx

После получения модели onnx вы можете использоватьonnx simplifer Объедините несколько разбросанных операторов или удалите некоторые избыточные операторы, этот инструмент используется в зависимости от обстоятельств.

python -m onnxsim input_onnx_model output_onnx_model

После преобразования в onnx нужно проверить, соответствует ли результат вывода pb, а затем перейти к следующему процессу! !

Второй шаг: onnx->caffe

Получил здесь модель onnx, но 99% пути к успеху! !

Этот раздел Базовый уровень:onnx2caffeСреда: caffe 1.0 + onnx 1.8.0 Код основной функции:

onnx2caffe
+-- onnx2caffe
|   +-- _operators.py
|   +-- _weightloader.py
+-- convertCaffe.py
+-- MyCaffe.py

Запустите команду:

python convertCaffe.py ./model/MobileNetV2.onnx ./model/MobileNetV2.prototxt ./model/MobileNetV2.caffemodel

Если вы столкнетесь с проблемами в процессе преобразования, вы можете адаптироваться к следующим аспектам.

(1) Если вы столкнулись с операторами, которые не поддерживаются caffe и NNIE, вы можете модифицировать узлы в модели onnx для адаптации к caffe (здесь вам нужно использовать свои собственные мозги, вы можете обратиться к некоторым заменам операторов)pytorch->caffeэтот блог).

(2) Если вы столкнулись с оператором, поддерживаемым NNIE и onnx, но официально не поддерживаемым caffe 1.0, вы можете добавить новый слой в caffe, перекомпилировать, а затем выполнить преобразование. Чтобы добавить новые слои в кафе, см.:caffe добавить новый узел

(3) Оператор, поддерживаемый как caffe, так и NNIE, но инструмент преобразования не поддерживает преобразование этого оператора, и в код преобразования добавляется соответствующая реализация оператора.

(4) В процессе преобразования преобразование оператора выполняется успешно, но есть проблема с формой.Вручную добавьте некоторые операции, которые не требуют параметров в сгенерированном протоколе.

Для каждого из вышеперечисленных методов даны соответствующие решения.

Измените узел в модели onnx, чтобы он соответствовал caffe.

Чтобы переписать модель onnx, сначала нужно понять, какие операторы поддерживаются onnx.

Операции, поддерживаемые onnx:onnx op

При изменении операции в модели проверьте режим ввода и вывода узла и перепишите модель в соответствии с форматом. Переписывание модели onnx происходит в самых разных ситуациях, вот некоторые часто используемые методы.

1. При перезаписи узла иногда необходимо знать размер его входных и выходных данных, поэтому сначала подготовьте модель onnx, содержащую входные и выходные данные каждого узла.

import onnx.helper as helper
from onnx import shape_inference, TensorProto
import onnxruntime
import onnx


def add_input_output_from_onnx(onnx_path, save_path):
    ONNX_DTYPE = {
        0: TensorProto.FLOAT,
        1: TensorProto.FLOAT,
        2: TensorProto.UINT8,
        3: TensorProto.INT8,
        4: TensorProto.UINT16,
        5: TensorProto.INT16,
        6: TensorProto.INT32,
        7: TensorProto.INT64,
        8: TensorProto.STRING,
        9: TensorProto.BOOL
    }


    # load model
    onnx_model = onnx.load(onnx_path)
    graph = onnx_model.graph


    # rewrite the input tensor of graph
    input_tensor = graph.input[0]
    input_shape = input_tensor.type.tensor_type.shape.dim
    input_tensor_new = onnx.helper.make_tensor_value_info(name = input_tensor.name, elem_type = 1, 
                                                          shape = [1, input_shape[1].dim_value, input_shape[2].dim_value, input_shape[3].dim_value])
    graph.input.remove(input_tensor)
    graph.input.insert(0, input_tensor_new)


    # append all tensor infos to graph input
    weight_infos = []
    tensors = graph.initializer
    for i, tensor in enumerate(tensors):
        value_info = helper.make_tensor_value_info(tensor.name, ONNX_DTYPE[tensor.data_type], tensor.dims)
        weight_infos.append(value_info)
        graph.input.insert(i+1, value_info) # because 0 is for placeholder, so start index is 1


    # run node shape inference
    node = graph.node
    value_info = graph.value_info
    inferred_onnx_model = shape_inference.infer_shapes(onnx_model)
    onnx.checker.check_model(onnx_model)
    inferred_graph = inferred_onnx_model.graph
    inferred_value_info = inferred_graph.value_info
    onnx.save(inferred_onnx_model,save_path)
    return

Откройте модель onnx с помощью netron, чтобы увидеть изменения после добавления размера:

将模型转为 NNIE 框架支持的 wk 模型第一步:tensorflow->caffe

2. При обнаружении операторов, не поддерживаемых caffe и NNIE, удалить узлы в модели onnx и выполнить соответствующие операции на этапе внешней предварительной обработки. Эта ситуация включает только удаление существующих узлов в модели onnx и изменение отношений между существующими граничными соединениями и не предполагает установление новых граничных отношений.

` 这里使用graph中node的index来访问node
  该代码删除graph node 0,1,2
  并且修改node 3的input边
  即   input_image --> mul_1 --> sub --> mul --> conv1
  变为 input_image --> conv1
`
def delete_node(onnx_path, save_path):
    onnx_model = onnx.load(onnx_path)
    graph = onnx_model.graph
    Mul_1 = graph.node[0]
    sub = graph.node[1]
    mul = graph.node[2]


    conv1 = graph.node[3]
    conv1.input[0] = Mul_1.input[0]


    graph.node.remove(Mul_1)
    graph.node.remove(sub)
    graph.node.remove(mul)


    onnx.checker.check_model(onnx_model)
    onnx.save(onnx_model, save_path)

3. Измените операторы, которые не поддерживаются caffe и NNIE, и измените узлы в модели onnx для адаптации. Например, оператор сжатия, оператор сжатия сообщит об ошибке, когда onnx->caffe, тогда сжатие в модели onnx можно заменить оператором изменения формы. Reshape требует двух входных данных, тогда как сжатие соответствует только одному входному сигналу, что требует создания нового постоянного входного тензора в графе. Эта ситуация включает замену существующих узлов и добавление новых постоянных тензоров, но не предполагает установление новых отношений ребер.

`查看onnx op的操作,reshape需要两个输入
对于reshape需要将一个shape tensor加入到onnx graph中,
tensor size可以查看第一步生成的onnx model中该squeeze node对应的output size


即   input --> squeeze --> output
变为 input --> reshape(shape) --> output`
def remove_headpose_squeeze_node(onnx_path, save_path):
    onnx_model = onnx.load(onnx_path)
    graph = onnx_model.graph
    ## 添加常数 input
    shape = onnx.helper.make_tensor('shape', onnx.TensorProto.INT64, [2], [1,3])
    graph.initializer.append(shape)
    for i in range(len(graph.node)):
        if graph.node[i].op_type == "Squeeze":
            reshape_node_def = helper.make_node(
                        'Reshape', # node name
                        inputs=[graph.node[i].input[0], 'shape'], # inputs
                        outputs=[graph.node[i].output[0]], # outputs
                        name = graph.node[i].name
                    )
            graph.node.remove(graph.node[i])
            graph.node.insert(i, reshape_node_def)


    onnx.checker.check_model(onnx_model)
    onnx.save(onnx_model, save_path)

4. Caffe не поддерживает оператор DIV, вы можете превратить оператор DIV для POW + MUL. Это включает в себя замену узла на два, нового постоянного тензора и новых боковых соединений.

операция div: z = x/y

Замените его на pow + mul, где pow — операция возведения в степень, а mul — операция умножения:

temp = pow(y, -1)

z = temp * x

`
即:
input_x    input_y
 \\   //
  \\ //
   div
更改为:
input_x         input_y
 \\         //
  \\       //
   \\      pow(常数tensor作为指数输入)
    \\    //
     \\  //  --> (新的边)
      mul
`
def change_headpose_div_node(onnx_path, save_path):
    onnx_model = onnx.load(onnx_path)
    graph = onnx_model.graph
    pow_scale = onnx.helper.make_tensor('pow_scale', onnx.TensorProto.FLOAT, [3], [-1.0, -1.0, -1.0])
    mul12_output = helper.make_tensor_value_info('pred_pose/mul_12_pow_output:0', onnx.TensorProto.FLOAT, [1, 3])
    graph.initializer.append(pow_scale)


   # 'pred_pose/mul_12:0' 类似于上图中的input_y
   #  pow_scale 为上面创建的相应的指数tensor
   # 'pred_pose/mul_12_pow_output:0' 为新建的output tensor
   #  pow name 给一个不与图中node重复的name
    mul12_pow_node_def = helper.make_node(
        'Pow', # node name
        inputs=['pred_pose/mul_12:0', 'pow_scale'], # inputs
        outputs=['pred_pose/mul_12_pow_output:0'], # outputs
        name = 'pred_pose/mul_12_pow'
    )
    graph.node.insert(len(graph.node), mul12_pow_node_def)


    for i in range(len(graph.node)):
        if graph.node[i].name == "pred_pose/truediv_3":
            input1 = graph.node[i].input[0]
            input2 = graph.node[i].input[1]
            output = graph.node[i].output[0]
            name = graph.node[i].name
            pow_node_def = helper.make_node(
                'Mul', # node name
                inputs=[input1, mul12_pow_node_def.output[0]], # inputs
                outputs=[output], # outputs
                name = name
            )
            print(graph.node[i].name, i)
            graph.node.remove(graph.node[i])
            graph.node.insert(i, pow_node_def)
            break


    graph = helper.make_graph(graph.node, graph.name, graph.input, graph.output, graph.initializer)
    info_model = helper.make_model(graph)
    model = onnx.shape_inference.infer_shapes(info_model)
    onnx.save(model, save_path)

​После этой модификации используйте netron для просмотра отношения ребра узла, чтобы убедиться, что оно правильное.

5. Чтобы вывести вывод узла в середине onnx, нужно добавить в граф выходной тензор.

def add_outputNode_info(onnx_path, add_name, output_size, save_path):
    onnx_model = onnx.load(onnx_path)
    graph = onnx_model.graph
    prob_info =  helper.make_tensor_value_info(add_name,onnx.TensorProto.FLOAT, output_size)
    graph.output.insert(0, prob_info)
    onnx.save(onnx_model, save_path)
    return


if __name__ == '__main__':
    onnx_model = './model.onnx'
    add_node_path = "./addPreprocessOutput.onnx"
    # "mul:0": 想要输出node的output name
    # [1,24,14,14]: 想要输出node的output size
    add_outputNode_info(onnx_model, "mul:0", [1,24,14,14], add_node_path)

Приведенный выше пример охватывает большинство случаев модификации узла.Чтобы изменить модель onnx, вы можете обратиться к приведенному выше коду.

Небольшие советы: изменение формы является хорошим методом. Вместо этого можно использовать все виды измерений, связанных с изменением формы. Кроме того, транспонирование также является узлом интернет-знаменитостей. Конкретные проблемы будут подробно проанализированы ~

Добавьте соответствующую реализацию оператора в код преобразования

Про добавление нового слоя в кафе и говорить нечего, просто перейдите по ссылке, указанной выше.Здесь мы в основном познакомимся с тем, как модифицировать код конвертации, чтобы он адаптировался к конкретной модели конверсии. После описанного выше шага изменения модели onnx мы заменили все узлы в модели onnx операторами, поддерживаемыми caffe и NNIE, но в это время могут возникнуть проблемы с onnx2caffe.Далее будет адаптирован код onnx2caffe для различных ситуаций. преобразование модели шаг за шагом.

1. И caffe, и NNIE поддерживают определенную операцию, но при преобразовании модели onnx2caffe сообщается об ошибке.

Например: операция TanH, см. реализацию слоя tanh из исходного кода /caffe/src/caffe/layers/, NNIE также поддерживает эту операцию, но сообщает об ошибке преобразования. Глядя на исходный код onnx2caffe, обнаруживается, что нет реализации преобразования TanH.На данный момент нам нужно добавить соответствующий код преобразования, в основном изменяя два файла _operators.py и _weightloader.py.Следующее принимает TanH как пример, объясняющий, как добавлять конверсионные узлы.

Файл _operators.py используется для преобразования операций onnx в операции Caffe. Для адаптации TanH необходимо добавить TanH в модуль оператора регистрации в конце файла, а затем добавить код конвертации.

`转换代码:`
def _convert_tanH(node,graph,err):
    input_name = str(node.inputs[0])
    output_name = str(node.outputs[0])
    name = str(node.name)
    layer = myf("TanH",name,[input_name],[output_name])
    graph.channel_dims[output_name] = graph.channel_dims[input_name]
    return layer
    
`添加注册算子:`
_ONNX_NODE_REGISTRY = {
    ……
    "Tanh": _convert_tanH,
}

Файл ​_weightloader.py используется для реализации передачи параметров узла из onnx в Caffe. Первый шаг — добавить оператор регистрации в конец файла, аналогичный _operators.py. Второй шаг — проверить, есть ли вес в операции tanh из caffe.proto:

message TanHParameter {
  enum Engine {
    DEFAULT = 0;
    CAFFE = 1;
    CUDNN = 2;
  }
  optional Engine engine = 1 [default = DEFAULT];
}

Поскольку у операции tanh нет веса, параметр, передаваемый от onnx к caffe, пуст:

def _convert_tanH(net, node, graph, err):
    pass

На данный момент добавление операции tanh в onnx2caffe завершено.Конкретный проект включает в себя изменение двух вышеуказанных папок, в основном регистрацию операторов, реализацию преобразования операций и передачу значений веса.

И caffe, и NNIE поддерживают определенную операцию, и onnx2caffe тоже поддерживает эту операцию, но один вход в операции записан как вес в модели, что не соответствует исходной реализации.

Например: mul оператор, обычный mul оператор обычно содержит два входа, в модели может быть mul оператор только с одним входом, а другой вход как весовой параметр, как показано ниже:

将模型转为 NNIE 框架支持的 wk 模型第一步:tensorflow->caffe

В этом случае, поскольку оператор регистрации mul уже существует, нам нужно добавить новую ветвь только при преобразовании оператора mul, или это требует только перезаписи двух файлов.

_operators.py добавить код ветки

def _convert_input1_is_weight_mul(node,graph,max_dim, err):
    node_name = node.name
    `这里的input_name需要在netron视图中观察一下是哪一个input作为外部输入,这里不能写 weight 的输入名称!`
    input_name = str(node.inputs[0])
    output_name = str(node.outputs[0])
    scale_layer = myf("Scale", node_name, [input_name],[output_name],in_place=False,bias_term=False)
    graph.channel_dims[output_name] = max_dim
    return scale_layer
def _convert_Mul(node,graph,err):
    input_name_list = [str(i) for i in node.inputs]
    output_name = str(node.outputs[0])
    node_name = node.name


    `这里使用node_name 判断mul算子是否是一个input,新增只有一个input的分支`
    if node_name == "mul_1":
        max_dim = 16
        return _convert_input1_is_weight_mul(node,graph,max_dim, err)


    ···
    ···

​_weightloader.py не требует перерегистрации, напрямую добавляйте код ветки

def _convert_input1_is_weight_mul(net, node, graph, err):
    node_name = node.name


    ` 注意!! 
      scale = np.ones(3) * 3.0 
      对应的是 外部输入size =(1,3), weight size = (1),
      这种情况可以借助 numpy 实现weight与外部输入的channel对齐


      这里还有另外一种情况,例如 外部输入 size = (1,128,8,8), weight = (1,128,1,1)
      可以这样操作:scale = node.input_tensors[node.inputs[1]]
                   scale = np.reshape(scale, scale.shape[1])
     `


    scale = np.ones(3) * 3.0
    np.copyto(net.params[node_name][0].data, scale, casting='same_kind')


`mul本身是没有weight的,所以之前就是直接pass`
def _convert_Mul(net, node, graph, err):
    node_name = node.name
    if node_name == "mul_1":
        _convert_input1_is_weight_mul(net, node, graph, err)
    else:
       pass

В реальном процессе преобразования оператор добавления также будет иметь вышеописанную ситуацию.В качестве параметра оператора есть ввод.На данный момент его можно сравнить с операцией взвешивания в _convert_BatchNorm, а вес взвешивания считается равным 1, а смещение рассчитывается как доп.По внутренним входным параметрам саба можно обратиться к BatchNorm для модификации кода, и я не буду здесь подробно расписывать.

В процессе преобразования преобразование оператора выполняется успешно, но возникает проблема с формой, вручную измените prototxt

Вышеупомянутое является адаптацией оператора, но иногда файл prototxt был сгенерирован после преобразования кода onnx2caffe, и, наконец, сообщается об ошибке, что форма карты признаков не соответствует, потому что инструмент onnx2caffe печатает вывод каждый слой во время преобразования. Сравнивая с представлением netron, найдите первый проблемный узел.

将模型转为 NNIE 框架支持的 wk 模型第一步:tensorflow->caffe

Зная себя и зная врага, можно победить в каждой битве.Чтобы определить, почему формы несовместимы, мы должны сначала понять стратегии заполнения различных фреймворков и соответствующий метод расчета выходного размера.

  • Проверьте метод расчета размера вывода caffe, согласно коду, вы можете получить:

output_size=floor((w+2*pad-(d(k-1)+1))/s)+1

template <typename Dtype>
void ConvolutionLayer<Dtype>::compute_output_shape() {
    const int* kernel_shape_data = this->kernel_shape_.cpu_data();
    const int* stride_data = this->stride_.cpu_data();
    const int* pad_data = this->pad_.cpu_data();
    const int* dilation_data = this->dilation_.cpu_data();
    this->output_shape_.clear();
    for (int i = 0; i < this->num_spatial_axes_; ++i) {
      // i + 1 to skip channel axis
      const int input_dim = this->input_shape(i + 1);
      const int kernel_extent = dilation_data[i] * (kernel_shape_data[i] - 1) + 1;
      const int output_dim = (input_dim + 2 * pad_data[i] - kernel_extent)/ stride_data[i] + 1;
      this->output_shape_.push_back(output_dim);
    }
}
  • Стратегия заполнения тензорного потока может быть рассчитана в соответствии с этим блогом в сочетании с вычислением выходного размера caffe выше, Кажется, что стратегия заполнения conv совместима с тензорной подушкой = ДЕЙСТВИТЕЛЬНО, и пиксели, которые не могут участвовать, будут автоматически удалены без расчет.

Что ж, после понимания стратегий заполнения различных фреймворков и метода расчета размера вывода, давайте проанализируем нашу модель Преобразование модели выглядит следующим образом:

将模型转为 NNIE 框架支持的 wk 模型第一步:tensorflow->caffe

Проанализируйте параметры таблицы приведенного выше преобразования модели:

  • tensorflow pad=SAME, чтобы все входные пиксели участвовали в вычислении, tensorflow тайно добавляет строку 0 в правый нижний угол ввода во время вывода, так что окончательный вывод:

output size = (112 - (1 * (3 - 1) + 1) + 1) / 2 + 1 = 56

где (112 - (1 * (3 - 1) + 1)+1) Курсив 1 указывает на подлый 0.

  • Для onnx после расспросов и экспериментов установлено, что параметр pads [0,0,1,1] означает, что карта признаков дополняется не сверху, не слева, с рядом 0 снизу и столбец справа, что соответствует tf, и с выводом проблем нет.
  • После преобразования в caffe все параметры conv pad модели caffe равны 0, а верхний, нижний, левый и правый не дополняются.В настоящее время, согласно формуле outputshape caffe, окончательный результат расчета равен (1 , 3, 55, 55), а последняя строка и последняя строка ввода напрямую удаляются. Столбец в расчете не участвует.

Чтобы форма вывода была согласованной, а результаты вычислений одинаковыми, я использовал следующий обходной путь.

Установите pad_h:2, pad_w:2 в кафе. Поскольку caffe симметрично заполняется нулями после установки параметра pad, то есть ввод заполняется двумя строками или двумя столбцами из нулей, а затем в сочетании с формулой output_shape окончательная выходная форма имеет вид:

output_shape = floor((112 + 2 * 2 - (1 *(3 - 1) + 1) + 1) / 2) + 1 = 57

Размышляя о принципе преобразования, вы знаете, что карта характеристик, полученная caffe в это время, всего на одну верхнюю строку и один левый столбец больше, чем у tf. Чтобы немного объяснить, хотя caffe устанавливает pad=2, согласно реализации caffe conv, строка и столбец, которые более дополнены, чем tf в правом нижнем углу, будут автоматически удалены и не будут участвовать в операции.

В настоящее время вывод карты объектов (1,3,57,57).Чтобы получить правильный результат, добавьте две операции среза после оператора conv файла prototxt, чтобы удалить верхнюю строку и крайний левый столбец.

layer {
  name: "add_slice1"
  type: "Slice"
  bottom: "depthwise:0"
  top: "add_slice1/split:0"
  top: "add_slice1/split:1"
  slice_param {
    axis: 2
    slice_point: 1
  }
}


layer {
  name: "add_slice2"
  type: "Slice"
  bottom: "add_slice1/split:1"
  top: "add_slice2/split:0"
  top: "add_slice2/split:1"
  slice_param {
    axis: 3
    slice_point: 1
  }
}

Вышеупомянутое является адаптацией модели caffe.Есть много сложных вещей, и иногда для решения проблемы необходимы какие-то новые идеи.Конечно, это также включает в себя изменение некоторых параметров оператора в файле prototxt.Специфический анализ конкретные проблемы здесь обсуждаться не будут.

Шаг 3. Подтвердите

Сравните вывод полученной модели caffe с выводом pb. Как правило, они должны быть точно такими же. Если они отличаются, сосредоточьтесь на предварительной обработке ввода и предварительной обработке вывода. Является ли вывод узла перед измененным узлом? ОК (в основном является ли позиционирование проблемой узла, который вы изменили сами), не теряйте терпения и осваивайте метод. Каждый раз, когда вносится волшебное изменение, делается рассуждение, что лучше для позиционирования.

Суммировать

Проблемы с конвертацией tf в caffe действительно есть, выше может быть перечислена только одна из десяти тысяч проблем, но я надеюсь, что это может помочь всем. Надеюсь, вы сможете обменяться идеями по этому поводу.

Волшебная модификация для модели onnx может быть лишней.Лучше было бы прописать соответствующие методы конвертации прямо в инструменте конвертации onnx2caffe, но я подумал, что проще модифицировать onnx, а потом надеюсь успеть модифицировать инструмент преобразования. более общий

Настоятельно рекомендуется, чтобы изучающие алгоритмы изучали типы операторов, поддерживаемые инфраструктурой NNIE, перед обучением модели! ! Для получения подробной информации см. поддерживаемые типы операторов в разделе 5.3.2 Руководства по разработке HiSVP и спецификации, поддерживаемые каждым оператором в разделе 3.1.6.2, чтобы избежать переделки, если модель не может быть преобразована! !

Нажмите «Подписаться», чтобы впервые узнать о новых технологиях HUAWEI CLOUD~