海思开发:海思上对 relu6、hswish、h-sigmoid 移植的探索

一、前言

最近在搞 mobilenet v2、v3,v2的激活函数是 relu6,v3有两个非线性函数:hswish 和 h-sigmoid,二者都用到了relu6,之前都是把它们替换,因为海思没有现成的relu6。当时就在想,能否利用现有op,组合成想要的relu6出来了? 这个想法在脑子里徘徊几天了,今天试着给它变现,结果如下。

二、经过

relu6与relu的最大区别就是前者的值域是[0, 6],而relu的值域是[0, +无穷]。所以对于x>6的值,我们要做个区分,于是看文档,终于发现可用的op,如下图,threshold layer 源码
在这里插入图片描述
用法很明显了,我们可以设阈值为 6,当输入大于6时,输出1,否则输出0,有了这个 threshold 输出 0/1,我们可以写个公式:

公式:
relu6_out =1 - threshold_out)* input + threshold_out * 6

注:
1. input 是上一级的输出,准确的说是 relu 的输出;
2. threshold_out 是 threshold layer 的输出,只有 0 / 1 两种值;
3.input > 6 时,threshold_out = 1 , relu6_out = 6;
4.input < 6 时,threshold_out = 0 , relu6_out = input.

公式里还有些乘、加的 op,本来想用 sclae 层,后来发现 power层 更好用,power layer 博客
在这里插入图片描述
好了,op都齐了,脑海中已经有了大致的结构与流程,给它画了出来,如下,下面所有的 op 作用等同于一个 relu6:
在这里插入图片描述
下面是代码编写部分,总体来说不算难写,调试时间也没多久。onnx2caffe转换代码在这里
老规矩,打开转换代码工程目录下的 onnx2caffe_operators.py 文件,在py文件末尾加上:

#  onnx2caffe\_operators.py 下
"Relu6": _convert_relu6  # 建议加在 relu 下面,方便查看

然后在上面添加一个_convert_relu6 函数:

#  onnx2caffe\_operators.py 下
def _convert_relu6(node, graph, err):
    relu6_input_name = str(node.inputs[0])
    relu6_output_name = str(node.outputs[0])
    old_name = str(node.name)
    name = old_name + "_relu6"
    layers = []

    # 首先做 relu,node 1
    relu_name = name + "_relu"

    layer = myf("ReLU", relu_name, [relu6_input_name], [relu_name + "_out"], in_place=False)
    graph.channel_dims[relu_name + "_out"] = graph.channel_dims[relu6_input_name]  # 输出节点的维度赋值
    layers.append(layer)

    # 其次做 threshold,node 2
    thre_name = name + "_thre"
    layer = myf("Threshold", thre_name, [relu_name + "_out"], [thre_name + "_out"],
                in_place=False,
                threshold_param=dict(threshold=6.0)  # 阈值,大于它输出 1,否则为 0
                )
    graph.channel_dims[thre_name + "_out"] = graph.channel_dims[relu_name + "_out"]
    layers.append(layer)

    # threshold 左输出做线性变化,与x相乘,node 3
    thre_left_power_name = name + "_thre_left_power"
    layer = myf("Power", thre_left_power_name, [thre_name + "_out"], [thre_left_power_name + "_out"],
                in_place=False,
                power_param=dict(
                    power=1.0,
                    scale=(-1.0),
                    shift=1.0
                )
                )
    graph.channel_dims[thre_left_power_name + "_out"] = graph.channel_dims[thre_name + "_out"]
    layers.append(layer)

    # relu 后 x 输出处理,node 4
    x_thre_out_name = name + "_x_mul_thre_out"
    layer = myf("Eltwise", x_thre_out_name, [relu_name + "_out", thre_left_power_name + "_out"],
                [x_thre_out_name + "_out"], operation=P.Eltwise.PROD)
    graph.channel_dims[x_thre_out_name + "_out"] = graph.channel_dims[relu_name + "_out"]
    layers.append(layer)

    # threshold 右输出做线性变化,node 5
    thre_right_power_name = name + "_thre_right_power"
    layer = myf("Power", thre_right_power_name, [thre_name + "_out"], [thre_right_power_name + "_out"],
                in_place=False,
                power_param=dict(
                    power=1.0,
                    scale=6.0,
                    shift=0.0
                )
                )
    graph.channel_dims[thre_right_power_name + "_out"] = graph.channel_dims[thre_name + "_out"]
    layers.append(layer)

    # 最后结果汇总,node 6
    add_name = name + "_add"
    layer = myf("Eltwise", add_name, [x_thre_out_name + "_out", thre_right_power_name + "_out"],
                [relu6_output_name], operation=P.Eltwise.SUM)
    graph.channel_dims[relu6_output_name] = graph.channel_dims[relu6_input_name]
    layers.append(layer)

    return tuple(layers)

然后在 onnx2caffe_weightloader,py 文件末尾同样加上:

#  onnx2caffe\_weightloader.py 下
"Relu6": _convert_relu6  # 建议加在 relu 下面,方便查看

再添加一个 _convert_relu6 函数

#  onnx2caffe\_weightloader.py 下
def _convert_relu6(net, node, graph, err):  # 因为没有参数,所以写个 pass 就好
    pass

再在 convertCaffe.py 文件中,加个判断条件,因为 relu6 在 onnx 是 clip,所以要加个条件让程序正确执行:

# convertCaffe.py 下,插入位置看我链接里的 demo 吧,说不清楚
 if op_type == "Clip":
     op_type = "Relu6"

之后就改好了,转换前后模型可视化图对比:
转换前:
在这里插入图片描述
转换后:
在这里插入图片描述
之后做了个pytorch版本与caffe版本的对比,二者除了保留位数不同外,结果一模一样!后面又把我的一个 mobilenet v2-relu6 模型给转换了一下,转换无报错,输出结果一样!所以兄弟们放心使用。

三、后言

仓促之下写成,如有遗漏、错误,还请指出,谢谢!此外,relu6搞定了, hswish 和 h-sigmoid 也可以实现了。我对 hswish 做了一下实现,至于 h-sigmoid 就没搞了,因为它和 hswish 很像。代码和上面放一起了,下面看看转换前后的模型图吧。
转换前:
在这里插入图片描述
转换后:
在这里插入图片描述
结果我也做了验证,输出一致,各位放心使用!

四、 说明

之所以标题最后是“探索”二字,是因为我并没有在海思板端部署过,只是对其转换成caffe模型做了工作,具体移植时,恐怕会遇到问题,比如一位热心网友说的:“threshold层的阈值如果设6是有问题的,因为海思平台上6用20.12格式表示就是24576,这是超过了[0,4096]这个范围,海思平台threshhold层的参数范围要求是[0,4096]”
对此我的建议是把tensor整体除以6,阈值降为1,这样应该能行。如果我来做,会选择直接替换成relu函数,一了百了。

猜你喜欢

转载自blog.csdn.net/tangshopping/article/details/113038925