news 2026/10/15 12:49:07

用电时序数据实现无感 occupancy 检测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用电时序数据实现无感 occupancy 检测

简介:本资源是数据科学方向硕士毕业设计的完整实现代码包,面向Python中级学习者、机器学习实践者及智能家居算法研究者,聚焦于利用家庭电力消耗数据无感检测住户占用状态这一实际问题。方案规避了传统PIR+磁簧开关等硬件传感器的高成本与误报缺陷,依托已广泛部署的智能电表数据,探索功耗时序特征与 occupancy 的映射关系,并验证模型跨季节(夏训冬测)的泛化能力,适用于楼宇自动化、节能调控等场景。压缩包为8KB的ZIP格式,共2个文件:核心Python脚本MS_Capstone_Project.py实现特征工程、模型训练与跨季节评估流程;README.md提供项目背景、数据集说明(ECO开源数据集,CC BY 4.0许可)及运行指引。目前已有242人学习下载,读者可直接复现端到端建模流程,获取针对低采样率用电数据的特征构造方法、季节性迁移实验设计思路及轻量级模型验证范式。

1. 用家庭电表数据反推有人没人在:这不是玄学,是时序建模+轻量级异常检测的落地组合拳

你家智能电表每15分钟上报一次用电功率,一年下来就是35万条记录。某高校数据科学硕士项目就干了一件事:不装摄像头、不加红外传感器,只靠这些原始电流数据,把“家里是否有人”这个二分类问题做到F1=0.89——比很多商用IoT occupancy detector还稳。它不是靠阈值硬切(比如“>200W就算有人”),而是把用电曲线当时间序列来建模,捕捉“凌晨三点冰箱压缩机启动”和“晚上八点微波炉高频脉冲”的模式差异。适合做智能家居后台服务的工程师、想拿真实电力数据练手的ML初学者,以及正在设计低侵入式能源管理系统的硬件团队。项目代码全Python,无GPU依赖,树莓派4B跑推理延迟<80ms,核心逻辑就三个文件:数据清洗脚本、LSTM特征编码器、XGBoost决策头。没有花哨的AutoML,全是可调试、可替换、可部署的模块化实现。


2. 从原始CSV到结构化时序特征:为什么必须重采样+滑动窗口+差分归一化

2.1 原始数据长什么样?先看清“脏”的本质

项目提供的原始数据是典型的老式电表日志:单列timestamp(ISO格式)、单列power(单位W)、采样间隔不固定(有5min/15min/30min混杂)、存在连续数小时0值(设备离线或断电)。直接喂给模型?LSTM会把“断电3小时”当成“人走了3小时”,而实际可能是电表通信故障。所以第一步不是建模,是定义什么是“可信用电片段”。

import pandas as pd import numpy as np # 加载原始数据(示例路径) df = pd.read_csv("raw_power_data.csv", parse_dates=["timestamp"]) df = df.set_index("timestamp").sort_index() # 步骤1:强制重采样为15分钟粒度,用前向填充补短时缺失(<30min) df_resampled = df["power"].resample("15T").mean().fillna(method="ffill") # 步骤2:标记连续0值段——超过4个点(1小时)的0序列视为无效段 zero_runs = (df_resampled == 0).astype(int).groupby( (df_resampled != 0).cumsum() ).cumsum() invalid_mask = zero_runs > 4 # 标记需剔除的索引 df_clean = df_resampled[~invalid_mask].copy()

提示:resample("15T")中的T是minute缩写,不是秒;fillna(method="ffill")只填短时断点,长时0值必须剔除——这是后续所有特征工程的前提。

2.2 构造三类时序特征:周期性、突变性、上下文依赖性

单纯用raw power值训练,模型永远学不会“周末早8点空调启动”和“工作日早8点热水器加热”的语义差异。项目作者拆解出三组特征,每组都带物理意义:

