使用RKNN在Orange Pi 5 (RK3588s) 上部署推理PPO深度学习模型

文章目录

  • 一、前言
    • 1️⃣、Orange Pi 是什么?
    • 2️⃣、PPO 是什么?
    • 3️⃣、RKNN 是什么?
    • 3️⃣、ONNX 是什么?
  • 二、项目简介
  • 三、部署流程
    • 1️⃣、PPO 网络结构
    • 2️⃣、PPO 输出模型,模型转换,以及对比检查
    • 3️⃣、.onnx -> .rknn
    • 4️⃣、RK3588s 推理 .rknn 模型
  • 总结

一、前言

本博客皆在展示如何在Orange Pi 5 上使用 RKNN C API 使用C语言来进行模型的部署,不设计以及讨论PPO网络的实现以及细节

当前,深度学习的热潮久退不下。许多爱好者也会使用 Pytorch 等工具在自己的 Windows 环境下结合训练自己的深度学习网络模型,然后再进行模型验证,在仿真环境发现模型能够达到自己的需求,许多人就到此就结束了,因为可能目标本就是学习目的或者是了解一二,仅此而已,也就不必再进一步。但是另一批人希望自己的模型能够部署在一些嵌入式设备上实现从仿真到实验的跳跃。现在有两条比较成熟的方案:
~~~~     1. 将嵌入式设备的数据发送到 Windows 上位机,在 Windows 配置好的环境下推理完毕后再将结果发送给嵌入式设备,实现 嵌入式->上位机->嵌入式 数据流的传输与控制。这个方法的优点是 Windows 环境的推理能力较为强劲,在处理一些例如图像分类,图像识别等任务的时候能够很好的完成需求,但是缺点是数据在各个端点传输的时候时间延迟应该会较大,对于一些对时间延迟较为苛刻的任务,此方法不能够保证实时性.
~~~~     2. 直接将模型部署在嵌入式设备上,实现端设备直接推理模型的功能。这样可以保证实时性,但是缺点是对嵌入式设备的计算能力需求较大,且部署时需要对厂家提供的API较为熟悉。对于小型网络,选此方法为上上签。

本博客将会展示如何将一个简单 PPO (Proximal Policy Optimization) 算法模型通过使用 RKNN C API 部署到 Orange Pi 5 上并实现一套完整的 推理+控制 流程。


1️⃣、Orange Pi 是什么?

引用自 Orange Pi 5 介绍页,跳转连接

Orange Pi 是深圳市迅龙软件有限公司的开源产品品牌。简单来说就是国产树莓派。本博客使用其旗下的 Orange Pi 5 系列产品。Orange Pi 5 采用了 瑞芯微RK3588S 新一代八核64位处理器,具体为四核A76+四核A55,采用了8nm工艺设计,主频最高可达2.4GHz,集成ARM Mali-G610 MP4 GPU,内嵌高性能3D和2D图像加速模块,内置高达6 Tops算力的AI加速器NPU,拥有4GB/8GB/16GB(LPDDR4/4X),具有高达8K显示处理能力。下图就是 Orange Pi 5 的俯视图了。

请添加图片描述

2️⃣、PPO 是什么?

引用自《PPO(Proximal Policy Optimization)算法原理及实现,详解近端策略优化》,跳转链接。

PPO(Proximal Policy Optimization,近端策略优化)是一种强化学习算法,由 John Schulman 等人在2017年提出。PPO属于策略梯度方法,这类方法直接对策略(即模型的行为)进行优化,试图找到使得期望回报最大化的策略。PPO旨在改进和简化以前的策略梯度算法,如TRPO(Trust Region Policy Optimization,信任域策略优化),它通过几个关键的技术创新提高了训练的稳定性和效率。

PPO的主要特点包括:
裁剪的概率比率:PPO使用一个目标函数,其中包含了一个裁剪的概率比率,这个比率是旧策略和新策略产生动作概率的比值。这个比率被限制在一个范围内,防止策略在更新时做出太大的改变。
多次更新:在一个数据批次上可以安全地进行多次更新,这对于样本效率非常重要,尤其是在高维输入和实时学习环境中。
简单实现:与TRPO相比,PPO更容易实现和调整,因为它不需要复杂的数学运算来保证策略更新的安全性。
平衡探索与利用:PPO尝试在学习稳定性和足够的探索之间取得平衡,以避免局部最优并改进策略性能。
PPO已被广泛应用于各种强化学习场景,包括游戏、机器人控制以及自然语言处理中的序列决策问题。它是目前最流行的强化学习算法之一。

