在python训练、保存、使用ANN(也称NN)网络,以及在单片机上的移植

网络=模型

结合我们学习到的机器学习,无论是构建线性函数来实现分类或回归的线性回归、通过“物以类聚,人以群分”原理的K近邻、通过计算每个节点的分类纯度来实现分类的决策树、通过核函数来提升维度实现分类的支持向量机、以及贝叶斯分类等,都是通过特定的某种方法来实现对我们的输入,实现分类回归

所以我们的模型训练出来,也就是实现对输入的分类和回归。

分类:(属于)对我输入的数据进行分类,属于哪一种,例如人类照片区分男性和女性。缺点,当输入数据不为人类照片时,也会输出男性或者女性。这就是分类。

回归:(预测)对于我们输入的随机照片,它会将它们预测为它们可能的物种。如果存在不属于我们模型结果的物种,可以预测出为“其他”这一结果。

训练网络

所谓网络的训练,就是通过已知的输入和输出构建一种类函数,来实现等式尽可能的成立。

大家都知道,我们在创建一个神经网络的过程中,使用到的数据集分为训练集和测试集合。因此对于数据集内容会有以下的要求:

  • 首先明确数据集用于训练出的网络用于分类还是回归。
  • 分类网络
    • 训练集和测试集都必须是同一类属性。比如需要训练出一个实现检测是否佩戴口罩的神经网络,我们的数据集应该是佩戴和未佩戴口罩的人脸,而不是公路上拍到的车辆数据集。
    • 对于数据集和训练集进行无关性处理。所谓无关性就是在数据集中存在非这两类的情况。例如口罩并未正确佩戴的情况。
  • 回归网络
    • 训练集和测试集都可以不是是同一类属性。比如有汽车、动物、人等。当我们输入一张照片时,他会预测出来这张最有可能是谁。
    • 无需数据集和训练集进行无关性处理。

通过训练集构建出的类函数,用测试集对模型准确性检测,使得类函数能够对输入得出一个对应的结果。

为什么说的出对应的结果,而不是正确的结果?我们在网络的使用进行说明。

保存模型

众所周知,构建一个模型包括训练和保存。训练即不断修改权值和偏置,而最终训练好的模型是依托构建的模型框架(几个输入,隐藏、输出等)。因此,对于模型的保存,我们应该保存的是:

  • 模型框架:输入层数量,隐藏层数量、连接方法、激活函数,输出层数量。这可以帮助我们复现该模型框架。
  • 权值和偏置:各层的输入权值和偏置。这可以帮助我们将训练好的模型导入。

由于保存模型框架的最终目的是,可以浮现模型的前行和后向传递函数。因此我们也可以保存模型的前向和后向传递函数。

模型使用

所谓模型的使用,即按要求给定模型输入,根据模型定义的输入,将会输出一个正确结果(完美模型)。

而模型的使用过程,即调用模型的框架和权值和偏置重新构建了模型的前向传递函数。

output0 = ReLU(np.dot(weight0, input) + bias0) if active_mode == "ReLU" else sigmoid(np.dot(weight0, input) + bias0)
output1 = ReLU(np.dot(weight1, output0) + bias1) if active_mode == "ReLU" else sigmoid(np.dot(weight1, output0) + bias1)
output = ReLU(np.dot(weight2, output1) + bias2) if active_mode == "ReLU" else sigmoid(np.dot(weight2, output1) + bias2)

或是直接调用保存的前向传递函数

output = forward(input, weight0, bias0, weight1, bias1, weight2, bias2, active_mode)

模型的移植

即训练好的模型在其他设备设备上使用。以单片机为例,将上节的NN网络移植上去,我们将仅完成以下步骤:

C语言复现前向传递函数

#include <math.h>

// 定义激活函数
float ReLU(float x) {
    return x > 0 ? x : 0;
}

float sigmoid(float x) {
    return 1 / (1 + exp(-x));
}

// 前向传播函数
void forward(float input[16], float weight0[16][16], float bias0[16], 
             float weight1[16][16], float bias1[16], 
             float weight2[16][16], float bias2[16], 
             char* active_mode, float output[16]) {
    
    float output0[16], output1[16], temp[16];
    
    // 第一层隐藏层输出
    for (int i = 0; i < 16; i++) {
        temp[i] = 0;
        for (int j = 0; j < 16; j++) {
            temp[i] += weight0[i][j] * input[j];
        }
        temp[i] += bias0[i];
        output0[i] = (active_mode == "ReLU") ? ReLU(temp[i]) : sigmoid(temp[i]);
    }
    
    // 第二层隐藏层输出
    for (int i = 0; i < 16; i++) {
        temp[i] = 0;
        for (int j = 0; j < 16; j++) {
            temp[i] += weight1[i][j] * output0[j];
        }
        temp[i] += bias1[i];
        output1[i] = (active_mode == "ReLU") ? ReLU(temp[i]) : sigmoid(temp[i]);
    }
    
    // 输出层
    for (int i = 0; i < 16; i++) {
        temp[i] = 0;
        for (int j = 0; j < 16; j++) {
            temp[i] += weight2[i][j] * output1[j];
        }
        temp[i] += bias2[i];
        output[i] = (active_mode == "ReLU") ? ReLU(temp[i]) : sigmoid(temp[i]);
    }
}

参数矩阵的保存

// 假设权重和偏置已初始化为某些值
float weight0[16][16] = { /* ...训练好的参数... */ };
float bias0[16] = { /* ...初始化参数... */ };
float weight1[16][16] = { /* ...初始化参数... */ };
float bias1[16] = { /* ...初始化参数... */ };
float weight2[16][16] = { /* ...初始化参数... */ };
float bias2[16] = { /* ...初始化参数... */ };

定义单片机的输入与输出

// 也可以是某个寄存器或其地址
float input[16] = { /* 从传感器或其他数据采集设备读取 */ };
float output[16];  // 输出数组,用来存储最终预测结果

以及模型预测的触发条件

// 可以是内部中断(定时器等),也可以是外部中断(外部电平触发中断等)
// 以外部按键触发中断为例。
void onButtonPress() { //这里定义的是按键响应函数,并不是触发函数,不同的单片机的触发函数不同。
    forward(input, weight0, bias0, weight1, bias1, weight2, bias2, "ReLU", output);
    
    // 输出处理,例如显示在LCD上或通过串口输出
    for (int i = 0; i < 16; i++) {
        printf("Output[%d] = %f\n", i, output[i]);
    // 也可以通过USART触底给上位机显示等。
    }
}

最后希望各位大佬多多提出建议,轻点踩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值