选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2021-04-10
  • 更新:2021-06-15
在V831上(awnn)跑 pytorch resnet18 模型, 模型转换方法


直接使用 pytorch hub 的与训练模型

这里省略了模型定义和训练过程, 直接使用 pytorch hub 的 resnet18 预训练模型进行简单介绍:
https://pytorch.org/hub/pytorch_vision_resnet/

在 PC 端测试模型推理

根据上面链接的使用说明, 使用如下代码可以运行模型

其中, label 下载: https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt

  1. import os
  2. import torch
  3. from torchsummary import summary
  4. ## model
  5. model = torch.hub.load('pytorch/vision:v0.6.0', 'resnet18', pretrained=True)
  6. model.eval()
  7. input_shape = (3, 224, 224)
  8. summary(model, input_shape, device="cpu")
  9. ## test image
  10. filename = "out/dog.jpg"
  11. if not os.path.exists(filename):
  12. if not os.path.exists("out"):
  13. os.makedirs("out")
  14. import urllib
  15. url, filename = ("https://github.com/pytorch/hub/raw/master/images/dog.jpg", filename)
  16. try: urllib.URLopener().retrieve(url, filename)
  17. except: urllib.request.urlretrieve(url, filename)
  18. print("test image:", filename)
  19. ## preparing input data
  20. from PIL import Image
  21. import numpy as np
  22. from torchvision import transforms
  23. input_image = Image.open(filename)
  24. # input_image.show()
  25. preprocess = transforms.Compose([
  26. transforms.Resize(max(input_shape[1:3])),
  27. transforms.CenterCrop(input_shape[1:3]),
  28. transforms.ToTensor(),
  29. transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
  30. ])
  31. input_tensor = preprocess(input_image)
  32. print("input data max value: {}, min value: {}".format(torch.max(input_tensor), torch.min(input_tensor)))
  33. input_batch = input_tensor.unsqueeze(0) # create a mini-batch as expected by the model
  34. ## forward model
  35. # move the input and model to GPU for speed if available
  36. if torch.cuda.is_available():
  37. input_batch = input_batch.to('cuda')
  38. model.to('cuda')
  39. with torch.no_grad():
  40. output = model(input_batch)
  41. ## result
  42. # Tensor of shape 1000, with confidence scores over Imagenet's 1000 classes
  43. # print(output[0])
  44. # The output has unnormalized scores. To get probabilities, you can run a softmax on it.
  45. max_1000 = torch.nn.functional.softmax(output[0], dim=0)
  46. max_idx = int(torch.argmax(max_1000))
  47. with open("imagenet_classes.txt") as f:
  48. labels = f.read().split("\n")
  49. print("result: idx:{}, name:{}".format(max_idx, labels[max_idx]))

