# UF-RL 训练与预测流程详解 ## 目录 1. [训练阶段完整流程](#训练阶段完整流程) 2. [预测阶段完整流程](#预测阶段完整流程) 3. [从训练到部署的完整链路](#从训练到部署的完整链路) --- ## 训练阶段完整流程 ### 概述:智能体如何学会决策 把训练过程想象成**培养一个工程师**: ``` 新手工程师(随机决策) ↓ 通过大量实践 ↓ 记住成功/失败的经验 ↓ 总结规律 ↓ 经验丰富的工程师(最优决策) ``` 强化学习就是这个过程的数学化实现。 --- ### 阶段0:准备工作(程序启动) #### 步骤0.1:固定随机种子 ```python def set_global_seed(seed=2025): random.seed(2025) np.random.seed(2025) torch.manual_seed(2025) torch.cuda.manual_seed_all(2025) ``` **作用**:保证每次训练结果一致,便于调试和复现 --- #### 步骤0.2:创建超滤系统参数 ```python 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秒 ... ) ``` **这些参数定义了**: - 物理系统的运行范围 - 决策空间的边界 - 约束条件 --- #### 步骤0.3:创建模拟环境 ```python env = UFSuperCycleEnv(params) env = Monitor(env) # 包装:记录统计信息 env = DummyVecEnv([env]) # 包装:向量化接口 ``` **环境的作用**: - 模拟超滤系统的运行 - 接收智能体的动作(产水时长、反洗时长) - 返回奖励和下一个状态 **为什么需要包装?** - `Monitor`:自动记录每个episode的奖励、长度等 - `DummyVecEnv`:统一单环境/多环境的接口(虽然这里只有1个) --- #### 步骤0.4:创建DQN智能体 ```python 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智能体包含**: 1. **Q网络(当前网络)**:估计每个动作的价值 2. **目标网络**:提供稳定的学习目标 3. **经验回放池**:存储历史经验 4. **优化器**:更新网络参数 **Q网络结构**: ```python 输入层:4维状态 → [TMP0, last_L, last_t_bw, max_TMP] 隐藏层1:4 → 64 (ReLU激活) 隐藏层2:64 → 64 (ReLU激活) 输出层:64 → 185 (每个动作的Q值) ``` --- #### 步骤0.5:创建回调和记录器 ```python recorder = UFEpisodeRecorder() # 记录每个episode的数据 callback = UFTrainingCallback(recorder) # 训练回调 ``` **记录内容**: - 每一步的状态、动作、奖励 - 每个episode的总奖励、回收率等 - 用于训练后分析 --- ### 阶段1:初始化(第1个Episode开始) #### 环境重置 ```python 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] ``` **状态解释**: - 当前是一个"全新的超滤膜",TMP=0.025 - 还没有历史操作(last_L和last_t_bw都是初始值) - 周期最高TMP就是当前TMP --- ### 阶段2:交互与学习循环(50000步) #### 完整的一步流程图 ``` ┌─────────────────────────────────────────────────────────┐ │ 第 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 ``` --- ### 深入理解:Q网络训练(第204步首次训练) ```python # ===== 第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% ``` --- ### 训练过程的关键时间点 #### 时间线(50000步训练) ``` 步数 | 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 | 训练结束 | 保存最终模型 ``` #### 示例:第5000步时的决策过程 ```python # 当前状态: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是一个还不错的选择 ``` --- ### 示例:第25000步时的决策过程 ```python # 同样的状态 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下的最佳平衡点 ``` --- ### 阶段3:训练结束与保存 ```python # 训练完成 model.learn(total_timesteps=50000) # 循环结束 # 保存模型 model.save("dqn_model.zip") # 模型文件包含: # 1. Q_network的所有参数(权重和偏置) # 2. 优化器状态 # 3. 训练配置(学习率等) # 不包含:经验回放池(太大且推理时不需要) ``` **训练日志统计**: ```python stats = recorder.get_episode_stats() print(f""" 训练完成统计: - 总步数:50000 - 总episode数:约2500(平均每episode 20步) - 最终平均奖励:0.85 - 约束违反率:5%(从初期80%大幅下降) - 平均回收率:0.968 """) ``` --- ## 预测阶段完整流程 ### 概述:训练好的智能体如何工作 训练完成后,模型已经学会了: ``` 给定状态(TMP, 历史操作) → 选择最优动作(L_s, t_bw_s) ``` 预测阶段不需要: - ✗ 探索(epsilon=0,总是选择最优) - ✗ 训练(不更新网络参数) - ✗ 经验池(不存储新经验) 预测阶段只需要: - ✓ 加载训练好的Q网络 - ✓ 输入当前状态 - ✓ 输出最优动作 --- ### 预测流程详解 #### 场景:工厂实时决策 假设当前时间:2025-01-15 10:00,超滤系统运行中,需要决定下一个周期的参数。 --- #### 步骤1:获取当前系统状态 ```python # 从工厂SCADA系统读取实时数据 current_TMP0 = 0.032 # 当前TMP(MPa) last_L_s = 4500 # 上一周期产水时长(秒) last_t_bw_s = 50 # 上一周期反洗时长(秒) max_TMP_last = 0.045 # 上一周期最高TMP(MPa) # 也可以从数据库查询历史记录 # 或者如果是第一次运行,使用默认值 ``` --- #### 步骤2:初始化决策环境和模型 ```python 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:执行决策 ```python # 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 ``` --- #### 步骤4:生成PLC指令(渐进式调整) 为了避免参数突变导致系统不稳定,使用渐进式调整策略: ```python 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轮 ``` **渐进调整的好处**: - ✅ 避免参数突变导致TMP急剧波动 - ✅ 给操作员时间观察和干预 - ✅ 系统更平稳过渡 --- #### 步骤5:计算预期性能指标 在实际下发指令前,先模拟计算性能: ```python 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:下发PLC指令 ```python # 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 ) ``` --- #### 步骤7:周期性重复预测 ```python # 每个超级周期结束后(约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. 上线新版本 ├─ 灰度发布 ├─ 性能对比 └─ 全量替换 ``` --- ### 训练vs预测对比表 | 维度 | 训练阶段 | 预测阶段 | |------|---------|---------| | **目标** | 学习最优策略 | 应用最优策略 | | **输入** | 随机初始状态 | 真实系统状态 | | **动作选择** | ε-贪心(探索+利用) | 贪心(纯利用) | | **奖励** | 计算并用于学习 | 计算但不学习 | | **网络更新** | 每4步更新参数 | 不更新参数 | | **经验池** | 存储并采样 | 不使用 | | **epsilon** | 1.0 → 0.02 | 0(不探索) | | **时间** | 数小时(50000步) | 毫秒级(单次推理) | | **输出** | dqn_model.zip | (L_s, t_bw_s) | --- ### 关键差异示例 #### 训练时的动作选择(第1000步) ```python 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%概率:利用 # 即使知道最优动作,也故意选择次优动作来探索 ``` #### 预测时的动作选择 ```python 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) # 一定选择最优 # 确定性决策,相同状态总是输出相同动作 ``` --- ## 常见问题解答 ### Q1:为什么训练需要50000步? **A**: - 185个动作 × 多种状态 = 需要大量经验 - 前30%(15000步)主要是探索,发现好动作 - 后70%(35000步)是优化,微调策略 - 太少(如5000步)策略不稳定,太多(如500000步)训练时间长且收益递减 ### Q2:预测速度有多快? **A**: ```python 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 ``` ### Q3:模型何时需要重新训练? **A**:以下情况需要重训: 1. **系统参数变化**:更换膜组、改变流量范围 2. **物理模型更新**:有了真实数据,改进模拟器 3. **性能下降**:实际回收率持续低于预期 4. **新约束**:增加了新的运行限制 通常**3-6个月**重训一次。 ### Q4:如何确保预测的动作安全? **A**:多重保障: ```python # 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. 人工监督 # 操作员可以随时覆盖模型决策 ``` ### Q5:训练时为什么要探索? **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 ``` ### Q6:预测可以并行吗? **A**:可以,但要注意: ```python # 单个预测(串行) 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并行计算 # 批量预测速度几乎和单次一样快 ``` --- ## 总结 ### 训练流程核心 1. **准备阶段**:创建环境、初始化Q网络 2. **探索阶段**(0-15000步):随机尝试,填充经验池 3. **学习阶段**(200-50000步):从经验中学习,更新Q值 4. **优化阶段**(15000-50000步):利用为主,微调策略 5. **保存模型**:导出dqn_model.zip ### 预测流程核心 1. **加载模型**:读取训练好的Q网络 2. **获取状态**:从系统读取TMP等信息 3. **模型推理**:Q网络计算,选择最优动作 4. **渐进调整**:生成PLC指令,逐步靠近目标 5. **下发执行**:发送到PLC,控制超滤系统 ### 关键洞察 - **训练是学习过程**:智能体从"一无所知"到"经验丰富" - **预测是应用过程**:智能体"发挥所学"解决实际问题 - **探索是必要代价**:没有探索就没有发现 - **渐进是安全保障**:避免参数突变引发风险