神经网络入门之三 C环境部署
此篇文章在2022年12月5日被记录
入门神经网络三
C环境部署
-
为什么要部署到C环境
上面两篇文章中,我们详细讲解了采集样本与样本训练,生成了一个模型文件,经过测试我们的模型成功率在90%以上,但是我们想将这个模型利用起来,实时检测我们的运动姿态,在ESP32上运行torch显然是不现实的。因此我们将参数保存下来,用C语言复现一遍,在esp32上运算这样才有实际使用价值。
-
网络结构中的数据变化
我们的第一层网络结构为5个卷积核的卷积层(包含relu),第二层为pooling的下采样,第三层为全连接层,传入的数据为3*50的数据,数据变化结构如下:
- 经过第一层五个卷积核的卷积后成为5X1X48的数据
- 经过relu后变为全大于5X1X48全大于零的数据
- 经过pool下采样后变为5X1X24的数据
- 将5个卷积完成的数据拉直,成为1X120的数据
- 三个全连接层参数分别乘积+偏执参数,得到1X3的数据,最终最大值为结果
-
从模型中中读取参数,做成C语言可以调用的数组:
上文提到在这个网址https://netron.app/,可以查看我们训练生成的模型,当然也可以查看模型的详细参数,如图是五个卷积核的参数,我们将它保存下来做成C数组存起来供后面调用。我们需要保存的参数总共有5个卷积核,三个全连接参数,三个全连接偏执参数,其中的五个卷积核偏执参数我没有引入,因为我认为它的作用太小了(其实是因为不知道咋算),我保存在/C_CNN_TEST/parameters.h。
-
C语言建立网络:
定义静态数组:
float convout[5][48] = {0}; //卷积完成的结果
float poolout[5][24] = {0}; //下采样完成的结果
float NormalizationOut[120] = {0}; //拍扁完成的结果
float linearOut[3] = {0}; //全连接层完成的结果
卷积激活操作:
void convetAndRelu(float in[3][50], float w[3][3], float out[48]) //用一种很笨的方法完成了卷积,仅仅适用于这个网络
{
float sum = 0;
for (int i = 0; i < 48; i++)
{
sum += in[0][i] * w[0][0];
sum += in[0][i + 1] * w[0][3];
sum += in[0][i + 2] * w[0][2];
sum += in[1][i] * w[1][0];
sum += in[1][i + 1] * w[1][4];
sum += in[1][i + 2] * w[1][2];
sum += in[2][i] * w[2][0];
sum += in[2][i + 1] * w[2][5];
sum += in[2][i + 2] * w[2][2];
(sum > 0) ? (out[i] = sum) : (out[i] = 0);
sum = 0;
}
}
下采样
void pool(float in[48], float out[24]) //下采样
{
for (int i = 0; i < 24; i++)
{
(in[2 * i] > in[2 * i + 1]) ? (out[i] = in[2 * i]) : (out[i] = in[2 * i + 1]);
}
}
归一操作
void Normalization(float in1[24], float in2[24], float in3[24], float in4[24], float in5[24], float out[120]) //归一化
{
for (int i = 0; i < 24; i++)
{
out[i] = in1[i];
out[24 + i] = in2[i];
out[48 + i] = in3[i];
out[72 + i] = in4[i];
out[96 + i] = in5[i];
}
}
全连接操作
void Linear(float in[120], float lin1[120], float lin2[120], float lin3[120], float out[3]) //全连接
{
for (int i = 0; i < 120; i++)
{
out[0] += in[i] * lin1[i];
out[1] += in[i] * lin2[i];
out[2] += in[i] * lin3[i];
}
out[0] += lin_bias[0]; //全连接层加bias
out[1] += lin_bias[1];
out[2] += lin_bias[2];
}
对外开放调用接口
void NetWork(float input[3][50])
{
//printf("convetAndReluAndpool\n");
for (int i = 0; i < 5; i++)
{
convetAndRelu(input, w[i], convout[i]);
pool(convout[i], poolout[i]);
}
//printf("Normalization\n");
Normalization(poolout[0], poolout[1], poolout[2], poolout[3], poolout[4], NormalizationOut);
//printf("Linear\n");
Linear(NormalizationOut, liner1, liner2, liner3, linearOut);
// printf("rest=%f", linearOut[0]);
// printf("walk=%f", linearOut[1]);
// printf("run=%f\n", linearOut[2]);
if (linearOut[0] > linearOut[1])
{
if (linearOut[0] > linearOut[2])
{
printf("state is rest\n");
}
else
{
printf("state is run\n");
}
}
else
{
printf("state is walk\n");
}
// SoftmaxFunc(linearOut,softmaxOut,3);
}
调用接口
NetWork(run_sample[3][50]);
C语言测试
我把python中的测试数据转换为C文件可调用的数组,在C环境下测试,休息与走路状态的正确率为100%,跑步状态下的准确率在70%往上。
esp32测试
我将网络移植到ESP32,间隔一段时间读取三轴的加速度数据,预测运动状态,当前运动状态用板载蜂鸣器表示,最终可以获得不错的预测结果:
uint16_t value_index = 0;
void loop() {
delay(153); //为什么是152ms,是因为采集数据的时候写道flash里面也需要时间,这部分不能省略
if(value_index<50)
{
mpu6050.update();
acc_buffer[0][value_index] = (float)mpu6050.getAccX()*1000;
acc_buffer[1][value_index] = (float)mpu6050.getAccY()*1000;
acc_buffer[2][value_index] = (float)mpu6050.getAccZ()*1000;
value_index++;
}
else
{
value_index = 0;
beepbeep(NetWork(acc_buffer));
}
}
后记,我的C语言功底比较差,用最简单的方式实现了网络预测,现在有较好的C语言深度学习框架,可以直接拿来使用:
https://github.com/tsk15535904190/TinyMaix
https://github.com/tsk15535904190/libonnx
本文作者:shumei52
本文链接:https://www.cnblogs.com/shumei52/p/18599779
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步