ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

80行Python实现Q-Learning:从零理解强化学习核心算法

80行Python实现Q-Learning:从零理解强化学习核心算法 1. 项目概述从“人狗大作战”到理解智能决策最近在社区里看到不少朋友在讨论用Python写游戏比如那个挺火的“人狗大作战”的变体很多代码背后其实都藏着同一个核心思想如何让程序自己学会做决策。这让我想起了刚入门机器学习那会儿觉得“强化学习”这个词儿特别高大上好像离我们很远。其实不然你看游戏里的AI自动寻路、自动战斗甚至是量化交易里程序自己调整买卖策略底层逻辑都可能和它有关。而Q-Learning可以说是打开强化学习大门最直接、最经典的一把钥匙。简单来说Q-Learning是一种无模型的强化学习算法。“无模型”意味着智能体不需要事先知道环境的运作规则比如游戏地图全貌、物理引擎公式它通过不断地试错自己摸索出一套在什么状态下、该做什么动作能获得最大长远回报的经验表。这个经验表就是我们常说的Q表。你可以把它想象成一个打游戏的经验本在“遇到Boss”状态S时你尝试了“放大招”动作A结果“赢了”奖励R并且发现“Boss残血时靠近攻击”下次可能更赚更新对未来回报的估计。你把这条经验记在本子上下次再遇到类似情况翻翻本子就知道怎么做了。Q-Learning干的就是这个“记经验”和“更新经验”的活儿。为什么80行Python代码就能实现它因为其核心思想非常简洁优美用一张表格Q表存储所有“状态-动作对”的价值通过一个迭代更新的公式让这张表越来越接近最优决策。它不依赖复杂的神经网络那是Deep Q-Learning的事了纯用矩阵和循环就能说清楚特别适合初学者理解强化学习的精髓。无论你是想为小游戏添加一个会学习的AI还是理解自动化决策系统的原理从Q-Learning入手都是绝佳的选择。接下来我就带你彻底拆解这个算法并用最纯粹的Python代码实现它你会发现看似神秘的智能学习其基础构建块如此清晰易懂。2. Q-Learning核心原理拆解与“摸石头过河”的类比要理解Q-Learning我们需要先搞懂几个关键概念它们共同构成了一个名为**马尔可夫决策过程MDP**的框架。别被名词吓到我们用一个“摸石头过河”的游戏来类比智能体 (Agent)就是过河的你。环境 (Environment)就是那条河以及河里分布不均匀、可能滑动的石头状态。状态 (State, s)你当前所在的那块石头。比如“3号石头上”。动作 (Action, a)你能做的事。比如“向前跳一块石头”、“向左跳一块石头”、“原地不动”。奖励 (Reward, R)环境对你动作的即时反馈。比如“成功跳到下一块石头”奖励10“踩空落水”奖励-50“原地不动”奖励-1鼓励你前进。策略 (Policy, π)你过河的方法。可以是一张地图知道哪块石头稳也可以是根据当前感觉状态临时决定往哪跳。Q-Learning的目标就是学出一个最好的策略。Q值 (Q-value, Q(s, a))这是核心中的核心。它不代表即时奖励而是代表在状态s下选择动作a并且此后一直按照最优策略行动所能获得的所有未来奖励的总和考虑折扣。它衡量的是一个动作的长期价值。比如虽然从当前石头跳到旁边那块小石头动作a有点晃即时奖励R不高但从那块小石头能一步跳到对岸未来回报高那么这个动作的Q值就会很高。Q-Learning的更新公式就是其学习的灵魂Q(s, a) Q(s, a) α * [ R γ * max(Q(s’, a’)) - Q(s, a) ]这个公式怎么理解我们一步步拆R在状态s下执行动作a后环境给的即时奖励。γ * max(Q(s’, a’))这是未来回报的估计。s’是执行动作后到达的新状态。max(Q(s’, a’))代表在新状态s’下所有可能动作中最大的Q值即认为在s’后会采取最优动作。γ是折扣因子0≤γ1意味着未来的奖励没有眼前的奖励值钱离得越远打折越狠。R γ * max(Q(s’, a’))可以理解为对“在状态s下执行动作a”这件事的新的、更全面的价值评估目标它结合了即时奖励和未来最优路径的折现价值。[目标 - 当前估计]即[R γ * max(Q(s’, a’)) - Q(s, a)]这被称为时序差分误差。它衡量了我们当前的Q值估计和新的目标值之间有多大差距。α学习率0α≤1。这个误差不会100%地被采纳而是按一定比例α来更新当前的Q值。如果α1则直接用目标值替换旧值如果α较小则缓慢地向目标值靠近。这有助于学习过程更稳定。注意折扣因子γ是平衡“短视”与“远见”的关键。γ接近0智能体变得非常短视只在乎眼前一步的奖励γ接近1智能体非常有远见会为长远的高回报牺牲即时利益。在“过河”游戏里设置一个合理的γ如0.9能让智能体愿意为了最终过河的大奖励100而忍受中途一些小心翼翼奖励为0或小负值的步骤。这个更新过程是离线策略的它学习的是最优策略的Q值通过max(Q(s’, a’))但实际探索环境时使用的行为策略可以是不同的比如ε-greedy策略下面会讲。这意味着它可以在“随机瞎逛”的过程中不断更新和逼近那个“最优路径”的知识。3. 算法流程与关键设计抉择理解了核心公式我们来看Q-Learning完整的算法步骤。你会发现代码实现几乎就是对这个步骤的直译。3.1 标准Q-Learning算法步骤初始化初始化Q表Q(s, a)通常全部设为0。设定学习率α折扣因子γ探索率ε总训练回合数episodes。对于每个训练回合 a.初始化状态环境重置智能体获得初始状态s。 b.当状态s不是终止状态时循环 i.动作选择根据当前Q表和状态s使用某种策略如ε-greedy选择一个动作a。 ii.执行动作在环境中执行动作a观察到新的状态s’和即时奖励R。 iii.Q值更新使用公式Q(s, a) Q(s, a) α * [ R γ * max(Q(s’, a’)) - Q(s, a) ]更新Q表。 iv.状态转移将当前状态更新为新状态s s’。 c. 回合结束。3.2 核心设计探索与利用的权衡ε-greedy策略这是实现中第一个关键点。如果智能体总是选择当前Q表认为最好的动作贪婪策略它可能很快陷入一个局部最优解而永远发现不了真正更好的路径。就像你过河每次都跳最稳的那块石头可能永远到不了对岸因为中途需要冒险跳一块有点滑但关键的石头。因此我们引入ε-greedy策略以概率ε探索率随机选择一个动作探索尝试新可能。以概率1-ε选择当前状态下Q值最大的动作利用使用已知最佳知识。通常训练初期ε设置得较高如0.9鼓励多探索随着训练进行逐渐衰减ε如每个回合乘以0.995让智能体后期更多地利用学到的知识。3.3 环境设计以“格子世界”为例为了用80行代码清晰展示我们需要一个极简的环境。经典的“格子世界”再合适不过状态网格中的每个格子坐标如(0,0),(0,1)等。动作上、下、左、右四个移动动作。奖励到达目标格子获得大奖励如100掉入陷阱格子获得大惩罚如-100每走一步消耗小惩罚如-1鼓励尽快到达目标。终止状态目标格子和陷阱格子。这个环境简单到可以用一个二维数组表示但其包含了强化学习的所有要素状态空间、动作空间、奖励函数、终止条件。我们将基于这个环境进行实现。4. Python实现详解逐行构建智能体下面我们开始用Python实现一个在4x4格子世界中学习的智能体。我会将代码分成几个部分并详细解释每一行。4.1 环境搭建首先我们定义一个简单的网格世界环境。import numpy as np class GridWorld: 一个简单的4x4格子世界环境 def __init__(self): self.size 4 self.start (0, 0) # 起点 self.goal (3, 3) # 终点奖励100 self.trap (1, 1) # 陷阱奖励-100 self.state self.start def reset(self): 重置环境到起点 self.state self.start return self.state def step(self, action): 执行动作 :param action: 0:上, 1:下, 2:左, 3:右 :return: 下一个状态, 即时奖励, 是否终止 x, y self.state # 定义动作效果 if action 0: # 上 x max(x - 1, 0) elif action 1: # 下 x min(x 1, self.size - 1) elif action 2: # 左 y max(y - 1, 0) elif action 3: # 右 y min(y 1, self.size - 1) self.state (x, y) # 计算奖励 if self.state self.goal: reward 100 done True elif self.state self.trap: reward -100 done True else: reward -1 # 每走一步的代价 done False return self.state, reward, done def get_all_states(self): 返回所有可能的状态列表用于初始化Q表 states [] for i in range(self.size): for j in range(self.size): states.append((i, j)) return states实操心得在自定义环境中step函数的设计至关重要。奖励reward的设置是引导智能体行为的“指挥棒”。这里我们使用稀疏奖励只在终点和陷阱有较大数值并配合每步-1的生存代价。这种设置非常经典它鼓励智能体尽快找到目标同时避开陷阱。你可以通过调整这些奖励值来 dramatically 改变智能体的学习行为。4.2 Q-Learning智能体实现接下来是核心的智能体类它包含Q表和学习逻辑。class QLearningAgent: Q-Learning智能体 def __init__(self, actions, learning_rate0.1, discount_factor0.9, epsilon0.9): 初始化 :param actions: 动作列表 [0,1,2,3] :param learning_rate: 学习率 alpha :param discount_factor: 折扣因子 gamma :param epsilon: 初始探索率 self.actions actions self.lr learning_rate self.gamma discount_factor self.epsilon epsilon self.epsilon_decay 0.995 # 探索率衰减因子 self.epsilon_min 0.01 # 最小探索率 self.q_table {} # 使用字典存储Q表键为(state)值为一个列表对应各个动作的Q值 def init_q_table(self, states): 初始化Q表所有Q值为0 for state in states: self.q_table[state] [0.0] * len(self.actions) def choose_action(self, state): 根据epsilon-greedy策略选择动作 if np.random.uniform(0, 1) self.epsilon: # 探索随机选择动作 action np.random.choice(self.actions) else: # 利用选择当前状态Q值最大的动作 state_q self.q_table[state] # 处理多个动作Q值相同的情况随机选一个 max_q max(state_q) count state_q.count(max_q) if count 1: best_actions [i for i in range(len(self.actions)) if state_q[i] max_q] action np.random.choice(best_actions) else: action state_q.index(max_q) return action def learn(self, state, action, reward, next_state, done): 执行Q-Learning更新 current_q self.q_table[state][action] if done: # 如果是终止状态则没有下一个状态的最大Q值 target reward else: # 非终止状态计算目标值 next_max_q max(self.q_table[next_state]) target reward self.gamma * next_max_q # Q值更新公式 self.q_table[state][action] self.lr * (target - current_q) # 衰减探索率但不低于最小值 if self.epsilon self.epsilon_min: self.epsilon * self.epsilon_decay关键代码解析Q表结构我们使用Python字典q_table来存储Q值。键是状态如(0,0)值是一个长度为4的列表分别对应[上下左右]动作的Q值。这种结构比二维数组更灵活易于处理非网格或状态空间很大的情况。动作选择choose_action函数实现了ε-greedy策略。注意处理了多个动作Q值相同的情况此时随机选择一个避免算法陷入固定的选择顺序。学习更新learn函数是算法核心。它根据当前状态、动作、奖励、下一个状态计算目标值并更新Q表。特别注意对done终止状态的处理在终止状态没有下一个状态因此未来回报为0目标值就是即时奖励reward。探索率衰减在每次学习后我们对epsilon进行衰减乘以epsilon_decay并设置一个下限epsilon_min。这是为了让智能体在训练初期充分探索在后期稳定利用学到的知识。衰减策略可以调整比如线性衰减或指数衰减这里用的是指数衰减。4.3 训练循环与主程序最后我们将环境和智能体组合起来进行训练。def train(episodes500): 训练函数 env GridWorld() agent QLearningAgent(actions[0, 1, 2, 3], learning_rate0.1, discount_factor0.9, epsilon0.9) # 获取所有状态并初始化Q表 all_states env.get_all_states() agent.init_q_table(all_states) success_count 0 # 记录成功到达终点的次数 steps_per_episode [] # 记录每回合步数 for episode in range(episodes): state env.reset() total_reward 0 steps 0 done False while not done: # 智能体选择动作 action agent.choose_action(state) # 环境执行动作返回反馈 next_state, reward, done env.step(action) # 智能体学习 agent.learn(state, action, reward, next_state, done) state next_state total_reward reward steps 1 # 安全措施防止无限循环 if steps 100: break steps_per_episode.append(steps) if env.state env.goal: success_count 1 # 每100回合打印一次进度 if (episode 1) % 100 0: avg_steps np.mean(steps_per_episode[-100:]) success_rate success_count / 100 if episode 99 else success_count / (episode 1) print(fEpisode {episode1}, Avg Steps: {avg_steps:.2f}, Success Rate: {success_rate:.2%}, Epsilon: {agent.epsilon:.3f}) success_count 0 # 重置计数 print(\n训练结束) # 打印最终策略每个状态下选择的最优动作 print(\n学习到的最优策略箭头表示动作方向) arrow_map {0: ↑, 1: ↓, 2: ←, 3: →} for i in range(env.size): row_str for j in range(env.size): state (i, j) if state env.goal: row_str G elif state env.trap: row_str T else: action agent.choose_action(state) # 此时epsilon应很小接近贪婪选择 row_str f {arrow_map[action]} print(row_str) return agent, steps_per_episode if __name__ __main__: agent, steps_history train(episodes500)训练过程分析外层循环for episode in range(episodes)每个episode代表智能体从起点开始直到到达终点/陷阱或步数超限的一次完整尝试。内层循环while not done在单个回合内智能体与环境持续交互选择动作-获得反馈-学习-状态转移。监控指标我们记录了每回合的步数steps和成功到达目标的次数success_count。步数越少说明策略越高效成功率越高说明学习越有效。安全措施if steps 100: break是一个重要的保险。防止因Q值未收敛或环境设计问题导致智能体在某个局部死循环。结果可视化训练结束后我们打印出智能体学到的策略。用箭头表示在每个格子状态下它会选择的最优动作。你可以直观地看到它是否学会了绕过陷阱走向目标。运行这段代码你会观察到在训练初期由于探索率高智能体步数很多成功率低。随着训练进行探索率下降Q表逐渐收敛平均步数会下降并稳定在一个较优值对于4x4网格避开陷阱的最短路径可能是6步成功率会接近100%。5. 参数调优与结果分析代码跑起来了但你可能发现结果时好时坏或者收敛速度不理想。这很大程度上取决于几个超参数的设置。我们来深入分析一下5.1 核心参数影响分析参数含义设置过高影响设置过低影响经验取值建议学习率 α控制每次Q值更新的幅度学习不稳定Q值震荡难以收敛学习速度极慢需要更多回合通常从0.1开始尝试稳定环境可取0.1-0.5复杂环境可取0.01-0.1折扣因子 γ衡量未来奖励的重要性智能体过于“远视”可能忽视近期关键奖励学习慢智能体过于“短视”只追求即时奖励可能学不到长远策略对于有明确终止目标的任务如到达终点0.9-0.99是常见选择初始探索率 ε控制探索新动作的概率前期盲目随机浪费训练时间学习效率低过早陷入局部最优可能永远找不到全局最优解通常设为0.9或1.0保证充分初始探索探索率衰减控制探索率随训练降低的速度衰减太快探索不充分策略可能不是最优衰减太慢训练后期仍在大量随机策略不稳定指数衰减因子常取0.995-0.999使ε在训练中期降至较低水平训练回合数总的训练次数计算资源浪费可能过拟合在训练环境上表现过好训练不充分Q表未收敛策略性能差需要观察学习曲线如平均步数直到曲线平稳实操心得没有一套参数放之四海而皆准。最好的方法是观察学习曲线。将每100回合的平均步数或成功率画成图。如果曲线持续下降后趋于平稳说明参数合适学习有效。如果曲线剧烈震荡可能是学习率α太高如果曲线下降极其缓慢可能是α太低或γ太低导致智能体缺乏学习动力。对于我们的格子世界α0.1, γ0.9, ε0.9衰减0.995是一个不错的起点。5.2 学习效果评估与策略解读训练完成后我们打印的策略图可能如下所示一种可能的结果↑ → → → → T → ↓ ↑ ← ← ↓ ↑ ↑ ↑ G假设起点(0,0)在左上角终点G在(3,3)陷阱T在(1,1)如何解读从起点(0,0)开始箭头是↑但上方是边界实际会留在原地。由于初始Q表为0且探索率衰减后很低这里的选择可能没有意义但通常智能体会很快学到应该向右或向下走。关键看陷阱(1,1)周围比如(0,1)和(1,0)的箭头都指向远离陷阱的方向(2,1)指向左远离陷阱(1,2)指向下远离陷阱。这说明智能体成功学到了避开陷阱。从网格右下部分指向终点的箭头形成了一个指向目标G的“流场”这说明智能体学到了走向目标的最优路径。你可以尝试修改陷阱的位置或奖励值观察学到的策略如何变化。例如把每步代价从-1改为-0.1智能体可能就不那么急于到达终点学到的路径可能不同。6. 常见问题、扩展与避坑指南在实际动手实现和调试的过程中你几乎一定会遇到下面这些问题。这里我把它们和解决方案整理出来希望能帮你节省大量时间。6.1 Q-Learning实战常见问题排查表问题现象可能原因排查与解决思路智能体完全不学习随机乱走1. 学习率α设置为0。2. 奖励函数设计不合理全是0或非常小。3. Q表初始化全为0且γ0导致目标值target永远等于reward若reward非正则Q值永不增长。1. 检查并确保α 0。2. 检查step函数确保有正负奖励区分。给目标一个大的正奖励如100给每步一个小的负奖励如-1。3. 确保γ 0让未来回报发挥作用。学习不稳定性能曲线剧烈震荡1. 学习率α过高。2. 探索率ε衰减太慢或始终很高导致策略一直在随机。3. 环境本身具有随机性随机转移或奖励但Q-Learning是确定性更新可能不适应。1. 降低α例如从0.5降到0.1或0.05。2. 增加探索率衰减速度或设置合理的ε_min如0.01。3. 考虑使用期望Sarsa等能更好处理随机性的算法。收敛到一个明显很差的策略1. 陷入了局部最优。2. 探索不充分ε衰减太快或初始值太小。3. 折扣因子γ太低智能体太“短视”看不到长远好处。1. 增加初始ε减缓衰减速度让智能体有机会跳出局部最优。2. 尝试增加γ让智能体更有“远见”。3. 检查奖励函数是否对期望行为给予了足够正向激励。训练后期性能突然下降可能出现了“灾难性遗忘”。如果Q表用神经网络近似DQN这是常见问题。但在表格型Q-Learning中较少见除非状态/动作空间极大且访问不均匀。对于表格法确保所有状态-动作对都被充分访问。可以尝试初始化Q值为小的随机数而非全0打破对称性。代码运行慢状态空间大时表格型Q-Learning的Q表大小是(状态数 × 动作数)。当状态空间巨大如围棋有10^170状态时表格无法存储和遍历。这是表格法Q-Learning的根本局限。此时需要升级到深度Q网络DQN用神经网络来近似Q函数处理高维状态输入。6.2 从表格法到函数逼近深度Q网络DQN简介当我们的“格子世界”变成真实的游戏屏幕像素矩阵状态空间几乎是无限的表格法就失效了。这时就需要深度Q网络Deep Q-Network, DQN。它用神经网络如CNN来代替Q表输入是状态如游戏画面输出是每个动作的Q值。DQN在Q-Learning基础上引入了两个关键技巧经验回放将智能体的经历状态动作奖励新状态是否终止存储在一个记忆库中。学习时随机从库中抽取一批经历来训练网络。这打破了数据间的相关性使训练更稳定。目标网络使用一个结构相同但参数更新较慢的“目标网络”来计算max(Q(s’, a’))而用另一个“在线网络”来选择动作和持续更新。这解决了目标值max(Q(s’, a’))随网络快速变化而导致的训练不稳定问题。实现DQN的代码量会远超过80行但其核心思想仍然是Q-Learning。理解了这个80行的基础版本你再去啃DQN的论文和代码会感觉脉络清晰很多。6.3 项目扩展思路掌握了基础版本后你可以尝试以下扩展让这个项目更丰满可视化学习过程使用matplotlib动态绘制智能体在网格中的移动路径或者绘制平均步数/成功率随训练回合变化的曲线图。设计更复杂的环境增加更多陷阱、奖励格、传送门、可移动的障碍物等。实现其他经典算法在同一个环境上实现Sarsa同策略TD控制、Expected Sarsa并与Q-Learning比较学习速度和最终策略的差异。解决经典控制问题使用Gymnasium原OpenAI Gym库中的经典环境如CartPole平衡杆、MountainCar爬山车将你的Q-Learning智能体适配过去。这需要你将连续状态如小车位置、速度离散化成桶这是将表格法应用于连续问题的常用技巧。回过头看这80行代码构建的不仅仅是一个格子世界的小游戏AI它完整呈现了强化学习智能体“感知-决策-学习”的核心闭环。Q-Learning的简洁与强大在于它用一张不断更新的价值表将试错的经验沉淀为确定的决策知识。这种“在交互中学习最优行为模式”的思想是贯穿从游戏AI到机器人控制从推荐系统到金融交易的通用范式。当你下次看到更复杂的深度强化学习模型时希望你能会心一笑认出它骨子里还是那个在不断试错、不断更新Q值的智能体。
返回列表