3️⃣、RKNN 是什么?

RKNN 是 Rockchip npu 平台使用的模型类型,以.rknn后缀结尾的模型文件。Rockchip 提供了完整了模型转换 Python 工具,方便用户将自主研发的算法模型转换成 RKNN 模型,同时 Rockchip 也提供了C/C++和Python API 接口。也就是说 RKNN 是来自瑞芯微公司旗下的产品,RK3588s 的 RKNN系列工具有其GitHub链接,跳转连接,以供参考。我们需要借助这个工具实现模型在 Orange Pi 上的部署。我们部署主要使用的是 RKNN API, 所以这里也放上 RKNN API的用户手册以供参考,跳转连接。其余的代码我会在下面的内容里直接贴出,就不附送跳转链接了。

对于 Ternsorflow, PyTorch 等其他模型,想要在 RK3588 平台运行,需要先进行模型转换。可以在搭载 Ubuntu 的PC上使用RKNN-Toolkit2工具将模型转化为 RKNN 格式,将其交叉编译后才可以部署到开发板上。

总体开发流程(以pytorch框架开发,C++部署):

1. 在 PC 端安装 RKNN-Toolkit2 工具
2. 使用 pytorch 搭建网络并训练,保存为 .pth 文件
3. 在 PC 端使用将 .pth 文件转化为 .onnx 文件,接下来使用 RKNN-Toolkit2 将 .onnx 文件转化为 .rknn 文件
4. 使用 RKNN SDK 提供的 C 接口 API 按照官方给定的调用流程交叉编译后即可执行调用 .rknn 模型文件

总结:如果想要部署模型,必须将模型转为 .rknn 格式的文件,设备才能够读取运行。但是由于一些限制以及不可抗力因素,在上位机训练得到的 .pth 文件不建议直接转为 .rknn,此时需要借助一个中间格式的文件,即:.onnx 格式文件

3️⃣、ONNX 是什么?

ONNX是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的深度学习框架( Pytorch, MXNet)可以采用相同格式存储模型数据。简而言之,ONNX是一种便于在各个主流深度学习框架中迁移模型的中间表达格式。本文不着重介绍此格式。

二、项目简介

本次的部署基于一个具体的项目,项目的目标是控制一个末端搭载了动量轮和电机的杆子抵达目标角度。整个系统是水平摆放,所以并不受到重力的影响。传统PID可以有效的控制这个系统,但是传统PID为了迅速让目标接近系统,很有出现超调现象,并且传统PID并不能实现中间过程零力矩的控制方法。使用基于强化学习训练的模型来控制整个系统,可实现零超调。因为强化学习训练本质就是一种求最优策略的过程。整个系统的概念图如下所示,
请添加图片描述
m b , m w m_b,m_w mb,mw 为系统主体和飞轮的重量。 I b I_b Ib 为主体绕枢轴的转动惯量。 I w I_w Iw 为轮子和电机转子绕电机旋转轴的转动惯量。 l w l_w lw 为电机轴与枢轴之间的距离。 l b l_b lb 为系统主体质心与枢轴之间的距离。由于整个系统垂直于重力方向,因此系统不需要考虑重力。 T m T_m Tm 为电机提供的扭矩, C w , C b C_w,C_b Cw,Cb为轮子和系统主体的动摩擦系数。

