Q-Learning Table收敛至-inf:自定义实现Mountain-Car任务遇困境
1. 初始代码Q值发散至-inf的根本原因
你的第一个训练循环的更新公式完全偏离了Q-Learning的核心时序差分(TD)逻辑:
# 错误的更新公式 Q[entry[0],entry[1]] = Q[entry[0],entry[1]] + lr * (entry[2] + latest_best * gamma)
标准Q-Learning的更新是基于TD误差,即目标值与当前Q值的差值:Q(s,a) = Q(s,a) + α * (r + γ * max_a' Q(s',a') - Q(s,a))
这个公式的作用是让Q值逐步向「当前奖励+未来最大预期奖励」的目标靠拢。
而你的代码缺少了减去当前Q(s,a)的关键步骤,相当于每次都在给Q(s,a)叠加一个新的数值。MountainCar环境的每步奖励固定为-1(直到到达终点),所以每次叠加的都是负数的累积值,随着训练轮数增加,Q值会不断向负方向累积,最终数值溢出变为-inf。
为什么初始效果好?因为初始时Q表全为0,第一次更新时Q(s,a) = 0 + lr*(r + γ*maxQ),这和标准更新的结果一致(因为0 - 0 = 0),所以前几轮看起来有效,但当Q值开始变化后,后续更新就会不断错误叠加,最终数值崩溃。
2. 修改后代码效果差的核心问题
你修改后的公式形式上接近标准Q-Learning,但存在两个关键问题:
(1)仿真阶段缺少探索机制
你的仿真代码是纯贪婪选择:
max_indexes = np.argwhere(Q[state,] == np.amax(Q[state,])).flatten() action = np.random.choice(max_indexes)
MountainCar是典型的需要探索的环境——初始时Q值全为0,纯贪婪选择会让智能体一直重复同一个动作,无法探索到能爬上山顶的动作组合。标准Q-Learning必须搭配ε-贪婪策略:以ε的概率随机选择动作(探索),以1-ε的概率选择当前最优动作(利用),并且ε可以随着训练逐步降低(减少探索,增加利用)。
(2)状态转移记录缺失(可选但重要)
你的history只记录了(state,action,reward),没有记录执行动作后进入的下一个状态。虽然反向遍历可以通过latest_best传递后续状态的Q值,但直接记录下一个状态会让训练逻辑更清晰,避免依赖反向遍历的顺序出错。
(3)参数设置可能不合理
- 学习率(lr):如果lr设置太大,Q值会波动剧烈,难以收敛。MountainCar适合较小的学习率(比如0.1~0.01)。
- 折扣因子(gamma):需要设置接近1的值(比如0.99),因为这是一个长期奖励问题,智能体需要考虑后续多步的奖励。
3. 修复后的完整代码示例
仿真阶段(加入ε-贪婪策略)
epsilon = 0.1 # 初始探索概率,可随训练逐步降低 state = env.reset() history = [] for t in range(SAMPLE_PER_EPISODE): # ε-贪婪选择动作 if np.random.uniform(0, 1) < epsilon: action = env.action_space.sample() # 随机探索 else: max_indexes = np.argwhere(Q[state,] == np.amax(Q[state,])).flatten() action = np.random.choice(max_indexes) # 利用最优动作 observation, reward, done, info = env.step(action) history.append((state, action, reward, observation)) # 记录下一个状态 state = observation total_reward += reward if done: break # 到达终点提前结束 # 可选:逐步降低探索概率 epsilon = max(0.01, epsilon * 0.99)
训练阶段(标准Q-Learning更新)
lr = 0.1 gamma = 0.99 latest_best = 0 # 终端状态的下一个状态无奖励,maxQ为0 # 反向遍历history,基于下一个状态的Q值更新当前状态 for entry in reversed(history): s, a, r, s_next = entry # 标准TD更新:目标值 = 当前奖励 + 未来最大预期奖励 target = r + gamma * latest_best Q[s, a] += lr * (target - Q[s, a]) # 更新latest_best为当前状态的maxQ,供前序状态使用 latest_best = np.max(Q[s, :])
内容的提问来源于stack exchange,提问作者greece57

