RL 实践(6)—— CartPole【REINFORCE with baseline A2C】

news2024/11/17 7:38:55
  • 本文介绍 REINFORCE with baseline 和 A2C 这两个带 baseline 的策略梯度方法,并在 CartPole-V0 上验证它们和无 baseline 的原始方法 REINFORCE & Actor-Critic 的优势
  • 参考:《动手学强化学习》
  • 完整代码下载:7_[Gym] CartPole-V0 (REINFORCE with baseline and A2C)

文章目录

  • 1. CartPole-V0 环境
  • 2. Policy Gradient with Baseline
    • 2.1 带 baseline 的策略梯度定理
    • 2.2 REINFORCE with baseline
      • 2.2.1 伪代码
      • 2.2.2 用 REINFORCE with baseline 方法解决 CartPole 问题
      • 2.2.3 性能
    • 2.3 Advantage Actor-Critic (A2C)
      • 2.3.1 伪代码
      • 2.3.2 用 A2C 方法解决 CartPole 问题
      • 2.3.3 性能
      • 2.3.4 引入目标网络
  • 3. 总结

1. CartPole-V0 环境

  • 本次实验使用 gym 自带的 CartPole-V0 环境。这是一个经典的一阶倒立摆控制问题,agent 的任务是通过左右移动保持车上的杆竖直,若杆的倾斜度数过大,或者车子离初始位置左右的偏离程度过大,或者坚持时间到达 200 帧,则游戏结束
    在这里插入图片描述

  • 此环境的状态空间为

    维度意义取值范围
    0滚球 x 轴坐标 [ 0 ,  width ] [0,\space \text{width}] [0, width]
    1滚球 y 轴坐标 [ − inf ⁡ ,   inf ⁡ ] [-\inf, \space \inf] [inf, inf]
    2滚球 x 轴速度 [ − 41.8 ° ,    41.8 ° ] [-41.8°,\space ~ 41.8°] [41.8°,  41.8°]
    3滚球 y 轴速度 [ − inf ⁡ ,   inf ⁡ ] [-\inf, \space \inf] [inf, inf]

    动作空间为

    维度意义
    0向左移动小车
    1向右移动小车

    奖励函数为每个 timestep 得到 1 的奖励,agent 坚持时间越长,则最后的分数越高,坚持 200 帧即可获得最高的分数 200

    倒立摆问题传统上可以用 pid 方法良好地解决。如果对 PID 这一套感兴趣,可以参考我的视频

    • 一看就懂的pid控制理论入门
    • 倒立摆模拟器
  • 下面给出环境的测试代码

    import os
    import sys
    import gym
    base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
    sys.path.append(base_path)
    
    import time
    from gym.utils.env_checker import check_env
    
    env_name = 'CartPole-v0'
    env = gym.make(env_name, render_mode='human')
    check_env(env.unwrapped)    # 检查环境是否符合 gym 规范
    env.action_space.seed(10)
    observation, _ = env.reset(seed=10)
    
    # 测试环境
    for i in range(100):
        while True:
            action = env.action_space.sample()
            state, reward, terminated, truncated, _ = env.step(action)
    
            if terminated or truncated:
                env.reset()
                break
    
            time.sleep(0.01)
            env.render()
    
    # 关闭环境渲染
    env.close()
    

2. Policy Gradient with Baseline

  • 上文 RL 实践(5)—— 二维滚球环境【REINFORCE & Actor-Critic】 介绍了两种经典的 Policy-Gradient 方法,Actor-Critic 和 REINFORCE,本文介绍这两种方法通用的一个改进技巧,可以有效提高算法的性能,并减小方差。
  • 首先回顾一下策略梯度方法的基本原理:对于用参数 θ \theta θ 形式化的策略网络 π θ : S → A \pi_\theta:\mathcal{S}\to\mathcal{A} πθ:SA,策略学习可以转换为如下优化问题
    max ⁡ θ { J ( θ ) = △ E s [ V π θ ( s ) ] } \max_\theta \left\{J(\theta) \stackrel{\triangle}{=} \mathbb{E}_s[V_{\pi_\theta}(s)] \right\} θmax{J(θ)=Es[Vπθ(s)]} 这个优化问题可以用梯度上升来解
    θ ← θ + β ⋅ ▽ θ J ( θ ) \theta \leftarrow \theta + \beta ·\triangledown_{\theta}J(\theta) θθ+βθJ(θ) 其中策略梯度可以用策略梯度定理计算
    ▽ θ J ( θ ) ∝ E S ∼ d ( ⋅ ) [ E A ∼ π θ ( ⋅ ∣ S ) [ ▽ θ ln ⁡ π θ ( A ∣ S ) ⋅ Q π θ ( S , A ) ] ] \triangledown_{\theta}J(\theta)\propto \mathbb{E}_{S \sim d(\cdot)}\Big[\mathbb{E}_{A \sim \pi_\theta(\cdot \mid S)}\left[\triangledown_{\theta}\ln \pi_\theta(A \mid S) \cdot Q_{\pi_\theta}(S, A)\right]\Big] θJ(θ)ESd()[EAπθ(S)[θlnπθ(AS)Qπθ(S,A)]] 我们通过两次 MC 近似消去两个积分,得到随机策略梯度
    g θ ( s , a ) = △ ▽ θ ln ⁡ π θ ( a ∣ s ) ⋅ Q π θ ( s , a ) g_\theta(s,a) \stackrel{\triangle}{=} \triangledown_{\theta}\ln \pi_\theta(a|s) \cdot Q_{\pi_\theta}(s,a) gθ(s,a)=θlnπθ(as)Qπθ(s,a) 其中 s , a s,a s,a 来自策略 π θ \pi_\theta πθ 和环境环境的某个具体 transition。最后,对动作价值函数 Q π ( s , a ) Q_\pi(s, a) Qπ(s,a) 的两种近似方案引出了两种策略梯度方法:
    1. REINFORCE:用实际 return u u u MC 近似 Q π ( s , a ) Q_\pi(s, a) Qπ(s,a)
    2. Actor-Critic:用神经网络(Critic) q w ( s , a ) q_w(s, a) qw(s,a) 近似 Q π ( s , a ) Q_\pi(s, a) Qπ(s,a)