在这个模型中,我们很容易知道轴绕着一个点旋转,飞轮绕着枢轴点旋转。所以,当我们进行分析时,我们需要分别包括飞轮的平动动能和旋转动能以及轴的旋转动能。
从拉格朗日法中,我们可以知道以下方程,
f ( x ) = { T = 1 2 I b θ ˙ b 2 + 1 2 I w ( θ ˙ b + θ ˙ w ) 2 + 1 2 m w ( l w θ ˙ b ) 2 V = 0 f(x)= \begin{cases} T=\frac{1}{2}I_b\dot\theta_b^2+\frac{1}{2}I_w(\dot\theta_b+\dot\theta_w)^2+\frac{1}{2}m_w(l_w\dot\theta_b)^2\\ V=0 \end{cases} f(x)={T=21Ibθ˙b2+21Iw(θ˙b+θ˙w)2+21mw(lwθ˙b)2V=0
最后我们可以解出此方程为,
θ ¨ b = − T m + C w θ ˙ w − C b θ ˙ b I b + m w l w 2 \ddot\theta_b=\frac{-T_m+C_w\dot\theta_w-C_b\dot\theta_b}{I_b+m_wl_w^2} θ¨b=Ib+mwlw2Tm+Cwθ˙wCbθ˙b
θ ¨ w = ( I b + I w + m w l w 2 ) ( T m − C w θ ˙ b ) I w ( I b + m w l w 2 ) + C b θ ˙ b I b + m w l w 2 \ddot\theta_w=\frac{(I_b+I_w+m_wl_w^2)(T_m-C_w\dot\theta_b)}{I_w(I_b+m_wl_w^2)}+\frac{C_b\dot\theta_b}{I_b+m_wl_w^2} θ¨w=Iw(Ib+mwlw2)(Ib+Iw+mwlw2)(TmCwθ˙b)+Ib+mwlw2Cbθ˙b
那么,现在拥有了模型,就可以基于模型来进行强化学习的训练了。

整个模型的控制大概是,
1. 实时给模型输入三个系统的当前状态,即: θ b , θ ˙ b , θ ˙ w \theta_b,\dot\theta_b,\dot\theta_w θb,θ˙b,θ˙w
2. 模型在接收到三个变量后,会放进网络中运算,给出三个概率

三个概率对应着的是反向最大力矩,零力矩,正向最大力矩的概率。所以可以说系统只能够以这三个状态进行运动。最后模型训练成功后可以得到如下的仿真,
在这里插入图片描述

三、部署流程

1️⃣、PPO 网络结构

本次在上位机训练的 PPO 的网络结构如下所示,创建了一个 1x3 输入,1x3输出的网路,中间层使用线性层链接。由于篇幅有限,且本次重点不在于PPO网络,故不展示完全代码。

# 创建价值网络
model = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.ReLU(),  torch.nn.Linear(nnn * 2, nnn), torch.nn.ReLU(),torch.nn.Linear(nnn, 1))
for module in model.modules():if isinstance(module, torch.nn.Linear):torch.nn.init.orthogonal_(module.weight) 
nnn = 512
# 创建策略网络
policy = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.Tanh(),torch.nn.Linear(nnn * 2, nnn), torch.nn.Tanh(),torch.nn.Linear(nnn, 3), torch.nn.Softmax(dim=1)) 

2️⃣、PPO 输出模型,模型转换,以及对比检查

经过训练,最后能够得到一个 .pth 文件,这里我分享一下我的 .pth文件,下载连接。 随后,我们需要将其转换为 .onnx 中间格式文件。代码如下:

import torchif torch.cuda.is_available():device = torch.device("cuda:0")print("Running on the GPU")
else:device = torch.device("cpu")print("Running on the CPU")
torch.cuda.set_device(device)nnn = 512
# 实例化策略网络
policy = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.Tanh(),  # 双曲正切激活函数torch.nn.Linear(nnn * 2, nnn), torch.nn.Tanh(),torch.nn.Linear(nnn, 3), torch.nn.Softmax(dim=1))  # 计算每个动作的概率
policy.to(device)
policy.load_state_dict(torch.load('C:/Users/Administrator/Desktop/Cases/RL-balance-robot/horizontal/training/outputs/5degrees-PPO-zero-torque.pth'))
policy.eval()
dummy_input = torch.randn(1, 3, device='cuda').reshape(1, 3)
torch.onnx.export(policy, dummy_input, "PPO.onnx", opset_version=11, verbose=True)

这里需要注意,因为我的输入是 1x3, 所以这里设置 torch.randn(1,3)。 如果你们的输入是1x5,则相应的应该换为torch.randn(1,5)。
这样我们就得到了一个 .onnx 中间格式的模型文件了。