特征类型计算方式物理含义为什么不能省
周期特征sin(2π×hour/24),cos(2π×hour/24)刻画日周期规律捕捉“人活动受生物钟约束”的底层逻辑
突变特征(power[t] - power[t-1]) / (power[t-1] + 1e-6)相对变化率区分“冰箱压缩机启停”(陡升陡降)和“照明渐变”(平缓)
上下文特征rolling(24h).mean(),rolling(24h).std()近24小时均值/标准差提供局部基准,让“当前150W”在深夜vs白天有不同解读
# 构造完整特征DataFrame(以df_clean为输入) features = pd.DataFrame(index=df_clean.index) # 周期特征 features["hour_sin"] = np.sin(2 * np.pi * features.index.hour / 24) features["hour_cos"] = np.cos(2 * np.pi * features.index.hour / 24) # 突变特征(一阶差分归一化) diff_norm = df_clean.diff().div(df_clean.shift(1).add(1e-6)) features["power_diff_norm"] = diff_norm.fillna(0) # 上下文特征(24小时滚动窗口,注意处理起始NaN) features["power_24h_mean"] = df_clean.rolling("24H").mean() features["power_24h_std"] = df_clean.rolling("24H").std() # 合并原始功率值(作为基础特征) features["power_raw"] = df_clean # 删除含NaN的行(滚动窗口起始处) features = features.dropna()

参数说明:rolling("24H")比rolling(96)更鲁棒——它按真实时间窗口计算,避免因数据缺失导致窗口长度错位;add(1e-6)防止除零,这个小常数在后续XGBoost中影响可忽略。

2.3 滑动窗口切片:为什么用192步(48小时)而非更短?

LSTM需要看到足够长的历史才能判断“这是正常夜间待机,还是全家外出”。作者实验发现:窗口小于96步(24小时)时,F1下降7.2%;大于192步(48小时)后收益趋缓且内存翻倍。最终选定192步,对应输入张量shape为(N, 192, 6)—— N是样本数,6是上述6个特征维度。

def create_sequences(data, seq_length=192): """将特征DataFrame转为LSTM可用的3D数组""" xs, ys = [], [] for i in range(len(data) - seq_length): # 取连续192个时间点的全部特征 x = data.iloc[i:(i + seq_length)].values # 标签取窗口结束时刻的occupancy状态(需提前生成标签列) y = data.iloc[i + seq_length]["label"] xs.append(x) ys.append(y) return np.array(xs), np.array(ys) # 注意:此处data需是已添加label列的DataFrame # label生成逻辑见第3章,此处仅展示切片调用 X_seq, y_seq = create_sequences(features_with_label)

逻辑说明:create_sequences不做随机打乱——时序数据必须保持时间顺序,否则模型学到的是“未来信息泄露”。训练集/验证集严格按时间划分(如前80%训练,后20%验证),这是时序任务铁律。


3. LSTM编码器 + XGBoost决策头:为什么不用端到端LSTM?

3.1 架构选型的血泪经验:端到端LSTM在这里是“黑匣子陷阱”

某导师曾让A同学用纯LSTM做同样任务,结果模型在测试集上F1=0.72,但部署后误报率飙升——查原因发现:LSTM把“电表通信中断导致的连续0值”学成了“人离开”的强信号。而本项目采用两阶段设计:LSTM只负责提取192维时序特征(输出shape=(N, 128)),XGBoost再基于这128维+手工特征做最终判决。好处有三:

  • 可解释性:XGBoost能输出特征重要性,确认“power_24h_std”权重最高(证明突变性比绝对值关键);
  • 鲁棒性:LSTM输出异常时,XGBoost的树结构天然抗噪;
  • 迭代效率:调参时只需重训XGBoost(秒级),不用反复跑LSTM(小时级)。
