Stanford斯坦福 CS 224R: 深度强化学习 (5)

离线强化学习:第一部分

强化学习(RL)旨在让智能体通过与环境交互来学习最优策略,从而最大化累积奖励。传统的RL训练都是在线(online)进行的,即智能体在训练过程中不断与环境交互,实时生成新的状态-动作数据,并基于新数据来更新策略。这种在线学习虽然简单直观,但也存在一些局限性:

  • 在线交互的样本效率较低,许多采集到的数据未被充分利用
  • 对于一些高风险场景(如自动驾驶),在线探索可能会带来安全隐患
  • 一些领域积累了大量历史数据,在线学习无法直接利用这些数据

为了克服在线学习的局限,研究者们提出了离线强化学习(Offline RL)范式。本章我们将系统地介绍离线RL的动机、挑战与核心方法,探讨如何从静态数据集出发来训练最优策略。

1. 为什么需要离线强化学习?

传统RL算法都是在线学习的,其训练流程可以概括为:

  1. 用当前策略 π θ \pi_{\theta} πθ 采集一批数据 D \mathcal{D} D
  2. 基于新数据 D \mathcal{D} D 或累积数据 D 1 : t \mathcal{D}_{1:t} D1:t 来更新策略
  3. 重复步骤1-2,直到策略收敛

这里的数据采集可以是on-policy的(即直接用 π θ \pi_{\theta} πθ 采集),也可以是off-policy的(用其他策略如 ϵ \epsilon ϵ-greedy采集)。但无论哪种方式,智能体都要在训练过程中持续与环境交互。

离线RL则打破了这一限制,其目标是仅利用一个静态数据集 D = { ( s , a , r , s ′ ) i } i = 1 N \mathcal{D}=\{(s,a,r,s')_i\}_{i=1}^N D={(s,a,r,s)i}i=1N 来学习最优策略 π ∗ \pi^* π。形式化地,这个数据集可以看作从某个未知的行为策略(behavior policy) π β \pi_{\beta} πβ 采样得到:

s ∼ d π β ( ⋅ ) a ∼ π β ( ⋅ ∣ s ) s ′ ∼ P ( ⋅ ∣ s , a ) r = r ( s , a ) \begin{aligned} s \sim d^{\pi_{\beta}}(\cdot) \\ a \sim \pi_{\beta}(\cdot \mid s) \\ s' \sim \mathcal{P}(\cdot \mid s,a) \\ r = r(s,a) \end{aligned} sdπβ()aπβ(s)sP(s,a)r=r(s,a)

d π β d^{\pi_{\beta}} dπβ 表示 π β \pi_{\beta} πβ 诱导的状态分布。值得注意的是, π β \pi_{\beta} πβ 可以是一组策略的混合,即数据集 D \mathcal{D} D 可能来自多个行为策略。

因此,离线RL的优化目标可以写为:

max ⁡ θ ∑ t E s t ∼ d π θ ( ⋅ ) , a t ∼ π θ ( ⋅ ∣ s t ) [ r ( s t , a t ) ] \max_{\theta} \sum_{t} \mathbb{E}_{s_t \sim d^{\pi_{\theta}}(\cdot),a_t \sim \pi_{\theta}(\cdot \mid s_t)} [r(s_t,a_t)] θmaxtEstdπθ(),atπθ(st)[r(st,at)]

即最大化新策略 π θ \pi_{\theta} πθ 在其诱导的状态分布上的期望累积奖励。

相比在线RL,离线RL有如下优势:

  • 可以充分利用人工采集的、历史系统产生的离线数据,提高样本利用率
  • 避免了在线交互的安全风险,适用于高风险场景的策略学习
  • 可以复用之前项目积累的数据,减少重复采集的成本

当然,我们也可以将离线学习和在线学习相结合,用离线数据进行预训练,再用在线数据进行finetune。但本章我们将重点放在纯离线RL设定上。

2. 离线RL能否直接套用off-policy算法?

基于离线数据集学习策略,这听起来与off-policy RL很相似。那么我们是否可以直接将off-policy算法应用到离线场景中呢?

以经典的Q-learning为例,它的目标是最小化temporal difference (TD) error:

min ⁡ Q E ( s , a , r , s ′ ) ∼ D [ ( Q ( s , a ) − ( r + γ max ⁡ a ′ Q ( s ′ , a ′ ) ) ) 2 ] \min_{Q} \mathbb{E}_{(s,a,r,s') \sim \mathcal{D}} \left[ \left( Q(s,a) - \left( r + \gamma \max_{a'} Q(s',a') \right) \right)^2 \right] QminE(s,a,r,s)D[(Q(s,a)(r+γamaxQ(s,a)))2]

其中 Q ( s , a ) Q(s,a) Q(s,a) 为状态-动作值函数。学到Q函数后,策略可以通过 ϵ \epsilon ϵ-greedy或直接选取最大Q值的动作来生成。

然而,如果我们直接用一个静态数据集(如由一个次优策略采集)来训练Q-learning,会发生什么?