现在我我们来测量模型转换是否成功。测试的方法是使用 .pth 模型文件,随机输入数据,看输出的三个概率是多大。然后再载入转换后的 .onnx 模型文件,输入同样的数据,看输出的结果是否和 .pth 模型文件输出的一样。若相同,则转换成功。测试 .pth 模型文件的代码如下所示,

import torchdevice = torch.device("cpu")
nnn = 512
policy1 = torch.nn.Sequential(torch.nn.Linear(3, nnn * 2), torch.nn.Tanh(),torch.nn.Linear(nnn * 2, nnn), torch.nn.Tanh(),torch.nn.Linear(nnn, 3), torch.nn.Softmax(dim=1))
policy1.load_state_dict(torch.load('C:/Users/Administrator/Desktop/Cases/RL-balance-robot/horizontal/training/outputs/5degrees-PPO-zero-torque.pth'))
policy1.to('cpu')
prob = policy1(torch.FloatTensor([[10, 200, 200]]))
print(prob)

输出结果如下所示,

tensor([[1.0021e-28, 1.5017e-13, 1.0000e+00]], grad_fn=<SoftmaxBackward0>)

可以看出,在此时的输入条件下,模型预测的结果第三个数最大。

现在看看 .onnx 文件输入相同的数据,是否能够和 .pth 模型输出一样的结果。测试 .onnx 模型文件的代码如下所示,

import onnx
import onnxruntime# Load the ONNX model
model = onnx.load("PPO.onnx")
# Check that the IR is well formed
onnx.checker.check_model(model)
# Print a human readable representation of the graph
print(onnx.helper.printable_graph(model.graph))session = onnxruntime.InferenceSession('PPO.onnx', None)
raw_result = session.run([], {"onnx::Gemm_0": [[10, 200, 200]]})
print(raw_result)

输出结果如下所示,

[array([[1.0021013e-28, 1.5016719e-13, 1.0000000e+00]], dtype=float32)]

.onnx 模型文件在输入数据相同的情况下,输出和 .pth 文件一样

为了预防偶然情况,本博客又多测试了几组数据,

输入数据.onnx预测结果.pth预测结果
[90,200,0][5.2034653e-33, 9.1289922e-17, 1.0000000e+00][5.2035e-33, 9.1290e-17, 1.0000e+00]
[-50,0,300][1.0000000e+00, 1.9378372e-13, 1.6095759e-31][1.0000e+00, 1.9378e-13, 1.6096e-31]
[30,50,50][2.486136e-35, 7.525642e-19, 1.000000e+00][2.4861e-35, 7.5257e-19, 1.0000e+00]
[-60,2000,0][4.5575372e-27, 7.3239895e-12, 1.0000000e+00][4.5576e-27, 7.3240e-12, 1.0000e+00]
[80,0,-3000][8.3561228e-28, 1.2708616e-14, 1.0000000e+00][8.3561e-28, 1.2709e-14, 1.0000e+00]

从表格测试结果来看,模型转换完全没问题。

3️⃣、.onnx -> .rknn

接下来我们需要用到 Linux 系统,这里使用的是虚拟机上的 Ubuntu 20.04 系统。我们需要在 Linux系统上将转换的 .onnx 文件再次转换为 .rknn 文件。使用的Ubuntu系统如下图所示:
在这里插入图片描述
我们还需要安装 RKNN-Toolkit2 环境以进行模型的转换,由于我已经安装好了,所以我这里就不再带领大家进行这一步了。这里给大家一个教程自行进行安装。这里是跳转链接

在安装 RKNN-Toolkit2 环境完毕以后,使用如下代码进行模型的转换:

from rknn.api import RKNN
import osif __name__ == '__main__':platform = 'rk3588s''''step 1: create RKNN object'''rknn = RKNN()'''step 2: load the .onnx model'''rknn.config(target_platform='rk3588s')print('--> Loading model')ret = rknn.load_onnx('PPO.onnx')if ret != 0:print('load model failed')exit(ret)print('done')'''step 3: building model'''print('-->Building model')ret = rknn.build(do_quantization=False)if ret != 0:print('build model failed')exit()print('done')'''step 4: export and save the .rknn model'''OUT_DIR = 'rknn_models'RKNN_MODEL_PATH = './{}/PPO.rknn'.format(OUT_DIR)if not os.path.exists(OUT_DIR):os.mkdir(OUT_DIR)print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH))ret = rknn.export_rknn(RKNN_MODEL_PATH)if ret != 0:print('Export rknn model failed.')exit(ret)print('done')'''step 5: release the model'''rknn.release()

