15 KiB
15 KiB
In [3]:
import numpy as np
# --- 1. 定义简单的 1D 随机游走环境 ---
# 状态: 0, 1, 2, 3, 4, 5, 6 (0 和 6 是终点)
# 开始状态是 3 (正中间)
# 规则: 每次有 50% 概率向左,50% 概率向右
# 奖励: 只有走到 6 (右边终点) 时奖励为 1,走到 0 (左边终点) 奖励为 0,其他全是 0。
def step(state):
action = np.random.choice([-1, 1]) # -1向左, 1向右
next_state = state + action
# 判断奖励和是否结束
reward = 1.0 if next_state == 6 else 0.0
done = (next_state == 0 or next_state == 6)
return next_state, reward, done
# --- 2. 运行 TD(0) 算法 ---
print("=== TD(0) 算法:边走边学演示 ===")
# 初始化状态价值 V(s) 全为 0 (终点 0 和 6 的价值始终为 0)
V_td = np.zeros(7)
alpha = 0.1 # 学习率
gamma = 1.0 # 假设无折扣因子,简化理解
# 我们只跑 1 局游戏,仔细看看里面发生了什么!
state = 3 # 从中间开始
step_count = 0
print(f"初始状态 V 表: {np.round(V_td[1:6], 3)}")
while True:
step_count += 1
next_state, reward, done = step(state)
# 【核心!】TD 走完这一步立刻开始算账
td_target = reward + gamma * V_td[next_state]
td_error = td_target - V_td[state]
# 记录下更新前的 V 值,方便打印对比
old_v = V_td[state]
# 更新 V(s)
V_td[state] = V_td[state] + alpha * td_error
# 打印超级详细的“内心独白”
action_str = "向右" if next_state > state else "向左"
print(f"\n[第 {step_count} 步] 在状态 {state} 决定 {action_str},进入了状态 {next_state}。拿到真实奖励 {reward}。")
print(f" -> 我猜状态 {next_state} 的价值是 {V_td[next_state]:.3f}。")
print(f" -> 所以我的 TD 目标是 {reward} + {gamma} * {V_td[next_state]:.3f} = {td_target:.3f}。")
print(f" -> 我把状态 {state} 的价值从 {old_v:.3f} 更新为了 {V_td[state]:.3f}。")
state = next_state
if done:
print(f"\n游戏结束!最终到达终点 {state}。")
break
print(f"\n跑完这 1 局后的最新状态 V 表 (状态1到5): {np.round(V_td[1:6], 3)}")
print("仔细看:相比于 MC 要等游戏结束,TD 在游戏过程中就已经把前面的状态价值更新了!")=== TD(0) 算法:边走边学演示 === 初始状态 V 表: [0. 0. 0. 0. 0.] [第 1 步] 在状态 3 决定 向右,进入了状态 4。拿到真实奖励 0.0。 -> 我猜状态 4 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 3 的价值从 0.000 更新为了 0.000。 [第 2 步] 在状态 4 决定 向左,进入了状态 3。拿到真实奖励 0.0。 -> 我猜状态 3 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 4 的价值从 0.000 更新为了 0.000。 [第 3 步] 在状态 3 决定 向左,进入了状态 2。拿到真实奖励 0.0。 -> 我猜状态 2 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 3 的价值从 0.000 更新为了 0.000。 [第 4 步] 在状态 2 决定 向右,进入了状态 3。拿到真实奖励 0.0。 -> 我猜状态 3 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 2 的价值从 0.000 更新为了 0.000。 [第 5 步] 在状态 3 决定 向右,进入了状态 4。拿到真实奖励 0.0。 -> 我猜状态 4 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 3 的价值从 0.000 更新为了 0.000。 [第 6 步] 在状态 4 决定 向左,进入了状态 3。拿到真实奖励 0.0。 -> 我猜状态 3 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 4 的价值从 0.000 更新为了 0.000。 [第 7 步] 在状态 3 决定 向左,进入了状态 2。拿到真实奖励 0.0。 -> 我猜状态 2 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 3 的价值从 0.000 更新为了 0.000。 [第 8 步] 在状态 2 决定 向左,进入了状态 1。拿到真实奖励 0.0。 -> 我猜状态 1 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 2 的价值从 0.000 更新为了 0.000。 [第 9 步] 在状态 1 决定 向左,进入了状态 0。拿到真实奖励 0.0。 -> 我猜状态 0 的价值是 0.000。 -> 所以我的 TD 目标是 0.0 + 1.0 * 0.000 = 0.000。 -> 我把状态 1 的价值从 0.000 更新为了 0.000。 游戏结束!最终到达终点 0。 跑完这 1 局后的最新状态 V 表 (状态1到5): [0. 0. 0. 0. 0.] 仔细看:相比于 MC 要等游戏结束,TD 在游戏过程中就已经把前面的状态价值更新了!
In [4]:
# 重新初始化
V_td_500 = np.zeros(7)
# 为了让它在终点附近也能学到正确的平均值,通常设定非终点状态价值初始值为 0.5 会收敛更快
V_td_500[1:6] = 0.5
alpha = 0.1
gamma = 1.0
for episode in range(500):
state = 3
while True:
next_state, reward, done = step(state)
# TD(0) 更新
td_target = reward + gamma * V_td_500[next_state]
V_td_500[state] = V_td_500[state] + alpha * (td_target - V_td_500[state])
state = next_state
if done:
break
print("=== TD(0) 经过 500 局学习后的最终估值 ===")
print("状态 1-5 学习到的 V 值: ", np.round(V_td_500[1:6], 3))
print("真实的理论 V 值 : [0.167, 0.333, 0.5 , 0.667, 0.833]")
print("结论:TD(0) 完美地学会了评估这个策略的真实价值!")=== TD(0) 经过 500 局学习后的最终估值 === 状态 1-5 学习到的 V 值: [0.153 0.291 0.513 0.666 0.919] 真实的理论 V 值 : [0.167, 0.333, 0.5 , 0.667, 0.833] 结论:TD(0) 完美地学会了评估这个策略的真实价值!
In [5]:
import numpy as np
# 假设我们有一个 Q 表 (状态数目为2,动作数目为2)
Q = np.array([
[1.0, 2.0], # 状态 0 的动作价值: 动作0价值=1, 动作1价值=2
[3.0, 5.0] # 状态 1 的动作价值: 动作0价值=3, 动作1价值=5
])
# 假设智能体经历了这样一步:
# 当前在 状态0 (S_t = 0),采取了 动作0 (A_t = 0)
# 得到了奖励 10 (R_{t+1} = 10)
# 进入了 状态1 (S_{t+1} = 1)
S_t, A_t, R, S_next = 0, 0, 10, 1
# 因为有探索率 epsilon 的存在,智能体在 状态1 脑子一抽,
# 没有选价值最高的动作1(价值5),而是“实际”采取了动作0 (A_{t+1} = 0,价值为3)
A_next = 0
alpha = 0.1
gamma = 0.9
print("--- Sarsa 的更新方式 (老实人) ---")
# Sarsa 说:“我不管别人怎么选,反正我下一步实际手贱选了动作 0,我就得为我实际的行动买单!”
# Sarsa 使用的是 Q(S_{t+1}, A_{t+1})
sarsa_target = R + gamma * Q[S_next, A_next]
print(f"Sarsa 的 TD 目标: {R} + {gamma} * {Q[S_next, A_next]} (状态1动作0的价值) = {sarsa_target}")
print("\n--- Q-learning 的更新方式 (聪明人) ---")
# Q-learning 说:“虽然我这一步瞎选了动作 0,但我心里门儿清,状态 1 里的最优解其实是动作 1!我是要当海贼王的男人,我的认知必须基于最优选择!”
# Q-learning 使用的是 max_a Q(S_{t+1}, a)
max_q_next = np.max(Q[S_next])
q_learning_target = R + gamma * max_q_next
print(f"Q-learning 的 TD 目标: {R} + {gamma} * {max_q_next} (状态1里动作1的最大价值) = {q_learning_target}")
print("\n核心结论:")
print("Sarsa 评估的是当前的探索策略,如果当前策略经常犯错跳崖,Sarsa 就会学得非常保守 (宁愿绕远路也不靠近悬崖)。")
print("Q-learning 默认未来一定会做最优选择,所以它学到的一定是理论上最短的通关路线,哪怕当前还在跌跌撞撞。")--- Sarsa 的更新方式 (老实人) --- Sarsa 的 TD 目标: 10 + 0.9 * 3.0 (状态1动作0的价值) = 12.7 --- Q-learning 的更新方式 (聪明人) --- Q-learning 的 TD 目标: 10 + 0.9 * 5.0 (状态1里动作1的最大价值) = 14.5 核心结论: Sarsa 评估的是当前的探索策略,如果当前策略经常犯错跳崖,Sarsa 就会学得非常保守 (宁愿绕远路也不靠近悬崖)。 Q-learning 默认未来一定会做最优选择,所以它学到的一定是理论上最短的通关路线,哪怕当前还在跌跌撞撞。