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

实用深度强化学习实现技术

强化学习(RL)是一种通过智能体与环境交互来学习最优决策的机器学习范式。而深度强化学习(DRL)则将深度学习技术引入RL领域,利用深度神经网络强大的函数拟合能力来处理高维观察空间,取得了显著的成功。本章我们将重点介绍一种经典的DRL算法:Q-Learning及其变体,探讨其背后的原理、实现技巧以及代表性应用。

1. 价值函数与Q-Learning原理

在介绍Q-Learning之前,我们先来回顾一下强化学习中的一些基本概念。

1.1 马尔可夫决策过程

马尔可夫决策过程(MDP)提供了一个标准的RL问题数学框架。一个MDP由一个五元组 M = ⟨ S , A , P , R , γ ⟩ \mathcal{M}=\langle\mathcal{S},\mathcal{A},\mathcal{P},\mathcal{R},\gamma\rangle M=S,A,P,R,γ 定义,其中:

  • 状态空间 S \mathcal{S} S:表示智能体所处的环境状态集合。
  • 动作空间 A \mathcal{A} A:表示智能体可采取的动作集合。
  • 转移概率 P \mathcal{P} P: P ( s ′ ∣ s , a ) \mathcal{P}(s'|s,a) P(ss,a) 表示在状态 s s s 下执行动作 a a a 后转移到状态 s ′ s' s 的概率。
  • 奖励函数 R \mathcal{R} R: R ( s , a ) \mathcal{R}(s,a) R(s,a) 表示在状态 s s s 下执行动作 a a a 后获得的即时奖励。
  • 折扣因子 γ ∈ [ 0 , 1 ] \gamma\in[0,1] γ[0,1]:表示未来奖励的折现程度,用于平衡即时奖励和长期奖励。

MDP的目标是寻找一个最优策略 π ∗ : S → A \pi^*:\mathcal{S}\rightarrow\mathcal{A} π:SA,使得智能体遵循该策略能获得最大的累积奖励:
π ∗ = arg ⁡ max ⁡ π E π [ ∑ t = 0 ∞ γ t r t ] \pi^* = \arg\max_{\pi} \mathbb{E}_{\pi}[\sum_{t=0}^{\infty} \gamma^t r_t] π=argπmaxEπ[t=0γtrt]
其中 r t r_t rt 表示在时刻 t t t 获得的奖励。

1.2 价值函数

为了评估一个状态或者一个状态-动作对的好坏,我们引入了价值函数的概念。在给定策略 π \pi π 的情况下,有两种常见的价值函数定义:

  • 状态价值函数 V π ( s ) V^{\pi}(s) Vπ(s): 表示从状态 s s s 开始,遵循策略 π \pi π 能获得的期望累积奖励。
    V π ( s ) = E π [ ∑ k = 0 ∞ γ k r t + k ∣ s t = s ] V^{\pi}(s)=\mathbb{E}_{\pi}[\sum_{k=0}^{\infty}\gamma^k r_{t+k}|s_t=s] Vπ(s)=Eπ[k=0γkrt+kst=s]

  • 动作价值函数 Q π ( s , a ) Q^{\pi}(s,a) Qπ(s,a): 表示在状态 s s s 下采取动作 a a a,并继续遵循策略 π \pi π 能获得的期望累积奖励。
    Q π ( s , a ) = E π [ ∑ k = 0 ∞ γ k r t + k ∣ s t = s , a t = a ] Q^{\pi}(s,a)=\mathbb{E}_{\pi}[\sum_{k=0}^{\infty}\gamma^k r_{t+k}|s_t=s,a_t=a] Qπ(s,a)=Eπ[k=0γkrt+kst=s,at=a]

两个价值函数之间满足如下关系:
V π ( s ) = ∑ a ∈ A π ( a ∣ s ) Q π ( s , a ) V^{\pi}(s) = \sum_{a\in\mathcal{A}} \pi(a|s) Q^{\pi}(s,a) Vπ(s)=aAπ(as)Qπ(s,a)

对于最优策略 π ∗ \pi^* π,对应的状态价值函数 V ∗ V^* V 和动作价值函数 Q ∗ Q^* Q 满足贝尔曼最优方程:
V ∗ ( s ) = max ⁡ a ∈ A Q ∗ ( s , a ) Q ∗ ( s , a ) = R ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) V ∗ ( s ′ ) = R ( s , a ) + γ ∑ s ′ ∈ S P ( s ′ ∣ s , a ) max ⁡ a ′ ∈ A Q ∗ ( s ′ , a ′ ) \begin{aligned} V^*(s) &= \max_{a\in\mathcal{A}} Q^*(s,a) \\ Q^*(s,a) &= \mathcal{R}(s,a) + \gamma \sum_{s'\in\mathcal{S}} \mathcal{P}(s'|s,a) V^*(s')\\ &= \mathcal{R}(s,a) + \gamma \sum_{s'\in\mathcal{S}} \mathcal{P}(s'|s,a) \max_{a'\in\mathcal{A}} Q^*(s',a') \end{aligned} V(s)Q(s,a)=aAmaxQ(s,a)=R(s,a)+γsSP(ss,a)V(s)=R(s,a)+γsSP(ss,a)aAmaxQ(s,a)

这两个方程揭示了最优价值函数的递归结构,为我们后续推导Q-Learning算法奠定了理论基础。如果我们能准确估计出 Q ∗ ( s , a ) Q^*(s,a) Q(s,a),那么最优策略可以通过贪心法(greedy)直接得到:
π ∗ ( s ) = arg ⁡ max ⁡ a ∈ A Q ∗ ( s , a ) \pi^*(s) = \arg\max_{a\in\mathcal{A}} Q^*(s,a) π(s)=argaAmaxQ(s,a)

1.3 Q-Learning算法

Q-Learning是一种经典的值迭代(value iteration)算法,它直接估计最优动作价值函数 Q ∗ ( s , a ) Q^*(s,a) Q(s,a),然后根据 Q ∗ Q^* Q 得到最优策略。

传统的Q-Learning使用一个表格(Q-table)来存储每个状态-动作对的Q值估计。初始时 Q ( s , a ) Q(s,a) Q(s,a) 可以被随机初始化。在与环境交互的每个时间步,智能体根据当前的Q值贪心地选择动作 a t a_t at,观察到奖励 r t r_t rt 和下一个状态 s t + 1 s_{t+1} st+1,然后通过如下的时序差分(TD)更新来优化Q值:
Q ( s t , a t ) ← Q ( s t , a t ) + α [ r t + γ max ⁡ a Q ( s t + 1 , a ) − Q ( s t , a t ) ] Q(s_t,a_t) \leftarrow Q(s_t,a_t) + \alpha[r_t + \gamma \max_{a} Q(s_{t+1},a) - Q(s_t,a_t)] Q(st,at)Q(st,at)+α[rt+γamaxQ(st+1,a)Q(st,at)]