这样我们就可以得到一个转换后的 .rknn 模型文件

4️⃣、RK3588s 推理 .rknn 模型

那么现在我们拥有了.rknn模型文件,理论上应该可以进行部署了,但是在此之前我们应该也在我们的Orange Pi 上安装好 RKNN 的运行环境。这里我们需要在 Orange Pi 上安装的是 RKNPU2。RKNPU2 提供访问Rockchip的NPU的便利接口——动态链接库librknnrt.so和C头文件rknn_api.h,我们可以编写C++版本的AI应用并利用RKNPU2加速。这里安装不详细介绍,提供一个跳转链接。

那么在安装完毕后,我们的板子理论上就可以运行 .rknn 模型文件了。首先打开MobaXterm进行ssh连接Orange Pi 然后将刚刚的模型文件上传到板子上。然后参考 RKNN C API 的用户手册,进行代码编写。

RK3588上有两组API可以使用,分别是通用API接口零拷贝流程API接口。两组API的主要区别在于,通用接口API每次更新帧数据,需要将外部模块分配的数据拷贝到NPU运行时的输入内存。而零拷贝流程的接口会直接使用预先分配的内存(包括NPU运行时创建的或外部其他框架创建的,比如DRM框架),减少了内存拷贝的花销。当用户输入数据只有虚拟地址时,只能使用通用API接口,当用户输入数据有物理地址或者fd时,两组接口都可以使用。对于通用API接口,首先初始化rknn_input结构体,帧数据包含在该结构体中,使用RKNN_inputs_set函数设置模型输入,等待推理结束后,使用rknn_outputs_get函数获取推理的输出,进行后处理。在每次推理前,更新帧数据。从RKNPU用户手册里摘抄的输入/输出内存由运行时分配的零拷贝API调用流程如下所示,
在这里插入图片描述
这里就不介绍通用API的流程了。虽然通用API可以将模型部署在rk3588s的npu之上,运行更快,但是我在尝试的时候发现我的模型使用通用API部署精度会大打折扣,目前还不知道有什么方法可以解决。于是本篇幅着重介绍零拷贝API的使用,这里给出一个零拷贝接口的参考例程。但是注意,本例程使用的零拷贝API无法将模型部署在npu之上,整个程序由cpu运行。初步估计可能是 rknn 给的 C 接口接受的是 float16 的变量类型,但是由 .onnx 转换的 .rknn 文件是 float32 类型的,故npu无法运行,会被切换至cpu运行。

