第一部分 · Python 与线性代数基础
目标:掌握强化学习中最常用的线性代数语言,并能用 NumPy 熟练实现。
强化学习里到处是向量(状态、动作、参数)和矩阵(状态转移、Q 表、神经网络权重)。 在正式学 RL 之前,我们先把这门“数学语言”打牢。别担心——我们只学用得到的部分,而且全部配代码。
📎 承接第一课那句话:局势是向量,决策是矩阵乘法,好坏是数。 本部分就是把这句话拆开、用代码实现。每个运算我们都会回到王者荣耀里问一句:“它对应游戏里的什么?”
1.1 为什么强化学习需要线性代数?
| 强化学习中的东西 | 其实就是 | 在王者荣耀 1v1 里 |
|---|---|---|
一个状态 s |
一个向量 | 战场快照:[我方血量, 蓝量, x, y, 1技能CD, 2技能CD, 敌方血量, 敌我距离, ...] |
| 所有状态之间的转移概率 | 一个矩阵 P |
做完某动作后,局势变成各种情况的概率(含暴击/对手反应) |
| Q 表(状态×动作 的价值) | 一个矩阵 | “每种局势 × 每个动作”值不值得做的打分表 |
| 神经网络的一层 | 一次矩阵乘法 + 非线性 | 把几百维局势向量压成“往哪走、放什么技能”的打分 |
| 求解贝尔曼方程 | 解一个线性方程组 | 一次性算出“每种局势有多好” |
所以:看懂向量与矩阵运算 = 看懂强化学习的一半公式。
1.2 标量、向量、矩阵
- 标量 (scalar):一个数,如
γ = 0.99。 - 向量 (vector):一列数,如状态
s = [x, y, θ]。 - 矩阵 (matrix):数的二维表格,如转移矩阵
P(行=当前状态,列=下一状态)。
import numpy as np
scalar = 0.99 # 标量
vector = np.array([1.0, 2.0, 3.0]) # 向量 (shape: (3,))
matrix = np.array([[1, 2],
[3, 4],
[5, 6]]) # 矩阵 (shape: (3, 2))
print(scalar) # 0.99
print(vector.shape) # (3,)
print(matrix.shape) # (3, 2) -> 3行2列
📌 shape(形状)是你最常打交道的东西。90% 的 bug 都是 shape 对不上。
1.3 向量运算
加法与标量乘法
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
print(a + b) # [5. 7. 9.] 逐元素相加
print(2 * a) # [2. 4. 6.] 每个元素乘 2
点积 (dot product)
点积把两个向量变成一个数,是 RL 里计算“加权和”的核心(比如 价值 = 概率 · 回报)。
a · b = a1*b1 + a2*b2 + ... + an*bn
print(np.dot(a, b)) # 1*4 + 2*5 + 3*6 = 32
print(a @ b) # 32 (@ 是矩阵/向量乘法运算符,推荐)
范数 (norm) —— 向量的“长度”
||a||₂ = sqrt(a1² + a2² + ... + an²) # L2 范数(欧氏长度)
print(np.linalg.norm(a)) # 3.7416... L2 范数
print(np.linalg.norm(a, ord=1)) # 6.0 L1 范数(绝对值之和)
在 RL 中,我们常用范数衡量两次迭代之间价值函数的变化量,用来判断是否收敛。
1.4 矩阵运算
转置 (transpose)
M = np.array([[1, 2, 3],
[4, 5, 6]]) # shape (2, 3)
print(M.T) # shape (3, 2),行列互换
矩阵乘法 (matrix multiplication)
这是深度强化学习里最重要的运算。规则:(m×n) @ (n×p) = (m×p),中间维度必须相等。
A = np.array([[1, 2],
[3, 4]]) # (2, 2)
x = np.array([1, 1]) # (2,)
print(A @ x) # [3 7] 矩阵 × 向量
print(A @ A) # [[ 7 10]
# [15 22]]
一次神经网络前向传播就是:y = W @ x + b。
单位矩阵与逆矩阵
I = np.eye(3) # 3x3 单位矩阵(对角线为1)
Ainv = np.linalg.inv(A) # A 的逆矩阵,满足 A @ Ainv = I
print(A @ Ainv) # 约等于单位矩阵
解线性方程组
贝尔曼方程 v = r + γ P v 可以整理成 (I - γP) v = r,直接用求解器解出价值 v:
gamma = 0.9
P = np.array([[0.5, 0.5],
[0.2, 0.8]]) # 转移矩阵(每行和为1)
r = np.array([1.0, 0.0]) # 每个状态的即时奖励
v = np.linalg.solve(np.eye(2) - gamma * P, r)
print(v) # 每个状态的价值(我们会在第二部分推导这条公式)
🎯 记住这段代码——第二部分你就会明白它在做什么:一次性精确求出所有状态的价值。
1.5 特征值与特征向量(了解即可)
对方阵 A,若存在向量 v 和标量 λ 使得 A v = λ v,则 λ 是特征值,v 是特征向量。
A = np.array([[2, 0],
[0, 3]])
vals, vecs = np.linalg.eig(A)
print(vals) # [2. 3.]
在 RL 中它的用处:马尔可夫链的平稳分布正是转移矩阵(转置)对应特征值为 1 的特征向量。 现在只需有个印象即可。
1.6 广播 (broadcasting)
NumPy 允许不同形状的数组自动对齐运算,这让批量计算非常简洁:
Q = np.zeros((4, 2)) # 4个状态 × 2个动作 的 Q 表
bonus = np.array([0.1, 0.2]) # 给两个动作不同的加成
Q = Q + bonus # 自动“广播”到每一行
print(Q)
1.7 用王者荣耀把这些运算串起来(本部分重点)
前面每个运算都单独讲了。现在把它们放进同一个王者 1v1 的决策瞬间,你就懂了“决策的本质就是线性代数”。
① 局势 = 一个状态向量
假设我们只挑 6 个关键变量描述当前这一帧(真实开悟环境是 491 维、由英雄/小兵/塔/时期 等 5 大组件拼成,见第七课 6.3,道理完全一样):
import numpy as np
# [我方血量, 我方蓝量, 敌方血量, 敌我距离, 我1技能CD, 我大招CD]
s = np.array([0.30, 0.80, 0.55, 3.0, 0.0, 2.0]) # 归一化/秒
这就是第一课 0.2 表里的状态
s——英雄此刻的处境,是 6 维空间里的一个点。
② 点积 = 算“当前有多危险”
给每个变量配一个“威胁权重”,加权求和成一个危险分数——这就是一次点积:
# 血量越低越危险(负权重),敌人越近越危险,大招没好也危险
w_danger = np.array([-2.0, 0.0, 1.0, -0.5, 0.0, -0.3])
danger = s @ w_danger
print(danger) # 一个数:越大越危险
血量 0.30 很低 → 贡献 -2.0*0.30,把危险分数推高。点积把一份局势压成一个判断。
③ 矩阵乘法 = 把局势翻译成“每个动作的打分”
我们有 4 个候选动作:[后撤, 平A, 1技能, 大招]。用一个权重矩阵 W(4×6)把 6 维局势
一次性映射成 4 个动作的打分——这正是神经网络的一层 y = W·s + b:
# W 的每一行 = 一个动作对各局势变量的“偏好”
W = np.array([
[ 2.0, 0.0, -1.0, 1.5, 0.0, 0.0], # 后撤:血量低/敌人近时更想撤
[ 0.0, 0.0, 0.5, -1.0, 0.0, 0.0], # 平A:敌人近才划算
[ 0.0, 0.5, 1.0, -0.5,-2.0, 0.0], # 1技能:有蓝且技能好了才行
[-1.0, 0.5, 2.0, -0.5, 0.0,-3.0], # 大招:敌人血少且大招好了才值
])
b = np.array([0.5, 0.0, 0.0, 0.0])
scores = W @ s + b # 4 个动作的打分
print(scores)
print(["后撤","平A","1技能","大招"][int(np.argmax(scores))]) # 选打分最高的动作
血量只剩 0.30、大招还有 2 秒没好 → “后撤”的打分自然最高。 AI 决策的本质,就是这样一连串矩阵乘法,把局势压成动作选择。
④ 合法动作掩码 = 逐元素相乘
大招还在 CD,不能选。用一个 0/1 的掩码向量把非法动作的打分压到极小:
legal = np.array([1, 1, 1, 0]) # 大招不可用
masked = np.where(legal == 1, scores, -1e9)
print(["后撤","平A","1技能","大招"][int(np.argmax(masked))])
这在开悟环境里就是 legal_action,第六部分会反复用到。
一次决策 = 向量表示局势 → 点积/矩阵乘法打分 → 掩码筛掉非法 → 取最大。 这四步,就是你在这一部分学的全部运算的“合体”。
✅ 小结
- 状态是向量(王者里=战场快照),转移/Q 表是矩阵;
- 点积做加权求和(王者里=算威胁分数),矩阵乘法把局势翻译成动作打分(神经网络一层);
- 贝尔曼方程可写成线性方程组
(I - γP)v = r,用np.linalg.solve求解; - NumPy 的
shape、@、broadcasting要用熟; - 记住 1.7 的四步:向量表示局势 → 矩阵乘法打分 → 掩码筛非法 → 取最大。
📝 练习
- 用两个长度为 5 的向量表示"我方英雄状态"与"威胁权重",计算它们的点积(危险分数)与各自的 L2 范数。
- 构造一个
3×3的转移矩阵(把对局状态设为安全/危险/阵亡,每行和为 1),验证每行之和确实为 1。 - 给定
γ=0.95、上题的P、奖励向量r(如[1, 0, -10]),用np.linalg.solve求出各状态价值v。 - 思考题:如果
(I - γP)不可逆会发生什么?γ的取值如何保证它可逆? - 王者场景:把 1.7 里的状态改成"我方满血、敌方残血、大招已就绪",重算
scores,看看 AI 现在会不会选"大招"。再调一调W里大招那一行的权重,体会"权重决定打法"。
📗 延伸参考:ZhiqingXiao/rl-book(NumPy 与环境接口入门,可延伸阅读)
⬅️ 上一课:第一课 · 强化学习入门 + 环境搭建 ➡️ 下一部分:第二部分 · 马尔可夫决策过程 (MDP)