课程首页· 代码速查· 第 3 课· 第 4 课 / 共 8 课

Lesson 04 · 支柱 A · 预测

Neural ODE 预测区域温度

用连续时间微分方程学建筑热演化——这是 BrainBox 自称"96% 精度"的那一根支柱。

第 3 课你已经看到:区域温度服从 C_z·dT/dt = (T_out−T_z)/R + … 这种 ODE。现在把它反过来用——不手写物理参数,而是让神经网络学这个 ODE 的右端,再用 ODE 求解器积分出未来温度轨迹。这就是 Neural ODE,也是 BrainBox 预测层的核心机制。

这一课给你完整的可跑实现:从数据准备,到定义 dz/dt = f(z,t),到用 torchdiffeq 积分、训练、6 小时前瞻预测。第 5 课再在它上面加物理正则化。

一、Neural ODE 到底在做什么

普通网络是一层一层离散地变换隐状态:z₁ = f(z₀), z₂ = f(z₁)…。Neural ODE 把"层"换成一个连续微分方程

Neural ODE 核心式复制
dz/dt = f_θ(z, t)          # θ 是网络参数,z 是隐状态
z(t₁) = ODESolve(z(t₀), f_θ, t₀, t₁)   # 用数值积分器从 t₀ 推到 t₁

对建筑温度预测来说:z 是区域热状态(温度、可能含墙体蓄热等),f_θ 是网络学到的"热演化律",t₀→t₁ 就是从现在推到 6 小时后。结构和第 3 课的 1R1C 同形——只不过右端 f 从手写公式变成了学习的网络。

二、两个求解器:odeint vs odeint_adjoint

torchdiffeq 给两个主接口,差别在梯度怎么算

选哪个求解器
接口梯度方式内存速度什么时候用
odeint普通反向传播(穿过每步求解)随步数线性涨较快轨迹短、原型阶段
odeint_adjointadjoint 敏感度(反向再解一次 ODE)O(1) 恒定约慢 1.5–2×长时序、6h 前瞻、显存敏感

三、动手做:训一个 Neural ODE 温度预测器

数据来源用第 2 课的 Sinergym:让 PID 基线跑一年,把每一时间步的 zone 温度 + 外部扰动存下来,就是训练数据。这里给完整训练脚本。

动手做 4.1 · 采集训练数据(Sinergym 跑一年,存 CSV)
python · collect_data.py复制
import gymnasium as gym, sinergym, numpy as np, csv

env = gym.make("Eplus-5zone-hot-continuous-stochastic-v1")
obs, _ = env.reset(seed=0)
rows = []
for _ in range(env.get_wrapper_attr("max_ep_data") - 1):
    # 用随机/规则动作采集;这里先随便给个动作
    action = env.action_space.sample()
    obs, reward, term, trunc, info = env.step(action)
    # 存:[时间索引, 室外温度, 太阳辐射, zone温度(5个), 能耗]
    rows.append([*obs.tolist(), info.get("total_power", 0.0)])
    if term or trunc:
        obs, _ = env.reset()
env.close()

with open("zone_data.csv", "w", newline="") as f:
    csv.writer(f).writerows(np.array(rows))
print("采集到", len(rows), "步")
预期结果:约 3504 行(一年,15min 步长)。真实复刻时应换成 PID / 规则基线采集(动作更合理),这里为简洁用随机动作,只为打通管线。采集一次可反复用。
动手做 4.2 · 训练 Neural ODE,6 小时前瞻(核心)
python · neural_ode_train.py复制
import torch, torch.nn as nn, numpy as np
from torchdiffeq import odeint_adjoint as odeint

# —— 1. 数据:滑窗构造 (t0 状态 → 未来 6h=24 步 轨迹) ——
data = np.loadtxt("zone_data.csv", delimiter=",")
T_zone = data[:, 3:8]          # 取 5 个 zone 温度列(按你的列调整)
dt = 0.25; horizon = 24          # 6h = 24 × 15min

def make_seqs(arr, lookback=1, horizon=24):
    X, Y = [], []
    for i in range(len(arr) - horizon):
        X.append(arr[i]); Y.append(arr[i:i+horizon])   # 起点 → 未来轨迹
    return torch.tensor(X, dtype=torch.float32), torch.tensor(torch.tensor(Y))
X, Y = make_seqs(T_zone)
Y = Y.squeeze(-2)                          # [N, horizon, zones]