运行后 结果:

  1. Using cache found in /home/neucrack/.cache/torch/hub/pytorch_vision_v0.6.0
  2. ----------------------------------------------------------------
  3. Layer (type) Output Shape Param #
  4. ================================================================
  5. Conv2d-1 [-1, 64, 112, 112] 9,408
  6. BatchNorm2d-2 [-1, 64, 112, 112] 128
  7. ReLU-3 [-1, 64, 112, 112] 0
  8. MaxPool2d-4 [-1, 64, 56, 56] 0
  9. Conv2d-5 [-1, 64, 56, 56] 36,864
  10. BatchNorm2d-6 [-1, 64, 56, 56] 128
  11. ReLU-7 [-1, 64, 56, 56] 0
  12. Conv2d-8 [-1, 64, 56, 56] 36,864
  13. BatchNorm2d-9 [-1, 64, 56, 56] 128
  14. ReLU-10 [-1, 64, 56, 56] 0
  15. BasicBlock-11 [-1, 64, 56, 56] 0
  16. Conv2d-12 [-1, 64, 56, 56] 36,864
  17. BatchNorm2d-13 [-1, 64, 56, 56] 128
  18. ReLU-14 [-1, 64, 56, 56] 0
  19. Conv2d-15 [-1, 64, 56, 56] 36,864
  20. BatchNorm2d-16 [-1, 64, 56, 56] 128
  21. ReLU-17 [-1, 64, 56, 56] 0
  22. BasicBlock-18 [-1, 64, 56, 56] 0
  23. Conv2d-19 [-1, 128, 28, 28] 73,728
  24. BatchNorm2d-20 [-1, 128, 28, 28] 256
  25. ReLU-21 [-1, 128, 28, 28] 0
  26. Conv2d-22 [-1, 128, 28, 28] 147,456
  27. BatchNorm2d-23 [-1, 128, 28, 28] 256
  28. Conv2d-24 [-1, 128, 28, 28] 8,192
  29. BatchNorm2d-25 [-1, 128, 28, 28] 256
  30. ReLU-26 [-1, 128, 28, 28] 0
  31. BasicBlock-27 [-1, 128, 28, 28] 0
  32. Conv2d-28 [-1, 128, 28, 28] 147,456
  33. BatchNorm2d-29 [-1, 128, 28, 28] 256
  34. ReLU-30 [-1, 128, 28, 28] 0
  35. Conv2d-31 [-1, 128, 28, 28] 147,456
  36. BatchNorm2d-32 [-1, 128, 28, 28] 256
  37. ReLU-33 [-1, 128, 28, 28] 0
  38. BasicBlock-34 [-1, 128, 28, 28] 0
  39. Conv2d-35 [-1, 256, 14, 14] 294,912
  40. BatchNorm2d-36 [-1, 256, 14, 14] 512
  41. ReLU-37 [-1, 256, 14, 14] 0
  42. Conv2d-38 [-1, 256, 14, 14] 589,824
  43. BatchNorm2d-39 [-1, 256, 14, 14] 512
  44. Conv2d-40 [-1, 256, 14, 14] 32,768
  45. BatchNorm2d-41 [-1, 256, 14, 14] 512
  46. ReLU-42 [-1, 256, 14, 14] 0
  47. BasicBlock-43 [-1, 256, 14, 14] 0
  48. Conv2d-44 [-1, 256, 14, 14] 589,824
  49. BatchNorm2d-45 [-1, 256, 14, 14] 512
  50. ReLU-46 [-1, 256, 14, 14] 0
  51. Conv2d-47 [-1, 256, 14, 14] 589,824
  52. BatchNorm2d-48 [-1, 256, 14, 14] 512
  53. ReLU-49 [-1, 256, 14, 14] 0
  54. BasicBlock-50 [-1, 256, 14, 14] 0
  55. Conv2d-51 [-1, 512, 7, 7] 1,179,648
  56. BatchNorm2d-52 [-1, 512, 7, 7] 1,024
  57. ReLU-53 [-1, 512, 7, 7] 0
  58. Conv2d-54 [-1, 512, 7, 7] 2,359,296
  59. BatchNorm2d-55 [-1, 512, 7, 7] 1,024
  60. Conv2d-56 [-1, 512, 7, 7] 131,072
  61. BatchNorm2d-57 [-1, 512, 7, 7] 1,024
  62. ReLU-58 [-1, 512, 7, 7] 0
  63. BasicBlock-59 [-1, 512, 7, 7] 0
  64. Conv2d-60 [-1, 512, 7, 7] 2,359,296
  65. BatchNorm2d-61 [-1, 512, 7, 7] 1,024
  66. ReLU-62 [-1, 512, 7, 7] 0
  67. Conv2d-63 [-1, 512, 7, 7] 2,359,296
  68. BatchNorm2d-64 [-1, 512, 7, 7] 1,024
  69. ReLU-65 [-1, 512, 7, 7] 0
  70. BasicBlock-66 [-1, 512, 7, 7] 0
  71. AdaptiveAvgPool2d-67 [-1, 512, 1, 1] 0
  72. Linear-68 [-1, 1000] 513,000
  73. ================================================================
  74. Total params: 11,689,512
  75. Trainable params: 11,689,512
  76. Non-trainable params: 0
  77. ----------------------------------------------------------------
  78. Input size (MB): 0.57
  79. Forward/backward pass size (MB): 62.79
  80. Params size (MB): 44.59
  81. Estimated Total Size (MB): 107.96
  82. ----------------------------------------------------------------
  83. out/dog.jpg
  84. tensor(2.6400) tensor(-2.1008)
  85. idx:258, name:Samoyed, Samoyede

