在Atlas 200 DK中部署深度学习模型

本文记录了将Tensorflow模型部署到Atlas 200 DK的过程,首先将SavedModel转换成ONNX模型,然后将ONNX模型转换为om格式,以及模型转换中遇到的一些问题

环境准备

本文部署的模型是使用TensorFlow2训练的ResNet网络,用于干扰信号识别,原本的模型文件将权重保存在ResNet.h5中。

从模型文件中读取权重,并使用测试数据进行推理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import ResNet as ResNet
import numpy as np

# 干扰类型
class_names = ['CSNJ', 'CW', 'LFM', 'MTJ', 'PBNJ', 'PPNJ']

# 权重文件的存储路径
model_path = "ResNet.h5"

data_dimension = (256, 256, 1)
model = ResNet.ResNet50(data_dimension)
# 导入网络模型
model.load_weights(model_path)

# 读取测试数据
test_img = np.load('test_img.npy')

# 转换输入数据的维度
test_img = np.array(test_img)  # (256, 256)
test_img = np.expand_dims(test_img, axis=2)  # (256, 256, 1)
test_img = np.expand_dims(test_img, 0)  # (1, 256, 256, 1)

test_output = model.predict(test_img)

# 模型输出的干扰类型
pred = class_names[test_output.argmax()]
print(pred)

Atlas 200 DK的环境部署采用“开发环境与运行环境分设”的方案,开发环境使用Windows下Linux子系统中的Ubuntu 22.04.3 LTS,CANN版本为6.0.RC1.alpha005。

参考官方教程开发环境与运行环境分设完成环境配置。

cann-toolkit 配置

在Ubuntu中安装完Ascend-cann-toolkit后,运行模型转换工具atc时可能会出现报错

1
error while loading shared libraries: libascend_hal.so: cannot open shared object file: No such file or directory

这是由于无法找到库文件libascend_hal.so,此时可以进入Ascend-cann-toolkit的安装路径,寻找libascend_hal.so

1
2
3
xu@DESKTOP-9B4N33I:~$ cd Ascend/ascend-toolkit/latest/
xu@DESKTOP-9B4N33I:~/Ascend/ascend-toolkit/latest$ find . -name libascend_hal.so
./x86_64-linux/devlib/libascend_hal.so

libascend_hal.so复制到任意路径,并将该路径添加到Ascend-cann-toolkit环境变量中的LD_LIBRARY_PATH

例如将libascend_hal.so复制到~/Ascend/missing_lib

1
2
xu@DESKTOP-9B4N33I:~$ mkdir ~/Ascend/missing_lib
xu@DESKTOP-9B4N33I:~$ cp ~/Ascend/ascend-toolkit/latestx86_64-linux/devlib/libascend_hal.so ~/Ascend/missing_lib

~/.bashrc中更改环境变量

1
export LD_LIBRARY_PATH=${ASCEND_TOOLKIT_HOME}/lib64:${ASCEND_TOOLKIT_HOME}/lib64/plugin/opskernel:${ASCEND_TOOLKIT_HOME}/lib64/plugin/nnengine:/home/xu/Ascend/missing_lib:$LD_LIBRARY_PATH

在终端输入source ~/.bashrc并重启终端,此时atc可以正常运行。

模型转换

由于TensorFlow2不再支持导出模型为FrozenGraphDef格式,而atc转换TensorFlow模型时只能使用FrozenGraphDef格式,所以本文采用的转换流程是,先将TensorFlow模型导出为SavedModel格式,再将模型转换为ONNX格式,最后使用atc将ONNX模型转换为.om格式。

将TensorFlow模型导出为SavedModel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import tensorflow as tf
import ResNet as ResNet
import os

model_path = 'ResNet.h5'  # 权重文件路径

data_dimension = (256, 256, 1)
model = ResNet.ResNet50(data_dimension)

# 读取网络模型
model.load_weights(model_path)

# export model to savedmodel
mobilenet_save_path = os.path.join("./model")
tf.saved_model.save(model, mobilenet_save_path)

将SavedModel转换为ONNX模型

使用tf2onnx将SavedModel转换为.onnx格式的模型。

1
python -m tf2onnx.convert --saved-model tensorflow-model-path --output model.onnx