其中 α ∈ ( 0 , 1 ] \alpha\in(0,1] α(0,1] 为学习率。这个更新过程可以被解释为:用 r t + γ max ⁡ a Q ( s t + 1 , a ) r_t + \gamma \max_{a} Q(s_{t+1},a) rt+γmaxaQ(st+1,a) 作为 Q ( s t , a t ) Q(s_t,a_t) Q(st,at) 的目标值进行监督学习,使得 Q ( s t , a t ) Q(s_t,a_t) Q(st,at) 逼近它的真实值 Q ∗ ( s t , a t ) Q^*(s_t,a_t) Q(st,at)。可以证明,在适当的条件下,Q-Learning最终会收敛到 Q ∗ Q^* Q

然而,当状态空间和动作空间很大时,用表格存储Q值是不现实的。这时,我们可以用一个参数化的函数 Q θ ( s , a ) Q_{\theta}(s,a) Qθ(s,a) 来近似Q值,其中 θ \theta θ 为函数的参数。近年来,随着深度学习的兴起,使用深度神经网络(DNN)作为Q函数的近似器得到了广泛应用,这就是著名的DQN算法。

2. 深度Q网络(DQN)

深度Q网络(DQN)由[Mnih et al., 2013]提出,是将Q-Learning与深度学习结合的代表性算法。它使用一个卷积神经网络(CNN)来拟合Q函数,并引入了两个重要的技巧:经验回放(experience replay)和目标网络(target network),极大地提升了训练的稳定性。

2.1 Q网络结构

在DQN中,Q函数 Q θ ( s , a ) Q_{\theta}(s,a) Qθ(s,a) 被参数化为一个CNN,它以状态 s s s (通常是原始像素)为输入,输出各个动作 a a a 对应的Q值。Q网络可以表示为:
Q θ ( s , ⋅ ) = f θ ( s ) ∈ R ∣ A ∣ Q_{\theta}(s,\cdot) = f_{\theta}(s) \in \mathbb{R}^{|\mathcal{A}|} Qθ(s,)=fθ(s)RA

其中 f θ f_{\theta} fθ 为CNN的前向传播函数。在实践中,Q网络的具体结构需要根据任务的特点来设计。

Q网络的训练目标是最小化TD误差,即让 Q θ ( s , a ) Q_{\theta}(s,a) Qθ(s,a) 尽可能逼近贝尔曼最优方程的右侧:
L ( θ ) = E ( s , a , r , s ′ ) ∼ D [ ( r + γ max ⁡ a ′ Q θ − ( s ′ , a ′ ) − Q θ ( s , a ) ) 2 ] \mathcal{L}(\theta) = \mathbb{E}_{(s,a,r,s')\sim \mathcal{D}} \left[ (r + \gamma \max_{a'} Q_{\theta^-}(s',a') - Q_{\theta}(s,a))^2 \right] L(θ)=E(s,a,r,s)D[(r+γamaxQθ(s,a)Qθ(s,a))2]

其中 D \mathcal{D} D 为经验回放池, θ − \theta^- θ 为目标网络的参数。接下来我们详细介绍这两个技巧。

2.2 经验回放(Experience Replay)

在Q-Learning中,我们通常假设训练数据 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s) 是独立同分布的。但在实际的在线学习中,智能体往往是连续与环境交互的,产生一个状态序列 { s 1 , s 2 , ⋯ , s t , ⋯ } \{s_1,s_2,\cdots,s_t,\cdots\} {s1,s2,,st,},相邻的状态之间具有很强的相关性。如果直接用序列数据训练神经网络,会导致训练过程不稳定,难以收敛。

经验回放(ER)通过构建一个 经验回放池 D \mathcal{D} D 来打破数据之间的相关性。具体来说,在每个时间步 t t t,智能体将当前的转移样本 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1) 存入 D \mathcal{D} D。训练时,从 D \mathcal{D} D 中随机采样一个批量(batch)的样本来更新Q网络参数。这个过程类似于监督学习,可以使用标准的优化算法如SGD、Adam等。

ER的优势包括:

  1. 打破了样本之间的相关性,使训练更稳定。
  2. 样本可以被多次使用,提高数据利用效率。
  3. 可以使用off-policy数据,如专家示范数据、历史版本策略的数据等。

在实现中,经验回放池 D \mathcal{D} D 通常被实现为一个固定大小的循环队列。当 D \mathcal{D} D 被填满时,新的样本会覆盖最老的样本。一个常见的技巧是在初始探索阶段,先不更新策略,只往 D \mathcal{D} D 中填充数据,直到收集到足够的样本才开始训练。

2.3 目标网络(Target Network)