在训练初期Q网络随机初始化时,对于某个状态 s ′ s' s,网络输出的各个动作值 Q ( s ′ , ⋅ ) Q(s',\cdot) Q(s,) 可能与实际值相差较大。假设某个动作 a ′ a' a 在数据集 D \mathcal{D} D 中从未出现过,但Q网络给出了一个非常乐观的估计 Q ( s ′ , a ′ ) Q(s',a') Q(s,a)。在Q-learning更新时,这个乐观估计会通过 max ⁡ \max max 操作传播到 Q ( s , a ) Q(s,a) Q(s,a),导致 Q ( s , a ) Q(s,a) Q(s,a) 被高估。

随着训练的进行,Q值的高估会不断放大,最终导致离线训练策略 π θ \pi_{\theta} πθ 过度偏离行为策略 π β \pi_{\beta} πβ。这种偏离一方面源自对未见过动作的乐观估计,另一方面也反映了分布漂移问题: π θ \pi_{\theta} πθ 访问了 π β \pi_{\beta} πβ 未覆盖的状态空间。当 π θ \pi_{\theta} πθ 部署到实际环境中时,这些偏差会导致严重的性能降级,甚至灾难性后果。

因此,如何缓解离线RL中的Q值过估计,是我们需要重点解决的问题。这也是离线RL区别于off-policy RL的关键。接下来我们将介绍两大类缓解过估计的方法。

3. 数据约束方法

既然Q值过估计源于对未见过动作的错误泛化,一个很自然的想法是:能否约束 π θ \pi_{\theta} πθ 不要偏离 π β \pi_{\beta} πβ 太远?直观地说,如果新策略的动作分布与数据集的动作分布接近,就可以避免查询到未见过的(可能高估的)Q值。

形式化地,带行为约束的离线RL目标可以写为:

max ⁡ θ ∑ t E s t ∼ d π θ ( ⋅ ) , a t ∼ π θ ( ⋅ ∣ s t ) [ r ( s t , a t ) ] s.t. D ( π θ , π β ) ≤ ϵ \begin{aligned} \max_{\theta} & \sum_{t} \mathbb{E}_{s_t \sim d^{\pi_{\theta}}(\cdot),a_t \sim \pi_{\theta}(\cdot \mid s_t)} [r(s_t,a_t)] \\ \text{s.t.} & D(\pi_{\theta}, \pi_{\beta}) \leq \epsilon \end{aligned} θmaxs.t.tEstdπθ(),atπθ(st)[r(st,at)]D(πθ,πβ)ϵ

其中 D ( ⋅ , ⋅ ) D(\cdot,\cdot) D(,) 为某种分布差异度量, ϵ \epsilon ϵ 为预设的容忍度。这样一来,我们限制了 π θ \pi_{\theta} πθ π β \pi_{\beta} πβ 的偏离程度,避免了对OOD(out-of-distribution)动作的过度乐观估计。一些常用的分布约束形式包括:

  • 支撑集约束: π θ ( a ∣ s ) > 0 \pi_{\theta}(a \mid s) > 0 πθ(as)>0 当且仅当 π β ( a ∣ s ) ≥ δ \pi_{\beta}(a \mid s) \geq \delta πβ(as)δ。即新策略只能选择在数据集中出现过的动作。
  • KL散度约束: D K L ( π θ ∥ π β ) ≤ ϵ D_{KL}(\pi_{\theta} \| \pi_{\beta}) \leq \epsilon DKL(πθπβ)ϵ。即限制新旧策略的KL散度在 ϵ \epsilon ϵ 以内。

前者比较符合我们的直观要求,但在实践中难以准确实现。后者便于优化求解,但约束相对宽泛。

不过这里还有一个问题:上述约束中的 π β \pi_{\beta} πβ 往往是未知的,因为离线数据集可能来自多个行为策略的混合。为了实现约束,我们需要先从数据中学习一个 π β \pi_{\beta} πβ 的逼近 π ^ β \hat{\pi}_{\beta} π^β。最简单的做法是行为克隆(behavior cloning),即监督学习动作概率:

π ^ β = arg ⁡ max ⁡ π E s , a ∼ D [ log ⁡ π ( a ∣ s ) ] \hat{\pi}_{\beta} = \arg\max_{\pi} \mathbb{E}_{s,a \sim \mathcal{D}} [\log \pi(a \mid s)] π^β=argπmaxEs,aD[logπ(as)]

这等价于对 π β \pi_{\beta} πβ 的最大似然估计。学到 π ^ β \hat{\pi}_{\beta} π^β 后,我们就可以基于它来施加行为约束。那么如何在优化过程中高效实现这些约束呢?下面介绍两种常见做法。

第一种做法是直接修改策略优化目标,将约束项合并进去。以KL散度约束为例:

max ⁡ θ E s ∼ D , a ∼ π θ [ Q ( s , a ) ] − α D K L ( π θ ( ⋅ ∣ s ) ∥ π ^ β ( ⋅ ∣ s ) ) \max_{\theta} \mathbb{E}_{s \sim \mathcal{D},a \sim \pi_{\theta}} [Q(s,a)] - \alpha D_{KL}(\pi_{\theta}(· \mid s) \| \hat{\pi}_{\beta}(· \mid s)) θmaxEsD,aπθ[Q(s,a)]αDKL(πθ(s)π^β(s))

这里 α \alpha α 为拉格朗日乘子,控制约束的强度。这种无约束化的优化目标在实践中更易求解。对于高斯、分类等常见的策略分布族,KL散度往往有简洁的解析形式。

