基于行空板与朴素贝叶斯的个人出行预测装置实践

基于行空板与朴素贝叶斯的个人出行预测装置实践

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(当前特征):这是当前特征组合出现的总概率,对于比较不同出行方式的后验概率来说,它是一个常数,所以实际计算中我们常常忽略它,只比较分子部分。

所以,算法的计算步骤就是:

  1. 从历史数据中统计出每个出行方式的先验概率P(出行方式)
  2. 统计出在每种出行方式下,每个特征取值出现的条件概率P(特征|出行方式)。例如,在“打车”的记录中,“天气=小雨”的记录占多少比例。
  3. 当面临新情况(新特征组合)时,为每个出行方式计算分子部分:P(出行方式) * Π P(特征_i | 出行方式)
  4. 比较所有出行方式的计算结果,数值最大的那个,就是模型预测的出行方式。

在我们的项目里,特征如何设计?我们需要把模糊的现实信息,变成模型能处理的离散值。例如:

  • 时间:可以离散化为[清晨, 上午, 中午, 下午, 傍晚, 夜间]
  • 天气[晴, 阴, 小雨, 中雨/大雨, 雪]
  • 个人状态[精力充沛, 一般, 有点累, 非常疲惫]
  • 距离[<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_size=0.2, random_state=42) # 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_names=label_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_encoderslabel_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.pkl
  • feature_encoders.pkl
  • label_encoder.pkl

你可以通过Web IDE的文件上传功能,或者用SCP命令从电脑传过去。

4.2 图形化界面与预测逻辑实现

我们在行空板上创建一个新的Python项目,主要实现以下功能:

  1. 加载训练好的模型和编码器。
  2. 创建一个图形界面,让用户通过按钮或下拉菜单选择当前的特征(时间、天气、状态、距离)。
  3. 当用户点击“预测”按钮时,将用户输入的特征按照编码规则转换,并调用模型进行预测。
  4. 将预测结果(如“推荐出行方式:公交/地铁”)显示在屏幕上,并可以配上一些图标增加趣味性。
# -*- 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="贝叶斯出行小助手", x=100, y=20, font_size=24) Label(page, text="请选择当前情况:", x=50, y=60) # 创建下拉菜单 y_offset = 100 Label(page, text="时间:", x=50, y=y_offset, width=80) time_dropdown = DropDown(page, options=TIME_OPTIONS, x=140, y=y_offset-5, width=150) time_dropdown.set_value(current_choices["time"]) Label(page, text="天气:", x=50, y=y_offset+50) weather_dropdown = DropDown(page, options=WEATHER_OPTIONS, x=140, y=y_offset+45, width=150) weather_dropdown.set_value(current_choices["weather"]) Label(page, text="状态:", x=50, y=y_offset+100) status_dropdown = DropDown(page, options=STATUS_OPTIONS, x=140, y=y_offset+95, width=150) status_dropdown.set_value(current_choices["status"]) Label(page, text="距离:", x=50, y=y_offset+150) distance_dropdown = DropDown(page, options=DISTANCE_OPTIONS, x=140, y=y_offset+145, width=150) distance_dropdown.set_value(current_choices["distance"]) # 预测按钮 predict_btn = Button(page, text="开始预测", x=100, y=y_offset+220, width=100) # 结果显示区域 result_label = Label(page, text="预测结果将显示在这里", x=50, y=y_offset+280, width=250, font_size=18, 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(text=f"推荐:{predicted_transport}") print(f"输入:{input_features} -> 预测:{predicted_transport}") predict_btn.on_click = on_predict_click # 5. 启动页面循环 page.start()

部署与调试要点:

  • 库安装:行空板系统可能未预装scikit-learnjoblib。需要通过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可以很小、很具体,并且完全由你掌控。行空板这样的硬件,则让这个想法快速地从代码变成了一个可以触摸、可以交互的实体。当你看着屏幕上的预测结果,回想起自己亲手标注的一条条数据时,那种“机器在学习我的习惯”的感觉,是非常奇妙的。