/*-------------------------------------------Includes
-------------------------------------------*/
#include "rknn_api.h"
#include <sys/time.h>
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;/*-------------------------------------------RKNN
-------------------------------------------*/
const int state_row = 1;  // input dimension
const int state_col = 3;   // input dimension
const int act_dim = 3;     // input dimension
float input_data[state_row][state_col] = {0.0 ,0.0 ,0.0}; // input matrix
int output_index = 0;    //index of the max value   static void dump_tensor_attr(rknn_tensor_attr *attr);
static inline int64_t getCurrentTimeUs();/*-------------------------------------------Main Functions
-------------------------------------------*/
int ret = 0;
rknn_context ctx = 0;
rknn_sdk_version sdk_ver;
rknn_input_output_num io_num;
rknn_tensor_attr input_attrs;
rknn_tensor_attr output_attrs;
int main(int argc, char *argv[])
{char *model_path = argv[1];// Load RKNN modelint model_len = 0;ret = rknn_init(&ctx, &model_path[0], 0, 0, NULL);if (ret < 0){printf("rknn_init fail! ret=%d\n", ret);}// Get sdk and driver versionret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &sdk_ver, sizeof(sdk_ver));if (ret != RKNN_SUCC){printf("rknn_query fail! ret=%d\n", ret);return -1;}printf("rknn_api/rknnrt version: %s, driver version: %s\n", sdk_ver.api_version, sdk_ver.drv_version);// Get Model Input Output Inforet = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));if (ret != RKNN_SUCC){printf("rknn_query fail! ret=%d\n", ret);return -1;}printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output);printf("input tensors:\n");memset(&input_attrs, 0, sizeof(rknn_tensor_attr));input_attrs.index = 0;ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs), sizeof(rknn_tensor_attr));if (ret < 0){printf("rknn_init error! ret=%d\n", ret);return -1;}input_attrs.type = RKNN_TENSOR_FLOAT32;input_attrs.size = input_attrs.size_with_stride = input_attrs.n_elems * sizeof(float);dump_tensor_attr(&input_attrs);printf("output tensors:\n");memset(&output_attrs, 0, sizeof(rknn_tensor_attr));output_attrs.index = 0;ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs), sizeof(rknn_tensor_attr));if (ret != RKNN_SUCC){printf("rknn_query fail! ret=%d\n", ret);return -1;}output_attrs.type = RKNN_TENSOR_FLOAT32;output_attrs.size = output_attrs.size_with_stride = output_attrs.n_elems * sizeof(float);dump_tensor_attr(&output_attrs);// create input tensor memoryrknn_tensor_mem *input_mems;input_mems = rknn_create_mem(ctx, input_attrs.size);// create output tensor memoryrknn_tensor_mem *output_mems;output_mems = rknn_create_mem(ctx, output_attrs.size);// Set input tensor memoryret = rknn_set_io_mem(ctx, input_mems, &input_attrs);if (ret < 0){printf("rknn_set_io_mem fail! ret=%d\n", ret);return 1;}ret = rknn_set_io_mem(ctx, output_mems, &output_attrs);if (ret < 0){printf("rknn_set_io_mem fail! ret=%d\n", ret);return 1;}input_data[0][0] = -50;input_data[0][1] = 0;input_data[0][2] = 300;// copy input data to input tensor memorymemcpy(input_mems->virt_addr, input_data, sizeof(input_data));// Runint64_t start_us = getCurrentTimeUs();ret = rknn_run(ctx, NULL);int64_t elapse_us = getCurrentTimeUs() - start_us;if (ret < 0){printf("rknn run error %d\n", ret);return 1;}printf("Elapse Time = %.2fms\n", elapse_us / 1000.f);// print the resultfloat *buffer = (float *)(output_mems->virt_addr);output_index = max_element(buffer,buffer+3) - buffer;for (int i = 0; i < state_col; i++){cout << input_data[0][i] << endl;}for (int i = 0; i < act_dim; i++){cout << buffer[i] << endl;}cout << output_index << endl;// Destroy rknn memoryrknn_destroy_mem(ctx, input_mems);rknn_destroy_mem(ctx, output_mems);// Destroyrknn_destroy(ctx);return 0;
}static inline int64_t getCurrentTimeUs()
{struct timeval tv;gettimeofday(&tv, NULL);return tv.tv_sec * 1000000 + tv.tv_usec;
}static void dump_tensor_attr(rknn_tensor_attr *attr)
{printf("  index=%d, name=%s, n_dims=%d, dims=[%d, %d, %d, %d], n_elems=%d, size=%d, fmt=%s, type=%s, qnt_type=%s, ""zp=%d, scale=%f\n",attr->index, attr->name, attr->n_dims, attr->dims[0], attr->dims[1], attr->dims[2], attr->dims[3],attr->n_elems, attr->size, get_format_string(attr->fmt), get_type_string(attr->type),get_qnt_type_string(attr->qnt_type), attr->zp, attr->scale);
}

其中,通过直接赋值进行输入数据的更新,第一行的意思是 θ b = − 5 0 ∘ \theta_b=-50^\circ θb=50,第二行的意思是 θ ˙ d = 0 ∘ / s \dot\theta_d=0^\circ/s θ˙d=0/s,第三行的意思是 θ w = 30 0 ∘ / s \theta_w=300^\circ/s θw=300/s

input_data[0][0] = -50;
input_data[0][1] = 0;
input_data[0][2] = 300;

运行结果如下所示,
在这里插入图片描述可以看到本模型使用了0.47ms就运行完毕一次循环,输入数据是[-50,0,300],输出概率为[1,6.10948e-05,6.10948e-05],因为0号位的概率最大,所以最后会实行0号位的动作。

为了防止偶然误差,输入和上方测试一样的数据,进行数据输出结果的对比,消除偶然误差。

