机器学习中的数据类型与水果分类项目实践

1. 数据类型概述

在机器学习中,我们会遇到多种类型的数据,不同类型的数据具有不同的特点和处理方式。

1.1 特征工程处理的数据

在初始阶段,特征工程通常处理单个样本。它所考虑的转换方式主要有两种:一种是针对单个样本的个体转换,另一种是相对于整个样本总体的全局转换。可处理的数据特征包括大气测量值(如温度、湿度、压力)、颜色和光强度、距离、速度、倾斜角度等。

1.2 时间序列数据

时间序列数据是指在收集的数据中增加了时间维度的情况。这种数据有两个重要的信息来源:
- 给定时间点的测量值。
- 数值随时间的变化情况。

时间序列数据可以以表格形式存储,但行的顺序至关重要。若随机重新排列行,会丢失大量数据的内在模式。并且,时间序列数据的采样频率可能不固定或不均匀,不同传感器的采样频率也可能不同,从每秒几个样本到数百个样本都有可能。

时间序列数据与表格数据有一些关键区别,如下表所示:
| 数据类型 | 时间重要性 | 数据类型 |
| ---- | ---- | ---- |
| 时间序列数据 | 与测量值同等重要,单个样本信息少,模式由值随时间的演变决定 | 仅为数值型 |
| 表格数据 | 行顺序不重要,单个样本相对独立 | 可能是数值和分类数据的组合 |

由于数据以值流的形式出现,而硬件内存有限,因此需要对数据进行窗口化处理。窗口化是指一次只考虑一部分数据(通常是最新的数据),当新数据进来时丢弃旧数据。

时间序列数据的典型来源包括:
- 加速度计和陀螺仪,用于检测运动或振动模式。
- 电流和电压,用于对设备的运行模式进行分类(如空闲、轻负载或重负载)。
- 大气传感器(温度、湿度、压力),用于预测下一小时是否会下雪。
- 大脑和肌肉电信号(EEG和ECG),用于远程控制设备。

时间序列的特征工程通常包括从时域窗口中提取统计值,例如:
- 最小值/最大值/平均值。
- 方差/偏度/峰度。
- 高于/低于均值的值的数量。
- 过零点。
- 频域(傅里叶变换)。

这些步骤不仅可以降低数据的维度,还能对输入进行去噪并提高其信息含量。

1.3 音频数据

音频是一种特殊的时间序列数据。在嵌入式环境中,音频通常以标准的采样频率(8、16或20 kHz)和幅度(以有符号16位整数表示,范围从 -32768 到 +32767)工作。在这些高频率下,在时域中提取特征会变得困难,即使可以提取,也可能不是最合适的解决方案。

对于音频分类,通常使用一种称为梅尔频谱图的自定义频域特征。这种转换将音频数据从时域映射到时间 - 频率域,在这个新的域中可以计算特定的特征,作为分类模型的输入。由于计算量较大且有(近)实时执行的约束,已经开发了优化算法来及时执行傅里叶变换,许多硬件供应商甚至引入了硬件加速功能。

1.4 图像和空间数据

图像是一种特殊的空间数据,在 XYZ 空间中有内在的排列。它是由像素组成的网格,按行和列排列,每个像素的值与其位置同样重要。每个像素与其相邻像素密切相关,随机重新排列像素会完全破坏原始图像,就像重新排列时间序列数据会破坏其模式一样。

空间数据并不局限于相机图像,在嵌入式环境中常见的还有:
- 热成像相机:该传感器感知与物体温度严格相关的红外光。根据传感器的不同,分辨率可以是 8×8、32×24 或 160×120。在这种情况下,每个像素的值代表估计的温度。
- 飞行时间阵列:飞行时间是一种通过测量光从物体反射并返回光源所需的时间来检测距离的技术。使用专用硬件(称为 SPAD 阵列),可以在一次运行中测量 3D 空间中多个点的距离。分辨率仍然有限,最常见的传感器输出为 4×4 或 8×8。

无论像素代表什么(光强度、温度或距离),空间数据都可以利用专门为图像开发的一类算法。只要数据看起来像图像(值限定在特定范围内,如 0 - 1 或 0 - 255),这些算法通常都能正常工作。

不过,“视觉”相机的分辨率通常比上述其他传感器高得多(一些微控制器的分辨率可达 5 MP)。因此,图像携带的信息比 8×8 的 SPAD 阵列输出多得多。处理如此大量的数据需要大量的时间和资源。考虑到嵌入式硬件的限制(2 MB 的闪存和 2 MB 的 RAM),图像分析通常应限于低分辨率图像(96×96 是在准确性和资源使用之间的一个不错的折衷方案)。而且,只有一部分可用模型适合硬件约束,一些常用于桌面硬件的高级深度学习模型(如 YOLO 架构)在默认的全宽度形式下并不适用。