可以看到模型有 11,689,512的参数, 即 11MiB左右, 这个大小也就几乎是实际在 831 上运行的模型的大小了

将模型转换为 V831 能使用的模型文件

转换过程如下:

使用 Pytorch 将模型导出为 onnx模型, 得到onnx文件

  1. def torch_to_onnx(net, input_shape, out_name="out/model.onnx", input_names=["input0"], output_names=["output0"], device="cpu"):
  2. batch_size = 1
  3. if len(input_shape) == 3:
  4. x = torch.randn(batch_size, input_shape[0], input_shape[1], input_shape[2], dtype=torch.float32, requires_grad=True).to(device)
  5. elif len(input_shape) == 1:
  6. x = torch.randn(batch_size, input_shape[0], dtype=torch.float32, requires_grad=False).to(device)
  7. else:
  8. raise Exception("not support input shape")
  9. print("input shape:", x.shape)
  10. # torch.onnx._export(net, x, "out/conv0.onnx", export_params=True)
  11. torch.onnx.export(net, x, out_name, export_params=True, input_names = input_names, output_names=output_names)
  12. onnx_out="out/resnet_1000.onnx"
  13. ncnn_out_param = "out/resnet_1000.param"
  14. ncnn_out_bin = "out/resnet_1000.bin"
  15. input_img = filename
  16. torch_to_onnx(model, input_shape, onnx_out, device="cuda:0")

如果你不是使用 pytorch 转换的, 而是使用了现成的 ncnn 模型, 不知道输出层的名字, 可以在 https://netron.app/ 打开模型查看输出层的名字

使用 onnx2ncnn 工具将onnx转成ncnn模型,得到一个.param文件和一个.bin文件

按照ncnn项目的编译说明编译,在build/tools/onnx目录下得到onnx2ncnn可执行文件

  1. def onnx_to_ncnn(input_shape, onnx="out/model.onnx", ncnn_param="out/conv0.param", ncnn_bin = "out/conv0.bin"):
  2. import os
  3. # onnx2ncnn tool compiled from ncnn/tools/onnx, and in the buld dir
  4. cmd = f"onnx2ncnn {onnx} {ncnn_param} {ncnn_bin}"
  5. os.system(cmd)
  6. with open(ncnn_param) as f:
  7. content = f.read().split("\n")
  8. if len(input_shape) == 1:
  9. content[2] += " 0={}".format(input_shape[0])
  10. else:
  11. content[2] += " 0={} 1={} 2={}".format(input_shape[2], input_shape[1], input_shape[0])
  12. content = "\n".join(content)
  13. with open(ncnn_param, "w") as f:
  14. f.write(content)
  15. onnx_to_ncnn(input_shape, onnx=onnx_out, ncnn_param=ncnn_out_param, ncnn_bin=ncnn_out_bin)

使用全志提供的awnn工具将ncnn模型进行量化到int8模型

maixhub 模型转换 将 ncnn 模型转换为 awnn 支持的 int8 模型 (网页在线转换很方便人为操作,另一个方面因为全志要求不开放 awnn 所以暂时只能这样做)

阅读转换说明,可以获得更多详细的转换说明
maixhub