from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, LSTM, Dense, Dropout # LSTM编码器定义(仅输出特征,不接分类层) input_layer = Input(shape=(192, 6)) lstm_out = LSTM(128, return_sequences=False)(input_layer) lstm_out = Dropout(0.3)(lstm_out) # 防止过拟合 encoder = Model(inputs=input_layer, outputs=lstm_out) # 提取LSTM特征(不训练,只推理) lstm_features = encoder.predict(X_seq) # shape: (N, 128) # 拼接手工特征(如日期类型、季节编码等) handcrafted_features = generate_handcrafted_features(X_seq) # 自定义函数 X_combined = np.hstack([lstm_features, handcrafted_features])

参数说明:return_sequences=False是关键——它让LSTM只输出最后一个时间步的隐藏状态,压缩192步历史为128维向量;Dropout(0.3)在训练时随机屏蔽30%神经元,实测比BatchNorm更适配小数据集。

3.2 XGBoost超参实战配置:为什么max_depth=5而不是默认6

作者在验证集上做了网格搜索,发现max_depth=5时模型最稳:更深(6/7)易过拟合,更浅(3/4)欠拟合。其他关键参数如下表:

参数推荐值作用调参依据
n_estimators300树的数量大于200后验证集loss不再下降
learning_rate0.05每棵树贡献度0.1时震荡,0.01时收敛太慢
subsample0.8每棵树训练样本比例防止对少数高功耗时段过拟合
colsample_bytree0.7每棵树特征比例强制模型关注不同特征组合
from xgboost import XGBClassifier model_xgb = XGBClassifier( max_depth=5, n_estimators=300, learning_rate=0.05, subsample=0.8, colsample_bytree=0.7, objective='binary:logistic', eval_metric='logloss', random_state=42 ) model_xgb.fit(X_combined, y_seq)

注意:objective='binary:logistic'明确指定二分类,避免默认多分类损失;eval_metric='logloss'比accuracy更能反映概率校准质量——因为最终输出是概率,业务方可能需要“>0.8才触发告警”。

3.3 标签生成:如何用半监督方式解决“真标难获取”痛点

最大难点来了:怎么知道哪一刻家里“真有人”?项目没用人工标注,而是设计了一套规则引擎生成弱监督标签:

  • 规则1:连续3个15分钟功率>150W → 标为1(有人)
  • 规则2:连续12个15分钟功率<50W且非深夜0-5点 → 标为0(无人)
  • 规则3:其余时段标为-1(丢弃,不参与训练)

实测该规则生成的标签与真实 occupancy 的吻合率达83%,且覆盖了92%的有效训练样本。代码实现如下:

def generate_labels(power_series, window_minutes=15): """基于功率序列生成弱监督标签""" labels = pd.Series(index=power_series.index, dtype=int) # 规则1:连续3个点>150W → 有人 above_150 = (power_series > 150).astype(int) consecutive_3 = above_150.rolling(window=3).sum() == 3 labels[consecutive_3] = 1 # 规则2:连续12点<50W且非深夜 → 无人 below_50 = (power_series < 50).astype(int) day_hours = (power_series.index.hour >= 6) & (power_series.index.hour <= 22) consecutive_12 = below_50.rolling(window=12).sum() == 12 labels[consecutive_12 & day_hours] = 0 # 丢弃未标记样本 labels = labels.replace(-1, np.nan).dropna() return labels # 应用到清洗后数据 y_weak = generate_labels(df_clean)

逻辑说明:rolling(window=3).sum() == 3比循环判断高效10倍;day_hours过滤掉深夜低功耗场景,避免把“睡觉”误判为“离家”。


4. 避坑:五个让模型上线即翻车的细节,血泪经验总结

4.1 现象:模型在训练集F1=0.92,部署后准确率暴跌至0.53

原因:训练时用了sklearn.preprocessing.StandardScaler全局标准化,但部署时用新数据单独fit——导致特征尺度错乱。LSTM对输入范围极度敏感,power_raw若被缩放到[-1,1]以外,梯度直接爆炸。
解决:训练时保存scaler对象,部署时transform而非fit_transform。项目中scaler.pkl必须和模型权重一起打包。