2.1 带 baseline 的策略梯度定理

  • 我们首先证明一个引理:设 b b b 是任意函数, b b b 不依赖于 A A A。那么对于任意的 s s s,有
    E A ∼ π θ ( ⋅ ∣ s ) [ b ⋅ ▽ θ ln ⁡ π θ ( A ∣ s ) ] = 0 \mathbb{E}_{A \sim \pi_{\boldsymbol{\theta}}(\cdot \mid s )}\left[b \cdot \triangledown_{\theta}\ln \pi_\theta(A \mid s)\right]=0 EAπθ(s)[bθlnπθ(As)]=0

    由于 b b b 不依赖于动作 A A A,首先把 b b b 提取到期望外面
    E A ∼ π ( ⋅ ∣ s ; θ ) [ b ⋅ ∂ ln ⁡ π ( A ∣ s ; θ ) ∂ θ ] = b ⋅ E A ∼ π ( ⋅ ∣ s ; θ ) [ ∂ ln ⁡ π ( A ∣ s ; θ ) ∂ θ ] = b ⋅ ∑ a ∈ A π ( a ∣ s ; θ ) ⋅ ∂ ln ⁡ π ( a ∣ s ; θ ) ∂ θ = b ⋅ ∑ a ∈ A π ( a ∣ s ; θ ) ⋅ 1 π ( a ∣ s ; θ ) ⋅ ∂ π ( a ∣ s ; θ ) ∂ θ = b ⋅ ∑ a ∈ A ∂ π ( a ∣ s ; θ ) ∂ θ \begin{aligned} \mathbb{E}_{A \sim \pi(\cdot \mid s ; \boldsymbol{\theta})}\left[b \cdot \frac{\partial \ln \pi(A \mid s ; \boldsymbol{\theta})}{\partial \boldsymbol{\theta}}\right] & =b \cdot \mathbb{E}_{A \sim \pi(\cdot \mid s ; \boldsymbol{\theta})}\left[\frac{\partial \ln \pi(A \mid s ; \boldsymbol{\theta})}{\partial \boldsymbol{\theta}}\right] \\ & =b \cdot \sum_{a \in \mathcal{A}} \pi(a \mid s ; \boldsymbol{\theta}) \cdot \frac{\partial \ln \pi(a \mid s ; \boldsymbol{\theta})}{\partial \boldsymbol{\theta}} \\ & =b \cdot \sum_{a \in \mathcal{A}} \pi(a \mid s ; \boldsymbol{\theta}) \cdot \frac{1}{\pi(a \mid s ; \boldsymbol{\theta})} \cdot \frac{\partial \pi(a \mid s ; \boldsymbol{\theta})}{\partial \boldsymbol{\theta}} \\ & =b \cdot \sum_{a \in \mathcal{A}} \frac{\partial \pi(a \mid s ; \boldsymbol{\theta})}{\partial \boldsymbol{\theta}} \end{aligned} EAπ(s;θ)[bθlnπ(As;θ)]=bEAπ(s;θ)[θlnπ(As;θ)]=baAπ(as;θ)θlnπ(as;θ)=baAπ(as;θ)π(as;θ)1θπ(as;θ)=baAθπ(as;θ) 上式最右边的连加是关于 a a a 求的,而偏导是关于 θ \theta θ 求的,因此可以把连加放入偏导内部
    E A ∼ π ( ⋅ ∣ s ; θ ) [ b ⋅ ∂ ln ⁡ π ( A ∣ s ; θ ) ∂ θ ] = b ⋅ ∂ ∂ θ ∑ a ∈ A π ( a ∣ s ; θ ) ⏟ 恒等于  1 . = b ⋅ ∂ 1 ∂ θ = 0. \begin{aligned} \mathbb{E}_{A \sim \pi(\cdot \mid s ; \boldsymbol{\theta})}\left[b \cdot \frac{\partial \ln \pi(A \mid s ; \boldsymbol{\theta})}{\partial \boldsymbol{\theta}}\right]&=b \cdot \frac{\partial}{\partial \boldsymbol{\theta}} \underbrace{\sum_{a \in \mathcal{A}} \pi(a \mid s ; \boldsymbol{\theta})}_{\text {恒等于 } 1} . \\ &=b \cdot \frac{\partial 1}{\partial \boldsymbol{\theta}}=0 . \end{aligned} EAπ(s;θ)[bθlnπ(As;θ)]=bθ恒等于 1 aAπ(as;θ).=bθ1=0.

  • 证明此引理后,我们可以把这一项引入到策略梯度定义给出的策略梯度中
    ▽ θ J ( θ ) ∝ E S ∼ d ( ⋅ ) [ E A ∼ π θ ( ⋅ ∣ S ) [ ▽ θ ln ⁡ π θ ( A ∣ S ) ⋅ Q π θ ( S , A ) ] ] = E S ∼ d ( ⋅ ) [ E A ∼ π θ ( ⋅ ∣ S ) [ ▽ θ ln ⁡ π θ ( A ∣ S ) ⋅ Q π θ ( S , A ) ] − 0 ] = E S ∼ d ( ⋅ ) [ E A ∼ π θ ( ⋅ ∣ S ) [ ▽ θ ln ⁡ π θ ( A ∣ S ) ⋅ Q π θ ( S , A ) ] − E A ∼ π θ ( ⋅ ∣ s ) [ b ⋅ ▽ θ ln ⁡ π θ ( A ∣ s ) ] ] = E S ∼ d ( ⋅ ) [ E A ∼ π θ ( ⋅ ∣ S ) [ ▽ θ ln ⁡ π θ ( A ∣ S ) ⋅ ( Q π θ ( S , A ) − b ) ] ] \begin{aligned} \triangledown_{\theta}J(\theta) &\propto \mathbb{E}_{S \sim d(\cdot)}\Big[\mathbb{E}_{A \sim \pi_\theta(\cdot \mid S)}\left[\triangledown_{\theta}\ln \pi_\theta(A \mid S) \cdot Q_{\pi_\theta}(S, A)\right]\Big] \\ &= \mathbb{E}_{S \sim d(\cdot)}\Big[\mathbb{E}_{A \sim \pi_\theta(\cdot \mid S)}\left[\triangledown_{\theta}\ln \pi_\theta(A \mid S) \cdot Q_{\pi_\theta}(S, A)\right]-0\Big] \\ &= \mathbb{E}_{S \sim d(\cdot)}\Big[\mathbb{E}_{A \sim \pi_\theta(\cdot \mid S)}\left[\triangledown_{\theta}\ln \pi_\theta(A \mid S) \cdot Q_{\pi_\theta}(S, A)\right]-\mathbb{E}_{A \sim \pi_{\boldsymbol{\theta}}(\cdot \mid s )}\left[b \cdot \triangledown_{\theta}\ln \pi_\theta(A \mid s)\right]\Big] \\ &= \mathbb{E}_{S \sim d(\cdot)}\Big[\mathbb{E}_{A \sim \pi_\theta(\cdot \mid S)}\left[\triangledown_{\theta}\ln \pi_\theta(A \mid S) \cdot \big(Q_{\pi_\theta}(S, A)-b\big)\right]\Big] \\ \end{aligned} θJ(θ)ESd()[EAπθ(S)[θlnπθ(AS)Qπθ(S,A)]]=ESd()[EAπθ(S)[θlnπθ(AS)Qπθ(S,A)]0]=ESd()[EAπθ(S)[θlnπθ(AS)Qπθ(S,A)]EAπθ(s)[bθlnπθ(As)]]=ESd()[EAπθ(S)[θlnπθ(AS)(Qπθ(S,A)b)]] 进而可以得到 带基线的随机策略梯度,它仍是对原策略梯度的无偏估计
    g θ ( s , a ; b ) = △ ▽ θ ln ⁡ π θ ( a ∣ s ) ⋅ [ Q π θ ( s , a ) − b ] g_\theta(s,a;b) \stackrel{\triangle}{=} \triangledown_{\theta}\ln \pi_\theta(a|s) \cdot \Big[Q_{\pi_\theta}(s,a)-b\Big] gθ(s,a;b)=θlnπθ(as)[Qπθ(s,a)b] 其中不依赖于 A A A 的任意函数 b b b 就是所谓的 基线(baseline),它的引入不会影响策略梯度(期望不变),但会影响随机策略梯度 g θ ( s , a ; b ) g_\theta(s,a;b) gθ(s,a;b),进而影响随机策略梯度的方差
    Var ⁡ = E S , A [ ∥ g θ ( s , a ; b ) − ∇ θ J ( θ ) ∥ 2 ] \operatorname{Var}=\mathbb{E}_{S, A}\left[\left\|g_\theta(s,a;b) -\nabla_{\boldsymbol{\theta}} J(\boldsymbol{\theta})\right\|^{2}\right] Var=ES,A[gθ(s,a;b)θJ(θ)2] 如果 b b b 很接近 Q π θ ( s , a ; b ) Q_{\pi_\theta}(s, a;b) Qπθ(s,a;b) 关于 a a a 的均值,那么方差会比较小。因此 b = V π θ ( s ) b = V_{\pi_\theta}(s) b=Vπθ(s) 是很好的基线

2.2 REINFORCE with baseline

  • 我们使用 2.1 节得到的带基线的随机策略梯度 g θ ( s , a ; b ) g_\theta(s,a;b) gθ(s,a;b) 改写前文的 REINFORCR 算法,基线设置为 b = V π θ ( s ) b = V_{\pi_\theta}(s) b=Vπθ(s),即
    g θ ( s , a ) = △ ▽ θ ln ⁡ π θ ( a ∣ s ) ⋅ [ Q π θ ( s , a ) − V π θ ( s ) ] g_\theta(s,a) \stackrel{\triangle}{=} \triangledown_{\theta}\ln \pi_\theta(a|s) \cdot \Big[Q_{\pi_\theta}(s,a)-V_{\pi_\theta}(s)\Big] gθ(s,a)=θlnπθ(as)[Qπθ(s,a)Vπθ(s)] 其中 Q π θ ( s , a ) Q_{\pi_\theta}(s,a) Qπθ(s,a) 仍然和 REINFORCR 一样使用轨迹的真实 return u u u 来估计, V π θ ( s ) V_{\pi_\theta}(s) Vπθ(s) 则引入一个价值网络 v ω v_\omega vω 来估计,估计方式为 MC(而非 TD bootstrap)

2.2.1 伪代码

  • 每轮迭代我们用当前策略 π θ \pi_\theta πθ 交互得到一条轨迹,然后计算出每一个 transition ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s) 对应的 return u u u首先用 u u u 作为标签用 mse loss 优化价值网络 v ω v_\omega vω(这样价值网络可以更好地估计 V π θ V_{\pi_\theta} Vπθ),然后利用更新后的 v w v_w vw 计算 g θ ( s , a ) g_\theta(s,a) gθ(s,a) 来更新策略网络 π θ \pi_\theta πθ。伪代码如下
    初始化策略网络  π θ  和价值网络  v ω f o r    e p i s o d e    e = 1 → E    d o : 用当前策略 π θ 交互一条轨迹  s 1 , a 1 , r 1 , . . . , s n , a n , r n 计算所有  r e t u r n   u t = ∑ k = t m γ k − t r k ,   t = 1 , 2 , . . . , n 更新  v ω  参数  l ω = 1 2 n ∑ t = 1 n [ v ω ( s t ) − u t ] 2 更新  π θ  参数  θ ← θ + β ⋅ ▽ θ ln ⁡ π θ ( a t ∣ s t ) ⋅ ( u t − v ω ( s t ) ) ,   t = 1 , 2 , . . . , n e n d    f o r \begin{aligned} &初始化策略网络 \space \pi_\theta \space 和价值网络 \space v_\omega \\ &for \space\space episode \space\space e=1 \rightarrow E \space\space do :\\ &\quad\quad 用当前策略 \pi_{\theta} 交互一条轨迹\space s_1, a_1, r_1,...,s_n, a_n, r_n \\ & \quad\quad 计算所有 \space return \space u_t = \sum_{k=t}^m \gamma^{k-t} r_k, \space t=1,2,...,n\\ &\quad\quad 更新 \space v_\omega \space 参数 \space l_\omega = \frac{1}{2n}\sum_{t=1}^n\Big[v_\omega(s_t) - u_t \Big]^2\\ &\quad\quad更新 \space \pi_\theta \space参数\space \theta \leftarrow \theta + \beta ·\triangledown_{\theta}\ln \pi_\theta(a_t|s_t) \cdot \big(u_t - v_\omega(s_t)\big), \space t=1,2,...,n \\ &end \space\space for \end{aligned} 初始化策略网络 πθ 和价值网络 vωfor  episode  e=1E  do:用当前策略πθ交互一条轨迹 s1,a1,r1,...,sn,an,rn计算所有 return ut=k=tmγktrk, t=1,2,...,n更新 vω 参数 lω=2n1t=1n[vω(st)ut]2更新 πθ 参数 θθ+βθlnπθ(atst)(utvω(st)), t=1,2,...,nend  for

2.2.2 用 REINFORCE with baseline 方法解决 CartPole 问题

  • 定义策略网络和价值网络

    class PolicyNet(torch.nn.Module):
        ''' 策略网络是一个两层 MLP '''
        def __init__(self, input_dim, hidden_dim, output_dim):
            super(PolicyNet, self).__init__()
            self.fc1 = torch.nn.Linear(input_dim, hidden_dim)
            self.fc2 = torch.nn.Linear(hidden_dim, output_dim)
    
        def forward(self, x):
            x = F.relu(self.fc1(x))             # (1, hidden_dim)
            x = F.softmax(self.fc2(x), dim=1)   # (1, output_dim)
            return x
    
    class VNet(torch.nn.Module):
        ''' 价值网络是一个两层 MLP '''
        def __init__(self, input_dim, hidden_dim):
            super(VNet, self).__init__()
            self.fc1 = torch.nn.Linear(input_dim, hidden_dim)
            self.fc2 = torch.nn.Linear(hidden_dim, 1)
    
        def forward(self, x):
            x = F.relu(self.fc1(x))
            x = self.fc2(x)
            return
    
  • 定义 REINFORCE with baseline agent

    class REINFORCE_Baseline(torch.nn.Module):
        def __init__(self, state_dim, hidden_dim, action_range, lr_policy, lr_value, gamma, device):
            super().__init__()
            self.policy_net = PolicyNet(state_dim, hidden_dim, action_range).to(device)
            self.v_net = VNet(state_dim, hidden_dim).to(device)
            self.optimizer_policy = torch.optim.Adam(self.policy_net.parameters(), lr=lr_policy)    # 使用Adam优化器
            self.optimizer_value = torch.optim.Adam(self.v_net.parameters(), lr=lr_value)           # 使用Adam优化器
            self.gamma = gamma
            self.device = device
    
        def take_action(self, state):  
            # 根据动作概率分布随机采样
            state = torch.tensor(state, dtype=torch.float).to(self.device)
            state = state.unsqueeze(0)
            probs = self.policy_net(state).squeeze()
            action_dist = torch.distributions.Categorical(probs)
            action = action_dist.sample()
            return action.item()
    
        def update(self, transition_dict):
            G, returns = 0, []
            for reward in reversed(transition_dict['rewards']):
                G = self.gamma * G + reward
                returns.insert(0, G)
    
            rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device).squeeze()     # (bsz, )
            returns = torch.tensor(returns, dtype=torch.float).view(-1, 1).to(self.device).squeeze()                        # (bsz, )
            states = torch.tensor(np.array(transition_dict['states']), dtype=torch.float).to(self.device)                   # (bsz, state_dim)
            actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)                                  # (bsz, action_dim)
    
            # 梯度清零
            self.optimizer_value.zero_grad()
            self.optimizer_policy.zero_grad()
            
            # 更新价值网络
            value_predicts = self.v_net(states).squeeze()                   # (bsz, )
            value_loss = torch.mean(F.mse_loss(value_predicts, returns))     
            value_loss.backward()
            self.optimizer_value.step()
    
            # 更新策略网络, 从轨迹最后一步起往前计算 return,每步回传累计梯度 
            for i in reversed(range(len(rewards))):
                action = actions[i]
                state = states[i]
                value = self.v_net(state).squeeze()                         # 使用更新过的价值网络预测价值
                G = returns[i]                                              # (state_dim, )
                probs = self.policy_net(state.unsqueeze(0)).squeeze()       # (action_range, )
                log_prob = torch.log(probs[action])
                policy_loss = -log_prob * (G - value.detach())              # value 是 v_net 给出的,将其 detach 以确保只更新 policy 参数
                policy_loss.backward()        
            self.optimizer_policy.step()    
    
  • 进行训练并绘制性能曲线

    if __name__ == "__main__":
        def moving_average(a, window_size):
            ''' 生成序列 a 的滑动平均序列 '''
            cumulative_sum = np.cumsum(np.insert(a, 0, 0)) 
            middle = (cumulative_sum[window_size:] - cumulative_sum[:-window_size]) / window_size
            r = np.arange(1, window_size-1, 2)
            begin = np.cumsum(a[:window_size-1])[::2] / r
            end = (np.cumsum(a[:-window_size:-1])[::2] / r)[::-1]
            return np.concatenate((begin, middle, end))
    
        def set_seed(env, seed=42):
            ''' 设置随机种子 '''
            env.action_space.seed(seed)
            env.reset(seed=seed)
            random.seed(seed)
            np.random.seed(seed)
            torch.manual_seed(seed)
    
        state_dim = 4                               # 环境观测维度
        action_range = 2                            # 环境动作空间大小
        lr_policy = 2e-3
        lr_value = 3e-3
        num_episodes = 500
        hidden_dim = 64
        gamma = 0.98
        device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    
        # build environment
        env_name = 'CartPole-v0'
        env = gym.make(env_name, render_mode='rgb_array')
        check_env(env.unwrapped)    # 检查环境是否符合 gym 规范
        set_seed(env, 42)
    
        # build agent
        agent = REINFORCE_Baseline(state_dim, hidden_dim, action_range, lr_policy, lr_value, gamma, device)
    
        # start training
        return_list = []
        for i in range(10):
            with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
                for i_episode in range(int(num_episodes / 10)):
                    episode_return = 0
                    transition_dict = {
                        'states': [],
                        'actions': [],
                        'next_states': [],
                        'rewards': [],
                        'dones': []
                    }
                    state, _ = env.reset()
    
                    # 以当前策略交互得到一条轨迹
                    while True:
                        action = agent.take_action(state)
                        next_state, reward, terminated, truncated, _ = env.step(action)
                        transition_dict['states'].append(state)
                        transition_dict['actions'].append(action)
                        transition_dict['next_states'].append(next_state)
                        transition_dict['rewards'].append(reward)
                        transition_dict['dones'].append(terminated or truncated)
                        state = next_state
                        episode_return += reward
                        
                        if terminated or truncated:
                            env.render()
                            break
                        #env.render()
    
                    # 用当前策略收集的数据进行 on-policy 更新
                    agent.update(transition_dict)
    
                    # 更新进度条
                    return_list.append(episode_return)
                    pbar.set_postfix({
                        'episode':
                        '%d' % (num_episodes / 10 * i + i_episode + 1),
                        'return':
                        '%.3f' % episode_return,
                        'ave return':
                        '%.3f' % np.mean(return_list[-10:])
                    })
                    pbar.update(1)
    
        # show policy performence
        mv_return_list = moving_average(return_list, 29)
        episodes_list = list(range(len(return_list)))
        plt.figure(figsize=(12,8))
        plt.plot(episodes_list, return_list, label='raw', alpha=0.5)
        plt.plot(episodes_list, mv_return_list, label='moving ave')
        plt.xlabel('Episodes')
        plt.ylabel('Returns')
        plt.title(f'{agent._get_name()} on CartPole-V0')
        plt.legend()
        plt.savefig(f'./result/{agent._get_name()}.png')
        plt.show()         
    

2.2.3 性能

  • 对比前文介绍的普通 REINFORCE 方法和以上 REINFORCE with baseline 方法的性能曲线,如下
    在这里插入图片描述
    可见引入 baseline 有效降低了方差,且加快了收敛速度

2.3 Advantage Actor-Critic (A2C)

  • 我们使用 2.1 节得到的带基线的随机策略梯度 g θ ( s , a ; b ) g_\theta(s,a;b) gθ(s,a;b) 改写前文的 REINFORCR 算法,基线设置为 b = V π θ ( s ) b = V_{\pi_\theta}(s) b=Vπθ(s),即
    g θ ( s , a ) = △ ▽ θ ln ⁡ π θ ( a ∣ s ) ⋅ [ Q π θ ( s , a ) − V π θ ( s ) ] g_\theta(s,a) \stackrel{\triangle}{=} \triangledown_{\theta}\ln \pi_\theta(a|s) \cdot \Big[Q_{\pi_\theta}(s,a)-V_{\pi_\theta}(s)\Big] gθ(s,a)=θlnπθ(as)[Qπθ(s,a)Vπθ(s)] 其中 Q π θ ( s , a ) − V π θ ( s ) Q_{\pi_\theta}(s,a)-V_{\pi_\theta}(s) Qπθ(s,a)Vπθ(s) 被称作优势函数 (advantage function),因此基于上面公式得到的 actor-critic 方法被称为 advantage actor-critic (A2C)。A2C 属于 actor-critic 方法,有一个策略网络 π θ \pi_\theta πθ 作为 Actor 用于控制 agent 运动,还有一个价值网络 v ω v_\omega vω 作为 Critic,他的评分可以帮助 Actor 改进。两个神经网络的结构与上一节中的完全相同,但是本节和上一节用不同的方法训练两个神经网络

2.3.1 伪代码

  • 这里训练价值网络时不像 REINFORCE with baseline 那样直接优化 mse loss 去靠近真实 return(MC),而是用 mse loss 去优化 Sarsa TD error。具体而言,给定 transition ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s),如下得到 TD error 的 mse loss
    l ω = 1 2 [ v ω ( s ) − ( r + v ω ( s ′ ) ) ] 2 l_\omega = \frac{1}{2}\Big[v_\omega(s) - \big(r+v_\omega(s')\big)\Big]^2 lω=21[vω(s)(r+vω(s))]2
  • 训练策略网络时利用 Bellman 公式得到 Q Q Q V V V 的关系
    Q π ( s t , a t ) = E S t + 1 ∼ p ( ⋅ ∣ s t , a t ) [ R t + γ V π ( S t + 1 ) ] Q_{\pi}\left(s_{t}, a_{t}\right) = \mathbb{E}_{S_{t+1}\sim p(·|s_t,a_t)}\Big[R_t + \gamma V_\pi(S_{t+1})\Big] Qπ(st,at)=ESt+1p(st,at)[Rt+γVπ(St+1)] 把带基线的随机策略梯度中的 Q π θ ( s , a ) Q_{\pi_\theta}(s,a) Qπθ(s,a) 进行替换
    g θ ( s , a ) = ▽ θ ln ⁡ π θ ( a ∣ s ) ⋅ [ Q π θ ( s , a ) − V π θ ( s ) ] = ▽ θ ln ⁡ π θ ( a ∣ s ) ⋅ [ E S t + 1 [ R t + γ V π θ ( S t + 1 ) ] − V π θ ( s ) ] \begin{aligned} \boldsymbol{g}_\theta\left(s,a\right) & =\triangledown_{\theta}\ln \pi_\theta(a|s) \cdot \Big[Q_{\pi_\theta}(s,a)-V_{\pi_\theta}(s)\Big] \\ & = \triangledown_{\theta}\ln \pi_\theta(a|s) \cdot \Big[\mathbb{E}_{S_{t+1}}\left[R_t+\gamma V_{\pi_\theta}\left(S_{t+1}\right)\right]-V_{\pi_\theta}(s)\Big] \end{aligned} gθ(s,a)=θlnπθ(as)[Qπθ(s,a)Vπθ(s)]=θlnπθ(as)[ESt+1[Rt+γVπθ(St+1)]Vπθ(s)] 使用真实的 transition ( s , a , r , s ′ ) (s,a,r,s') (s,a,r,s) 进行 MC 近似,得到随机策略梯度
    g ~ θ ( s , a ) ≜ [ r + γ ⋅ v ω ( s ′ ) − v ω ( s ) ⏟ T D  Error ] ⋅ ∇ θ ln ⁡ π θ ( a ∣ s ) . \tilde{\boldsymbol{g}}_\theta\left(s,a\right) \triangleq[\underbrace{r+\gamma \cdot v_\omega\left(s'\right)-v_\omega\left(s\right)}_{\mathrm{TD} \text { Error} }] \cdot \nabla_{\boldsymbol{\theta}} \ln \pi_\theta\left(a \mid s\right) . g~θ(s,a)[TD Error r+γvω(s)vω(s)]θlnπθ(as). 用这个随机策略梯度做梯度上升即可优化策略网络
  • A2C 的伪代码如下
    初始化策略网络  π θ  和价值网络  v ω f o r    e p i s o d e    e = 1 → E    d o : 用当前策略 π θ 交互一条轨迹  s 1 , a 1 , r 1 , . . . , s n , a n , r n 计算所有 TD erro r   δ t = r t + γ ⋅ v ω ( s t + 1 ) − v ω ( s t ) 更新  v ω  参数  l ω = 1 2 n ∑ t = 1 n [ δ t ] 2 更新  π θ  参数  θ ← θ + β ⋅ ▽ θ ln ⁡ π θ ( a t ∣ s t ) ⋅ δ t ,   t = 1 , 2 , . . . , n e n d    f o r \begin{aligned} &初始化策略网络 \space \pi_\theta \space 和价值网络 \space v_\omega \\ &for \space\space episode \space\space e=1 \rightarrow E \space\space do :\\ &\quad\quad 用当前策略 \pi_{\theta} 交互一条轨迹\space s_1, a_1, r_1,...,s_n, a_n, r_n \\ & \quad\quad 计算所有 \space \text{TD erro}r \space \delta_t = r_t+\gamma \cdot v_\omega\left(s_{t+1}\right)-v_\omega\left(s_t\right)\\ &\quad\quad 更新 \space v_\omega \space 参数 \space l_\omega = \frac{1}{2n}\sum_{t=1}^n\Big[ \delta_t \Big]^2\\ &\quad\quad更新 \space \pi_\theta \space参数\space \theta \leftarrow \theta + \beta ·\triangledown_{\theta}\ln \pi_\theta(a_t|s_t) · \delta_t , \space t=1,2,...,n \\ &end \space\space for \end{aligned} 初始化策略网络 πθ 和价值网络 vωfor  episode  e=1E  do:用当前策略πθ交互一条轨迹 s1,a1,r1,...,sn,an,rn计算所有 TD error δt=rt+γvω(st+1)vω(st)更新 vω 参数 lω=2n1t=1n[δt]2更新 πθ 参数 θθ+βθlnπθ(atst)δt, t=1,2,...,nend  for

2.3.2 用 A2C 方法解决 CartPole 问题

  • 只需要重新定义 A2C agent,其他代码全部和 REINFORCE with baseline 一致
    class A2C(torch.nn.Module):
        def __init__(self, state_dim, hidden_dim, action_range, actor_lr, critic_lr, gamma, device):
            super().__init__()
            self.gamma = gamma
            self.device = device
            
            self.actor = PolicyNet(state_dim, hidden_dim, action_range).to(device)
            self.critic = VNet(state_dim, hidden_dim).to(device) 
            self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)
            self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)
            
        def take_action(self, state):
            state = torch.tensor(state, dtype=torch.float).to(self.device)
            state = state.unsqueeze(0)
            probs = self.actor(state)
            action_dist = torch.distributions.Categorical(probs)
            action = action_dist.sample()
            return action.item()
    
        def update(self, transition_dict):
            states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
            actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
            rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
            next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)
            dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)
    
            # Cirtic loss 
            td_target = rewards + self.gamma * self.critic(next_states) * (1-dones)
            critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))
    
            # Actor loss 
            td_error = td_target - self.critic(states)  
            probs = self.actor(states).gather(1, actions)
            log_probs = torch.log(probs)
            actor_loss = torch.mean(-log_probs * td_error.detach())
    
            # 更新网络参数
            self.actor_optimizer.zero_grad()
            self.critic_optimizer.zero_grad()
            actor_loss.backward()           
            critic_loss.backward()      
            self.actor_optimizer.step()     
            self.critic_optimizer.step()    
    

2.3.3 性能

  • 对比前文介绍的普通 Actor-Critic 方法和以上 A2C 方法的性能曲线,如下
    在这里插入图片描述
    可见普通 Actor-Critic 方法即使交互轨迹数量翻倍也难以收敛,而 A2C 收敛迅速,性能稳定

2.3.4 引入目标网络

  • 之前我们讲 DQN 时提到过关于 bootstrap 迭代的一个问题

    TD bootstrap 是在使用由 DQN 生成的优化目标 TD target 来优化 DQN 网络。这就导致优化目标随着训练进行不断变化,违背了监督学习的 i.i.d 原则,导致训练不稳定

    A2C 中的 critic 网络同样具有此问题,为了稳定训练,我们可以像 DQN 那样引入一个参数更新频率更低的目标网络来稳定 TD target,从而稳定训练过程。考虑到 A2C 本身是 on-policy 方法,这里不适合像 DQN 那样按照一定周期去替换目标网络参数,而是应该使用加权平均的方式来更新。设引入目标网络 v w ′ v_{w'} vw 和更新权重 τ \tau τ,A2C 算法的伪代码变为
    初始化策略网络  π θ   , 价值网络  v ω 和目标网络  v ω ′ f o r    e p i s o d e    e = 1 → E    d o : 用当前策略 π θ 交互一条轨迹  s 1 , a 1 , r 1 , . . . , s n , a n , r n 计算所有 TD erro r   δ t = r t + γ ⋅ v ω ′ ( s t + 1 ) − v ω ( s t ) 更新  v ω  参数  l ω = 1 2 n ∑ t = 1 n [ δ t ] 2 更新  v ω ′  参数  ω ′ ← τ w ′   +   ( 1 − τ ) w 更新  π θ  参数  θ ← θ + β ⋅ ▽ θ ln ⁡ π θ ( a t ∣ s t ) ⋅ δ t ,   t = 1 , 2 , . . . , n e n d    f o r \begin{aligned} &初始化策略网络 \space \pi_\theta \space ,价值网络 \space v_\omega 和目标网络 \space v_{\omega'} \\ &for \space\space episode \space\space e=1 \rightarrow E \space\space do :\\ &\quad\quad 用当前策略 \pi_{\theta} 交互一条轨迹\space s_1, a_1, r_1,...,s_n, a_n, r_n \\ & \quad\quad 计算所有 \space \text{TD erro}r \space \delta_t = r_t+\gamma \cdot v_{\omega'}\left(s_{t+1}\right)-v_\omega\left(s_t\right)\\ &\quad\quad 更新 \space v_\omega \space 参数 \space l_\omega = \frac{1}{2n}\sum_{t=1}^n\Big[ \delta_t \Big]^2\\ &\quad\quad 更新 \space v_{\omega'} \space 参数 \space \omega' \leftarrow \tau w' \space + \space (1-\tau)w \\ &\quad\quad更新 \space \pi_\theta \space参数\space \theta \leftarrow \theta + \beta ·\triangledown_{\theta}\ln \pi_\theta(a_t|s_t) · \delta_t , \space t=1,2,...,n \\ &end \space\space for \end{aligned} 初始化策略网络 πθ ,价值网络 vω和目标网络 vωfor  episode  e=1E  do:用当前策略πθ交互一条轨迹 s1,a1,r1,...,sn,an,rn计算所有 TD error δt=rt+γvω(st+1)vω(st)更新 vω 参数 lω=2n1t=1n[δt]2更新 vω 参数 ωτw + (1τ)w更新 πθ 参数 θθ+βθlnπθ(atst)δt, t=1,2,...,nend  for

  • 带目标网络的的 A2C agent 实现如下,其他代码基本和 A2C 一致

    class A2C_Target(torch.nn.Module):
        def __init__(self, state_dim, hidden_dim, action_range, target_weight, actor_lr, critic_lr, gamma, device):
            super().__init__()
            self.gamma = gamma
            self.device = device
            self.target_weight = target_weight 
    
            self.actor = PolicyNet(state_dim, hidden_dim, action_range).to(device)
            self.critic = VNet(state_dim, hidden_dim).to(device) 
            self.target = VNet(state_dim, hidden_dim).to(device) 
            self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)
            self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)
            
        def take_action(self, state):
            state = torch.tensor(state, dtype=torch.float).to(self.device)
            state = state.unsqueeze(0)
            probs = self.actor(state)
            action_dist = torch.distributions.Categorical(probs)
            action = action_dist.sample()
            return action.item()
    
        def update(self, transition_dict):
            states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
            actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
            rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
            next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)
            dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)
    
            # Cirtic loss 
            td_target = rewards + self.gamma * self.target(next_states) * (1-dones)
            critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))
    
            # Actor loss 
            td_error = td_target - self.critic(states)  
            probs = self.actor(states).gather(1, actions)
            log_probs = torch.log(probs)
            actor_loss = torch.mean(-log_probs * td_error.detach())
    
            # 更新网络参数
            self.actor_optimizer.zero_grad()
            self.critic_optimizer.zero_grad()
            actor_loss.backward()           
            critic_loss.backward()      
            self.actor_optimizer.step()     
            self.critic_optimizer.step()    
    
            # 更新 target 网络参数为 target 和 critic 的加权平均
            w = self.target_weight  
            params_target = list(self.target.parameters())
            params_critic = list(self.critic.parameters())
    
            for i in range(len(params_target)):
                new_param = w * params_target[i] + (1 - w) * params_critic[i]
                params_target[i].data.copy_(new_param)
    
  • τ = 0.95 \tau=0.95 τ=0.95 时性能较好,和 A2C 相比如下
    在这里插入图片描述
    可见引入目标网络后收敛更快,收敛后也更稳定

3. 总结

  • 在策略梯度中加入基线 (baseline) 可以降低方差,显著提升实验效果。实践中常用 b = V π ( s ) b = V_\pi(s) b=Vπ(s) 作为 baseline。
    • 可以用基线来改进 REINFORCE 算法。这时我们仍然用真实 return 来估计策略梯度中的 Q Q Q 价值,并引入价值网络 v w v_w vw,使用 MC 方法来估计状态价值函数 V π ( s ) V_\pi(s) Vπ(s) 作为 baseline。如此计算出随机策略梯度后,和原始 REINFORCE 一样用随机策略梯度上升更新策略网络 π θ ( a ∣ s ) π_\theta(a|s) πθ(as)
    • 可以用基线来改进 actor-critic,得到的方法叫做 advantage actor-critic (A2C)。A2C 也有一个策略网络 π θ ( a ∣ s ) π_\theta(a|s) πθ(as) 和一个价值网络 v ω ( s ) v_\omega(s) vω(s)。它使用 TD 算法(Sarsa)来更新价值网络计算随机策略梯度,并同样用梯度上升更新策略网络 π θ ( a ∣ s ) π_\theta(a|s) πθ(as)。为了稳定 TD bootstarp,可以像 DQN 那样引入目标价值网络 v ω ′ ( s ) v_{\omega'}(s) vω(s) 用于计算 TD target,目标网络通过加权平均方式进行 soft update,可以进一步提高 A2C 的性能

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

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

相关文章

复现YOLOv8改进最新MPDIoU:有效和准确的边界盒回归的损失,打败G/E/CIoU,效果明显!!!

MPDIoU: A Loss for Efficient and Accurate Bounding Box Regression 论文简介MPDIoU核心设计思路论文方法实验部分加入YOLOv5代码论文地址:https://arxiv.org/pdf/2307.07662.pdf 论文简介 边界盒回归(Bounding box regression, BBR)广泛应用于目标检测和实例分割,是目标…

详细介绍如何使用HuggingFace和PyTorch进行医学图像分割-附源码

医学图像分割是一种创新过程,使外科医生能够拥有虚拟的“X 射线视觉”。它是医疗保健领域非常有价值的工具,可提供非侵入性诊断和深入分析。考虑到这一点,在这篇文章中,我们将探索威斯康辛大学麦迪逊分校胃肠道图像分割Kaggle 挑战数据集。作为该项目的一部分,我们将使用 …

python核心-面向对象-三大特性-综合案例

# 定义三个类, 小狗, 小猫, 人 # 小狗: 姓名, 年龄(默认1岁); 吃饭, 玩, 睡觉, 看家(格式: 名字是xx, 年龄xx岁的小狗在xx) # 小猫: 姓名, 年龄(默认1岁); 吃饭, 玩, 睡觉, 捉老鼠(格式: 名字是xx, 年龄xx岁的小猫在xx) # 人: 姓名, 年龄(默认1岁), 宠物; 吃饭…

【替换】批量替换命令文件名称或者文件内容

你是否困扰过,每次文件F2进行修改,十几个没关系,百来个写代码麻烦,那就使用shell命令吧 执行语句 先养成习惯查询验证,因为是批量替换,为了防止全局替换嘎嘎叫 替换文件名 # 【习惯】先使用顾虑查询&…

暴力猴插件简明开发教程->百度首页默认设置为我的关注

文章目录 暴力猴插件开发简明教程->百度首页默认设置为我的关注缘起缘灭思路实现尾声 暴力猴插件开发简明教程->百度首页默认设置为我的关注 缘起 在我的百度首页有很多自己设置的导航链接(接近100个),里面放了我常用的网站, 如下图 但是最近一段时间, 我发现百度做了一…

算法38:反转链表

一、需求 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2: 输入:head [1,2] 输出:[2,1] 示例3&#xff…

电器水族设备加热器UL1018检测报告

UL1018---电气水族设备 适用范围:本标准要求适用于300V或以下,用于室内商业或家用的水族器材上的设备。如加热器、水泵、过滤器、反射器 电线支架等相似用途的产品。 本标准不包括: 不是和水族器材一起用的照明器或台子;水族容器 一、UL1018标准测试内…

R中无法安装GitHub包 | 花费大量时间的小小经验

写在前面 最近在安装R包时,遇到无法安装包的情况,然后自己就各种捣鼓。最后,R环境也会让自己弄崩,如果你遇到此情况R语言无法调用stats.dll的问题解决方案,可以参考我前面的方法捣鼓一下。 那么,这个推文也是记录自己…

【Golang】Golang进阶系列教程--为什么 Go 不支持 []T 转换为 []interface

文章目录 前言官方解释内存布局程序运行中的内存布局通用方法 前言 在 Go 中,如果 interface{} 作为函数参数的话,是可以传任意参数的,然后通过类型断言来转换。 举个例子: package mainimport "fmt"func foo(v inter…

2023年自然语言处理与信息检索国际会议(ECNLPIR 2023) | EI Compendex, Scopus双检索

会议简介 Brief Introduction 2023年自然语言处理与信息检索国际会议(ECNLPIR 2023) 会议时间:2023年9月22日-24日 召开地点:中国杭州 大会官网:ECNLPIR 2023-2023 Eurasian Conference on Natural Language Processing and Information Retr…

【GO】go语言入门实战 —— 命令行在线词典

文章目录 程序介绍抓包代码生成生成request body解析respond body完整代码 字节青训营基础班学习记录。 程序介绍 在运行程序的时候以命令行的形式输入要查询的单词,然后程序返回单词的音标、释义等信息。 示例如下: 抓包 我们选择与网站https://fany…

【Linux命令200例】用ln创建链接文件

🏆作者简介,黑夜开发者,全栈领域新星创作者✌,2023年6月csdn上海赛道top4。 🏆本文已收录于专栏:Linux命令大全。 🏆本专栏我们会通过具体的系统的命令讲解加上鲜活的实操案例对各个命令进行深入…

【Linux下6818开发板(ARM)】在液晶屏上显示RGB颜色和BMP图片

(꒪ꇴ꒪ ),hello我是祐言博客主页:C语言基础,Linux基础,软件配置领域博主🌍快上🚘,一起学习!送给读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!作者水平很有限,如果发现错误&#x…

Leangoo领歌敏捷看板工具,什么是敏捷看板?

敏捷看板 看板是一个团队共享的工作区,在看板上团队可以进行实时的工作任务协同,团队的工作以卡片的形式体现。通过泳道和任务列表组织管理。需求、任务、问题、缺陷 都作为卡片放在看板上,通过看板实现可视化和透明化的 管理,通…

mybatisx插件使用

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.java1234.mapper.SysUserMap…

听说 Spring Bean 的创建还有一条捷径?

文章目录 1. resolveBeforeInstantiation1.1 applyBeanPostProcessorsBeforeInstantiation1.2 applyBeanPostProcessorsAfterInitialization1.3 案例 2. 源码实践2.1 切面 Bean2.2 普通 Bean 在 Spring Bean 的创建方法中&#xff0c;有如下一段代码&#xff1a; AbstractAutow…

uniapp 微信小程序:v-model双向绑定问题(自定义 props 名无效)

uniapp 微信小程序&#xff1a;v-model双向绑定问题&#xff08;自定义 props 名无效&#xff09; 前言问题双向绑定示例使用 v-model使用 v-bind v-on使用 sync 修饰符 参考资料 前言 VUE中父子组件传递数据的基本套路&#xff1a; 父传子 props子传父 this.$emit(事件名, …

Linux 终端生成二维码

1、安装qrencode [rootnode1 script]# yum -y install qrencode2、输出正常的 [rootnode1 ~]# echo https://www.github.com|qrencode -o - -t utf83、输出彩色的 [rootnode1 ~]# qrencode -t utf8 -s 1 https://www.github.com|lolcatPS&#xff1a;没有lolcat命令 #由于…

动静态网页、Django创建表关系、Django框架的请求生命周期流程图

一、request对象的几个方法 在视图函数中写方法的时候&#xff0c;都会有一个形参requestdef index(request):passrequest.method # GET POST request.GET.get() # 它获取最后一个元素值 request.GET.getlist() # 获取到所有的request.POST.get() # 它获取最后一个元素值 req…

2023年Q2京东清洁电器行业分析报告(京东数据平台)

如今&#xff0c;消费者尤其是年轻一代对智能产品替代繁琐家务劳动的需求日趋强烈&#xff0c;这激发了对清洁电器产品需求的增长。随着各类清洁电器的热度持续增长&#xff0c;我国清洁电器的零售额也在逐年攀升。 接下来结合鲸参谋平台数据&#xff0c;我们一起来分析当前清洁…