第二种做法是修改策略的奖励函数,引入一项鼓励与 π β \pi_{\beta} πβ 接近的附加奖励:

r ~ ( s , a ) = r ( s , a ) + α log ⁡ π ^ β ( a ∣ s ) \tilde{r}(s,a) = r(s,a) + \alpha \log \hat{\pi}_{\beta}(a \mid s) r~(s,a)=r(s,a)+αlogπ^β(as)

这相当于把约束的误差项分摊到每一步的奖励中,让策略不仅追求高累积奖励,也避免选择 π β \pi_{\beta} πβ 很少访问的动作。

事实上,这两种做法在数学上是等价的。感兴趣的读者可以参考[Wu et al., 2019]了解更多实现细节。

4. 保守估计方法

数据约束方法本质上是通过约束策略行为来规避Q值过估计。与之互补地,我们也可以直接针对值函数,在估计过程中鼓励保守、惩罚过于乐观的预测。这类方法的优点在于不需要显式地建模行为策略 π β \pi_{\beta} πβ

其中一个代表性算法是保守Q学习(Conservative Q-Learning, CQL)[Kumar et al., 2020]。回顾标准的Q学习目标:

min ⁡ Q E ( s , a , r , s ′ ) ∼ D [ ( Q ( s , a ) − ( r + γ E π θ [ Q ( s ′ , ⋅ ) ] ) ) 2 ] \min_{Q} \mathbb{E}_{(s,a,r,s') \sim \mathcal{D}} \left[ \left( Q(s,a) - \left( r + \gamma \mathbb{E}_{\pi_{\theta}}[Q(s',\cdot)] \right) \right)^2 \right] QminE(s,a,r,s)D[(Q(s,a)(r+γEπθ[Q(s,)]))2]

CQL在此基础上引入了一项保守正则项:

min ⁡ Q E ( s , a , r , s ′ ) ∼ D [ ( Q ( s , a ) − ( r + γ E π θ [ Q ( s ′ , ⋅ ) ] ) ) 2 ] + α E s ∼ D , a ∼ μ ( ⋅ ∣ s ) [ Q ( s , a ) ] − α E ( s , a ) ∼ D [ Q ( s , a ) ] \begin{aligned} \min_{Q} & \mathbb{E}_{(s,a,r,s') \sim \mathcal{D}} \left[ \left( Q(s,a) - \left( r + \gamma \mathbb{E}_{\pi_{\theta}}[Q(s',\cdot)] \right) \right)^2 \right] \\ & + \alpha \mathbb{E}_{s \sim \mathcal{D},a \sim \mu(\cdot \mid s)} [Q(s,a)] - \alpha \mathbb{E}_{(s,a) \sim \mathcal{D}} [Q(s,a)] \end{aligned} QminE(s,a,r,s)D[(Q(s,a)(r+γEπθ[Q(s,)]))2]+αEsD,aμ(s)[Q(s,a)]αE(s,a)D[Q(s,a)]

其中 μ ( ⋅ ∣ s ) \mu(\cdot \mid s) μ(s) 为任意一个先验行为分布(可学习), α \alpha α 为权重系数。

这个正则项由两部分组成:

  • E s ∼ D , a ∼ μ ( ⋅ ∣ s ) [ Q ( s , a ) ] \mathbb{E}_{s \sim \mathcal{D},a \sim \mu(\cdot \mid s)} [Q(s,a)] EsD,aμ(s)[Q(s,a)] 对所有状态-动作对的Q值求期望,通过最大化 μ \mu μ 来放大大的Q值
  • E ( s , a ) ∼ D [ Q ( s , a ) ] \mathbb{E}_{(s,a) \sim \mathcal{D}} [Q(s,a)] E(s,a)D[Q(s,a)] 只对数据集 D \mathcal{D} D 中observerd的状态-动作对求期望,作为对比

第一项相当于鼓励 μ \mu μ 去寻找预测值最大的(可能高估的)状态-动作对,而第二项则把这些对的预测值拉回到实际观测值。两项的差形成了一个惩罚,抑制了Q网络在数据集外对动作值的过高估计。

可以证明,当 α \alpha α 足够大时,用 L C Q L L_{CQL} LCQL 学到的Q函数 Q ^ π \hat{Q}^{\pi} Q^π 可以下界真实Q函数 Q π Q^{\pi} Qπ:

Q ^ π ( s , a ) ≤ Q π ( s , a ) , ∀ ( s , a ) \hat{Q}^{\pi}(s,a) \leq Q^{\pi}(s,a), \forall (s,a) Q^π(s,a)Qπ(s,a),(s,a)

因此CQL确保了对Q值的保守估计,避免了过度乐观。

根据 Q ^ π \hat{Q}^{\pi} Q^π,我们可以得到一个下界最优策略 π C Q L \pi_{CQL} πCQL:

π C Q L ( ⋅ ∣ s ) = arg ⁡ max ⁡ π E a ∼ π ( ⋅ ∣ s ) [ Q ^ π ( s , a ) ] \pi_{CQL}(\cdot \mid s) = \arg\max_{\pi} \mathbb{E}_{a \sim \pi(\cdot \mid s)} [\hat{Q}^{\pi}(s,a)] πCQL(s)=argπmaxEaπ(s)[Q^π(s,a)]