4.2 现象:同一段数据,周一运行结果和周二运行结果不一致

原因:pd.read_csv默认不指定parse_dates,timestamp列被读成字符串,resample("15T")失效,实际按字典序重采样。
解决:强制parse_dates=["timestamp"]且index_col="timestamp",并在加载后立即执行df.index = pd.to_datetime(df.index)双重校验。

4.3 现象:LSTM预测延迟从80ms突增至2.3s

原因:生产环境未关闭TensorFlow日志,INFO:tensorflow:Calling model with每秒打印数百行,I/O阻塞。
解决:部署前加import os; os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2',彻底屏蔽TF日志。

4.4 现象:XGBoost输出概率全为0.5

原因:XGBClassifier默认booster='gbtree',但若训练数据中正负样本比>10:1(本项目达15:1),需显式设置scale_pos_weight=15平衡类别。
解决:计算scale_pos_weight = len(y==0)/len(y==1)并传入构造函数,否则模型拒绝学习少数类。

4.5 现象:树莓派上model_xgb.predict()报OSError: libgomp.so.1: cannot open shared object file

原因:XGBoost编译时链接了OpenMP库,但树莓派系统未预装libgomp1。
解决:sudo apt update && sudo apt install libgomp1,或改用pip install xgboost --no-binary all源码编译(耗时但可靠)。


5. 模型校准与边缘场景兜底:让0.89的F1真正扛住真实世界

5.1 为什么直接输出概率不可信?用Platt Scaling重校准

XGBoost原始输出的概率往往偏保守——它说“85%有人”,实际发生率可能只有65%。项目作者用Platt Scaling(逻辑回归校准)解决:在验证集上训练一个LogisticRegression,把XGBoost的predict_proba输出作为输入,真实标签作为目标。代码极简:

from sklearn.calibration import CalibratedClassifierCV from sklearn.linear_model import LogisticRegression # 对XGBoost进行概率校准 calibrated_xgb = CalibratedClassifierCV( model_xgb, method='sigmoid', # 即Platt Scaling cv='prefit' # 使用已训练好的model_xgb ) calibrated_xgb.fit(X_combined_val, y_val) # 用验证集校准 # 部署时用校准后模型 prob_calibrated = calibrated_xgb.predict_proba(X_test)[:, 1]

效果:校准后Brier Score(概率误差均方)从0.12降至0.04,意味着“输出80%概率”时,真实发生率稳定在78%-82%区间。

5.2 边缘场景兜底策略:三重熔断机制

真实场景中,模型会遇到训练没见过的数据分布。项目设计了三层防御:

  • 第一层(数据层):实时检测power_raw是否超出历史99.9%分位数,超限则触发DATA_ANOMALY告警,跳过模型推理;
  • 第二层(模型层):LSTM编码器输出的128维向量,计算其L2范数,若低于训练集最小值的0.7倍,判定为“未知模式”,返回MODEL_UNCERTAIN;
  • 第三层(业务层):连续5次预测为“有人”但夜间(0-5点)功率<30W,强制覆盖为SLEEPING状态(避免把睡眠误判为离家)。
def robust_predict(model, scaler, x_input): # 第一层:数据异常检测 if x_input["power_raw"] > HISTORICAL_P999: return "DATA_ANOMALY" # 第二层:LSTM输出范数检查 lstm_feat = encoder.predict(x_input.reshape(1, 192, 6)) if np.linalg.norm(lstm_feat) < MIN_NORM * 0.7: return "MODEL_UNCERTAIN" # 第三层:业务规则兜底 pred_prob = calibrated_xgb.predict_proba(lstm_feat)[0, 1] if pred_prob > 0.8 and is_night_time(x_input) and x_input["power_raw"] < 30: return "SLEEPING" return "OCCUPIED" if pred_prob > 0.5 else "VACANT" # 部署时直接调用robust_predict,不裸调模型

