第一部分 · 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 的四步:向量表示局势 → 矩阵乘法打分 → 掩码筛非法 → 取最大

📝 练习

  1. 用两个长度为 5 的向量表示"我方英雄状态"与"威胁权重",计算它们的点积(危险分数)与各自的 L2 范数。
  2. 构造一个 3×3 的转移矩阵(把对局状态设为 安全/危险/阵亡,每行和为 1),验证每行之和确实为 1。
  3. 给定 γ=0.95、上题的 P、奖励向量 r(如 [1, 0, -10]),用 np.linalg.solve 求出各状态价值 v
  4. 思考题:如果 (I - γP) 不可逆会发生什么?γ 的取值如何保证它可逆?
  5. 王者场景:把 1.7 里的状态改成"我方满血、敌方残血、大招已就绪",重算 scores,看看 AI 现在会不会选"大招"。再调一调 W 里大招那一行的权重,体会"权重决定打法"。

📗 延伸参考:ZhiqingXiao/rl-book(NumPy 与环境接口入门,可延伸阅读)

⬅️ 上一课:第一课 · 强化学习入门 + 环境搭建 ➡️ 下一部分:第二部分 · 马尔可夫决策过程 (MDP)

results matching ""

    No results matching ""