当动作空间离散时,这个问题的解为确定性策略:

π C Q L ( a ∣ s ) = { 1 if  a = arg ⁡ max ⁡ a ′ Q ^ π ( s , a ′ ) 0 otherwise \pi_{CQL}(a \mid s) = \begin{cases} 1 & \text{if } a = \arg\max_{a'} \hat{Q}^{\pi}(s,a') \\ 0 & \text{otherwise} \end{cases} πCQL(as)={10if a=argmaxaQ^π(s,a)otherwise

当动作空间连续时,我们可以通过梯度上升来逼近最优策略:

θ ← θ + η ∇ θ E s ∼ D , a ∼ π θ ( ⋅ ∣ s ) [ Q ^ π ( s , a ) ] \theta \leftarrow \theta + \eta \nabla_{\theta} \mathbb{E}_{s \sim \mathcal{D},a \sim \pi_{\theta}(\cdot \mid s)} [\hat{Q}^{\pi}(s,a)] θθ+ηθEsD,aπθ(s)[Q^π(s,a)]

其中 η \eta η 为学习率。

完整的CQL算法流程如下:

  1. L C Q L L_{CQL} LCQL 和数据集 D \mathcal{D} D 来训练Q网络 Q ^ π \hat{Q}^{\pi} Q^π
  2. 根据 Q ^ π \hat{Q}^{\pi} Q^π 来更新策略 π θ \pi_{\theta} πθ
  3. 重复1-2步骤,直到 Q ^ π \hat{Q}^{\pi} Q^π π θ \pi_{\theta} πθ 都收敛

实践中,我们还需要给先验分布 μ ( ⋅ ∣ s ) \mu(\cdot \mid s) μ(s) 赋予一个具体的形式。一个常见的选择是指数型分布族:

μ ( a ∣ s ) ∝ exp ⁡ ( Q ^ π ( s , a ) ) \mu(a \mid s) \propto \exp(\hat{Q}^{\pi}(s,a)) μ(as)exp(Q^π(s,a))

此时第一个正则项可以化简为:

E s ∼ D , a ∼ μ ( ⋅ ∣ s ) [ Q ( s , a ) ] = E s ∼ D [ log ⁡ ∑ a exp ⁡ ( Q ^ π ( s , a ) ) ] \mathbb{E}_{s \sim \mathcal{D},a \sim \mu(\cdot \mid s)} [Q(s,a)] = \mathbb{E}_{s \sim \mathcal{D}} [\log \sum_{a} \exp(\hat{Q}^{\pi}(s,a))] EsD,aμ(s)[Q(s,a)]=EsD[logaexp(Q^π(s,a))]

这样我们就无需显式地优化 μ \mu μ,而是通过对数求和运算来放大Q值的差异。

CQL从惩罚乐观估计的角度出发,巧妙地规避了分布漂移问题,在许多离线RL基准上取得了sota的性能。其优势在于:

  • 算法简单,易于实现,可以与任意Q学习变体结合
  • 超参数( α \alpha α)较少,调参负担小
  • 通过下界Q值来保证策略改进,而不需要显式地约束策略行为
  • 在低数据质量场景下表现稳健

当然,CQL也非万能。其主要局限包括:

  • 在复杂环境中,估计的Q值下界可能过于保守,限制了策略改进的空间
  • 即便估计了状态-动作对的真实Q值,在离线数据稀疏时策略的泛化性能仍然堪忧

未来还需要在这些方面进一步改进CQL框架。但它为离线值估计提供了一种新的视角,为后续算法的发展奠定了基础。

除了CQL,研究者还提出了其他一些惩罚乐观估计的方法。感兴趣的读者可以进一步参考:

  • Conservative Offline Policy Evaluation (CopE) [Voloshin et al., 2021]
  • Critic Regularized Regression (CRR) [Wang et al., 2020]
  • Advantage-Weighted Regression (AWR) [Peng et al., 2019]

5. 基于模型的离线RL

前面介绍的CQL通过正则化Q值来避免过估计。另一种思路是利用环境模型,用模型生成的虚拟数据来辅助值估计。

具体来说,我们先从离线数据集 D \mathcal{D} D 中学习一个环境模型 p ^ ( s ′ ∣ s , a ) \hat{p}(s' \mid s,a) p^(ss,a)。然后用 p ^ \hat{p} p^ 从真实数据中采样的状态出发,滚动生成多条虚拟轨迹 { ( s , a ) i } \{(s,a)_i\} {(s,a)i}。接下来把这些轨迹添加到数据集 D \mathcal{D} D 中,扩充训练集。最后把扩充后的数据集用于Q网络的训练。

这一过程可以总结为:

  • 模型训练: p ^ = arg ⁡ max ⁡ p E D [ log ⁡ p ( s ′ ∣ s , a ) ] \hat{p} = \arg\max_{p} \mathbb{E}_{\mathcal{D}} [\log p(s' \mid s,a)] p^=argmaxpED[logp(ss,a)]
  • 虚拟数据生成: D ~ = { ( s , a ) i ∣ s ∼ D , a ∼ π β ( ⋅ ∣ s ) , s ′ ∼ p ^ ( ⋅ ∣ s , a ) } \tilde{\mathcal{D}} = \{(s,a)_i \mid s \sim \mathcal{D}, a \sim \pi_{\beta}(\cdot \mid s), s' \sim \hat{p}(\cdot \mid s,a)\} D~={(s,a)isD,aπβ(s),sp^(s,a)}
  • 数据扩充: D a u g = D ∪ D ~ \mathcal{D}_{aug} = \mathcal{D} \cup \tilde{\mathcal{D}} Daug=DD~
  • Q值训练: 用 D a u g \mathcal{D}_{aug} Daug 训练Q网络

直观地说,由模型生成的虚拟数据覆盖了真实数据未覆盖的状态-动作空间。如果模型预测得足够准,就能帮助Q网络减少外推误差。此外,这些虚拟数据服从行为策略 π β \pi_{\beta} πβ 的分布,因此也起到了隐式约束 π θ \pi_{\theta} πθ 的作用。

当然,这一切的前提是学到一个准确可靠的环境模型 p ^ \hat{p} p^。一个差的模型反而会引入有偏的估计,误导Q网络的训练。因此基于模型的离线RL对模型质量要求很高,这在复杂环境中往往难以满足。同时即便有了一个完美模型,我们仍然面临探索不足的问题:如果某些重要状态在真实数据中没有被访问到,仅靠模型想象也无济于事。

尽管如此,将规划与学习相结合仍是一个有前景的方向。环境模型可以充分利用静态数据中蕴含的先验知识,加速值估计与策略搜索的过程。未来还需进一步探索更鲁棒的模型学习算法,以及模型不确定性评估与主动采样等技术。

6. 离线RL:一种数据驱动的范式

通过以上讨论,我们看到离线RL的核心挑战在于如何缓解利用静态数据训练时的过估计问题。这需要我们在策略约束和值估计两个层面进行针对性的优化。

数据约束方法从策略出发,通过模仿行为策略来规避分布漂移。而保守估计方法则从值函数出发,通过惩罚过高的外推值来纠偏。二者分别从横向和纵向阻断了Q值过估计的来源。我们还可以用模型生成的数据来加强估计,前提是要有一个高置信的环境模型。

值得一提的是,离线RL并非要完全取代传统的在线学习范式,而是作为一种互补的视角,从数据的角度重新审视强化学习问题。在许多在线交互受限的场景下,离线RL让我们能最大限度地利用先验知识,用更少的样本学到更好的策略。同时离线数据也为在线学习提供了一个有价值的预训练,大大缩短了实际部署中的探索成本。

展望未来,离线RL有望成为一种更为普适的范式,为RL在更广泛领域的应用扫清障碍。这需要我们在算法、工程、应用等多个层面协同发力:

  • 算法层面,需要进一步完善离线值估计、策略优化、模型学习等核心组件,提高其效率与鲁棒性。
  • 工程层面,需要开发成熟的离线RL代码库与工具链,简化算法实现与部署流程。
  • 应用层面,需要在更多领域积累有价值的离线数据集,验证算法性能,并反哺算法迭代。

总之,离线RL代表了一种数据驱动的智能优化思路。它凝结了统计学习和最优控制的精华,为AI在开放环境中的自主决策铺平了道路。让我们拭目以待这一领域的蓬勃发展,期待RL在人类社会的更多应用!

7. 代码实战

下面我们通过几个简单的例子来演示离线RL算法的实现。我们选择了OpenAI Gym中的Pendulum环境作为测试平台。该环境的状态空间为3维,动作空间为1维连续。我们的目标是控制摆球快速、平稳地悬停在竖直位置。

首先,我们定义一些辅助函数来采集离线数据。collect_demo使用专家策略(此处为PID控制器)来采集示教轨迹。collect_random则使用随机策略采集数据,模拟低质量的离线数据集。

import gym
import numpy as np
import torch
from typing import Dict, List, Tuple

def collect_demo(env: gym.Env, buffer_size: int) -> List[Tuple[np.ndarray, float, np.ndarray, bool]]:
    """Use PID controller to collect expert trajectories
    """
    buffer = []
    state = env.reset()
    
    for _ in range(buffer_size):
        setpoint = np.array([1.0, 0.0, 0.0])
        pid = PIDController(setpoint, 0.5, 1.3, 0.1, 1.0, 0.1, 0.1)
        
        while True:
            action = pid(state)
            next_state, reward, done, _ = env.step(action)
            buffer.append((state, action, reward, next_state, done))
            state = next_state
            
            if done:
                state = env.reset()
                break
    
    return buffer

def collect_random(env: gym.Env, buffer_size: int) -> List[Tuple[np.ndarray, float, np.ndarray, bool]]:
    """Collect trajectories using uniform random policy
    """  
    buffer = []
    state = env.reset()
    
    for _ in range(buffer_size):
        action = env.action_space.sample()
        next_state, reward, done, _ = env.step(action)
        buffer.append((state, action, reward, next_state, done))
        state = next_state
        
        if done:
            state = env.reset() 
            
    return buffer

接下来,我们定义CQL算法的核心组件:Q网络和策略网络。Q网络使用一个3层MLP来拟合状态-动作值函数。策略网络也是一个3层MLP,输出动作的均值和方差,服从高斯分布。

class QNetwork(torch.nn.Module):
    def __init__(self, state_dim: int, action_dim: int, hidden_dim: int):
        super().__init__()
        self.layers = torch.nn.Sequential(
            torch.nn.Linear(state_dim + action_dim, hidden_dim),
            torch.nn.ReLU(),
            torch.nn.Linear(hidden_dim, hidden_dim),   
            torch.nn.ReLU(),
            torch.nn.Linear(hidden_dim, 1)
        )
        
    def forward(self, state: torch.Tensor, action: torch.Tensor) -> torch.Tensor:
        x = torch.cat([state, action], dim=1)
        q_value = self.layers(x)
        return torch.squeeze(q_value, -1)
    

class Policy(torch.nn.Module):
    def __init__(self, state_dim: int, action_dim: int, hidden_dim: int):
        super().__init__()
        self.layers = torch.nn.Sequential(
            torch.nn.Linear(state_dim, hidden_dim),
            torch.nn.ReLU(),
            torch.nn.Linear(hidden_dim, hidden_dim),
            torch.nn.ReLU()  
        )
        self.mean_layer = torch.nn.Linear(hidden_dim, action_dim)
        self.log_std_layer = torch.nn.Linear(hidden_dim, action_dim)
        
    def forward(self, state: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
        x = self.layers(state)
        mean = self.mean_layer(x)
        log_std = self.log_std_layer(x)
        log_std = torch.clamp(log_std, -20, 2)  # avoid numerical issues
        return mean, log_std
    
    def sample_action(self, state: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
        mean, log_std = self(state)
        std = torch.exp(log_std)  
        dist = torch.distributions.Normal(mean, std)
        action = dist.rsample()
        log_prob = dist.log_prob(action).sum(dim=-1)
        return action, log_prob

现在我们可以实现完整的CQL算法了。我们将离线数据集 D \mathcal{D} D 随机划分为训练集和验证集。在每个训练步中,从验证集采样一批状态作为 s ∼ D s \sim \mathcal{D} sD,根据当前Q网络计算正则项 E s ∼ D , a ∼ μ ( ⋅ ∣ s ) [ Q ( s , a ) ] \mathbb{E}_{s \sim \mathcal{D},a \sim \mu(\cdot \mid s)} [Q(s,a)] EsD,aμ(s)[Q(s,a)]。然后从训练集采样一批转移 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s) 来计算TD误差。最终基于TD误差、正则项和在 D \mathcal{D} D 上的期望值 E ( s , a ) ∼ D [ Q ( s , a ) ] \mathbb{E}_{(s,a) \sim \mathcal{D}} [Q(s,a)] E(s,a)D[Q(s,a)] 来优化Q网络参数。策略网络则基于更新后的Q网络用 E s ∼ D , a ∼ π θ ( ⋅ ∣ s ) [ Q ^ π ( s , a ) ] \mathbb{E}_{s \sim \mathcal{D},a \sim \pi_{\theta}(\cdot \mid s)} [\hat{Q}^{\pi}(s,a)] EsD,aπθ(s)[Q^π(s,a)] 来优化。

def cql(env: gym.Env, 
        buffer: List[Tuple[np.ndarray, float, np.ndarray, bool]],
        hidden_dim: int,learning_rate: float,
        alpha: float,
        gamma: float,
        tau: float,
        batch_size: int,
        num_epochs: int):
    """Train soft actor-critic algorithm with CQL regularization.
    
    Args:
        env: gym training environment
        buffer: offline dataset
        hidden_dim: hidden dimension of MLP  
        learning_rate: learning rate for Adam optimizer
        alpha: weight for CQL regularizer
        gamma: discount factor
        tau: target network update rate
        batch_size: batch size for sampling 
        num_epochs: number of training epochs
    """
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.shape[0]
    
    q1 = QNetwork(state_dim, action_dim, hidden_dim)
    q2 = QNetwork(state_dim, action_dim, hidden_dim)
    target_q1 = QNetwork(state_dim, action_dim, hidden_dim)
    target_q2 = QNetwork(state_dim, action_dim, hidden_dim)
    target_q1.load_state_dict(q1.state_dict())
    target_q2.load_state_dict(q2.state_dict())
    
    pi = Policy(state_dim, action_dim, hidden_dim)
    
    q1_optimizer = torch.optim.Adam(q1.parameters(), lr=learning_rate)
    q2_optimizer = torch.optim.Adam(q2.parameters(), lr=learning_rate)
    pi_optimizer = torch.optim.Adam(pi.parameters(), lr=learning_rate)
    
    # split buffer into train and val sets  
    val_size = int(0.2 * len(buffer))
    train_buffer, val_buffer = buffer[:-val_size], buffer[-val_size:] 
    
    for epoch in range(num_epochs):
        # sample a batch of transitions from train set 
        state_batch, action_batch, reward_batch, next_state_batch, done_batch = zip(*random.sample(train_buffer, batch_size))
        state_batch = torch.tensor(state_batch, dtype=torch.float32)
        action_batch = torch.tensor(action_batch, dtype=torch.float32).unsqueeze(1)  
        reward_batch = torch.tensor(reward_batch, dtype=torch.float32).unsqueeze(1)
        next_state_batch = torch.tensor(next_state_batch, dtype=torch.float32)
        done_batch = torch.tensor(done_batch, dtype=torch.float32).unsqueeze(1)
        
        # compute conservative q-values
        with torch.no_grad():
            next_action, next_log_pi = pi.sample_action(next_state_batch)
            q1_next = target_q1(next_state_batch, next_action)
            q2_next = target_q2(next_state_batch, next_action)
            min_q_next = torch.min(q1_next, q2_next) - alpha * next_log_pi
            q_target = reward_batch + gamma * (1 - done_batch) * min_q_next
            
        q1_pred = q1(state_batch, action_batch)  
        q1_loss = 0.5 * torch.mean((q1_pred - q_target)**2)
        q2_pred = q2(state_batch, action_batch)  
        q2_loss = 0.5 * torch.mean((q2_pred - q_target)**2)
        
        # compute CQL regularizer
        cql_state_batch = torch.tensor(random.sample(val_buffer, batch_size), dtype=torch.float32) 
        cql_actions = torch.linspace(-2.0, 2.0, steps=51).unsqueeze(0).repeat(batch_size, 1)  
        q1_values = q1(cql_state_batch, cql_actions)
        q2_values = q2(cql_state_batch, cql_actions)
        cql_values = torch.logsumexp(torch.cat([q1_values, q2_values], dim=1), dim=1)
        
        q1_data_values = q1(state_batch, action_batch)
        q2_data_values = q2(state_batch, action_batch)
        
        cql1_loss = torch.mean(cql_values - q1_data_values)
        cql2_loss = torch.mean(cql_values - q2_data_values)
        
        q1_loss += alpha * cql1_loss 
        q2_loss += alpha * cql2_loss
        
        q1_optimizer.zero_grad()
        q1_loss.backward()
        q1_optimizer.step()
        
        q2_optimizer.zero_grad()
        q2_loss.backward()
        q2_optimizer.step()
        
        # update policy
        pi_actions, pi_log_prob = pi.sample_action(state_batch)
        q1_pi = q1(state_batch, pi_actions)  
        q2_pi = q2(state_batch, pi_actions)
        min_q_pi = torch.min(q1_pi, q2_pi)
        
        pi_loss = torch.mean(alpha * pi_log_prob - min_q_pi)
        
        pi_optimizer.zero_grad()
        pi_loss.backward()
        pi_optimizer.step()
        
        # update target networks
        for target_param, param in zip(target_q1.parameters(), q1.parameters()):
            target_param.data.copy_(tau * param + (1 - tau) * target_param)  
        for target_param, param in zip(target_q2.parameters(), q2.parameters()):
            target_param.data.copy_(tau * param + (1 - tau) * target_param)
            
    return pi  # return the trained policy

现在让我们测试一下CQL在Pendulum环境中的表现:

env = gym.make('Pendulum-v1')

expert_buffer = collect_demo(env, buffer_size=5000)
random_buffer = collect_random(env, buffer_size=5000)

# combine expert and random data to create the offline dataset
buffer = expert_buffer + random_buffer

cql_policy = cql(env, buffer, hidden_dim=256, 
                 learning_rate=3e-4, alpha=0.5, gamma=0.99, tau=0.005,
                 batch_size=256, num_epochs=100)

# evaluate the trained policy
for _ in range(10):
    state = env.reset()
    done = False
    total_reward = 0
    
    while not done:
        action, _ = cql_policy.sample_action(torch.from_numpy(state))
        next_state, reward, done, _ = env.step(action.detach().numpy())
        total_reward += reward
        state = next_state
        
    print(f'Total reward: {total_reward:.2f}')

运行这段代码,你会看到CQL学到的策略能以较高的累积奖励控制Pendulum。我们可以通过调节混合数据集中的专家数据比例,来考察CQL在不同离线数据质量下的表现。你还可以尝试修改网络结构、超参数,甚至环境本身,来深入理解CQL算法的特点。

8. 总结与展望

本章我们系统地介绍了离线强化学习的背景、挑战与代表性算法。与传统RL依赖在线交互不同,离线RL旨在从静态数据集中学习最优策略。这为RL在高风险、数据稀缺场景的应用扫清了障碍。

我们首先分析了Q值过估计这一离线RL的核心难题,即如何缓解策略优化时的分布漂移。针对这一问题,研究者分别从数据约束和保守估计两个角度提出了解决方案。前者通过模仿行为策略来限制策略搜索空间,后者则通过惩罚过高的Q值来纠正乐观估计。我们分别以BCQ和CQL为例,详细讲解了这两类方法的动机和实现。

此外,我们也探讨了基于模型的离线RL,即先从数据中学习一个环境模型,再用模型产生的虚拟轨迹来辅助Q函数训练。尽管对模型质量和探索效率提出了更高要求,但这一思路为离线数据的充分利用提供了新的视角。

纵观全章,我们强调了离线RL作为一种数据驱动范式的优势与潜力。通过从人类专家、历史系统等渠道收集知识,它让我们得以快速、低成本地构建高性能智能体。未来,随着离线RL算法的不断完善,以及应用领域的持续拓展,相信它必将在自动驾驶、智能家居、工业控制等诸多领域发挥重要作用。

与此同时,我们也要看到离线RL仍面临不少理论和实践挑战:

  • 在高维、稀疏数据上如何更高效地进行离线值估计与策略优化?
  • 如何评估和提高离线数据集的质量,尤其是在reward含噪、分布不平衡时?
  • 如何设计更鲁棒、更可解释的模型学习算法,降低泛化风险?
  • 如何权衡离线训练和在线微调,实现策略的安全部署与持续进化?

这需要RL、统计学、因果推断、泛化理论等多个领域的交叉创新。让我们携手探索,共同推动离线RL的发展,用人工智能点亮未来!

参考文献

  1. Levine S, Kumar A, Tucker G, et al. Offline reinforcement learning: Tutorial, review, and perspectives on open problems. arXiv preprint arXiv:2005.01643, 2020.

  2. Fujimoto S, Meger D, Precup D. Off-policy deep reinforcement learning without exploration. In International Conference on Machine Learning, 2019.

  3. Kumar A, Zhou A, Tucker G, et al. Conservative q-learning for offline reinforcement learning. arXiv preprint arXiv:2006.04779, 2020.

  4. Wu Y, Tucker G, Nachum O. Behavior regularized offline reinforcement learning. arXiv preprint arXiv:1911.11361, 2019.

  5. Kidambi R, Rajeswaran A, Netrapalli P, et al. Morel: Model-based offline reinforcement learning. arXiv preprint arXiv:2005.05951, 2020.

Q&A

我用一个简单的例子来说明离线强化学习的应用场景和优势。

假设我们要开发一个智能推荐系统,为用户推荐感兴趣的商品。传统的推荐算法主要基于用户的历史交互数据(如点击、购买记录)来挖掘用户兴趣,然后匹配相似商品。这种方法虽然简单直观,但也存在一些局限性:

  • 只利用了用户的历史数据,难以适应用户兴趣的变化
  • 无法主动探索用户对新品类、新特性的喜好
  • 容易陷入"马太效应",加剧推荐的同质化

如果我们把推荐问题建模为一个强化学习任务,就可以在一定程度上克服这些局限。具体来说,我们可以把:

  • 用户的个人信息和历史行为作为状态
  • 向用户推荐的商品作为动作
  • 用户的反馈(如点击、购买、评分)作为奖励

这样一来,RL智能体的目标就是学习一个推荐策略,使得累积奖励(用户满意度)最大化。通过与用户的长期交互,智能体可以逐步优化推荐策略,及时捕捉用户兴趣的变化,并主动探索新的可能性。

然而,在实际的推荐系统中,我们很难让智能体与真实用户频繁交互。一方面,过于频繁的推荐会影响用户体验;另一方面,将未经验证的策略直接应用于线上系统是有风险的,可能导致用户流失、收入下降等不良后果。

离线RL为这一问题提供了一个理想的解决方案。我们可以利用系统积累的海量历史交互数据,从中学习用户兴趣分布和行为模式,再利用这些先验知识指导推荐策略的离线训练。具体流程如下:

  1. 收集用户与推荐系统的历史交互日志,包括用户特征、推荐商品、用户反馈等
  2. 对日志数据进行预处理,提取状态、动作、奖励,并划分为训练集和测试集
  3. 用离线RL算法在训练集上学习推荐策略,并在测试集上评估性能
  4. 选择性能最优的策略在线上系统中小流量测试,并根据反馈进一步调优
  5. 逐步扩大优化后策略的应用范围,持续监测和改进

可以看到,离线RL让我们能在不影响线上系统的情况下,充分利用历史数据来优化推荐策略。它不仅降低了试错成本,也提高了策略迭代的效率。一些研究表明,基于离线RL的推荐算法能显著提升用户的点击率、转化率和满意度,为企业创造更多商业价值。

当然,要让离线RL在推荐系统中真正发挥作用,还需要解决一些问题:

  • 如何保证离线数据的覆盖性和质量,尽可能减少分布漂移?
  • 如何权衡探索和利用,在满足用户当前兴趣的同时,发掘用户的潜在需求?
  • 如何引入因果推断,学习推荐行为对用户反馈的因果影响,而不是简单的相关性?
  • 如何在离线评估和在线测试间建立有效映射,减少策略部署的风险?

这需要算法、工程、产品等多团队的通力合作。但离线RL为构建更加智能、高效、以用户为中心的推荐系统铺平了道路。相信未来它将与其他机器学习方法一起,为智能推荐的发展注入新的活力。

总结一下,离线强化学习的主要优势包括:

  • 充分利用历史数据,减少在线试错,降低探索成本
  • 避免对真实环境的干扰,提高优化效率和安全性
  • 自动学习复杂的用户行为模式,捕捉用户兴趣变化
  • 通过主动探索,发现新的推荐可能,提升用户满意度

因此,当我们拥有大量高质量的离线数据,且在线交互代价较高时,离线RL就是一个不错的选择。它为传统的监督学习和在线学习方法提供了有益补充,让我们能更从容、更智能地决策。

希望这个例子能帮你理解离线RL的应用场景和优势。我们只是以推荐系统为例,展示了它的一种可能性。事实上,离线RL还可以用于广告投放、搜索引擎、智能助理等多个领域。随着算法的进步和数据的积累,相信它必将在更多场景中大放异彩,为人类生活带来更多便利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值