第3章:Q-Learning入门:Q表的概念、Q值更新公式、探索与利用的平衡(ε-greedy)

好,咱们今天来啃一块硬骨头——Q-Learning。说实话,我刚入行那会儿,觉得这东西玄乎得很。一个智能体,凭什么能学会走路、下棋、甚至打游戏?后来我亲手写了一个Q-Learning的demo,看着小车在迷宫里一点点学会找出口,那种感觉,嗯,就像看着自家孩子学会走路一样。

Q-Learning是强化学习里最经典的算法之一。它不依赖环境模型,直接通过跟环境交互来学习最优策略。说白了,就是「摸着石头过河」,边试边学。

3.1 Q表:智能体的「经验笔记本」

先问一个问题:你到一个新城市,怎么找到最好吃的餐馆?

正常人会这样:先看地图(状态),然后选一条路(动作),吃完评价一下(奖励),下次就知道该不该再去。Q-Learning也是这个逻辑,但它需要一个东西来记录经验——这就是Q表。

Q表是什么?

Q表就是一个二维表格。行代表状态,列代表动作。每个格子里的数值,就是「在这个状态下,执行这个动作,未来能获得多少累计奖励」的估计值。这个值,我们叫它Q值。

举个例子。假设机器人有3个状态(位置A、B、C),2个动作(向左、向右)。Q表长这样:

状态 \ 动作 向左 向右
位置A 0.5 1.2
位置B -0.3 0.8
位置C 2.1 0.0

你看,在位置A,向右的Q值是1.2,比向左的0.5高。所以智能体会倾向于向右走。这就是Q表的作用——告诉智能体「哪个动作更靠谱」。

核心要点:Q表是智能体的「经验存储器」。训练的过程,就是不断更新Q表的过程。Q表收敛了,策略也就学好了。

我在项目中遇到过一个问题:状态空间太大怎么办?比如围棋,状态数比宇宙原子还多。这时候Q表就存不下了。嗯,这就是后面要讲的深度Q网络(DQN)的用武之地。但那是后话,咱们先把Q表搞明白。

3.2 Q值更新公式:学习的核心引擎

Q表不是一成不变的。每次智能体执行一个动作,环境会给一个奖励,然后Q值就要更新。更新的公式长这样:

Q(s, a) ← Q(s, a) + α [ r + γ * max_a' Q(s', a') - Q(s, a) ]

别被公式吓到。咱们拆开来看:

  • Q(s, a):当前状态s下,执行动作a的旧Q值
  • α:学习率,控制更新步长。0到1之间,越大学得越快,但也容易震荡
  • r:执行动作后,环境给的即时奖励
  • γ:折扣因子,控制未来奖励的重要性。越接近1,越看重长远利益
  • max_a' Q(s', a'):在下一个状态s'中,所有动作里最大的Q值。这代表「未来的最佳期望」

这个公式的核心思想是:用「现实奖励 + 未来最佳期望」来修正「当前估计」。你想想看,这不就跟我们人类学习一样吗?

比如你第一次吃火锅点了毛肚,觉得好吃(即时奖励r)。然后你听说毛肚涮8秒最嫩(未来期望),下次你就知道该怎么做了。Q-Learning就是在做这件事——不断用「实际体验」来修正「预估价值」。

我的经验:学习率α别设太大。我刚开始做的时候设了0.9,结果Q值来回震荡,死活不收敛。后来改成0.1,稳多了。记住:慢就是快。

咱们来看一段简单的Q-Learning更新代码:

def q_learning_update(q_table, state, action, reward, next_state, alpha=0.1, gamma=0.9):
    # 当前Q值
    current_q = q_table[state][action]
    
    # 下一个状态的最大Q值
    max_next_q = max(q_table[next_state])
    
    # 计算目标Q值
    target_q = reward + gamma * max_next_q
    
    # 更新Q值
    new_q = current_q + alpha * (target_q - current_q)
    q_table[state][action] = new_q
    
    return q_table

这段代码看着简单,但它是整个Q-Learning的灵魂。每次交互,就调用一次这个函数。跑上千上万次,Q表就慢慢变准了。

3.3 探索与利用的平衡:ε-greedy策略

好,现在Q表有了,更新公式也有了。但有个问题:智能体该怎么选择动作?

两种极端:

  • 完全利用:每次都选Q值最大的动作。好处是「稳」,坏处是可能错过更好的选择
  • 完全探索:随机选动作。好处是「广」,坏处是永远学不到最优

这就引出了强化学习里最经典的一个矛盾——探索与利用的平衡。说白了,就是「吃老本」和「闯新路」之间的权衡。

解决办法?ε-greedy策略。名字听着高大上,其实逻辑很简单:

import random

def epsilon_greedy(q_table, state, epsilon=0.1):
    # 以epsilon的概率随机探索
    if random.random() < epsilon:
        return random.choice(range(len(q_table[state])))  # 随机选动作
    else:
        return q_table[state].index(max(q_table[state]))  # 选最大Q值动作

逻辑就两句话:

  • 生成一个0到1的随机数
  • 如果小于ε,就随机选动作(探索);否则选Q值最大的动作(利用)

ε通常设0.1,也就是10%的概率探索,90%的概率利用。这个比例是我试过很多次后觉得比较舒服的。

避坑指南:我曾经犯过一个错误——ε设成固定值0.5。结果智能体一直在随机乱逛,永远学不会。后来我改成「ε衰减」:训练初期ε=0.9(多探索),后期ε=0.01(多利用)。效果立竿见影。

为什么会这样?你想想看,刚开始Q表全是0,智能体啥都不知道。这时候不多探索,难道闭着眼睛瞎选?等学得差不多了,再慢慢减少探索,专心利用已有的经验。这个「先探索后利用」的思路,在很多工程问题上都适用。

3.4 知识体系总览

说了这么多,咱们用一张图把本章的核心逻辑串起来:

Q-Learning核心知识体系 Q表 行:状态 列:动作 值:Q(s,a)估计 Q值更新公式 Q(s,a) ← Q(s,a) + α[ r + γ·maxQ(s',a') - Q(s,a)] α学习率 | γ折扣因子 ε-greedy策略 概率ε:随机探索 概率1-ε:贪心利用 探索 vs 利用的平衡 读取/写入 指导选择 训练循环:观察状态 → ε-greedy选动作 → 执行得奖励 → 更新Q表 重复直到收敛 关键参数设置建议 α=0.1~0.3 | γ=0.9~0.99 | ε初始0.9→衰减至0.01 | 训练轮次:视任务复杂度而定

这张图把Q-Learning的三个核心要素串在了一起。Q表是基础,更新公式是引擎,ε-greedy是方向盘。三者缺一不可。

3.5 一个完整的Q-Learning流程

最后,咱们把整个流程串起来,看看一个完整的训练循环长什么样:

# 伪代码:Q-Learning完整流程
初始化Q表为全0
设置α=0.1, γ=0.9, ε=0.9

for episode in range(总轮数):
    初始化状态s
    
    while 未到达终止状态:
        # 1. 用ε-greedy选动作
        a = epsilon_greedy(Q, s, ε)
        
        # 2. 执行动作,获得奖励和下一个状态
        s_next, r = environment.step(s, a)
        
        # 3. 更新Q值
        Q = q_learning_update(Q, s, a, r, s_next, α, γ)
        
        # 4. 状态转移
        s = s_next
    
    # 每轮结束后衰减ε
    ε = max(0.01, ε * 0.995)

这段代码我写过不下50遍。每次写都觉得简单,但每次跑起来都能发现新问题。比如ε衰减太快,智能体还没探索够就收敛了;或者γ设太小,智能体只看眼前利益,走不出迷宫。

一个小技巧:训练过程中,我习惯把每轮的总奖励打印出来。如果奖励曲线在稳步上升,说明学对了。如果震荡剧烈,检查一下α是不是太大。如果一直上不去,可能是ε衰减太快。

嗯,Q-Learning的内容就这些。说白了就三件事:一张表、一个公式、一个策略。但就是这三件事,构成了强化学习最经典的算法之一。后面咱们会在这个基础上,一步步深入到更复杂的算法中去。

本章总结:

  • Q表是智能体的经验存储器,行是状态,列是动作
  • Q值更新公式用「现实+未来期望」修正当前估计
  • ε-greedy策略平衡探索与利用,ε衰减是关键
  • 训练循环:观察→选择→执行→更新→重复

可可精品资料库