由于atc不支持ONNX的高版本算子,转换时tf2onnx的–opset 参数值需使用默认值15。

导出的ONNX模型可以使用Netron查看网络结构。使用ONNX模型完成推理以验证模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import onnxruntime as onnxrt
import numpy as np

class_names = ['CSNJ', 'CW', 'LFM', 'MTJ', 'PBNJ', 'PPNJ']

# load model
model = onnxrt.InferenceSession("resnet.onnx",
                providers=['AzureExecutionProvider', 'CPUExecutionProvider'])
print("loading model success!")

# input data
test_img = np.load('test_img.npy')
test_img = np.array(test_img, dtype=np.float32)  # (256, 256)
test_img = np.expand_dims(test_img, axis=2)  # (256, 256, 1)
test_img = np.expand_dims(test_img, 0)  # (1, 256, 256, 1)

# input
sf_input = {model.get_inputs()[0].name:test_img}

# output
output = model.run(None, sf_input)
print("get output of spatial filter success!")
output = np.array(output)

pred = class_names[output.argmax()]
print(pred)

将ONNX模型转换为om格式

domain_version报错

在Ubuntu终端中运行atc模型转换工具

1
atc --model=resnet.onnx --framework=5 --output=resnet --soc_version=Ascend310

可能会出现报错

1
E16005: The model has [2] [--domain_version] fields, but only one is allowed.

这是由于在将模型转换为ONNX是产生了两个(domain, version)(domain_version的概念参考ONNX Concepts)。

获取ONNX模型中的(domain,version)

1
2
3
4
import onnx

model = onnx.load("resnet.onnx")
print(model.opset_import)

得到输出

1
2
3
4
5
[domain: ""
version: 15
, domain: "ai.onnx.ml"
version: 2
]

此时只需去除多余的(domain,version),保留一个即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import onnx

model = onnx.load("resnet.onnx")

# atc 模型转换只支持一个domain_version
if len(model.opset_import) > 1:
    # 删除多余的domain_version
    model.opset_import.pop()

# 将删除domain_version后的模型保存
onnx.save(model, "./resnet.onnx")

模型输入维度报错

再次运行atc,出现报错

1
E10001: Value [-1] for parameter [input_2] is invalid. Reason: maybe you should set input_shape to specify its shape.

使用Netron查看网络结构,发现input_2是输入节点,Netron中显示的该节点信息为

1
2
name: input_2
tensor: float32[unk__618,256,256,1]

输入数据为四维张量,每一个维度分别表示N(数量)H(高)W(宽)C(通道数),例如前面模型推理时使用的数据维度为(1, 256, 256, 1)表示1个高和宽都为256,通道数为1的图像。

在导出的ONNX模型中维度N的数值为指定,需要在模型转换时使用--input-shape参数指定输入节点的维度

1
atc --model=resnet.onnx --framework=5 --output=resnet --input-shape="input_2:1,256,256,1" --soc_version=Ascend310

在Atlas 200 DK中进行模型推理

模型推理时使用的是pyACL接口,并使用了第三库acllite

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
import sys
import os

# 将acllite路径添加到python环境变量
path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(path, ".."))
sys.path.append(os.path.join(path, "../../common"))
sys.path.append(os.path.join(path, "../../common/acllite"))

from acllite_model import AclLiteModel
from acllite_resource import AclLiteResource

MODEL_PATH = "../model/resnet.om"

# init
acl_resource = AclLiteResource()
acl_resource.init()
# load model
model = AclLiteModel(model_path=MODEL_PATH)

test_img = np.load("../data/test_img.npy")
test_img = np.array(test_img)  # (256, 256)
test_img = np.expand_dims(test_img, axis=2)  # (256, 256, 1)
test_img = np.expand_dims(test_img, 0)  # (1, 256, 256, 1)
test_img = test_img.astype(np.float32)

# get output of model
output = model.execute(test_img)

output = np.squeeze(output)
print(output)

class_names = ['CSNJ', 'CW', 'LFM', 'MTJ', 'PBNJ', 'PPNJ']
pred = class_names[output.argmax()]
print(pred)
Built with Hugo
主题 StackJimmy 设计