Q-Learning本质上是一个回归问题,即用 r + γ max ⁡ a ′ Q θ ( s ′ , a ′ ) r + \gamma \max_{a'} Q_{\theta}(s',a') r+γmaxaQθ(s,a) 作为Q值 Q θ ( s , a ) Q_{\theta}(s,a) Qθ(s,a) 的目标值。然而在DQN中,目标值本身也在随着参数 θ \theta θ 的更新而变化,这就引入了一个不稳定的"移动目标"问题。

为了缓解这个问题,DQN引入了一个目标网络 Q θ − Q_{\theta^-} Qθ,它的结构与Q网络相同,但参数更新频率较低。在计算TD目标值时使用目标网络的输出:
y = r + γ max ⁡ a ′ Q θ − ( s ′ , a ′ ) y = r + \gamma \max_{a'} Q_{\theta^-}(s',a') y=r+γamaxQθ(s,a)

这使得目标值在一段时间内保持不变,类似于监督学习。目标网络的参数 θ − \theta^- θ 每隔一定的时间步(如1000步)从Q网络复制一次:
θ − ← θ \theta^- \leftarrow \theta θθ

或者采用软更新的方式:
θ − ← τ θ + ( 1 − τ ) θ − , τ ≪ 1 \theta^- \leftarrow \tau \theta + (1-\tau) \theta^-, \quad \tau \ll 1 θτθ+(1τ)θ,τ1

目标网络的引入极大地提升了DQN的性能,成为了许多DRL算法的标准配置。

2.4 DQN算法

结合经验回放和目标网络,DQN的完整算法描述如下:


算法 DQN

初始化Q网络参数 θ \theta θ, 目标网络参数 θ − ← θ \theta^- \leftarrow \theta θθ
初始化经验回放池 D \mathcal{D} D
for episode = 1 to M do:
初始化初始状态 s 1 s_1 s1
for t = 1 to T do:
根据 ϵ -greedy \epsilon\text{-greedy} ϵ-greedy 策略选择动作 a t = { arg ⁡ max ⁡ a Q θ ( s t , a ) , with prob.  1 − ϵ random action , with prob.  ϵ a_t=\begin{cases} \arg\max_a Q_{\theta}(s_t,a), & \text{with prob. } 1-\epsilon \\ \text{random action}, & \text{with prob. } \epsilon \end{cases} at={argmaxaQθ(st,a),random action,with prob. 1ϵwith prob. ϵ
执行动作 a t a_t at,观察奖励 r t r_t rt 和下一状态 s t + 1 s_{t+1} st+1
将样本 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1) 存入 D \mathcal{D} D
D \mathcal{D} D 中随机采样一个批量的样本 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s)
计算TD目标: y = { r , if  s ′ is terminal r + γ max ⁡ a ′ Q θ − ( s ′ , a ′ ) , otherwise y=\begin{cases} r, & \text{if } s' \text{ is terminal}\\ r + \gamma \max_{a'} Q_{\theta^-}(s',a'), & \text{otherwise} \end{cases} y={r,r+γmaxaQθ(s,a),if s is terminalotherwise
最小化TD误差: L ( θ ) = 1 N ∑ ( y − Q θ ( s , a ) ) 2 \mathcal{L}(\theta)=\frac{1}{N}\sum(y-Q_{\theta}(s,a))^2 L(θ)=N1(yQθ(s,a))2
每隔C步更新目标网络: θ − ← θ \theta^- \leftarrow \theta θθ
end for
end for


其中,超参数包括:

  • M M M: 训练的episode数
  • T T T: 每个episode的最大时间步
  • ϵ \epsilon ϵ: ϵ -greedy \epsilon\text{-greedy} ϵ-greedy 探索中选择随机动作的概率
  • γ \gamma γ: 奖励折扣因子
  • C C C: 目标网络更新频率
  • N N N: 批量大小(batch size)

DQN在Atari游戏上取得了超越人类的表现,证明了端到端深度强化学习的有效性,掀起了DRL研究的高潮。此后,各种DQN的改进变体不断涌现,极大地推动了DRL技术的发展。

3. DQN变体与改进

尽管DQN取得了巨大成功,但它仍然存在一些问题,如过估计(overestimation)、采样效率低、探索不足等。研究者针对这些问题提出了许多改进方法。本节我们介绍几个代表性的DQN变体算法。

3.1 Double DQN

Q-Learning算法(包括DQN)存在一个固有的过估计问题。在计算TD目标值时,我们用 max ⁡ a ′ Q ( s ′ , a ′ ) \max_{a'} Q(s',a') maxaQ(s,a) 来估计 max ⁡ a ′ q ∗ ( s ′ , a ′ ) \max_{a'} q_*(s',a') maxaq(s,a) ( q ∗ q_* q 表示真实的Q值)。然而,由于对 Q ( s ′ , a ′ ) Q(s',a') Q(s,a) 估计的误差,这个最大化操作会导致Q值的过估计,使得学到的策略次优。

Double DQN(DDQN) [Van Hasselt et al., 2016]通过解耦动作选择和动作评估来缓解过估计问题。具体来说,DDQN维护两个Q网络 Q θ 1 Q_{\theta_1} Qθ1 Q θ 2 Q_{\theta_2} Qθ2,它们独立学习、互为目标网络。在计算TD目标时,一个网络负责选择动作,另一个网络负责评估动作的值:
a ∗ = arg ⁡ max ⁡ a ′ Q θ 1 ( s ′ , a ′ ) y = r + γ Q θ 2 ( s ′ , a ∗ ) \begin{aligned} a^* &= \arg\max_{a'} Q_{\theta_1}(s',a') \\ y &= r + \gamma Q_{\theta_2}(s',a^*) \end{aligned} ay=argamaxQθ1(s,a)=r+γQθ2(s,a)

这里动作选择和评估使用了不同的Q网络,从而减少了过估计。DDQN在Atari游戏上的表现优于DQN,成为了DRL领域的标配技术之一。

3.2 Prioritized Experience Replay

传统的经验回放对回放池 D \mathcal{D} D 中的样本一视同仁,uniformly随机采样。然而直觉上,有些样本可能比其他样本更"重要",值得被多次采样学习。

Prioritized Experience Replay(PER)[Schaul et al., 2016]基于这个想法,根据样本的TD误差来衡量其重要性,对 D \mathcal{D} D 中的样本赋予不同的采样概率。给定一个样本 ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s), 它的优先级定义为:
p = ∣ δ ∣ + ϵ p = |\delta| + \epsilon p=δ+ϵ

其中 δ = r + γ max ⁡ a ′ Q θ − ( s ′ , a ′ ) − Q θ ( s , a ) \delta=r+\gamma \max_{a'}Q_{\theta^-}(s',a')-Q_{\theta}(s,a) δ=r+γmaxaQθ(s,a)Qθ(s,a) 为TD误差, ϵ \epsilon ϵ 是一个小的正常数,以确保每个样本都有被采样的可能。采样概率正比于优先级:
P ( i ) = p i α ∑ j p j α P(i) = \frac{p_i^{\alpha}}{\sum_j p_j^{\alpha}} P(i)=jpjαpiα

其中 α \alpha α 控制了优先级的强度。当 α = 0 \alpha=0 α=0 时退化为uniform采样。

为了校正优先级采样引入的偏差,PER在更新Q网络时对不同样本引入了重要性权重(importance weight):
w i = ( 1 N ⋅ 1 P ( i ) ) β w_i = (\frac{1}{N} \cdot \frac{1}{P(i)})^{\beta} wi=(N1P(i)1)β

最小化加权的TD误差:
L ( θ ) = 1 N ∑ w i ⋅ ( y i − Q θ ( s i , a i ) ) 2 \mathcal{L}(\theta) = \frac{1}{N}\sum w_i \cdot (y_i-Q_{\theta}(s_i,a_i))^2 L(θ)=N1wi(yiQθ(si,ai))2

其中 β \beta β 控制了重要性权重的强度,从初始值 β 0 \beta_0 β0 线性增加到1。

PER能更有效地利用样本,在许多游戏上取得了sota的表现。此外它是一个通用的技术,可以和其他DRL算法结合,如DDQN、Dueling DQN等。

3.3 Dueling DQN

在许多RL任务中,状态价值 V ( s ) V(s) V(s) 和状态-动作价值 Q ( s , a ) Q(s,a) Q(s,a) 都包含有用的信息。标准的DQN只学习 Q ( s , a ) Q(s,a) Q(s,a),忽略了 V ( s ) V(s) V(s)

Dueling DQN[Wang et al., 2016]通过将Q网络分解为两个流来显式地学习 V ( s ) V(s) V(s) A ( s , a ) A(s,a) A(s,a):
Q ( s , a ) = V ( s ) + A ( s , a ) Q(s,a) = V(s) + A(s,a) Q(s,a)=V(s)+A(s,a)

其中 V ( s ) V(s) V(s) 表示状态价值, A ( s , a ) = Q ( s , a ) − V ( s ) A(s,a)=Q(s,a)-V(s) A(s,a)=Q(s,a)V(s) 称为优势函数(advantage function),表示动作 a a a 相对于状态平均的优劣程度。网络包含一个共享的卷积层,然后分为两个独立的全连接层,分别输出 V ( s ) V(s) V(s) A ( s , a ) A(s,a) A(s,a),最后组合成Q值。

实践中为了保持恒等性,使用如下的组合方式:
Q ( s , a ) = V ( s ) + ( A ( s , a ) − max ⁡ a ′ A ( s , a ′ ) ) Q(s,a) = V(s) + (A(s,a)-\max_{a'} A(s,a')) Q(s,a)=V(s)+(A(s,a)amaxA(s,a))

Dueling DQN在Atari游戏中有超过一半的任务上取得了优于DQN的表现。它也可以跟其他技术结合,如DDQN、PER等。

3.4 其他改进

除了上述经典变体,DQN还有许多其他的改进方法,如:

  • Multi-step learning: 使用多步回报 ∑ k = 0 n γ k r t + k + γ n max ⁡ a ′ Q θ − ( s t + n , a ′ ) \sum_{k=0}^{n} \gamma^k r_{t+k} + \gamma^n \max_{a'}Q_{\theta^-}(s_{t+n},a') k=0nγkrt+k+γnmaxaQθ(st+n,a) 作为目标值,权衡bias和variance。
  • Distributional DQN: 学习值分布 Z ( s , a ) Z(s,a) Z(s,a) 而不是期望 Q ( s , a ) Q(s,a) Q(s,a),捕捉环境的随机性。
  • Noisy DQN: 在Q网络中加入参数噪声,自适应地调节探索。
  • Rainbow: 将DDQN、PER、Dueling、Multi-step、Distributional、Noisy结合在统一的框架下。

这些改进极大地提升了Q学习的性能,推动了DRL在游戏、机器人、自然语言等领域的应用。但Q学习仍然有其局限性,如采样效率不高、难以适用于连续动作空间等。为此,研究者探索了策略梯度、actor-critic等其他类型的DRL算法。

4. 从Atari到AlphaGo

DQN在Atari游戏中的成功彰显了深度强化学习的威力,掀起了将DRL应用于各种领域的浪潮。本节我们简要回顾几个里程碑式的工作。

4.1 Atari Games

Atari 2600是一款经典的游戏机,包含数十个游戏。[Bellemare et al., 2013]基于这些游戏构建了Arcade Learning Environment(ALE),将其作为RL算法的测试平台。玩家通过原始像素感知游戏状态,用操纵杆控制角色的行动。

DQN首次在ALE上实现了超越人类的表现[Mnih et al., 2015],之后各种DQN改进算法纷纷在ALE上刷新纪录[Hessel et al., 2018]。Atari游戏具有复杂的状态空间和稀疏的奖励信号,对探索提出了挑战,因此成为了DRL算法的标准测试任务。

4.2 AlphaGo

围棋是一种古老的棋类游戏,状态空间和动作空间极其庞大,被认为是人工智能的巅峰挑战。DeepMind提出的AlphaGo系列算法[Silver et al., 2016, 2017, 2018]将深度学习与蒙特卡洛树搜索相结合,在围棋上成功击败了人类顶尖高手。

AlphaGo使用深度卷积网络来逼近策略网络 p σ ( a ∣ s ) p_{\sigma}(a|s) pσ(as) 和价值网络 v θ ( s ) v_{\theta}(s) vθ(s),并用它们指导树搜索,不断完善估值和走法选择。策略网络通过监督学习人类专家棋谱和自对弈数据来训练,价值网络则类似DQN用TD learning来训练。

AlphaGo证明了DRL可以在超大规模问题上取得成功,揭示了深度学习、强化学习、树搜索、领域知识相结合的威力,被誉为人工智能发展的里程碑。

小结

我们深入探讨了DRL中的Q学习方法,重点介绍了DQN及其变体。Q学习通过值迭代来逼近最优Q函数,再用Q函数生成最优策略,是一种简洁有效的RL算法。DQN利用深度卷积网络来泛化Q学习,并引入了经验回放、目标网络等技术来提高训练稳定性。此后,各种改进技术如Double DQN、Prioritized replay、Dueling network等进一步提升了DQN的性能。

5. Q-Learning在连续控制中的应用

之前我们主要讨论了Q-Learning在离散动作空间中的应用,如Atari游戏。但在实际应用如机器人控制中,动作空间往往是连续的。为了将Q-Learning扩展到连续动作空间,一个简单的方法是将动作空间离散化。然而这会带来维度灾难,限制了策略的表达能力。

QT-Opt[Kalashnikov et al., 2018]提出一种适用于连续动作空间的Q-Learning变体,并将其应用于机器人抓取任务。QT-Opt用一个Q网络 Q θ ( s , a ) Q_{\theta}(s,a) Qθ(s,a) 来拟合Q值,其中状态 s s s 为机器人摄像头拍摄的RGB图像,动作 a a a 为抓取位置在相机坐标系下的平移和转动参数。

5.1 任务建模

QT-Opt将机器人抓取建模为一个MDP:

  • 状态 s s s: 俯视RGB相机图像,不包括深度信息
  • 动作 a a a: 末端执行器的4-DoF变换(3个平移自由度+1个转动自由度)
  • 奖励 r r r: 如果目标物体被成功抓起,给予+1的稀疏奖励,否则为0。成功的判定通过检测物体是否离开桌面来自动完成。
  • 折扣因子 γ \gamma γ: 令为1,只关注最终的抓取效果

在这个MDP中,状态与真实环境存在偏差(没有深度信息),奖励函数也是稀疏的。这对Q-Learning算法提出了挑战。

5.2 连续动作空间优化

QT-Opt使用Cross-Entropy Method(CEM)来处理连续动作空间上的优化问题。CEM是一种基于采样的黑箱优化算法:

  1. 从一个高斯分布 N ( μ , Σ ) \mathcal{N}(\mu,\Sigma) N(μ,Σ) 中采样N个动作 { a i } i = 1 N \{a_i\}_{i=1}^N {ai}i=1N
  2. 用Q网络评估这些动作的Q值 { Q θ ( s , a i ) } i = 1 N \{Q_{\theta}(s,a_i)\}_{i=1}^N {Qθ(s,ai)}i=1N
  3. 选出Q值最高的前K个动作,用它们更新高斯分布的均值和协方差
  4. 重复步骤1-3直到高斯分布收敛

CEM通过迭代优化一个采样分布来逼近最优动作:
a ∗ ( s ) = arg ⁡ max ⁡ a Q θ ( s , a ) ≈ arg ⁡ max ⁡ a N ( μ ∗ , Σ ∗ ∣ μ ∗ , Σ ∗ ) a^*(s) = \arg\max_a Q_{\theta}(s,a) \approx \arg\max_a \mathcal{N}(\mu^*,\Sigma^*|\mu^*,\Sigma^*) a(s)=argamaxQθ(s,a)argamaxN(μ,Σμ,Σ)

相比随机采样,CEM能更有效地集中在高Q值区域进行采样。QT-Opt将CEM嵌入到Q-Learning的训练循环中,交替地更新Q网络和最优化动作分布。

5.3 海量离线数据

QT-Opt充分利用了仿真和真实环境中收集的海量离线数据。这些数据来自之前的各种抓取策略,质量参差不齐。QT-Opt将所有数据汇总到一个巨大的离线经验池中,供Q网络训练。

离线数据的优势在于:

  1. 提高样本利用效率,减少真实环境交互
  2. 汇聚多种策略探索出的数据,增加样本多样性
  3. 在仿真环境中预训练,减少真实环境的磨合成本

QT-Opt表明,即使在没有在线探索的情况下,也可以从大规模离线数据中学到一个鲁棒的抓取策略。

5.4 实验结果

QT-Opt在7个Kuka机械臂上进行了抓取实验,总计收集了58万次抓取样本,其中48万次来自仿真,10万次来自真实机器人。所有样本被汇总到一个离线经验池中用于训练。

测试时,QT-Opt在48个陌生物体上的平均成功率达到96%,超过了人类专家的表现。大规模的仿真和真实数据结合DRL算法,使得端到端地从视觉信息中学习抓取控制策略成为可能。QT-Opt为将DRL应用于真实世界机器人控制任务提供了新的思路。

小结

QT-Opt展示了Q-Learning在连续动作空间上的扩展及其在机器人抓取任务上的应用。为了处理连续动作空间,QT-Opt引入了交叉熵方法(CEM)来优化动作分布。此外,QT-Opt还利用了大规模的仿真和真实数据,通过离线学习来提高样本效率。

基于视觉的机器人学习控制是一个极具挑战性的任务,需要克服视觉和控制的双重难题。QT-Opt的成功得益于:

  1. 用深度神经网络将高维视觉信息映射到紧凑的状态表征
  2. 通过离线学习充分利用多源异构的历史数据
  3. 有效的连续动作空间优化算法如CEM
  4. 大规模分布式训练系统和高效的数据管理

展望未来,视觉驱动的机器人学习控制仍有许多亟待解决的问题:

  • 提高泛化能力,使策略能适应动态变化的环境
  • 实现长期规划能力,求解多阶段复杂任务
  • 确保安全性,避免意外碰撞和损坏
  • 引入语言指令,实现人机交互和协作
  • 提高策略的可解释性,便于调试和优化

6. 代码实战

本章我们通过几个具体的代码示例,演示如何用Python和PyTorch实现DQN及其变体算法。

6.1 DQN

我们首先实现一个基本的DQN算法,并将其应用于CartPole游戏环境。

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import gym# Q网络
class QNetwork(nn.Module):def __init__(self, state_dim, action_dim):super(QNetwork, self).__init__()self.fc1 = nn.Linear(state_dim, 64)self.fc2 = nn.Linear(64, 64)self.fc3 = nn.Linear(64, action_dim)def forward(self, x):x = torch.relu(self.fc1(x))x = torch.relu(self.fc2(x))x = self.fc3(x)return x# DQN智能体
class DQNAgent:def __init__(self, state_dim, action_dim):self.state_dim = state_dimself.action_dim = action_dimself.gamma = 0.95self.epsilon = 1.0self.epsilon_decay = 0.995self.epsilon_min = 0.01self.batch_size = 64self.train_start = 1000self.memory = []self.model = QNetwork(state_dim, action_dim)self.target_model = QNetwork(state_dim, action_dim)self.optimizer = optim.Adam(self.model.parameters())self.loss_fn = nn.MSELoss()def get_action(self, state):if np.random.rand() <= self.epsilon:return np.random.randint(self.action_dim)else:state = torch.tensor(state, dtype=torch.float32)q_value = self.model(state)action = q_value.argmax().item()return actiondef train_model(self):if len(self.memory) < self.train_start:returnbatch_data = np.random.choice(self.memory, self.batch_size, replace=False)states = torch.tensor(np.array([data[0] for data in batch_data]), dtype=torch.float32)actions = torch.tensor(np.array([data[1] for data in batch_data]), dtype=torch.int64)rewards = [data[2] for data in batch_data]next_states = torch.tensor(np.array([data[3] for data in batch_data]), dtype=torch.float32)dones = [data[4] for data in batch_data]# 计算当前状态的Q值q_values = self.model(states).gather(1, actions.unsqueeze(1)).squeeze(1)# 计算下一状态的最大Q值max_next_q_values = self.target_model(next_states).max(1)[0]max_next_q_values[dones] = 0.0max_next_q_values = max_next_q_values.detach()# 计算目标Q值target_q_values = rewards + self.gamma * max_next_q_valuestarget_q_values = target_q_values.to(torch.float32)# 更新Q网络loss = self.loss_fn(q_values, target_q_values)self.optimizer.zero_grad()loss.backward()self.optimizer.step()if self.epsilon > self.epsilon_min:self.epsilon *= self.epsilon_decaydef update_target_model(self):self.target_model.load_state_dict(self.model.state_dict())if __name__ == "__main__":env = gym.make("CartPole-v1") agent = DQNAgent(state_dim=4, action_dim=2)for episode in range(1000):state = env.reset()episode_reward = 0done = Falsewhile not done:action = agent.get_action(state)next_state, reward, done, _ = env.step(action)agent.memory.append((state, action, reward, next_state, done))episode_reward += rewardstate = next_stateagent.train_model()if episode % 10 == 0:agent.update_target_model()print(f"Episode: {episode}, Reward: {episode_reward}, Epsilon: {agent.epsilon:.2f}")

这个示例代码实现了一个基本的DQN算法,包括经验回放、目标网络、epsilon贪婪探索等。我们将其应用于CartPole环境,智能体通过DQN学习控制一根杆子保持平衡。代码的主要组成部分有:

  • QNetwork: 定义了一个简单的MLP作为Q网络,输入状态,输出各个动作的Q值。
  • DQNAgent: 实现了DQN算法的核心逻辑,包括经验回放、模型训练、目标网络更新、动作选择等。
  • 训练循环: 智能体与环境交互,将转移样本存入回放缓冲区,并进行Q网络训练。每隔一定episode更新目标网络。

运行这个代码,你可以看到智能体的性能随着训练不断提高,最终能够使杆子长时间保持平衡。你可以尝试调整超参数如batch_size、learning_rate、epsilon_decay等,观察它们对算法性能的影响。

6.2 Double DQN

下面我们在DQN的基础上实现Double DQN算法,只需对calculate_target函数做少量修改:

    def train_model(self):...# 计算下一状态的最大Q值max_action = self.model(next_states).max(1)[1].unsqueeze(1)max_next_q_values = self.target_model(next_states).gather(1, max_action).squeeze(1)max_next_q_values[dones] = 0.0max_next_q_values = max_next_q_values.detach()...

相比DQN,Double DQN用Q网络来选择下一状态的最优动作,再用目标网络来评估该动作的Q值。这个简单的改动可以显著减少Q值的过估计问题,提高算法的稳定性和性能。你可以对比运行DQN和Double DQN,体会它们在训练过程中的差异。

6.3 DQN with Prioritized Replay

接下来我们实现优先经验回放(Prioritized Replay),为experience replay引入非均匀采样,提高样本利用效率。

首先定义一个概率分布类SumTree,用于管理样本的优先级:

class SumTree:def __init__(self, capacity):self.capacity = capacityself.tree = np.zeros(2 * capacity - 1)self.data = np.zeros(capacity, dtype=object) self.size = 0self.ptr = 0def add(self, p, data):tree_idx = self.ptr + self.capacity - 1self.data[self.ptr] = dataself.update(tree_idx, p)self.ptr += 1if self.ptr >= self.capacity:self.ptr = 0self.size = min(self.size + 1, self.capacity)def update(self, tree_idx, p):change = p - self.tree[tree_idx]self.tree[tree_idx] = pwhile tree_idx != 0:tree_idx = (tree_idx - 1) // 2self.tree[tree_idx] += changedef get_leaf(self, v):parent_idx = 0while True:left_idx = 2 * parent_idx + 1right_idx = left_idx + 1if left_idx >= len(self.tree):leaf_idx = parent_idxbreakif v <= self.tree[left_idx]:parent_idx = left_idxelse:v -= self.tree[left_idx]parent_idx = right_idxdata_idx = leaf_idx - self.capacity + 1return leaf_idx, self.tree[leaf_idx], self.data[data_idx]@propertydef total_p(self):return self.tree[0]

然后修改DQNAgent,将普通的经验回放替换为优先经验回放:

class DQNAgent:def __init__(self, state_dim, action_dim):...self.alpha = 0.6self.beta = 0.4self.beta_increment = 0.001self.epsilon = 1e-6self.memory = SumTree(capacity=10000)...def store_transition(self, state, action, reward, next_state, done):transition = (state, action, reward, next_state, done)self.memory.add(self.max_priority, transition)def train_model(self):if self.memory.size < self.train_start:returnself.beta = min(1.0, self.beta + self.beta_increment)batch_idx, batch_priorities, batch_data = self.sample_batch()states = torch.tensor(np.array([data[0] for data in batch_data]), dtype=torch.float32)actions = torch.tensor(np.array([data[1] for data in batch_data]), dtype=torch.int64)rewards = [data[2] for data in batch_data]next_states = torch.tensor(np.array([data[3] for data in batch_data]), dtype=torch.float32)dones = [data[4] for data in batch_data]... # 计算Q值与目标Q值# 计算prioritiespriorities = (torch.abs(target_q_values - q_values) + self.epsilon).cpu().data.numpy()# 更新prioritiesfor i in range(self.batch_size):idx = batch_idx[i]self.memory.update(idx, priorities[i])def sample_batch(self):batch_idx = np.empty((self.batch_size,), dtype=np.int32)batch_priorities = np.empty((self.batch_size,), dtype=np.float32)batch_data = []segment = self.memory.total_p / self.batch_sizeself.max_priority = max(self.memory.tree[-self.memory.capacity:])p_sum = self.memory.tree[0]for i in range(self.batch_size):a = segment * ib = segment * (i + 1)upperbound = random.uniform(a, b)idx, p, data = self.memory.get_leaf(upperbound)batch_idx[i] = idxbatch_priorities[i] = p / p_sumbatch_data.append(data)batch_priorities /= p_sum  # normalizebatch_weights = (p_sum * batch_priorities) ** (-self.beta)batch_weights /= batch_weights.max()return batch_idx, batch_priorities, batch_data

优先经验回放的核心是SumTree数据结构,它以树形结构组织样本,树节点存储样本priority的累加和,叶节点存储样本priority。这种结构能够以O(logN)的复杂度实现权重采样和priority更新。

DQNAgent的主要变化包括:

  • store_transition: 将样本存储到SumTree中,优先级初始化为当前最大值
  • train_model: 从SumTree中采样一个batch,并根据TD误差更新样本的优先级
  • sample_batch: 基于SumTree实现加权采样,权重与优先级成正比

与普通的经验回放相比,优先经验回放能够更频繁地采样到有信息量的样本(如TD误差大的样本),从而加速训练收敛。你可以通过调节alpha和beta系数来权衡优先级分布的陡峭程度。

6.4 Dueling DQN

最后我们来实现Dueling DQN,修改Q网络的结构,显式估计状态值函数和优势函数。

class DuelingQNetwork(nn.Module):def __init__(self, state_dim, action_dim):super(DuelingQNetwork, self).__init__()self.feature = nn.Sequential(nn.Linear(state_dim, 64),nn.ReLU(),nn.Linear(64, 64),nn.ReLU())self.advantage = nn.Sequential(nn.Linear(64, 64),nn.ReLU(),nn.Linear(64, action_dim))self.value = nn.Sequential(nn.Linear(64, 64),nn.ReLU(),nn.Linear(64, 1))def forward(self, x):feature = self.feature(x)advantage = self.advantage(feature)value = self.value(feature)q = value + advantage - advantage.mean()return q

将QNetwork替换为DuelingQNetwork,其他部分代码保持不变即可。

Dueling网络将Q函数分解为状态值函数V(s)和优势函数A(s,a),其中A(s,a)表示动作a相对于状态s的平均优势。这种分解使得网络能够学到更稳定、更健壮的值函数,提高策略的质量。你可以思考为什么Dueling架构能学到更优的策略。

总结

通过以上代码示例,相信你已经掌握了DQN及其变体算法的核心思想和实现要点。这些示例代码提供了一个可扩展的框架,你可以在此基础上轻松实现其他DRL算法,或将它们应用到不同的环境任务中。

当然,实际应用还需要考虑诸多工程细节,如超参数调优、模型存储、日志记录、可视化分析等。这需要你在实践中不断摸索和积累。但只要掌握了DQN的核心原理,相信你一定能够驾驭复杂的DRL系统,让智能体学会从环境中汲取知识,实现智能决策。

让我们以DQN为起点,一起探索DRL的精彩世界,用智能点亮未来!

7. 前沿探索

强化学习是一个蓬勃发展的研究领域,DQN的提出掀起了深度强化学习的浪潮。近年来,研究者们不断探索DRL的新方法、新架构、新应用,取得了一系列令人瞩目的成果。本章我们简要介绍几个有前景的研究方向,帮助读者把握DRL的最新进展。

7.1 分层强化学习

现实世界中很多任务都具有层次结构,可以分解为多个子任务。例如做饭可以分解为采购食材、清洗切配、烹饪等步骤。标准的DRL算法通常学习一个扁平的策略,缺乏对任务内在结构的建模,难以应对复杂的长期规划问题。

分层强化学习(Hierarchical RL)旨在学习多层次的策略,自底向上地解决子任务,再组合成整体的解决方案。一个代表性工作是Feudal Networks(FuNs)[Vezhnevets et al., 2017],它引入了君臣网络结构,高层网络设置子目标,低层网络执行基本动作,实现了策略的分层抽象。这种分层结构使得智能体能够学到更高效、更具可解释性的策略。

分层强化学习让智能体掌握了"分而治之"的解题思想,有望攻克现实世界中的复杂规划难题。未来的一个重要方向是探索自适应的层次结构,让智能体自主地发现任务的内在逻辑。

7.2 元强化学习

元学习(Meta Learning)的目标是学会如何学习,让模型具备快速适应新任务的能力。将元学习思想引入强化学习,就得到了元强化学习(Meta RL)。

Model-Agnostic Meta-Learning(MAML)[Finn et al., 2017]是一个著名的元学习框架。它通过两级优化来学习一个任务初始化参数,使其能在新任务上通过少量梯度下降快速适应。这种思想也被引入强化学习,称之为Meta RL[Finn et al., 2017]。Meta RL旨在学习一个初始化策略,使其能在相似的MDP家族上快速适应。具体来说,它在一个分布的MDP上训练,优化策略在新MDP上经过少量梯度下降后的累积回报。

Meta RL让智能体具备了"学习转移"的技能,可大大减少新环境中所需的数据量和训练时间,提高学习的泛化能力。未来需进一步提高元策略的适应性和鲁棒性,实现跨领域的知识迁移。

7.3 多智能体强化学习

很多实际应用如自动驾驶、智慧城市等,都涉及多个智能体的协作与博弈。将DRL拓展到多智能体场景,就得到了多智能体强化学习(Multi-Agent RL, MARL)。

经典的MARL算法如Independent Q-Learning(IQL)简单地为每个智能体训练一个独立的Q网络,缺乏对其他智能体行为的建模,难以学到协作策略。为此,研究者们提出了一系列协作感知(cooperation-aware)的MARL算法。其中Value Decomposition Networks(VDN)[Sunehag et al., 2017]假设联合动作值函数可分解为各智能体值函数之和,从而将联合Q学习简化为分布式单智能体学习。QMIX[Rashid et al., 2018]进一步放宽了可加性约束,引入一个非线性混合网络来组合各智能体的值函数,增强了值函数的表达能力。

多智能体学习让智能体掌握了"协同合作"的能力,是应对复杂社会性任务的关键。未来的重点是提高算法的可扩展性和鲁棒性,在面对大规模非稳态环境时,依然能学到有效的协作策略。

7.4 安全强化学习

在高风险领域如自动驾驶、医疗诊断部署DRL系统时,不仅要考虑性能,还要确保策略的安全性。让智能体规避危险行为,一直是强化学习的难题。

安全强化学习(Safe RL)[Garcıa et al., 2015]旨在学习满足安全约束的最优策略。一种常见做法是将约束建模为惩罚项,纳入环境奖励。但这种软约束难以严格保证安全性。另一种思路是将安全判别器嵌入策略搜索过程[Dalal et al., 2018],引导智能体远离危险状态。但判别器的准确性依赖专家知识,且难以适应环境变化。

如何在保证安全的前提下,最大化任务性能,是一个亟待攻克的难题。未来需进一步完善安全理论与算法,在实践中平衡安全性、样本效率与计算效率,最终实现可信、可用、可靠的DRL系统。

展望未来

纵观全书,我们系统地介绍了DRL的基本原理与核心算法,聚焦DQN及其变体,展示了DRL在游戏、机器人等领域的应用进展,探讨了若干有前景的研究方向。DRL作为连接深度学习与强化学习的桥梁,大大拓展了智能系统的应用边界,使之有能力直接从原始高维数据中学习复杂的策略,实现端到端的决策优化。

当前,DRL在解决现实世界复杂问题的道路上仍面临诸多挑战:

  • 样本效率:DRL需要大量的数据来学习值函数或策略,在很多实际任务中代价难以承受。元学习、迁移学习等技术有望缓解这一问题。
  • 泛化能力:DRL模型在训练环境中表现优异,但迁移到新环境后往往难以适应。增强学习(augmented learning)等方法通过数据增强来提高模型鲁棒性。
  • 安全可信:DRL系统缺乏可解释性,难以验证其安全性与正确性。将因果推理、逻辑规划等符号化方法与DRL相结合,有望实现可解释、可证明的策略学习。
  • 工程实现:DRL涉及环境构建、神经网络设计、分布式训练等诸多工程问题。TensorFlow、PyTorch等深度学习框架对DRL的支持还不够完善。

尽管道阻且长,但DRL作为通用人工智能的核心技术之一,正在深刻影响和重塑人类社会。智能助理、无人驾驶、智能医疗等领域已初见DRL的身影。随着5G、物联网、大数据等新一代信息技术的发展,DRL将迎来更广阔的应用空间。DRL与多智能体系统、群体智能的结合,将开创性地模拟复杂社会系统,优化资源配置。DRL与知识图谱、逻辑推理等符号AI技术的融合,将从数据驱动进化到知识驱动,实现更高层次的认知与规划。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/16071.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Qt 学习笔记】Qt窗口 | 菜单栏 | QMenuBar的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 菜单栏 | QMenuBar的使用及说明 文章编号&#xff1a;Qt 学习…

第20届文博会:“特别呈现”—周瑛瑾雷米·艾融双个展,著名美术评论家,批评家彭德教授对周瑛瑾作品进行评论

周瑛瑾不是学院派艺术家&#xff0c;但在彩墨画领域的天赋超出中国八大美院的同类型画家。相比具有批判意识的当代艺术&#xff0c;他的彩墨艺术如同我们这个苦难世界的创可贴和安慰剂。当我面对他的彩墨画&#xff0c;首先是惊艳&#xff0c;随之想到屈原的离骚&#xff0c;还…

无源相控阵雷达

什么是无源相控阵雷达 无源相控阵雷达&#xff08;Passive Electronically Scanned Array Radar&#xff0c;简称PESA雷达&#xff09;是一种雷达系统。这里的“无源”并未指其不发射信号&#xff0c;而是指其阵列单元不会产生并发射信号&#xff0c;其特点在于天线表面的阵列…

Vue与React、Angular的比较

Vue、React和Angular是前端开发中三个流行的JavaScript框架&#xff0c;它们各自具有不同的特点、优势和适用场景。以下是对这三个框架的比较&#xff1a; 1. 基本概念 Vue&#xff1a;Vue是一套用于构建用户界面的渐进式框架&#xff0c;其核心库专注于视图层&#xff0c;易…

[CISCN 2024] Crypto部分复现

文章目录 OvOez_rsacheckin浅记一下 迟来的文章 OvO 题目描述&#xff1a; from Crypto.Util.number import * from secret import flagnbits 512 p getPrime(nbits) q getPrime(nbits) n p * q phi (p-1) * (q-1) while True:kk getPrime(128)rr kk 2e 65537 kk …

【三维修复、分割与编辑】InFusion、Bootstrap 3D、GaussianGrouping、GaussianEditor等(论文总结)

提示&#xff1a; 文章目录 前言一、InFusion&#xff1a;扩散模型助力&#xff0c;效率提高20倍&#xff01;(2024)1. 摘要2. 算法3. 效果 二、2D Gaussian Splatting三、Bootstrap 3D:从扩散模型引导三维重建1.摘要2.相关工作3.方法1.Boostrapping by Diffusion 通过扩散模型…

学习存储协议的利器,聊聊tcpdump和Wireshark

数据存储技术分为多个方面,包括数据持久化、数据映射、数据压缩和通信协议等等。其中通信协议是数据存储技术中非常重要的一部分,正是通信协议使得计算节点可以访问存储设备。同时,也正是不同的协议让存储系统呈现不同的形态。 如下图所示,通过iSCSI协议,可以将存储端的存…

使用std::vector<char>作为数据缓冲区分析

文章目录 0. 引言1. 内存分配分析2. 性能影响3. 性能优化策略4. 实际性能测试5. 优化建议6. 总结额外建议 0. 引言 在 C 网络编程中&#xff0c;std::vector<char> 常被用作数据缓冲区。与普通数组相比&#xff0c;std::vector 的内存分配在堆上&#xff0c;而非栈上&am…

【JVM实践与应用】

JVM实践与应用 1.类加载器(加载、连接、初始化)1.1 类加载要完成的功能1.2 加载类的方式1.3 类加载器1.4 双亲委派模型1.5自定义ClassLoader1.6 破坏双亲委派模型2.1 类连接主要验证内容2.2 类连接中的解析2.3 类的初始化3.1 类的初始化时机3.2 类的初始化机制和顺序3.2 类的卸…

C从零开始实现贪吃蛇大作战

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 : C语言 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 有关Win32API的知识点在上一篇文章&#xff1a; 目录 一.地图 1.控制台基本介绍 2.宽字符 1.本地化 2.类项 3.setlocale函…

解释Vue中transition的理解

在Vue中&#xff0c;transition组件用于在元素或组件插入、更新或移除时应用过渡效果。Vue 2和Vue 3都提供了transition组件&#xff0c;但两者之间有一些差异和更新。以下是关于Vue 2和Vue 3中transition组件的理解&#xff1a; Vue 2中的transition 在Vue 2中&#xff0c;t…

Golang 如何使用 gorm 存取带有 emoji 表情的数据

Golang 如何使用 gorm 存取带有 emoji 表情的数据 结论&#xff1a;在 mysql 中尽量使用 utf8mb4&#xff0c;不要使用 utf8。db报错信息&#xff1a;Error 1366 (HY000): Incorrect string value: \\xE6\\x8C\\xA5\\xE7\\xAC\\xA6...根本原因&#xff1a;emoji 4个字节&#x…

MybatisPlus分页查询

分页查询controller写法 public PageResult findByList(RequestBody UserDTO userDTO) {// 分页IPage<User> page new Page(UserDTO.getPageNumber(), UserDTO.getPageSize());// 条件构造器QueryWrapper queryWrapper new QueryWrapper();queryWrapper.eq("user…

【深度学习】第1章

概论: 机器学习是对研究问题进行模型假设,利用计算机从训练数据中学习得到模型参数,并最终对数据进行预测和分析,其基础主要是归纳和统计。 深度学习是一种实现机器学习的技术,是机器学习重要的分支。其源于人工神经网络的研究。深度学习的模型结构是一种含多隐层的神经…

Springboot应用的配置管理

Spring Boot应用的配置管理 在本文中&#xff0c;我们将深入探讨Spring Boot的配置文件&#xff08;application.properties/yaml&#xff09;&#xff0c;以及如何在不同环境中管理配置和使用Spring Config Server。此外&#xff0c;我们还将分享一些高级配置技巧&#xff0c…

Spring Cloud Alibaba 架构-Sentinel整合nacos和gateway

官网地址 sentinel官网: https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5 sentinel 下载地址: https://github.com/alibaba/Sentinel/releases nacos官网: https://nacos.io/zh-cn/docs/deployment.html nacos下载地址: https://github.com/alibaba/nacos/releas…

Shopee单个商品详情采集

Shopee商品详情页数据采集实战 作为东南亚地区最大的电商平台之一,Shopee拥有超过3亿活跃用户。对于跨境电商企业、市场分析师等角色而言,从Shopee获取商品数据是非常有价值的。本文将介绍如何使用Python程序采集Shopee单个商品详情页数据。 1. 确定采集目标和技术方案 确定…

路由传参和获取参数的三种方式

路由传参和获取参数在前端开发中是一个常见的需求&#xff0c;特别是在使用如 Vue.js、React 等前端框架时。下面&#xff0c;我将以 Vue.js 为例&#xff0c;介绍三种常见的路由传参和获取参数的方式&#xff1a; 1. 使用 params 传参 传参&#xff1a; 在路由配置中&#…

SQL Server 2022 STRING_SPLIT表值函数特性增强

SQL Server 2022 STRING_SPLIT表值函数特性增强 1、本文内容 List item语法参数返回类型注解 适用于&#xff1a;SQL Server 2016 (13.x) 及更高版本Azure SQL 数据库Azure SQL 托管实例Azure Synapse AnalyticsMicrosoft Fabric 中的 SQL 分析终结点Microsoft Fabric 中的仓…

golang内置包strings和bytes中的Map函数的理解和使用示例

在go的标志内置包strings和bytes中都有一个函数Map, 这个函数的作用是&#xff1a; 将输入字符串/字节切片中的每个字符使用函数处理后映射后返回一份字符串/字节切片的副本&#xff0c;如果函数中的某个字符返回负数则删除对应的字符。 作用很简单&#xff0c;当时对于新手来…