import gymnasium as gym
import numpy as np
import matplotlib.pyplot as plt
def run(episodes, img_name):
rng = np.random.default_rng(1) # 随机种子?
env = gym.make('FrozenLake-v1', map_name="4x4", is_slippery=False, render_mode=None)
# 说白了,q-table 记录的就是: 「在第 X 个格子,往 Y 方向走,能有多大收益。」
q = np.zeros((env.observation_space.n, env.action_space.n))
print(q.shape) # 16, 4
learning_rate_a = 0.9 # alpha
discount_factor_g = 0.9 # gamma
# epsilon, 探索率, 随机率,ε-贪婪策略参数
# ε 越大,随机成分越多,探索越多。
# “ε”(希腊字母 epsilon,读作「艾普西龙」)。
# 假如 epsilon = 0.2, 那么 20% 的时间, 随机选择动作, 80% 的时间, 选择 q-table 给出的最优动作。
epsilon = 1 # 1 = 100% 随机选择
# 训练多少轮? 1 / 0.0001 = 10,000
# 其实这个参数,是控制 ε 衰减速度 的参数。
# 因为,每一轮的结尾都需要执行:
# epsilon = max(epsilon - epsilon_decay_rate, 0),
# 即, 每一轮减少 0.0001
epsilon_decay_rate = 0.0001
# 统计一下训练的效果。
rewards_per_episode = np.zeros(episodes)
# 训练多少轮?比如 15000 轮。
for i in range(episodes):
print(f"episode {i+1}/{episodes}, epsilon={epsilon:.4f}")
state = env.reset()[0]
terminated = truncated =False
# ----- start ------------------------------------------------------------
# 下面的过程,只是一局游戏。
while not terminated and not truncated:
# 这里直接比较大小???
if rng.random() < epsilon:
action = env.action_space.sample()
else:
action = np.argmax(q[state, :]) # 使用 q-table 的最大值,来选择行为, 方向。
new_state, reward, terminated, truncated, info = env.step(action)
# 更新 q-table, 根据获得的奖励,来更新
q[state, action] = q[state, action] + learning_rate_a * ( reward + discount_factor_g * np.max(q[new_state, :]) - q[state, action] )
state = new_state
# 一轮结束,更新 epsilon 探索率, 每次减少 0.0001
# 下面这些,依然是属于一次训练过程。
epsilon = max(epsilon - epsilon_decay_rate, 0)
if epsilon == 0:
epsilon_decay_rate = 0.0001
if reward == 1:
rewards_per_episode[i] = 1
env.close()
# 画图。
plt.figure()
sum_rewards = np.zeros(episodes)
for t in range(episodes):
# 这里表示的是, 每100轮训练的奖励总共是多少
sum_rewards[t] = np.sum(rewards_per_episode[max(0, t-100): (t+1)])
plt.plot(sum_rewards)
plt.savefig(f"q-table--4x4-{img_name}.png")
if __name__ == '__main__':
run(15000, 1)
源码中第166行提到了:
# 源码位置是:
# G:\Dogg\venv\Lib\site-packages\gymnasium\envs\toy_text\frozen_lake.py
from gymnasium.envs.toy_text.frozen_lake import generate_random_map
gym.make('FrozenLake-v1', desc=generate_random_map(size=8))
# 那么我就可以修改为, 把 size 改为 10
env = gym.make('FrozenLake-v1', desc=generate_random_map(size=10), render_mode="human", is_slippery=False)
env.reset()
q = np.zeros((env.observation_space.n, env.action_space.n))
q = np.zeros((16, 4)) # 4x4 = 16 个位置, 4个 方向
值:代表这个状态下执行这个动作,预计可以得到的“总回报”(长期奖励)。
说白了,q-table 记录的就是: 「在第 X 个格子,往 Y 方向走,能有多大收益。」
Q-learning 核心思想一句话总结:
“用表格(q-table)记住在每个状态下做每个动作的价值,
然后根据这个表格不断优化,找到最好的行为方式。”
epsilon ,这个概念很关键。 ChatGPT 下面几句话写的很好:
epsilon=1
,完全乱走,多看看世界。epsilon
一点点减小,开始用经验行动。epsilon=0
,每次都选最好走的路。参数 | 读音 | 作用 | 推荐范围 |
---|---|---|---|
ε(epsilon) | 艾普西龙 | 随机探索概率 | 0 ~ 1 |
α(alpha) | 阿尔法 | 学习率,经验更新强度 | 0 ~ 1 |
γ(gamma) | 伽马 | 折扣因子,未来奖励重要性 | 0 ~ 1 |
Q ( s , a ) = Q ( s , a ) + α ∗ ( r + γ ∗ m a x ( Q ( s ′ , : ) ) − Q ( s , a ) ) Q(s, a) = Q(s, a) + α * (r + γ * max(Q(s', :)) - Q(s, a)) Q(s,a)=Q(s,a)+α∗(r+γ∗max(Q(s′,:))−Q(s,a))
自己写的时候,我发现,其实可以简写为: Q = Q + new_stuff
也可以理解为:新 Q 值 = 老 Q 值的 (1 - α) + 新经验的 α
其实, 就是用 numpy 生成一个随机数, 来跟 epsilon 进行比较。
理解: 一个随机数,被 epsilon 分割为左右2个区域, 分别对应不同的选择。
if rng.random() < epsilon:
action = env.action_space.sample()
else:
action = np.argmax(q[state, :]) # 使用 q-table 来选择行为, 方向。
这种写法简单又高效,几乎所有强化学习代码都会用这种判断。 ???!!!