入门神经网络三

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。

q.png

  • 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