输入数据.rknn预测结果
[90,200,0][6.10948e-05,6.10948e-05,1]
[-50,0,300][1,6.10948e-05,6.10948e-05]
[30,50,50][6.10948e-05,6.10948e-05,1]
[-60,2000,0][6.10948e-05,6.10948e-05,1]
[80,0,-3000][6.10948e-05,6.10948e-05,1]

由于数据类型的限制,rknn输出的值最小为 6.10948e-05, 但总体结果匹配一致,可以证明此次部署成功。

总结

本次展示了如何在 OrangePi 5 上部署深度学习模型的例子,皆在展示可行性。但问题仍然存在,例如尝试使用通用API接口部署失败,无法使用npu运行模型等等。博主仍在调查研究当中,希望早日解决此类问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/402571.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ECMAScript6语法:默认参数和rest参数

1、默认参数 默认参数即在定义函数的参数列表中指定了默认值的参数。在 ES5 中&#xff0c;并没有提供在参数列表中指定参数默认值的语法&#xff0c;要想为函数的参数指定默认值&#xff0c;只能在函数体中实现&#xff0c;示例代码如下&#xff1a; function table(width, …

【性能优化】使用Perfetto定位应用启动性能的瓶颈

Android应用启动优化相关的文章已经有很多人都写过了&#xff0c;但是主要都是聚焦在&#xff0c;为了启动性能都做了哪些改动上&#xff0c;少见有文章会说应该如何分析、识别应用的启动性能。 本篇文章将会结合我个人对Perfetto的实际使用经历&#xff0c;讲解车载应用的启动…

前端post传入拿到数据,后端报null,并且能够添加或者编辑成功

检查conterller层注解接到实体类的注解是不是没加&#xff08; RequestBody &#xff09; 后端&#xff1a; 前端&#xff1a; 那么就看注解&#xff0c;因为contrller层有个接值注解&#xff08; RequestBody &#xff09;

MySQL基础练习题44-只出现一次的最大数字

目录 题目 情况一 准备数据 分析数据 情况二 准备数据 实现一 题目 单一数字 是在 MyNumbers 表中只出现一次的数字。 找出最大的 单一数字 。如果不存在 单一数字 &#xff0c;则返回 null 。 情况一 准备数据 ## 创建库 create database db; use db;## 创建表 Cre…

代码随想录算法训练营Day42||Leetcode300.最长递增子序列 、 674. 最长连续递增序列 、 718. 最长重复子数组

一、最长递增子序列 简单&#xff0c;只不过返回值不是dp数组最后一个元素了&#xff0c;自己做出来AC了 class Solution { public:int lengthOfLIS(vector<int>& nums) {vector<int>dp(nums.size()1,1);for(int i1;i<nums.size();i){for(int ji-1;j>0…

自动化运维---ansible

ansible是一种由Python开发的自动化运维工具&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 特点: 部署简单 默认使用ssh进行管理&#xff0c;基于py…

万能钥匙:解锁 C++ 模板的无限可能

1.泛型编程 1.1:交换两个数(C语言) 1.2:交换两个数(C) 1.3:泛型编程 2:函数模板 2.1:函数模板的概念 2.2:函数模板的格式 ​编辑 2.3:函数模板的原理 2.4:模板的实例化 2.4.1:隐式实例化 2.4.2:显式实例化:在函数名后的<>中指定模板参数的实际类型. 2.4.2.1…

Docker 部署 XXL-JOB

Docker 部署 XXL-JOB 目录 引言环境准备创建 MySQL 用户并授予权限使用 Docker 部署 XXL-JOB配置 XXL-JOB验证部署总结 1. 引言 XXL-JOB 是一个开源的分布式任务调度平台&#xff0c;旨在简化定时任务的管理和调度操作。其强大的功能和灵活性&#xff0c;使其在互联网公司和…

WebSocket 快速入门

WebSocket是什么 WebSocket 是基于 TCP 的一种新的应用层网络协议。它实现了浏览器与服务器全双工通信&#xff0c;即允许服务器主动发送信息给客户端。因此&#xff0c;在 WebSocket 中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性…

谷歌开源Gemma-2 百亿参数大模型,性能超越Llama-3模型,免费使用