下面是不同数据类型处理的 mermaid 流程图:

graph LR
    A[特征工程处理的数据] --> B[时间序列数据]
    A --> C[音频数据]
    A --> D[图像和空间数据]
    B --> B1[窗口化处理]
    B --> B2[特征提取]
    C --> C1[梅尔频谱图转换]
    D --> D1[低分辨率处理]

2. 表格数据分类 - 水果分类项目

2.1 项目概述

表格数据是指以行和列的形式结构化组织的数据,类似于电子表格。表格数据分类的任务是接收单行各列的输入,并确定该行所属的类别。在这个项目中,我们将进行水果分类,根据水果的颜色成分来识别不同的水果。该项目不使用 TensorFlow 或神经网络,而是采用传统的机器学习方法,生成的模型可以在几乎任何微控制器上运行,甚至是 8 位的微控制器(如 ATMega 或 Attiny 系列),只需要 10 - 20 KB 的 RAM(具体取决于模型大小)。

2.2 项目步骤

要完成这个项目,需要按照以下步骤进行:
1. 使用微控制器和外部传感器捕获数据。
2. 使用 Python 加载和检查数据。
3. 使用 Python 进行特征工程。
4. 使用 Python 训练分类模型。
5. 将模型转换为 C++ 并部署回微控制器。

2.3 所需硬件

由于要根据水果的颜色进行分类,因此需要一个颜色传感器。以下两种设置均可:
- Arduino Nano BLE Sense,内置 APDS9960 传感器。
- 任何带有外部 TCS3200 传感器的微控制器。该传感器有四个控制引脚(S0/S1 用于频率缩放,S2/S3 用于颜色),需要连接到数字输出引脚,还有一个用于检测信号的输出引脚(使用 pulseIn 函数读取)。

如果没有不同颜色的水果(如香蕉、苹果、橙子),可以用任何有明显颜色区别的物体代替,但要确保每个物体的颜色清晰、独特。

2.4 所需软件

该项目的严格要求是在 PC 上安装 Python 3.10 或更高版本,以及 Arduino IDE(支持 Arduino 的 Platform IO 也可以)。

2.5 创建 Python 虚拟环境

为了使代码和依赖项相互隔离,我们将创建一个 Python 虚拟环境。虚拟环境是一个独立的、特定于项目的 Python 解释器安装,包含项目所需的所有额外库。由于 PC 上可能有多个不同时间创建的 Python 项目,如果全局安装包,可能会因依赖项的更新而出现版本兼容性问题。为每个项目创建虚拟环境可以避免这些冲突。

创建虚拟环境的步骤如下:
1. 打开终端,进入一个专用文件夹,运行以下命令创建新的虚拟环境(只需运行一次):

$ python -m venv <name of the environment>
  1. 激活环境(在根文件夹内),根据不同的系统运行相应的命令:
    • Windows Command Prompt:
$ <name of the environment>\Scripts\activate.bat
- Windows PowerShell:
$ <name of the environment>\Scripts\activate.ps1
- Linux/OS X:
$ source <name of the environment>/bin/activate

如果想将虚拟环境命名为 tinyml,命令如下:

$ python -m venv tinyml
# Windows Command Prompt
$ tinyml\Scripts\activate.bat
# Windows PowerShell
$ tinyml\Scripts\activate.ps1
# Linux/OS X
$ source tinyml/bin/activate

激活虚拟环境后,可以开始安装所需的包。这里有一个配套的包叫 tinyml4all,在虚拟环境中执行以下命令进行安装:

(tinyml)$ python -m pip install tinyml4all

2.6 tinyml4all Arduino 库

为了方便在微控制器上执行任务,有一个配套的 Arduino 库 tinyml4all,可以从 Arduino 库管理器中安装。虽然这是可选的,但强烈建议安装,这样可以直接运行相关代码而无需修改。打开库管理器的方法是:打开 Arduino IDE,导航到 Sketch ➤ Include library ➤ Manager libraries。

2.7 数据采集

在任何机器学习项目中,数据采集都是第一步。有时可能已经有了过去收集或从互联网下载的数据,有时则需要从开发板和传感器收集新数据。

