1. 项目缘起当一块“行空板”遇见“贝叶斯”最近在折腾一个挺有意思的小项目起因是身边总有朋友抱怨说现在各种地图App的出行方式推荐有时候感觉“不太聪明”。比如明明外面下着瓢泼大雨它还在推荐你骑共享单车或者你刚下班身心俱疲它却给你规划了一条需要频繁换乘、步行距离超长的公交路线。这些推荐背后往往是基于海量历史数据和简单规则的算法缺乏对“此时此刻、此情此景”下个人偏好的动态感知。于是我就想能不能自己动手做一个更“懂我”的出行小助手它不需要联网获取复杂的实时路况也不需要庞大的用户画像数据它只需要根据我出发前能轻松获取的几个关键信息——比如天气、时间、我的精神状态——就能快速预测出我最可能选择的出行方式。这个想法听起来有点“人工智能”的味道但实现起来其实用一块小小的开发板和一个经典的机器学习算法就能搞定。我选择的硬件是行空板。对于不熟悉的朋友简单介绍一下行空板是一款面向教育和创客的国产开源硬件集成了屏幕、Wi-Fi/蓝牙、多种传感器接口和Python编程环境开箱即用特别适合快速原型开发。它就像一台微型的、可触摸交互的Linux电脑能跑完整的Python程序做数据采集、逻辑判断和结果展示都绰绰有余。而算法的核心我选择了朴素贝叶斯分类器。为什么是它首先我们这个预测问题本质上是一个分类问题将当前的“情境”特征分类到“步行”、“骑行”、“公交/地铁”、“驾车”等出行方式类别。朴素贝叶斯算法原理清晰、计算高效并且特别适合处理特征之间相对独立或我们假设它们独立的场景。出行决策中天气、时间、距离这些因素虽然有关联但在算法层面做“朴素”的独立性假设往往能取得不错的效果且模型训练和预测的速度都很快非常适合在行空板这样的嵌入式设备上运行。所以这个“基于行空板的贝叶斯出行方式预测装置”就诞生了。它的目标不是取代专业的地图应用而是作为一个有趣的、可定制的个人化工具探索机器学习如何以极低的门槛融入日常生活决策。接下来我将从零开始拆解整个项目的实现过程包括数据准备、模型训练、行空板程序开发以及实际应用中的思考。2. 核心原理朴素贝叶斯如何“思考”出行选择在动手写代码和接线之前我们必须先搞清楚朴素贝叶斯分类器到底是怎么工作的。很多教程一上来就摆公式容易让人望而生畏。我会尽量用“人话”和出行的例子来解释。想象一下你每天下班前决定怎么回家大脑其实在进行一个快速的“贝叶斯”计算。你无意识地会考虑几个因素现在几点时间外面下雨了吗天气我今天累不累个人状态从公司到家的距离大概多远距离然后基于你过去无数次回家的经验历史数据你的大脑会估算出每种出行方式步行、骑车、公交、打车在“当前这种组合条件下”发生的可能性最后选择可能性最高的那个。朴素贝叶斯算法就是把上面这个感性的过程用数学公式清晰地表达出来。它的核心是贝叶斯定理P(出行方式 | 当前特征) [ P(当前特征 | 出行方式) * P(出行方式) ] / P(当前特征)看起来复杂我们拆开看P(出行方式 | 当前特征)这是我们最终想求的即在“当前特征”如傍晚、小雨、有点累、5公里条件下选择“打车”的概率有多大算法会为每个出行方式都计算这个概率。P(出行方式)这叫“先验概率”。比如你回顾过去100天的下班记录发现打了30次车那么P(打车)就是 30/100 0.3。它代表了在没有任何额外信息时每种方式的基础偏好。P(当前特征 | 出行方式)这叫“条件概率”或“似然度”。意思是在你选择“打车”的那些日子里“傍晚、小雨、有点累、5公里”这些特征组合出现的概率有多大这里就引入了“朴素”的假设我们认为天气、时间、状态这些特征之间是相互独立的。因此这个组合的概率可以简化为各个特征条件概率的乘积P(傍晚|打车) * P(小雨|打车) * P(有点累|打车) * P(5公里|打车)。这个假设大大简化了计算。P(当前特征)这是当前特征组合出现的总概率对于比较不同出行方式的后验概率来说它是一个常数所以实际计算中我们常常忽略它只比较分子部分。所以算法的计算步骤就是从历史数据中统计出每个出行方式的先验概率P(出行方式)。统计出在每种出行方式下每个特征取值出现的条件概率P(特征|出行方式)。例如在“打车”的记录中“天气小雨”的记录占多少比例。当面临新情况新特征组合时为每个出行方式计算分子部分P(出行方式) * Π P(特征_i | 出行方式)。比较所有出行方式的计算结果数值最大的那个就是模型预测的出行方式。在我们的项目里特征如何设计我们需要把模糊的现实信息变成模型能处理的离散值。例如时间可以离散化为[清晨 上午 中午 下午 傍晚 夜间]。天气[晴 阴 小雨 中雨/大雨 雪]。个人状态[精力充沛 一般 有点累 非常疲惫]。距离[1km 1-3km 3-10km 10km]。这些都需要我们在前期收集数据时进行定义和标注。理解了这个原理我们就知道接下来要准备什么样的数据以及模型训练究竟在做什么了。3. 数据准备与模型训练打造属于你的“决策记忆库”模型的好坏很大程度上取决于“喂”给它的数据。我们不可能为了这个小项目去爬取百万级的数据但我们可以为自己“量身定制”一个小型数据集。这个过程虽然有点枯燥但至关重要。3.1 构建个人出行日志我建议用一个简单的CSV文件来记录每一行代表一次出行决策。表格的列包括date日期。time_period时间段如“傍晚”。weather天气如“小雨”。personal_status个人状态如“有点累”。distance_range距离范围如“3-10km”。transportation实际选择的出行方式标签如“打车”。你可以手动记录一周或更长时间的真实数据。如果觉得样本太少也可以基于自己的决策规则“合理”地虚构一部分数据来扩充数据集。例如“如果晚上10点后且下雨我100%打车”“如果周末晴天且距离3km我80%选择步行”。这样生成的数据虽然不如真实数据但能让模型快速学习到你的核心决策模式。我的初始数据集大约有150条记录涵盖了不同季节和情景的搭配。3.2. 使用Python进行模型训练数据准备好后我们在电脑上而不是行空板用Python进行模型训练和测试。这里用到scikit-learn这个强大的机器学习库。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.naive_bayes import CategoricalNB # 处理分类特征 from sklearn.preprocessing import LabelEncoder from sklearn.metrics import accuracy_score, classification_report import joblib # 用于保存模型 # 1. 加载数据 df pd.read_csv(my_transportation_log.csv) # 2. 定义特征和标签 features [time_period, weather, personal_status, distance_range] X df[features] y df[transportation] # 3. 将文本特征转换为数字编码朴素贝叶斯需要数值输入 label_encoders {} for col in X.columns: le LabelEncoder() X[col] le.fit_transform(X[col]) label_encoders[col] le # 保存编码器预测新数据时要用同样的编码规则 # 对标签也进行编码 label_encoder_y LabelEncoder() y_encoded label_encoder_y.fit_transform(y) # 4. 划分训练集和测试集8:2 X_train, X_test, y_train, y_test train_test_split(X, y_encoded, test_size0.2, random_state42) # 5. 创建并训练朴素贝叶斯模型 # 使用CategoricalNB它假设特征服从分类分布更适合我们的离散数据。 model CategoricalNB() model.fit(X_train, y_train) # 6. 在测试集上评估模型 y_pred model.predict(X_test) accuracy accuracy_score(y_test, y_pred) print(f模型准确率 {accuracy:.2%}) print(\n详细分类报告) print(classification_report(y_test, y_pred, target_nameslabel_encoder_y.classes_)) # 7. 保存模型和编码器 joblib.dump(model, transportation_bayes_model.pkl) joblib.dump(label_encoders, feature_encoders.pkl) joblib.dump(label_encoder_y, label_encoder.pkl) print(模型和编码器已保存。)关键点解析与实操心得为什么用CategoricalNB我们特征都是离散的类别型数据如“晴”、“雨”CategoricalNB是专门为这类数据设计的比通用的GaussianNB假设特征服从高斯分布或MultinomialNB常用于文本计数更合适。编码器LabelEncoder必须保存这是极易踩坑的地方。模型训练时把“傍晚”编码成了数字“4”。当我们在行空板上预测时输入的特征“傍晚”也必须被转换成同样的数字“4”。因此训练时用的label_encoders和label_encoder_y必须保存下来joblib.dump后续在行空板上加载使用。如何解读结果首次训练我的模型准确率大概在85%左右。查看classification_report可以发现某些类别如“步行”和“骑行”在短距离时可能容易混淆。这很正常也是后续可以优化的方向例如增加“是否有共享单车”这样的特征。注意如果你的数据量很小模型可能会过拟合或表现不稳定。可以考虑使用sklearn.model_selection.StratifiedKFold进行交叉验证来更可靠地评估模型性能。4. 行空板端应用开发让预测触手可及模型训练好之后接下来的任务就是将它部署到行空板上并开发一个交互界面。行空板支持Python并且有丰富的UI库pinpong我们可以轻松创建一个触摸应用。4.1 环境准备与文件传输首先确保你的行空板连接到网络。通过行空板的Web IDE浏览器访问其IP地址或使用VS Code的Remote SSH插件我们可以方便地编写代码。需要将之前保存的三个文件上传到行空板transportation_bayes_model.pklfeature_encoders.pkllabel_encoder.pkl你可以通过Web IDE的文件上传功能或者用SCP命令从电脑传过去。4.2 图形化界面与预测逻辑实现我们在行空板上创建一个新的Python项目主要实现以下功能加载训练好的模型和编码器。创建一个图形界面让用户通过按钮或下拉菜单选择当前的特征时间、天气、状态、距离。当用户点击“预测”按钮时将用户输入的特征按照编码规则转换并调用模型进行预测。将预测结果如“推荐出行方式公交/地铁”显示在屏幕上并可以配上一些图标增加趣味性。# -*- coding: utf-8 -*- import sys import joblib from pinpong.board import Board from pinpong.libs.dfrobot_ui import Button, Label, DropDown, Page import time # 初始化行空板 Board().begin() # 1. 加载模型和编码器 try: model joblib.load(transportation_bayes_model.pkl) feature_encoders joblib.load(feature_encoders.pkl) label_encoder joblib.load(label_encoder.pkl) print(模型加载成功) except Exception as e: print(f加载模型失败 {e}) sys.exit(1) # 定义特征选项与训练时一致 TIME_OPTIONS [清晨, 上午, 中午, 下午, 傍晚, 夜间] WEATHER_OPTIONS [晴, 阴, 小雨, 中雨/大雨, 雪] STATUS_OPTIONS [精力充沛, 一般, 有点累, 非常疲惫] DISTANCE_OPTIONS [1km, 1-3km, 3-10km, 10km] # 存储当前选择的变量 current_choices { time: TIME_OPTIONS[4], # 默认傍晚 weather: WEATHER_OPTIONS[0], # 默认晴 status: STATUS_OPTIONS[1], # 默认一般 distance: DISTANCE_OPTIONS[2] # 默认3-10km } # 2. 创建UI页面 page Page(title出行方式预测器) # 创建标题和说明标签 Label(page, text贝叶斯出行小助手, x100, y20, font_size24) Label(page, text请选择当前情况, x50, y60) # 创建下拉菜单 y_offset 100 Label(page, text时间, x50, yy_offset, width80) time_dropdown DropDown(page, optionsTIME_OPTIONS, x140, yy_offset-5, width150) time_dropdown.set_value(current_choices[time]) Label(page, text天气, x50, yy_offset50) weather_dropdown DropDown(page, optionsWEATHER_OPTIONS, x140, yy_offset45, width150) weather_dropdown.set_value(current_choices[weather]) Label(page, text状态, x50, yy_offset100) status_dropdown DropDown(page, optionsSTATUS_OPTIONS, x140, yy_offset95, width150) status_dropdown.set_value(current_choices[status]) Label(page, text距离, x50, yy_offset150) distance_dropdown DropDown(page, optionsDISTANCE_OPTIONS, x140, yy_offset145, width150) distance_dropdown.set_value(current_choices[distance]) # 预测按钮 predict_btn Button(page, text开始预测, x100, yy_offset220, width100) # 结果显示区域 result_label Label(page, text预测结果将显示在这里, x50, yy_offset280, width250, font_size18, color#FF9800) # 3. 定义下拉菜单的回调函数更新当前选择 def on_time_change(dropdown, value): current_choices[time] value def on_weather_change(dropdown, value): current_choices[weather] value def on_status_change(dropdown, value): current_choices[status] value def on_distance_change(dropdown, value): current_choices[distance] value time_dropdown.on_change on_time_change weather_dropdown.on_change on_weather_change status_dropdown.on_change on_status_change distance_dropdown.on_change on_distance_change # 4. 定义预测按钮的回调函数 def on_predict_click(btn): # 获取当前选择 input_features [ current_choices[time], current_choices[weather], current_choices[status], current_choices[distance] ] # 将文本特征转换为模型所需的数字编码 encoded_features [] for i, (feature_name, feature_value) in enumerate(zip([time_period, weather, personal_status, distance_range], input_features)): le feature_encoders[feature_name] # 注意如果遇到训练时没见过的特征值transform会报错。这里简单处理转为-1但更好的做法是确保UI选项与训练数据一致。 try: encoded_val le.transform([feature_value])[0] except ValueError: encoded_val -1 # 或使用一个默认值 encoded_features.append(encoded_val) # 检查是否有未知特征 if -1 in encoded_features: result_label.config(text错误包含未知的特征值) return # 进行预测模型需要2D数组输入所以要包一层[] prediction_encoded model.predict([encoded_features])[0] # 将预测结果数字解码回出行方式文本 predicted_transport label_encoder.inverse_transform([prediction_encoded])[0] # 显示结果 result_label.config(textf推荐{predicted_transport}) print(f输入{input_features} - 预测{predicted_transport}) predict_btn.on_click on_predict_click # 5. 启动页面循环 page.start()部署与调试要点库安装行空板系统可能未预装scikit-learn和joblib。需要通过SSH连接行空板使用pip安装pip3 install scikit-learn joblib。注意行空板的存储空间和算力安装过程可能需要一些时间。编码一致性这是核心陷阱。务必确保UI下拉菜单里的选项和训练数据集中出现的特征值完全一致连一个标点符号都不能差。否则编码器transform时会报ValueError。我的代码里用了try-except做简单保护但根本解决办法是保证数据源头一致。性能考量朴素贝叶斯预测速度极快在行空板上几乎是瞬间完成用户体验很好。界面优化你可以为不同的出行方式结果配上不同的颜色或小图标行空板UI支持显示图片让界面更生动。5. 项目优化与扩展思考一个能跑通的原型只是起点。要让这个“出行预测装置”真正变得有用和智能还有很长的路可以走。以下是我在项目过程中和完成后的一些思考方向。5.1 模型与特征的优化特征工程当前的特征比较基础。可以考虑引入更有区分度的特征例如实时信息通过行空板的网络功能获取实时天气API数据精确到“降水量”、“温度”而不是手动选择。上下文信息“是否携带重物”、“是否与人同行”、“当前所在位置家/公司/商圈”等。时间细化不仅分时段还可以考虑“是否工作日/周末”、“是否节假日”。处理连续特征距离其实是一个连续值我们粗暴地分桶了。可以尝试用高斯朴素贝叶斯来处理距离这个连续特征或者探索更优的分桶策略。模型对比可以尝试其他轻量级分类模型如决策树、随机森林在电脑上对比效果。但要注意模型复杂度确保能在行空板上顺利运行。在线学习能否让模型在行空板上进行“微调”每次实际出行后用户可以在界面上确认或纠正预测结果这条新的“特征实际方式”数据就可以用来更新模型的概率计数。这需要实现贝叶斯模型的增量更新算法挑战较大但会非常有趣。5.2 硬件与交互的增强自动化数据输入手动选择天气、状态还是很麻烦。可以集成传感器温湿度/气压传感器间接判断天气状况。GPS模块结合地理围栏自动判断“出发地”和“目的地”并估算距离。心率传感器如果可行客观量化“疲惫程度”。语音交互结合行空板的语音识别模块直接说“现在下雨我好累回家5公里”设备自动预测并语音播报结果。物理输出用行空板的GPIO控制几个不同颜色的LED灯分别代表不同的出行方式预测结果一亮灯更加直观。5.3 从玩具到工具的挑战这个项目目前更像一个有趣的机器学习教学演示。要变成一个可靠的工具必须面对真实世界的复杂性数据稀疏性对于某些罕见特征组合如“深夜大雪精力充沛10km”历史数据中可能根本没有模型预测会不可靠。需要设计回退策略如使用边缘概率。个性化与泛化这个模型是完全个人化的。它的“知识”只来自于你自己的数据。如果想做一个通用版本需要解决数据隐私和收集的难题。决策的不确定性模型给出的是“最可能”的方式但有时第二、第三可能的选项概率相差无几。可以向用户展示Top 2或3的推荐及其置信度把最终决定权交还给用户。回过头看这个项目最大的价值不在于做出了一个多精准的预测工具而在于完整地体验了从问题定义、算法理解、数据准备、模型训练到嵌入式部署的全流程。它把看似高深的“贝叶斯”、“机器学习”从云端拉到了手边让你真切地感受到AI可以很小、很具体并且完全由你掌控。行空板这样的硬件则让这个想法快速地从代码变成了一个可以触摸、可以交互的实体。当你看着屏幕上的预测结果回想起自己亲手标注的一条条数据时那种“机器在学习我的习惯”的感觉是非常奇妙的。