17、深度学习模型部署与特征工程全解析
深度学习模型部署与特征工程全解析
1. 2D CNN 的部署
要运行 2D CNN,需要 2D 输入,如图片、随时间变化的传感器值或多点传感器读数。不过,TensorFlow 运行时期望的是 1D 输入数据,所以必须先将输入展平,以数组而非矩阵的形式提供。以下是常见数据类型的展平技术示例:
-
图像
:灰度图像的预期输入格式是按行优先(第一行像素、第二行像素等)。对于 RGB 图像,格式是 R1, G1, B1, R2, G2, B2 等。灰度情况适用于所有其他类似图像的输入。
-
随时间变化的传感器数据
:预期输入格式是按时间优先(SensorAt=1, SensorBt=1, SensorAt=2, SensorBt=2)。
1.1 在 Arduino 上运行 CNN2D 的示例代码
#include "./PetsCNN2D.h"
// ARENA 是为模型预留的内存量
// 较大的模型需要更多内存,但没有计算最优值的公式
// 这是一个反复试验的过程
#define ARENA 20000
tinyml4all::CNN2D<ARENA> cnn;
uint8_t dog[48*48] = {...};
uint8_t cat[48*48] = {...};
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("TensorFlow 2D CNN demo");
cnn.begin();
}
void loop() {
// 对狗图像进行分类
if (!cnn.predict(dog)) {
Serial.println(nn.error());
return;
}
// .label 保存预测类别的名称
// .output 保存数值输出(用于回归)或类别 ID(用于分类)
// .outputs 是包含所有输出的数组(分类时每个类别一个)
// .runtime_ms 保存预测的持续时间
// outputsAsString 返回每个类别的得分,范围从 0 到 1(仅适用于分类)
Serial.print("Expected dog, predicted ");
Serial.print(cnn.label);
Serial.print(" with confidence ");
Serial.println(cnn.confidence);
Serial.print(" > Scores: ");
Serial.println(cnn.outputsAsString());
// 对猫图像进行分类
if (!cnn.predict(cat)) {
Serial.println(cnn.error());
return;
}
Serial.print("Expected cat, predicted ");
Serial.print(cnn.label);
Serial.print(" with confidence ");
Serial.println(cnn.confidence);
Serial.print(" > Scores: ");
Serial.println(nn.outputsAsString());
delay(1000);
}
1.2 猫狗分类的混淆矩阵
| True vs Predicted | dog | cat |
|---|---|---|
| dog | 8 | 3 |
| cat | 4 | 5 |
从这个混淆矩阵中,我们可以分析模型的分类性能。例如,有 8 次正确将狗预测为狗,但有 3 次将狗错误预测为猫;有 5 次正确将猫预测为猫,但有 4 次将猫错误预测为狗。
2. 特征工程操作符
特征工程对于提高机器学习模型的性能至关重要。以下介绍几种特征工程操作符。
2.1 特征缩放
特征缩放用于改变输入数据的范围,可以通过线性变换(如最小 - 最大、z - 分数、鲁棒操作符)、非线性变换(如 Box - Cox 和 Yeo - Johnson)或基于实例的变换(如范数)。
2.1.1 Z - 分数归一化
也称为标准化,z - 分数归一化是一种特征缩放方法,通过以下公式将每列重新缩放为均值为 0、方差为 1。
[z = \frac{x - \text{mean}(x)}{\text{std}(x)}]
需要注意的是,z - 分数归一化对异常值敏感。以下是应用该归一化到表格数据集的示例代码:
standard = Scale(method="zscore")
table2 = standard(table)
print(table2.describe())
归一化后的表格统计信息如下:
| | r | g | b |
| — | — | — | — |
| count | 150 | 150 | 150 |
| mean | 0 | 0 | 0 |
| std | 1.0034 | 1.0034 | 1.0034 |
| min | -1.7455 | -2.1319 | -2.3148 |
| 25% | -0.926 | -0.6539 | -0.4955 |
| 50% | -0.0246 | -0.1612 | 0.0243 |
| 75% | 0.7539 | 0.5554 | 0.5441 |
| max | 2.5978 | 7.0049 | 7.5613 |
2.1.2 鲁棒归一化
当数据包含异常值时,鲁棒归一化就派上用场了。它将均值替换为中位数,标准差替换为四分位距(IQR),公式如下:
[x’ = \frac{x - \text{median}(x)}{\text{IQR}}]
以下是应用鲁棒缩放的示例代码:
robust = Scale(method="robust")
table2 = robust(table)
print(table2.describe())
鲁棒归一化后的表格统计信息如下:
| | r | g | b |
| — | — | — | — |
| count | 150 | 150 | 150 |
| mean | 0.0146 | 0.1333 | -0.0233 |
| std | 0.5972 | 0.8297 | 0.9651 |
| min | -1.0244 | -1.6296 | -2.25 |
| 25% | -0.5366 | -0.4074 | -0.5 |
| 50% | 0 | 0 | 0 |
| 75% | 0.4634 | 0.5926 | 0.5 |
| max | 1.561 | 5.9259 | 7.25 |
2.1.3 单位范数
这是一种基于实例的特征缩放策略,不计算全局统计信息。它通过计算样本的范数并将每个分量除以该范数来实现。有不同阶的范数可供选择,如 L1 范数、L2 范数和 L - max 范数。
-
L1 范数(曼哈顿范数)
:计算为向量分量绝对值的总和。
[||x|| 1 = \sum {i = 1}^{N} |x_i|] -
L2 范数(欧几里得范数)
:定义为向量每个分量平方和的平方根。
[||x|| 2 = \sqrt{\sum {i = 1}^{N} x_i^2}] -
L - max 范数
:是向量分量绝对值的最大值。
[||x||_{\max} = \max(|x_i|)]
以下是应用不同范数的示例代码:
# L1 范数
L1 = Scale(method="L1")
table2 = L1(table)
print(table2.describe())
# L2 范数
L2 = Scale(method="L2")
table2 = L2(table)
print(table2.describe())
# L - max 范数
Lmax = Scale(method="Lmax")
table2 = Lmax(table)
print(table2.describe())
2.2 幂变换
2.2.1 Box - Cox 幂变换
Box - Cox 变换属于幂变换家族,通过非线性映射来稳定方差并使数据更接近正态分布。公式如下:
[y^{(\lambda)} =
\begin{cases}
\frac{y^{\lambda} - 1}{\lambda}, & \lambda \neq 0 \
\ln(y), & \lambda = 0
\end{cases}
]
需要注意的是,Box - Cox 变换仅适用于严格正的数据。以下是应用该变换的示例代码:
from tinyml4all.tabular.features import BoxCox
boxcox = BoxCox()
table2 = boxcox(table)
变换后的表格统计信息如下:
| | r | g | b |
| — | — | — | — |
| count | 150 | 150 | 150 |
| mean | 6.4087 | 2.2936 | 2.0929 |
| std | 1.1713 | 0.1512 | 0.1115 |
| min | 3.83 | 1.7369 | 1.6699 |
| 25% | 5.3378 | 2.1957 | 2.0421 |
| 50% | 6.5263 | 2.2897 | 2.1103 |
| 75% | 7.3512 | 2.4006 | 2.1693 |
| max | 8.9006 | 2.8958 | 2.5908 |
2.2.2 Yeo - Johnson 幂变换
Yeo - Johnson 变换与 Box - Cox 变换属于同一家族,但它适用于任何数据,包括负数。以下是应用该变换的示例代码:
from tinyml4all.tabular.features import YeoJohnson
yeojohnson = YeoJohnson()
table2 = yeojohnson(table)
变换后的表格统计信息如下:
| | r | g | b |
| — | — | — | — |
| count | 150 | 150 | 150 |
| mean | 6.276 | 2.1292 | 1.9302 |
| std | 1.0779 | 0.1168 | 0.0831 |
| min | 3.9118 | 1.7014 | 1.6159 |
| 25% | 5.2894 | 2.0534 | 1.8922 |
| 50% | 6.3834 | 2.1262 | 1.9432 |
| 75% | 7.1437 | 2.2122 | 1.9873 |
| max | 8.5706 | 2.5908 | 2.2987 |
2.3 离散化
特征离散化是将连续变量转换为离散变量的过程,许多机器学习算法可以从中受益。
2.3.1 二值化
有时,数值包含的信息过多,只需要一个二进制特征。例如,在气象站中,对于降雨量,可能只需要知道是否下雨,而不需要具体的降雨量。这一过程称为二值化,需要定义一个阈值,低于阈值的值转换为 False,高于阈值的值转换为 True。
以下是仅对“rain”列进行二值化的示例代码:
from tinyml4all.tabular.features import Discretize
bin_rain = Discretize(column="rain", threshold=0.1)
table2 = bin_rain(table)
二值化后的表格如下:
| temperature | humidity | rain |
| — | — | — |
| 22.1 | 40 | False |
| 23.3 | 41 | False |
| 9.6 | 60 | True |
| 9.7 | 61 | True |
| 9.5 | 65 | True |
如果要对多个列进行二值化,并且每个列有不同的阈值,可以链式调用。以下是示例代码:
from tinyml4all.tabular.features import Discretize
from tinyml4all.tabular.classification import Chain
# 1. 对温度高于 20° 进行二值化并创建 "hot" 列
# 2. 对湿度高于 80% 进行二值化并创建 "wet" 列
# 3. 对湿度低于 20% 进行二值化并创建 "dry" 列
# 4. 对降雨量高于 0.1 进行二值化
binarize_many = Chain(
Discretize("temperature", threshold=20, append="hot"),
Discretize("humidity", threshold=80, append="wet"),
Discretize("humidity", threshold=20, append="dry", flip=True),
Discretize(column="rain", threshold=0.1)
)
table2 = binarize_many(table)
2.3.2 分箱
分箱(也称为桶化)是将连续数据拟合到有限范围的可能值(“箱”或“桶”)的过程。这可以平滑数据的小波动,使模型更不易过拟合。例如,在气象站中,对于湿度,可能不需要精确到 1 的分辨率,以 10 为增量的粗略值可能就足够了。以下是对湿度列进行分箱的示例代码:
bin = Discretize("humidity", bins=10, append="humidity_bin")
table2 = bin(table)
分箱后的表格如下:
| humidity | humidity_bin |
| — | — |
| 42 | 4 |
| 41 | 4 |
| 60 | 6 |
| 61 | 6 |
| 62 | 6 |
2.3.3 独热编码
独热编码将分类列(只能取有限值的列,如星期几)转换为二进制值列表,其中只有第 i 个位置的值为 1(i 表示该值在可用值列表中的位置)。这有助于那些为每个特征分配重要性得分的模型。需要注意的是,独热编码仅适用于分类、离散数据,不能对实值、连续列进行编码。以下是应用独热编码的示例代码:
from tinyml4all.tabular.features import OneHot
onehot = OneHot(column="fruit")
table2 = onehot(fruit)
独热编码后的表格如下:
| fruit | fruit=orange | fruit=tomato | fruit=zucchini |
| — | — | — | — |
| orange | TRUE | FALSE | FALSE |
| orange | TRUE | FALSE | FALSE |
| tomato | FALSE | TRUE | FALSE |
| tomato | FALSE | TRUE | FALSE |
| zucchini | FALSE | FALSE | TRUE |
下面是特征工程操作的流程图:
graph LR
A[特征工程] --> B[特征缩放]
A --> C[幂变换]
A --> D[离散化]
B --> B1[Z - 分数归一化]
B --> B2[鲁棒归一化]
B --> B3[单位范数]
C --> C1[Box - Cox 幂变换]
C --> C2[Yeo - Johnson 幂变换]
D --> D1[二值化]
D --> D2[分箱]
D --> D3[独热编码]
3. 机器学习中的数据类型与处理
3.1 数据类型概述
机器学习中常见的数据类型有以下几种:
-
音频数据
:可用于音频分类、语音识别等任务。例如,在音频唤醒词检测中,通过对音频数据的处理来判断是否检测到特定的唤醒词。
-
图像和空间数据
:适用于图像分类、目标检测等场景。像使用卷积神经网络(CNN)对图像中的物体进行识别。
-
表格数据
:具有固定的结构,列代表特征,行代表样本。常用于分类和回归任务,如根据水果的特征进行分类。
-
时间序列数据
:按时间顺序排列的数据,可用于预测、趋势分析等。例如,根据历史的股票价格数据预测未来的价格走势。
3.2 不同数据类型的处理流程
3.2.1 表格数据分类处理流程
| 步骤 | 操作内容 |
|---|---|
| 数据采集 | 可以通过传感器采集数据,如使用颜色传感器采集水果的 RGB 数据。数据采集时要注意数据的质量,确保数据的准确性和完整性。 |
| 数据加载与检查 | 使用 Python 脚本加载数据,可使用 tinyml4all 包进行操作。可以对数据进行可视化检查,如绘制散点图、矩阵对图等,以了解数据的分布情况。 |
| 特征工程 | 包括特征缩放、特征选择等操作。特征缩放可以使用多种方法,如 z - 分数归一化、鲁棒归一化等;特征选择可以采用递归特征消除(RFE)、基于分数的选择等方法。 |
| 模型训练与评估 | 选择合适的分类模型,如决策树、逻辑回归、支持向量机等,并对模型进行训练和评估。可以使用混淆矩阵、准确率、召回率等指标来评估模型的性能。 |
| 模型部署 | 将训练好的模型部署到目标设备上,如 Arduino 板。 |
3.2.2 时间序列数据分类处理流程
graph LR
A[数据采集] --> B[特征工程]
B --> C[模型训练]
C --> D[模型部署]
A --> E[数据标注]
E --> C
- 数据采集 :可以使用惯性测量单元(IMU)传感器采集数据,如加速度计、磁力计的数据。采集的数据可以是连续的运动数据或间歇性的时间序列数据。
- 特征工程 :对时间序列数据进行特征提取,如频域特征、时域特征等。可以使用 FIFO 数据结构、窗口逻辑等方法进行特征工程。
- 模型训练 :选择合适的模型,如 1D CNN、LSTM 等,并对模型进行训练。
- 模型部署 :将训练好的模型部署到 Arduino 板上,需要注意编译时间、推理串行输出等问题。
4. 机器学习模型类型与特点
4.1 分类模型
4.1.1 常见分类模型
| 模型名称 | 特点 | 优点 | 缺点 |
|---|---|---|---|
| 决策树 | 基于树结构进行决策,易于理解和解释。 | 可以处理非线性数据,不需要特征缩放。 | 容易过拟合,尤其是在树的深度较大时。 |
| 逻辑回归 | 使用逻辑函数进行分类,适用于二分类问题。 | 计算效率高,可解释性强。 | 对非线性数据的处理能力有限。 |
| 支持向量机(SVM) | 通过寻找最优的超平面进行分类。 | 在处理高维数据和非线性数据时表现较好。 | 计算复杂度较高,对参数的选择比较敏感。 |
| 随机森林 | 由多个决策树组成的集成模型。 | 具有较高的准确率和稳定性,能够处理高维数据。 | 模型解释性相对较差,训练时间较长。 |
| 极端梯度提升(XGBoost) | 一种高效的梯度提升算法。 | 具有较高的准确率和效率,能够处理大规模数据。 | 对内存要求较高,容易过拟合。 |
4.1.2 分类模型的评估指标
- 准确率 :预测正确的样本数占总样本数的比例。
- 精确率 :预测为正类的样本中实际为正类的比例。
- 召回率 :实际为正类的样本中被预测为正类的比例。
- 混淆矩阵 :用于展示模型在不同类别上的分类情况,包括真正类、假正类、真负类、假负类。
4.2 回归模型
4.2.1 常见回归模型
- 普通最小二乘法(OLS) :通过最小化误差平方和来拟合数据。适用于线性回归问题。
- 多项式回归 :通过对特征进行多项式扩展来处理非线性关系。
- 决策树回归 :基于决策树的结构进行回归预测。
4.2.2 回归模型的评估指标
- 均绝对误差(MAE) :预测值与真实值之间绝对误差的平均值。
- 均方根误差(RMSE) :预测值与真实值之间误差平方的平均值的平方根。
- 决定系数 :衡量回归模型对数据的拟合程度。
5. 模型部署与优化
5.1 模型部署到 Arduino
在将模型部署到 Arduino 时,需要注意以下几点:
-
内存管理
:为模型预留足够的内存空间,如在运行 2D CNN 时,需要定义合适的 ARENA 值。
-
编译时间
:不同的模型和代码可能会导致不同的编译时间,需要进行优化。
-
推理串行输出
:确保模型在 Arduino 上的推理结果能够正确输出,以便进行后续的处理和分析。
5.2 模型优化
- 特征工程优化 :选择合适的特征工程方法,如特征缩放、特征选择等,以提高模型的性能。
- 模型选择优化 :根据数据的特点和任务的需求,选择合适的模型。例如,对于图像分类任务,选择 CNN 模型;对于时间序列数据,选择 LSTM 模型。
- 参数调优 :对模型的参数进行调优,以提高模型的准确率和稳定性。可以使用网格搜索、随机搜索等方法进行参数调优。
总之,机器学习涉及到数据处理、模型选择、特征工程、模型训练和部署等多个环节。通过合理地运用各种技术和方法,可以提高模型的性能,实现更准确的预测和分类任务。在实际应用中,需要根据具体的问题和数据特点,选择合适的方法和模型,并进行不断的优化和改进。
更多推荐



所有评论(0)