入门神经网络-搭建自己的网络结构之C环境部署(三)
入门神经网络三
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
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。