这里有几组参数:

  • 均值 和 归一化因子: 在 pytorch 中一般是 (输入值 - mean ) / std, awnn对输入的处理是 (输入值 - mean ) * norm, 总之,让你训练的时候的输入到第一层网络的值范围和给awnn量化工具经过(输入值 - mean ) * norm 计算后的值范围一致既可。 比如 这里打印了实际数据的输入范围是[-2.1008, 2.6400], 是代码中preprocess 对象处理后得到的,即x = (x - mean) / std ==> (0-0.485)/0.229 = -2.1179, 到awnn就是x = (x - mean_2*255) * (1 / std * 255)mean2 = mean * 255, norm = 1/(std * 255), 更多可以看这里
    所以我们这里可以设置 均值为 0.485 * 255 = 123.675, 设置 归一化因子为1/ (0.229 * 255) = 0.017125, 另外两个通道同理,但是目前 awnn 只能支持三个通道值一样。。。所以填123.675, 123.675, 123.6750.017125, 0.017125, 0.017125 即可,因为这里用了pytorch hub的预训练的参数,就这样吧, 如果自己训练,可以好好设置一下

  • 图片输入层尺寸(问不是图片怎么办?貌似 awnn 暂时之考虑到了图片。。)

  • RGB 格式: 如果训练输入的图片是 RGB 就选 RGB
  • 量化图片, 选择一些和输入尺寸相同的图片,可以从测试集中拿一些,不一定要图片非常多,但尽量覆盖全场景(摊手

自己写的其它模型转换如果失败,多半是啥算子不支持,需要在 使用说明里面看支持的 算子,比如现在的版本view、 flatten、reshape 都不支持所以写模型要相当小心, 后面的版本会支持 flatten reshape 等 CPU 算子

如果不出意外, 终于得到了量化好的 awnn 能使用的模型, *.param*.bin

使用模型,在v831上推理

可以使用 python 或者 C 写代码,以下两种方式

MaixPy3

python 请看MaixPy3

不想看文档的话,就是在系统开机使用的基础上, 更新 MaixPy3 就可以了:

  1. pip install --upgrade maixpy3

然后在终端使用 python 运行脚本(可能需要根据你的文件名参数什么的改一下代码):

https://github.com/sipeed/MaixPy3/blob/main/ext_modules/_maix_nn/example/load_forward_camera.py

label 在这里: https://github.com/sipeed/MaixPy3/blob/main/ext_modules/_maix_nn/example/classes_label.py

  1. from maix import nn
  2. from PIL import Image, ImageDraw
  3. from maix import camera, display
  4. test_jpg = "/root/test_input/input.jpg"
  5. model = {
  6. "param": "/root/models/resnet_awnn.param",
  7. "bin": "/root/models/resnet_awnn.bin"
  8. }
  9. camera.config(size=(224, 224))
  10. options = {
  11. "model_type": "awnn",
  12. "inputs": {
  13. "input0": (224, 224, 3)
  14. },
  15. "outputs": {
  16. "output0": (1, 1, 1000)
  17. },
  18. "first_layer_conv_no_pad": False,
  19. "mean": [127.5, 127.5, 127.5],
  20. "norm": [0.00784313725490196, 0.00784313725490196, 0.00784313725490196],
  21. }
  22. print("-- load model:", model)
  23. m = nn.load(model, opt=options)
  24. print("-- load ok")
  25. print("-- read image")
  26. img = Image.open(test_jpg)
  27. print("-- read image ok")
  28. print("-- forward model with image as input")
  29. out = m.forward(img, quantize=True)
  30. print("-- read image ok")
  31. print("-- out:", out.shape)
  32. out = nn.F.softmax(out)
  33. print(out.max(), out.argmax())
  34. from classes_label import labels
  35. while 1:
  36. img = camera.capture()
  37. if not img:
  38. time.sleep(0.02)
  39. continue
  40. out = m.forward(img, quantize=True)
  41. out = nn.F.softmax(out)
  42. msg = "{:.2f}: {}".format(out.max(), labels[out.argmax()])
  43. print(msg)
  44. draw = ImageDraw.Draw(img)
  45. draw.text((0, 0), msg, fill=(255, 0, 0))
  46. display.show(img)

C语言 SDK, libmaix

访问这里,按照 https://github.com/sipeed/libmaix 的说明克隆仓库,并编译 https://github.com/sipeed/libmaix/tree/master/examples/nn_resnet

上传编译成功后dist目录下的所有内容到 v831, 然后执行./start_app.sh即可

文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-3klgzy.jpg