Gemma 模型 Gemma模型是谷歌发布的一个开源模型&#xff0c;任何人都可以免费下载预训练模型&#xff0c;进行使用。而谷歌最近也发布了Gemma 2 模型&#xff0c;模型参数超过了 200 亿大官&#xff0c;果真大模型最后都是拼参数的时候吗。 Gemma 2 模型发布 Gemma 2 模型可以…

使用 Python 解密加密的 PDF 文件

使用 Python 进行 PDF 文件加密-CSDN博客文章浏览阅读89次&#xff0c;点赞2次&#xff0c;收藏2次。定义一个名为的函数&#xff0c;该函数接受三个参数&#xff1a;输入的 PDF 文件路径input_pdf、输出的加密 PDF 文件路径output_pdf和密码password。https://blog.csdn.net/q…

Ubuntu中设置环境变量 PATH 的命令,不生效的问题“PATH=~/bin:$PATH”

1. 知识点 PATH~/bin:$PATH PATH&#xff1a;这是一个环境变量&#xff0c;用于指定操作系统在哪些目录中查找可执行文件。 ~&#xff1a;这是一个特殊的符号&#xff0c;代表当前用户的主目录。 /bin&#xff1a;这通常是存放标准实用程序&#xff08;如 ls, cp 等&#xff…

为什么神经网络常常是linear+relu的堆叠

特征提取&#xff1a;每一层的线性变换可以看作是在提取输入数据的不同特征。通过堆叠多个这样的层&#xff0c;网络能够学习从原始数据中提取越来越复杂的特征表示非线性关系&#xff1a;单个神经元的线性变换是线性的&#xff0c;但通过引入非线性激活函数&#xff08;例如Re…

【vue讲解:vue3介绍、setup、ref、reactive、监听属性、生命周期、toRef、setup写法】

1 vue3介绍 # Vue3的变化-vue3完全兼容vue2---》但是vue3不建议用vue2的写法-拥抱TypeScript-之前咱们用的JavaScript---》ts完全兼容js- 组合式API和配置项APIvue2 是配置项apivue3 组合式api# vue4必须要用2 vue3项目创建和启动 # 创建vue3项目-vue-cli 官方不太建议用了…

C语言典型例题40

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 题目 例题3.8 运输公司对用户计算运费。路程&#xff08;以s表示&#xff0c;单位为千米&#xff09;&#xff0c;吨/千米运费越低。标准如下&#xff1a; s<250 没…

深度学习中的模型架构详解

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;模型架构的不断发展极大地推动了技术的进步。从早期的循环神经网络&#xff08;RNN&#xff09;到长短期记忆网络&#xff08;LSTM&#xff09;、再到卷积神经网络&#xff08;TextCNN&#xff09;和Transformer&…

完美解决html2canvas + jsPDF导出pdf分页内容截断问题

代码地址&#xff1a;https://github.com/HFQ12333/export-pdf.git html2canvas jspdf方案是前端实现页面打印的一种常用方案&#xff0c;但是在实践过程中&#xff0c;遇到的最大问题就是分页截断的问题&#xff1a;当页面元素超过一页A4纸的时候&#xff0c;连续的页面就会…

python处理时间,按照周分割时间段

在实际的开发中&#xff0c;我们经常要设计一些工具类&#xff0c;对于时间来说&#xff0c;有时候需要将其处理成时间段。 例如&#xff0c;对于2024年08月01日到2024年08月16日的时间段&#xff0c;我们如何将其处理成时间段[2024-08-01, 2024-08-03], [2024-08-04, 2024-08-…

OSL 冠名赞助Web3峰会 “FORESIGHT2024”圆满收官

OSL 望为香港数字资产市场发展建设添砖加瓦 &#xff08;香港&#xff0c;2024 年 8 月 13 日&#xff09;- 8 月 11 日至 12 日&#xff0c; 由 香港唯一专注数字资产的上市公司 OSL 集团&#xff08;863.HK&#xff09;冠名赞助&#xff0c;Foresight News、 Foresight Ventu…

基于免疫算法的最优物流仓储点选址方案MATLAB仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于免疫算法的最优物流仓储点选址方案MATLAB仿真。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 &#xff08;完整程序运行后无水印&#xff09; 3…