在之前的一篇文章《第一台微控制器》中,讨论了如何使用微控制器(MCU)来创建家庭自动化系统。本文将在此基础上,进一步探讨微控制器的强大功能和灵活性,尽管它们存在一定的局限性。本文的核心是DIY精神,即从零开始的精神。
微控制器是一种非常小型的计算机,它们没有常见的外设(如扬声器、显示器、键盘、鼠标等),但它们拥有一些新的特性:I/O点,这些点可以连接到其他设备,以监控和管理这些外部设备。微控制器是智能设备如洗衣机、汽车燃油喷射系统、手机等的核心。
下图是Raspberry Pi Pico(简称Pico),将在这款微控制器上实现一个包含KNN分类算法的报警系统。整个板子的尺寸仅为21毫米×51毫米。
如果已经阅读了《第一台微控制器》,就会知道它使用两个温度测量值来判断冰箱门是否意外地被打开。Pico每5秒获取一次这两个温度,评估它们的差异,并在冰箱门下方的温度比环境温度低5摄氏度以上时点亮红色LED。可以在GitHub仓库中看到运行在Pico上的Python代码。
选择使用KNN算法,因为它易于理解(因此也易于调试),并且网络上有许多从头开始编写的版本。虽然KNN不需要事先训练,但它需要训练数据。典型的机器学习数据集(例如鸢尾花数据集)基于经验观察,但这个应用的训练数据完全是合成的。
train_data = []
def generate_training_data():
global train_data
train_data = []
for temp_amb in [10 + 2.5 * n for n in range(13)]:
for temp_door in [10 + 2.5 * n for n in range(13)]:
if temp_door < temp_amb + 2.5:
train_data.append([temp_amb, temp_door, 'FAULT'])
else:
train_data.append([temp_amb, temp_door, 'NORMAL'])
generate_training_data()
训练数据包含169个样本,每个样本包含两个特征(温度)和一个类别(NORMAL、ALARM或FAULT)。下图展示了这些训练数据的散点图。
由于Pico的MicroPython不支持NumPy,通常的机器学习库如Scikit-learn(几乎所有这些都依赖于NumPy)自动被排除在外。因此,使用从头开始的KNN版本是不可避免的。
from math import sqrt
def encode_class_names(dataset):
class_names = [row[-1] for row in dataset]
class_names_set = set(class_names)
index_lookup = {name: i for i, name in enumerate(class_names_set)}
for row in dataset:
row[-1] = index_lookup[row[-1]]
return {i: name for i, name in enumerate(class_names_set)}
def distance(row1, row2):
feature_pairs = zip(row1, row2)
deltas = [(a - b) for a, b in feature_pairs]
sum_diagonals_squared = sum([delta ** 2 for delta in deltas])
return sqrt(sum_diagonals_squared)
def nearest_neighbors(dataset, test_row, num_neighbors):
distances = [distance(test_row, train_row) for train_row in dataset]
row_distance_pairs = list(zip(dataset, distances))
row_distance_pairs.sort(key=lambda tup: tup[1])
neighbor_rows = [pair[0] for pair in row_distance_pairs[:num_neighbors]]
return neighbor_rows
def predict(dataset, test_row, num_neighbors):
neighbors = nearest_neighbors(dataset, test_row, num_neighbors)
neighbor_class_names = [row[-1] for row in neighbors]
prediction = max(set(neighbor_class_names), key=neighbor_class_names.count)
return prediction
上述代码是基于Jason Brownlee的教程“从头开始用Python开发k-最近邻算法”(访问日期:2021年7月26日)。该教程提供了KNN工作原理的详细解释。
dataset_copy = [e[:] for e in train_data[:]]
predictions = []
for temp_amb in [t / 2 for t in range(20, 81)]:
for temp_door in [t / 2 for t in range(20, 81)]:
label = predict(dataset_copy, [temp_amb, temp_door], num_neighbors)
predictions.append([temp_amb, temp_door, label])
df2 = pd.DataFrame(predictions, columns=['temp-ambient', 'temp-door', 'state'])
print(len(df2))
print(df2['state'].value_counts())
colormap = {'FAULT': 'royalblue', 'ALARM': 'coral', 'FIRE': 'maroon', 'NORMAL': 'limegreen'}
colors = df2['state'].map(colormap)
ax = df2.plot(kind='scatter', x='temp-ambient', y='temp-door', grid=True, figsize=(8, 8), c=colors, s=96)
ax.set_xlabel('环境温度 (C)', fontsize=14)
ax.set_xlim([8, 42])
ax.set_ylabel('门温度 (C)', fontsize=14)
ax.set_ylim([8, 42])