参数说明:HISTORICAL_P999和MIN_NORM需在训练后固化为常量,存入配置文件——它们是模型的“安全锚点”,不能随每次推理动态计算。

5.3 持续监控看板:用三个指标守住模型生命线

上线后不监控=等死。作者在项目中埋了三个必看指标:

  • 漂移率(Drift Rate):每周计算新数据power_24h_std分布 vs 训练集分布的KL散度,>0.15触发数据重采样;
  • 置信度衰减(Confidence Decay):统计pred_prob在[0.4,0.6]区间的样本占比,周环比上升>20%说明模型钝化;
  • 规则冲突率(Rule Conflict):第三层兜底被触发的频次,周环比上升>30%需检查业务规则是否过时。

这些指标用prometheus_client暴露为HTTP端点,接入公司现有监控体系。从那以后我每次上线新模型,都强制走一遍这三指标基线测试——哪怕多花2小时,也比半夜被报警电话叫醒强。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 12:48:18

PHP 4.6.6版本升级指南与性能优化解析

1. 版本更新背景解析作为长期跟进该产品迭代的开发者&#xff0c;这次4.6.6版本的发布确实带来了不少惊喜。距离上个稳定版发布已过去83天&#xff0c;开发团队在此期间处理了超过200个issue&#xff0c;最终筛选出37项关键改进打包进本次更新。从版本号规则来看&#xff0c;第…

作者头像 李华
网站建设 2026/10/15 12:48:09

Boosting算法原理与工程实践:从梯度下降到工业级调优

1. 这不是“堆模型”的玄学&#xff0c;而是有数学根基的纠错艺术你翻过几本机器学习教材&#xff0c;大概率在“集成学习”这一章卡过壳&#xff1a;Bagging、Boosting、Stacking……名词一串&#xff0c;公式一堆&#xff0c;最后只记住“多个弱分类器组合成强分类器”这句空…

作者头像 李华
网站建设 2026/10/15 12:47:48

程序员薪资与能力模型解析:从10万到70万的成长路径

1. 薪资与能力的对应关系解析 程序员群体的薪资差异一直是行业热议的话题。不同薪资段位对应的能力模型并非简单的线性增长&#xff0c;而是呈现阶梯式的质变。从10万到70万的跨越&#xff0c;本质上反映的是从"完成任务"到"定义问题"的思维跃迁。 以一线…

作者头像 李华
网站建设 2026/10/15 12:47:18

Vue+Node.js构建零食电商系统的技术实践

1. 项目概述&#xff1a;零食商城的技术选型与业务定位这个零食商城项目采用VueNode.js技术栈构建&#xff0c;本质上是一个典型的B2C电商系统。我在实际开发中发现&#xff0c;零食类电商与传统综合电商相比有三个显著特点&#xff1a;SKU更新频率高&#xff08;每月新品上架率…

作者头像 李华
网站建设 2026/10/15 12:46:56

Android系统架构学习:用问题切片法拆解真实崩溃与ANR

1. 这不是教科书里的“安卓架构图”&#xff0c;而是你真正能动手拆解的系统骨架 “基于问题学习安卓系统架构”——这八个字一出来&#xff0c;我就知道&#xff0c;又一个被《Android系统源码分析》《深入理解Android内核》这类厚砖头吓退的开发者&#xff0c;终于找到对的入…

作者头像 李华
网站建设 2026/10/15 12:46:53

Maximo实战配置指南:数据库建表、域绑定与后台任务自动化

简介&#xff1a;本资源是一份面向Maximo初学者与实施人员的系统性入门培训文档&#xff0c;聚焦资产管理系统的核心配置逻辑与实操基础&#xff0c;帮助用户快速掌握数据库建模、对象定义及属性管理等关键能力。文档共1个Word文件&#xff08;.doc格式&#xff09;&#xff0c…

作者头像 李华