把训练过程想象成培养一个工程师:
新手工程师(随机决策)
↓ 通过大量实践
↓ 记住成功/失败的经验
↓ 总结规律
↓
经验丰富的工程师(最优决策)
强化学习就是这个过程的数学化实现。
def set_global_seed(seed=2025):
random.seed(2025)
np.random.seed(2025)
torch.manual_seed(2025)
torch.cuda.manual_seed_all(2025)
作用:保证每次训练结果一致,便于调试和复现
params = UFParams(
q_UF=360.0, # 进水流量:360 m³/h
TMP0=0.03, # 初始TMP:0.03 MPa
TMP_max=0.06, # TMP上限:0.06 MPa
L_min_s=3800.0, # 产水时长下限:3800秒
L_max_s=6000.0, # 产水时长上限:6000秒
t_bw_min_s=40.0, # 反洗时长下限:40秒
t_bw_max_s=60.0, # 反洗时长上限:60秒
...
)
这些参数定义了:
env = UFSuperCycleEnv(params)
env = Monitor(env) # 包装:记录统计信息
env = DummyVecEnv([env]) # 包装:向量化接口
环境的作用:
为什么需要包装?
Monitor:自动记录每个episode的奖励、长度等DummyVecEnv:统一单环境/多环境的接口(虽然这里只有1个)model = DQN(
policy="MlpPolicy", # 使用多层感知机策略网络
env=env,
learning_rate=1e-4, # 学习率
buffer_size=10000, # 经验回放池大小
learning_starts=200, # 开始学习前的随机探索步数
batch_size=32, # 每次训练的样本数
gamma=0.95, # 折扣因子(重视长期奖励)
train_freq=4, # 每4步训练一次
target_update_interval=1, # 目标网络更新间隔
tau=0.005, # 软更新系数
exploration_initial_eps=1.0, # 初始探索率100%
exploration_fraction=0.3, # 前30%训练时间探索衰减
exploration_final_eps=0.02, # 最终探索率2%
verbose=1,
tensorboard_log="./uf_dqn_tensorboard/"
)
DQN智能体包含:
Q网络结构:
输入层:4维状态 → [TMP0, last_L, last_t_bw, max_TMP]
隐藏层1:4 → 64 (ReLU激活)
隐藏层2:64 → 64 (ReLU激活)
输出层:64 → 185 (每个动作的Q值)
recorder = UFEpisodeRecorder() # 记录每个episode的数据
callback = UFTrainingCallback(recorder) # 训练回调
记录内容:
state = env.reset()
# 环境内部执行:
self.TMP0 = np.random.uniform(0.01, 0.03) # 随机初始TMP,例如0.025
self.current_step = 0
self.last_action = (3800, 40) # 初始动作:最保守的选择
self.max_TMP = self.TMP0
# 计算初始状态
state = [
(0.025 - 0.01) / 0.04, # TMP0归一化 = 0.375
(3800 - 3800) / 2200, # last_L归一化 = 0.0
(40 - 40) / 20, # last_t_bw归一化 = 0.0
(0.025 - 0.01) / 0.04 # max_TMP归一化 = 0.375
]
# state = [0.375, 0.0, 0.0, 0.375]
状态解释:
┌─────────────────────────────────────────────────────────┐
│ 第 N 步 │
└─────────────────────────────────────────────────────────┘
1. 当前状态
state = [0.375, 0.491, 0.5, 0.429]
↓
2. 动作选择(ε-贪心)
┌────────────────────────────┐
│ if random() < epsilon: │
│ action = random(0-184) │ ← 探索
│ else: │
│ Q值 = Q_network(state) │ ← 利用
│ action = argmax(Q值) │
└────────────────────────────┘
↓
假设选择 action = 92
↓
3. 动作解码
L_idx = 92 // 5 = 18
t_bw_idx = 92 % 5 = 2
L_s = 3800 + 18×60 = 4880秒
t_bw_s = 40 + 2×5 = 50秒
↓
4. 执行模拟
simulate_one_supercycle(TMP0=0.025, L_s=4880, t_bw_s=50)
┌──────────────────────────────────┐
│ 计算小周期次数:k = 35 │
│ For i in range(35): │
│ 产水:TMP增长 │
│ 反洗:TMP部分恢复 │
│ CEB:TMP完全恢复 │
│ 计算指标:回收率、净供水率等 │
└──────────────────────────────────┘
↓
返回:feasible=True, info={recovery:0.97, net_rate:338, ...}
↓
5. 计算奖励
reward = _score(info)
= 0.8×0.97 + 0.2×0.94 - 0.2×0.00
= 0.964(基础奖励)
放大后 = (0.964-0.5)² × 5 = 1.076
↓
6. 观察新状态
TMP0_new = 0.025(CEB后恢复)
next_state = [0.375, 0.491, 0.5, 0.429]
↓
7. 存储经验
buffer.add(
state = [0.375, 0.0, 0.0, 0.375],
action = 92,
reward = 1.076,
next_state = [0.375, 0.491, 0.5, 0.429],
done = False
)
↓
8. 训练网络(每4步,且步数>200)
┌──────────────────────────────────────┐
│ if step % 4 == 0 and step > 200: │
│ batch = buffer.sample(32) │
│ 训练Q网络(详见下方) │
└──────────────────────────────────────┘
↓
9. 更新epsilon
epsilon = max(0.02, 1.0 - step/15000)
↓
10. 记录数据
callback.record_step(state, action, reward, ...)
↓
11. 检查episode是否结束
if done or step >= 20:
state = env.reset() # 开始新episode
else:
state = next_state # 继续当前episode
# ===== 第204步:终于可以开始学习了!=====
# 1. 从经验池随机采样32条经验
batch = buffer.sample(32)
# 采样结果示例:
batch = {
'state': [
[0.375, 0.0, 0.0, 0.375], # 第1条经验的state
[0.425, 0.2, 0.3, 0.450], # 第2条经验的state
... # 共32条
],
'action': [92, 105, 78, ...], # 32个动作
'reward': [1.076, 0.845, -20, ...],# 32个奖励
'next_state': [...], # 32个next_state
'done': [False, False, True, ...] # 32个done标志
}
# 2. 转换为PyTorch张量
state_tensor = torch.FloatTensor(batch['state']) # [32, 4]
action_tensor = torch.LongTensor(batch['action']) # [32]
reward_tensor = torch.FloatTensor(batch['reward']) # [32]
next_state_tensor = torch.FloatTensor(batch['next_state']) # [32, 4]
done_tensor = torch.FloatTensor(batch['done']) # [32]
# 3. 计算当前Q值(Q_current)
q_values = Q_network(state_tensor) # [32, 185]
# 对于第1条经验,Q网络预测所有185个动作的Q值
# 例如:[0.5, 0.6, ..., 1.2, ..., 0.8](185个值)
q_current = q_values.gather(1, action_tensor.unsqueeze(1)) # [32, 1]
# gather操作:取出实际执行的动作对应的Q值
# 对于第1条经验,action=92,取出q_values[92] = 1.2
# 结果:[1.2, 0.9, -5.0, ...](32个值)
# 4. 计算目标Q值(Q_target)
with torch.no_grad(): # 不计算梯度,加速
# 用目标网络预测next_state的Q值
next_q_values = Q_target(next_state_tensor) # [32, 185]
# 取每个next_state的最大Q值
next_q_max, _ = next_q_values.max(dim=1) # [32]
# 例如:[1.5, 1.3, 0.0, ...]
# 贝尔曼方程:Q_target = reward + gamma × max(Q_next) × (1-done)
target = reward_tensor + 0.95 × next_q_max × (1 - done_tensor)
# 对于第1条经验:
# target = 1.076 + 0.95 × 1.5 × (1-0) = 2.501
# 对于第3条经验(done=True):
# target = -20 + 0.95 × 0.0 × (1-1) = -20
# 5. 计算TD误差(损失函数)
loss = F.mse_loss(q_current.squeeze(), target)
# MSE = mean((q_current - target)²)
# 例如:mean([(1.2-2.501)², (0.9-2.135)², ...])
# 假设 loss = 3.45
# 6. 反向传播更新Q网络
optimizer.zero_grad() # 清空之前的梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数
# 更新后,Q_network的参数被调整:
# - 如果q_current < target,增大该动作的Q值
# - 如果q_current > target,减小该动作的Q值
# 7. 软更新目标网络
for param, target_param in zip(Q_network.parameters(), Q_target.parameters()):
target_param.data.copy_(
0.005 × param.data + 0.995 × target_param.data
)
# 目标网络缓慢追踪Q网络,tau=0.005表示每次只更新0.5%
步数 | epsilon | 行为 | 学习状态
---------|---------|------------------------|------------------
0-200 | 1.0 | 纯随机探索 | 填充经验池
204 | 1.0 | 首次训练 | Q值从随机初始化开始学习
500 | 0.98 | 探索为主 | Q值逐渐有意义
2000 | 0.87 | 探索与利用并存 | 策略初步成型
5000 | 0.67 | 开始偏向利用 | Q值趋于稳定
15000 | 0.02 | 探索衰减完成 | 基本使用最优策略
15000+ | 0.02 | 98%利用,2%探索 | 策略优化与稳定
50000 | 0.02 | 训练结束 | 保存最终模型
# 当前状态:TMP稍高
state = [0.625, 0.55, 0.6, 0.650] # TMP0=0.035 MPa
# epsilon=0.67,仍有67%概率随机探索
if random() < 0.67:
action = random.randint(0, 184) # 假设随机到115
else:
# 33%概率使用Q网络
q_values = Q_network(state)
# Q值示例(部分):
# action 0 (L=3800, t_bw=40): Q=0.45 (保守,TMP低但产水少)
# action 92 (L=4880, t_bw=50): Q=0.78 (平衡)
# action 115 (L=5080, t_bw=55): Q=0.82 (激进,产水多但TMP升高)
# action 184 (L=6000, t_bw=60): Q=-2.5 (太激进,违反约束)
action = argmax(q_values) = 115
# 解码动作
L_s = 5080, t_bw_s = 55
# 执行模拟
feasible, info = simulate_one_supercycle(0.035, 5080, 55)
# 结果:feasible=True(刚好没违反约束)
# recovery=0.965, net_rate=330
# 计算奖励
reward = 0.75(较好但不是最优,因为TMP有点高)
# 存储经验并训练
# Q网络学到:在TMP=0.035时,action=115是一个还不错的选择
# 同样的状态
state = [0.625, 0.55, 0.6, 0.650] # TMP0=0.035 MPa
# epsilon=0.02,只有2%概率随机探索
if random() < 0.02:
action = random(0-184)
else:
# 98%概率使用Q网络(此时Q值已经很准确)
q_values = Q_network(state)
# 经过25000步学习,Q值更精准:
# action 0: Q=0.52 (保守,稳定)
# action 85: Q=0.88 (最优!)✓
# action 92: Q=0.85 (次优)
# action 115: Q=0.65 (之前试过,风险高)
# action 184: Q=-15.0(确定违反约束)
action = 85 # 选择最优动作
# 解码
L_s = 4720, t_bw_s = 45
# 执行
feasible, info = simulate(0.035, 4720, 45)
# 结果:feasible=True
# recovery=0.972, net_rate=335
# TMP贴边度低,安全
# 奖励
reward = 0.92(接近最优)
# 智能体学会了:
# - 在TMP较高时,要稍微保守一点
# - L_s=4720是在高TMP下的最佳平衡点
# 训练完成
model.learn(total_timesteps=50000) # 循环结束
# 保存模型
model.save("dqn_model.zip")
# 模型文件包含:
# 1. Q_network的所有参数(权重和偏置)
# 2. 优化器状态
# 3. 训练配置(学习率等)
# 不包含:经验回放池(太大且推理时不需要)
训练日志统计:
stats = recorder.get_episode_stats()
print(f"""
训练完成统计:
- 总步数:50000
- 总episode数:约2500(平均每episode 20步)
- 最终平均奖励:0.85
- 约束违反率:5%(从初期80%大幅下降)
- 平均回收率:0.968
""")
训练完成后,模型已经学会了:
给定状态(TMP, 历史操作) → 选择最优动作(L_s, t_bw_s)
预测阶段不需要:
预测阶段只需要:
假设当前时间:2025-01-15 10:00,超滤系统运行中,需要决定下一个周期的参数。
# 从工厂SCADA系统读取实时数据
current_TMP0 = 0.032 # 当前TMP(MPa)
last_L_s = 4500 # 上一周期产水时长(秒)
last_t_bw_s = 50 # 上一周期反洗时长(秒)
max_TMP_last = 0.045 # 上一周期最高TMP(MPa)
# 也可以从数据库查询历史记录
# 或者如果是第一次运行,使用默认值
from DQN_decide import run_uf_DQN_decide
from DQN_env import UFParams
# 2.1 创建系统参数(与训练时一致)
uf_params = UFParams(
q_UF=360.0,
TMP_max=0.06,
# ... 其他参数
)
# 2.2 加载训练好的模型(自动完成)
# 模型文件:dqn_model.zip
# 内部会执行:
# model = DQN.load("dqn_model.zip")
# 3.1 调用决策接口
result = run_uf_DQN_decide(
uf_params=uf_params,
TMP0_value=0.032 # 输入当前TMP
)
# 3.2 决策内部流程详解
def run_uf_DQN_decide(uf_params, TMP0_value):
# Step A: 创建环境实例
env = UFSuperCycleEnv(uf_params)
# Step B: 设置当前TMP
env.current_params.TMP0 = 0.032
env.last_action = (4500, 50) # 使用历史动作
env.max_TMP_during_filtration = 0.045
# Step C: 获取归一化状态
obs = env._get_obs()
# obs = [
# (0.032 - 0.01) / 0.04 = 0.55, # TMP0
# (4500 - 3800) / 2200 = 0.318, # last_L
# (50 - 40) / 20 = 0.5, # last_t_bw
# (0.045 - 0.01) / 0.04 = 0.875 # max_TMP
# ]
# obs = [0.55, 0.318, 0.5, 0.875]
# Step D: 模型预测(确定性,不探索)
obs_reshaped = obs.reshape(1, -1) # [1, 4]
action, _states = model.predict(obs_reshaped, deterministic=True)
# 模型内部执行:
# q_values = Q_network(obs_reshaped) # [1, 185]
# 例如 q_values = [[0.45, 0.67, ..., 0.89, ..., -3.2]]
# action = argmax(q_values) = 105
# (选择Q值最大的动作)
action = action[0] # 105
# Step E: 解码动作
L_s, t_bw_s = env._get_action_values(105)
# L_idx = 105 // 5 = 21
# t_bw_idx = 105 % 5 = 0
# L_s = 3800 + 21×60 = 5060秒
# t_bw_s = 40 + 0×5 = 40秒
# Step F: 模拟验证(可选,检查可行性)
next_obs, reward, terminated, truncated, info = env.step(105)
# Step G: 返回决策结果
return {
"action": 105,
"L_s": 5060.0,
"t_bw_s": 40.0,
"next_obs": next_obs,
"reward": reward,
"terminated": terminated,
"truncated": truncated,
"info": info
}
# 3.3 获取决策结果
print(f"""
模型决策结果:
- 建议产水时长:{result['L_s']} 秒 (约{result['L_s']/60:.1f}分钟)
- 建议反洗时长:{result['t_bw_s']} 秒
- 预期回收率:{result['info']['recovery']:.3f}
- 预期净供水率:{result['info']['net_delivery_rate_m3ph']:.1f} m³/h
- 预期周期最高TMP:{result['info']['max_TMP_during_filtration']:.4f} MPa
""")
# 输出示例:
# 模型决策结果:
# - 建议产水时长:5060 秒 (约84.3分钟)
# - 建议反洗时长:40 秒
# - 预期回收率:0.968
# - 预期净供水率:332.5 m³/h
# - 预期周期最高TMP:0.0485 MPa
为了避免参数突变导致系统不稳定,使用渐进式调整策略:
from DQN_decide import generate_plc_instructions
# 4.1 准备输入
current_L_s = 4500 # 工厂当前设定值
current_t_bw_s = 50 # 工厂当前设定值
model_prev_L_s = 4800 # 模型上一轮建议值(如果有)
model_prev_t_bw_s = 45 # 模型上一轮建议值
model_L_s = 5060 # 模型本轮建议值
model_t_bw_s = 40 # 模型本轮建议值
# 4.2 生成渐进式指令
next_L_s, next_t_bw_s = generate_plc_instructions(
current_L_s,
current_t_bw_s,
model_prev_L_s,
model_prev_t_bw_s,
model_L_s,
model_t_bw_s
)
# 4.3 内部逻辑详解
def generate_plc_instructions(...):
# Step 1: 选择基准值(上一轮模型值 vs 当前值)
# 选择更接近本轮模型建议的那个
if abs(current_L_s - model_L_s) <= abs(model_prev_L_s - model_L_s):
effective_current_L = 4500 # 当前值更接近
else:
effective_current_L = 4800 # 上轮值更接近
# 假设选择了4800
# Step 2: 计算差异
L_diff = model_L_s - effective_current_L
# = 5060 - 4800 = 260秒
# Step 3: 渐进调整(每次最多变化1个步长)
L_step_s = 60 # 步长60秒
threshold = 1.0
if abs(L_diff) >= threshold * L_step_s:
if L_diff > 0:
L_adjustment = +60 # 向上调整
else:
L_adjustment = -60 # 向下调整
else:
L_adjustment = 0 # 差异小,不调整
next_L_s = 4800 + 60 = 4860秒
# 同样处理t_bw_s
# t_bw_diff = 40 - 45 = -5秒
# abs(-5) >= 1.0 × 5 → True
# t_bw_adjustment = -5
next_t_bw_s = 45 - 5 = 40秒
return 4860, 40
# 4.4 结果
print(f"""
PLC指令:
- 下发产水时长:{next_L_s} 秒(从{effective_current_L}秒逐步调整)
- 下发反洗时长:{next_t_bw_s} 秒
- 调整方向:向模型建议值({model_L_s}秒, {model_t_bw_s}秒)靠拢
- 需要继续调整轮数:约{abs(model_L_s - next_L_s) // 60}轮
""")
# 输出:
# PLC指令:
# - 下发产水时长:4860 秒(从4800秒逐步调整)
# - 下发反洗时长:40 秒
# - 调整方向:向模型建议值(5060秒, 40秒)靠拢
# - 需要继续调整轮数:约3轮
渐进调整的好处:
在实际下发指令前,先模拟计算性能:
from DQN_decide import calc_uf_cycle_metrics
# 5.1 计算指令对应的性能
TMP0 = 0.032
max_tmp = 0.048 # 如果工厂有实测数据
min_tmp = 0.025
L_s = 4860
t_bw_s = 40
metrics = calc_uf_cycle_metrics(
uf_params,
TMP0,
max_tmp,
min_tmp,
L_s,
t_bw_s
)
# 5.2 内部计算流程
def calc_uf_cycle_metrics(...):
# 模拟一个完整超级周期
feasible, info = simulate_one_supercycle(params, L_s, t_bw_s)
# 提取关键指标
k_bw_per_ceb = info["k_bw_per_ceb"] # 小周期次数
recovery = info["recovery"] # 回收率
net_rate = info["net_delivery_rate_m3ph"] # 净供水率
daily_prod_time = info["daily_prod_time_h"] # 日均产水时间
ton_water_energy = info["ton_water_energy_kWh_per_m3"] # 吨水电耗
# 计算渗透率
if min_tmp is not None:
max_permeability = 100 * q_UF / (膜面积) / min_tmp
else:
max_permeability = info中计算
return {
"k_bw_per_ceb": 36,
"recovery": 0.968,
"net_delivery_rate_m3ph": 332.8,
"daily_prod_time_h": 18.5,
"ton_water_energy_kWh_per_m3": 0.1019,
"max_permeability": 58.3 # lmh/bar
}
# 5.3 展示结果
print(f"""
预期性能指标:
- 小周期次数(48h内):{metrics['k_bw_per_ceb']}
- 回收率:{metrics['recovery']:.2%}
- 净供水率:{metrics['net_delivery_rate_m3ph']:.1f} m³/h
- 日均产水时间:{metrics['daily_prod_time_h']:.1f} 小时/天
- 吨水电耗:{metrics['ton_water_energy_kWh_per_m3']:.4f} kWh/m³
- 最高渗透率:{metrics['max_permeability']:.1f} lmh/bar
""")
# 输出:
# 预期性能指标:
# - 小周期次数(48h内):36
# - 回收率:96.80%
# - 净供水率:332.8 m³/h
# - 日均产水时间:18.5 小时/天
# - 吨水电耗:0.1019 kWh/m³
# - 最高渗透率:58.3 lmh/bar
# 6.1 准备PLC通信数据包
plc_command = {
"timestamp": "2025-01-15 10:00:00",
"L_s": 4860, # 产水时长(秒)
"t_bw_s": 40, # 反洗时长(秒)
"source": "AI_DQN", # 指令来源
"confidence": 0.95 # 模型置信度
}
# 6.2 发送到PLC(伪代码)
# plc_client.write_registers(
# address=1000,
# values=[4860, 40]
# )
# 6.3 记录操作日志
log_decision(
timestamp="2025-01-15 10:00:00",
TMP0=0.032,
model_L_s=5060,
model_t_bw_s=40,
plc_L_s=4860,
plc_t_bw_s=40,
expected_recovery=0.968
)
# 每个超级周期结束后(约48小时)重新预测
while True:
# 等待当前周期结束
wait_for_cycle_end()
# 获取最新系统状态
current_state = get_system_state()
# 执行决策
result = run_uf_DQN_decide(uf_params, current_state.TMP0)
# 渐进调整
next_L, next_t_bw = generate_plc_instructions(...)
# 下发指令
send_to_plc(next_L, next_t_bw)
# 记录日志
log_decision(...)
# 等待下一周期
sleep(48_hours)
┌─────────────────────────────────────────────────────────────┐
│ 阶段1:离线训练 │
└─────────────────────────────────────────────────────────────┘
1. 数据准备
├─ 历史运行数据(可选,用于物理模型)
└─ 系统参数配置(UFParams)
2. 模拟器开发
├─ 物理模型:TMP增长、反洗恢复
└─ 约束检查:TMP上限、残余增量
3. 强化学习训练
├─ 环境:UFSuperCycleEnv
├─ 算法:DQN
├─ 训练:50000步,约数小时
└─ 输出:dqn_model.zip
4. 模型验证
├─ 测试不同TMP条件
├─ 检查约束满足率
└─ 评估性能指标
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段2:在线部署 │
└─────────────────────────────────────────────────────────────┘
5. 部署准备
├─ 将dqn_model.zip部署到服务器
├─ 配置PLC通信接口
└─ 搭建监控系统
6. 实时决策循环
Every 48 hours:
├─ 从SCADA读取TMP0
├─ 调用run_uf_DQN_decide()
├─ 生成PLC指令(渐进式)
├─ 下发到PLC
└─ 记录日志
7. 持续监控
├─ 实时性能追踪
├─ 异常告警
└─ 人工干预接口
↓
┌─────────────────────────────────────────────────────────────┐
│ 阶段3:持续优化 │
└─────────────────────────────────────────────────────────────┘
8. 数据收集
├─ 记录实际运行数据
├─ 标注异常事件
└─ 构建真实数据集
9. 模型迭代
├─ 用真实数据训练物理模型
├─ 重新训练强化学习策略
└─ A/B测试新旧模型
10. 上线新版本
├─ 灰度发布
├─ 性能对比
└─ 全量替换
| 维度 | 训练阶段 | 预测阶段 |
|---|---|---|
| 目标 | 学习最优策略 | 应用最优策略 |
| 输入 | 随机初始状态 | 真实系统状态 |
| 动作选择 | ε-贪心(探索+利用) | 贪心(纯利用) |
| 奖励 | 计算并用于学习 | 计算但不学习 |
| 网络更新 | 每4步更新参数 | 不更新参数 |
| 经验池 | 存储并采样 | 不使用 |
| epsilon | 1.0 → 0.02 | 0(不探索) |
| 时间 | 数小时(50000步) | 毫秒级(单次推理) |
| 输出 | dqn_model.zip | (L_s, t_bw_s) |
state = [0.55, 0.318, 0.5, 0.875]
epsilon = 0.93 # 仍在高探索期
if random() < 0.93:
action = 127 # 93%概率:随机探索
else:
q_values = Q_network(state)
action = argmax(q_values) # 7%概率:利用
# 即使知道最优动作,也故意选择次优动作来探索
state = [0.55, 0.318, 0.5, 0.875]
epsilon = 0 # 预测时不探索
# 总是选择Q值最大的动作
q_values = Q_network(state)
# q_values = [0.45, ..., 0.89, ..., 0.76, ...]
action = argmax(q_values) # 一定选择最优
# 确定性决策,相同状态总是输出相同动作
A:
A:
import time
start = time.time()
result = run_uf_DQN_decide(uf_params, 0.032)
end = time.time()
print(f"预测耗时:{(end-start)*1000:.2f} 毫秒")
# 典型输出:预测耗时:15.32 毫秒
# 分解:
# - 状态归一化:<1ms
# - Q网络前向传播:5-10ms
# - 动作解码:<1ms
# - 模拟验证:5-10ms
A:以下情况需要重训:
通常3-6个月重训一次。
A:多重保障:
# 1. 训练时的约束学习
# 模型在训练时已经学会避免违反约束
# 2. 预测后的模拟验证
feasible, info = simulate_one_supercycle(TMP0, L_s, t_bw_s)
if not feasible:
# 回退到保守策略
L_s, t_bw_s = safe_default_action()
# 3. 渐进式调整
# 每次只调整60秒,避免突变
# 4. 人工监督
# 操作员可以随时覆盖模型决策
A:
假设没有探索(epsilon=0):
初始Q值是随机的,假设:
- action 50 的Q值 = 0.8(最高)
- action 92 的Q值 = 0.3
- action 120 的Q值 = 0.5
智能体会一直选择action 50,永远不会尝试92和120。
但实际上:
- action 50 的真实价值 = 0.6(不够好)
- action 92 的真实价值 = 0.9(最优!)
- action 120 的真实价值 = 0.7
没有探索,永远发现不了action 92才是最优的。
有探索:
- 前期随机尝试各种动作
- 发现action 92 获得高奖励
- 更新Q值:Q(92) = 0.3 → 0.9
- 后期选择action 92
A:可以,但要注意:
# 单个预测(串行)
result = run_uf_DQN_decide(uf_params, TMP0)
# 批量预测(并行,需要修改代码)
TMP0_batch = [0.025, 0.030, 0.035, 0.040]
results = run_uf_DQN_decide_batch(uf_params, TMP0_batch)
# 内部并行:
obs_batch = np.array([
[0.375, 0.0, 0.0, 0.375],
[0.55, 0.2, 0.3, 0.6],
...
]) # [4, 4]
q_values = Q_network(obs_batch) # [4, 185]
actions = q_values.argmax(dim=1) # [4]
# GPU加速:
# PyTorch自动利用GPU并行计算
# 批量预测速度几乎和单次一样快