收集高质量的数据至关重要,机器学习行业有句俗语:“垃圾进,垃圾出”。如果输入数据嘈杂、缺失且质量低下,模型的输出也会反映出同样的质量问题。因此,要尽力收集尽可能好的数据。收集数据通常既耗时又成本高,需要根据项目要求评估高质量输入数据的价值。不重视这一步可能会使后续的所有处理无效,无法获得成功的结果。

在为 Arduino 项目收集数据时,数据通常来自传感器。需要将这些数据保存并转移到 PC 上。以下是一种常见的数据采集方法:

手动从串口监视器复制数据

这是一种简单直接的从微控制器获取数据的方法,对于快速项目来说是最快的启动方式。

在打印数据之前,需要对其进行适当的格式化,以便后续在 Python 中轻松处理。如果安装了 tinyml4all Arduino 库,这应该比较容易。最适合 Python 处理的格式是逗号分隔值(CSV)编码,这是一种基于文本的格式,数据以行和列的形式存储,每行在一行上,每列用逗号分隔。

在这个项目中,每行存储颜色传感器每秒采集的红、绿、蓝(RGB)光分量。根据使用的颜色传感器类型(内置 APDS9960 的 Arduino Nano BLE Sense 或外部 TCS3200),实例化传感器的代码会略有不同,但其他部分基本相同。如果使用 Arduino Nano BLE Sense 开发板,创建一个新的 Arduino 项目并复制以下代码:

/**
* Listing 2-1
* Collect RGB data in CSV format from user prompt.
*
* Required hardware: Arduino Nano BLE Sense.
*/
#include <Arduino_APDS9960.h>
#include <tinyml4all.h>
using tinyml4all::promptString;
using tinyml4all::promptInt;
using tinyml4all::printCSV;
tinyml4all::APDS9960 sensor;
void setup() {
    Serial.begin(115200);
    while (!Serial);
    Serial.println("Collect RGB values as CSV");
    // init sensor (will throw an error if it fails)
    sensor.begin();
}
void loop() {
    // get fruit name and number of samples from user
    String fruit   = promptString("Which fruit is this?");
    int numSamples = promptInt("How many samples to capture?");
    for (int i = 0; i < numSamples; i++) {
        // read sensor values and print in CSV format
        sensor.readColor();
        printCSV(sensor.r, sensor.g, sensor.b, fruit);
        delay(1000);
    }
}

将代码上传到开发板,打开串口监视器。选择一个水果,将开发板指向水果,距离保持在 15 - 30 cm,当提示时输入水果名称,然后输入要采集的样本数量(建议为 50)。每秒会出现一行 CSV 数据。

为了收集高质量和健壮的数据,在采集过程中可以稍微移动传感器,靠近、远离、向左、向右移动(同时仍指向水果),以增加数据的多样性。对要识别的每个水果重复此过程,建议每个水果收集 30 - 50 个样本。

完成采集后,将串口监视器的内容复制到 Python 项目文件夹中名为 fruits.csv 的文件中。为了使其成为一个合适的 CSV 文件,需要删除所有不包含数据的行(如“Which fruit is this?”和“How many samples to capture”),并添加一个包含列名的标题行,指定每列代表的内容。在这个项目中,前三个值代表光的红、绿、蓝分量,第四个是水果的名称,因此在文件开头添加一行“r, g, b, fruit”。例如:

r,g,b,fruit
17,12,9,banana

通过以上步骤,我们完成了水果分类项目的数据采集部分,后续可以进行数据的加载、特征工程、模型训练和部署等操作。

3. 数据处理与模型训练

3.1 使用 Python 加载和检查数据

完成数据采集后,接下来使用 Python 加载和检查采集到的 CSV 数据。可以使用 pandas 库来完成这个任务,以下是示例代码:

import pandas as pd

# 加载数据
data = pd.read_csv('fruits.csv')

# 查看数据基本信息
print('数据基本信息:')
data.info()

# 查看数据集行数和列数
rows, columns = data.shape

if rows < 100:
    # 样本少于 100 查看全量数据信息
    print('数据全部内容信息:')
    print(data.to_csv(sep='\t', na_rep='nan'))
else:
    # 样本多于 100 查看数据前几行信息
    print('数据前几行内容信息:')
    print(data.head().to_csv(sep='\t', na_rep='nan'))

上述代码首先使用 pandas read_csv 函数加载 fruits.csv 文件,然后通过 info 方法查看数据的基本信息,包括数据类型、列名、非空值数量等。接着根据数据行数的多少,选择输出全量数据信息或前几行数据信息。

3.2 使用 Python 进行特征工程

特征工程是机器学习中非常重要的一步,它可以提高模型的性能。在这个水果分类项目中,我们可以对 RGB 特征进行标准化处理,以消除不同特征之间的量纲差异。以下是使用 sklearn 库进行特征工程的示例代码:

from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# 提取特征和标签
X = data.drop('fruit', axis=1)
y = data['fruit']

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 特征标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

上述代码首先从数据中提取特征 X 和标签 y ,然后使用 train_test_split 函数将数据划分为训练集和测试集,测试集占比为 20%。接着使用 StandardScaler 对训练集和测试集的特征进行标准化处理。

3.3 使用 Python 训练分类模型

在完成特征工程后,我们可以选择合适的机器学习算法进行模型训练。这里我们选择使用决策树分类器,它是一种简单且常用的分类算法。以下是使用 sklearn 库训练决策树分类器的示例代码:

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 创建决策树分类器
model = DecisionTreeClassifier()

# 训练模型
model.fit(X_train_scaled, y_train)

# 在测试集上进行预测
y_pred = model.predict(X_test_scaled)

# 计算模型准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"模型准确率: {accuracy}")

上述代码首先创建了一个决策树分类器对象,然后使用训练集数据对模型进行训练。接着使用训练好的模型对测试集进行预测,并计算预测结果的准确率。

3.4 模型转换与部署

训练好模型后,我们需要将模型转换为 C++ 代码,并部署回微控制器。可以使用 micromlgen 库将 sklearn 模型转换为 C++ 代码。以下是示例代码:

from micromlgen import port

# 将模型转换为 C++ 代码
cpp_code = port(model)

# 将 C++ 代码保存到文件
with open('fruit_classifier.h', 'w') as f:
    f.write(cpp_code)

上述代码使用 micromlgen port 函数将决策树分类器模型转换为 C++ 代码,并将代码保存到 fruit_classifier.h 文件中。

在微控制器端,我们可以包含生成的头文件,并使用其中的模型进行预测。以下是一个简单的 Arduino 示例代码:

#include "fruit_classifier.h"
#include <Arduino_APDS9960.h>

// 实例化传感器
tinyml4all::APDS9960 sensor;

void setup() {
    Serial.begin(115200);
    while (!Serial);
    Serial.println("开始水果分类");
    // 初始化传感器
    sensor.begin();
}

void loop() {
    // 读取传感器值
    sensor.readColor();
    int r = sensor.r;
    int g = sensor.g;
    int b = sensor.b;

    // 特征标准化
    float r_scaled = (r - scaler.mean_[0]) / scaler.scale_[0];
    float g_scaled = (g - scaler.mean_[1]) / scaler.scale_[1];
    float b_scaled = (b - scaler.mean_[2]) / scaler.scale_[2];

    // 构建输入特征向量
    float input[3] = {r_scaled, g_scaled, b_scaled};

    // 进行预测
    int prediction = classifier.predict(input);

    // 输出预测结果
    Serial.print("预测结果: ");
    switch (prediction) {
        case 0:
            Serial.println("香蕉");
            break;
        case 1:
            Serial.println("苹果");
            break;
        case 2:
            Serial.println("橙子");
            break;
        default:
            Serial.println("未知水果");
    }

    delay(1000);
}

上述代码在 Arduino 中包含了生成的 fruit_classifier.h 头文件,并使用传感器读取 RGB 值。对 RGB 值进行标准化处理后,构建输入特征向量并使用模型进行预测,最后输出预测结果。

4. 总结

本文详细介绍了机器学习中不同类型的数据,包括特征工程处理的数据、时间序列数据、音频数据以及图像和空间数据,并阐述了它们各自的特点和处理方法。同时,通过一个水果分类项目,展示了表格数据分类的完整流程,包括数据采集、数据处理、特征工程、模型训练以及模型部署。

整个流程可以用以下 mermaid 流程图表示:

graph LR
    A[数据采集] --> B[使用 Python 加载和检查数据]
    B --> C[使用 Python 进行特征工程]
    C --> D[使用 Python 训练分类模型]
    D --> E[模型转换为 C++ 代码]
    E --> F[部署回微控制器]

通过这个项目,我们可以看到传统机器学习方法在嵌入式设备上的应用,这些方法可以在资源有限的微控制器上运行,为实际应用提供了可行的解决方案。在实际应用中,我们可以根据具体需求选择合适的数据类型和处理方法,以达到最佳的效果。同时,数据质量对于模型的性能至关重要,因此在数据采集阶段要尽可能收集高质量的数据。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