# —— 2. 定义 dz/dt = f_θ(z, t) ——
class ODEFunc(nn.Module):
    def __init__(self, dim=5):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim, 32), nn.Tanh(),
            nn.Linear(32, 32), nn.Tanh(),
            nn.Linear(32, dim))
    def forward(self, t, z):
        return self.net(z)        # 这里先纯数据驱动;第 5 课注入物理项

func = ODEFunc(dim=5)
opt = torch.optim.Adam(func.parameters(), lr=1e-3)
t_grid = torch.linspace(0, dt*horizon, horizon)   # 0 → 6h

# —— 3. 训练循环 ——
for epoch in range(30):
    opt.zero_grad()
    pred = odeint(func, X[:64].unsqueeze(-2)..., ...)   # 见下方说明
    # odeint 返回 [T, batch, dim],转成 [batch, T, dim] 对齐 Y
    pred_traj = odeint(func, X[:64], t_grid,
                       method="dopri5", rtol=1e-3, atol=1e-3)
    pred_traj = pred_traj.transpose(0, 1)         # [batch, horizon, dim]
    loss = nn.MSELoss()(pred_traj, Y[:64])
    loss.backward(); opt.step()
    print(f"epoch {epoch}  mse={loss.item():.4f}")
预期结果:MSE 逐 epoch 下降。6 小时前瞻预测的 MAPE(平均绝对百分比误差)通常能压到 3–6%——换算成 BrainBox 口径的"96% 精度"(=1−MAPE)是个可对标的区间。你的实际数取决于数据质量与训练时长。

注意:上面训练循环里那行带 ... 的是示意,请用下面那行干净的 odeint(func, X[:64], t_grid, ...)odeint 第一参数是函数、第二是初值 z(t0)、第三是时间网格。

四、训练之外:怎么算"96% 精度"

BrainBox 的"96% predictive accuracy"是一个具体口径,复刻时要对齐,否则数字不可比。

"96% 精度"的可能口径与你的复现
口径公式说明
MAPE 反算acc = 1 − mean(|y−ŷ|/|y|)最常见,本课用这个对齐
R² 决定系数1 − SS_res/SS_tot越接近 1 越好,但与"96%"口径不同
CV-RMSERMSE / mean(y)ASHRAE Guideline 14 常用,工程验收口径
python · 算精度复制
with torch.no_grad():
    pred = odeint(func, X[-200:], t_grid, method="dopri5").transpose(0,1)
mape = (torch.abs(pred - Y[-200:]) / torch.clamp(torch.abs(Y[-200:]), 1e-6)).mean()
accuracy = 1.0 - mape.item()
print(f"6h 前瞻精度 ≈ {accuracy*100:.1f}%(MAPE 口径)")

五、这一课的局限:纯数据驱动的 Neural ODE 还不够"BrainBox"

你刚才训的 Neural ODE 是纯黑箱——右端 f 完全由网络学,没有物理。它能拟合数据,但有两个问题:

  1. 数据外推不稳:碰到训练时没见过的极端天气,可能预测出物理上不可能的温度(比如负的、或飙升到 50°C)。
  2. 没有用上"我们已经知道热方程"这个先验——相当于把第 3 课白捡的物理知识扔了。

BrainBox 的精髓在于把物理接回去。这正是第 5 课 PINN 的主题——也是"少数据、更准"的真正来源。

本课收获

1. Neural ODE = 学一个 dz/dt = f_θ(z,t) 再积分,结构和建筑热 ODE 同形,这是它适合 HVAC 的根本原因。

2. torchdiffeq 两个接口:odeint(快、吃显存)/ odeint_adjoint(O(1) 显存、慢)。长时序前瞻用 adjoint。

3. 你跑通了一个完整训练管线:Sinergym 采数 → 滑窗构造 → Neural ODE 训练 → 6h 前瞻 → MAPE 口径算精度。

4. 但纯黑箱还不够——它没用到已知的物理。第 5 课把它变成 BrainBox 式的灰箱

小测一下

关于 Neural ODE 在 HVAC 预测上的优势,下面哪个说法不准确

有疑问?尽管问。比如:odeintmethod 怎么选(dopri5/rk4/euler 区别)?训练发散/NaN 怎么调?怎么把多个 zone 和外部扰动一起塞进 z?——都能展开。
参考来源: