Hands on RL 之 Proximal Policy Optimization (PPO)

news2024/12/26 21:23:12

Hands on RL 之 Proximal Policy Optimization (PPO)

文章目录

  • Hands on RL 之 Proximal Policy Optimization (PPO)
    • 1. 回顾Policy Gradient和TRPO
    • 2. PPO (Clip)
    • 3. PPO(Penalty)
    • 4. PPO中Advantage Function的计算
    • 5.实现 PPO-Clip
      • Reference

1. 回顾Policy Gradient和TRPO

​ 首先回顾一下Policy Gradient (PG)的方法,在策略梯度方法PG中,我们使用参数化的神经网络(Neural Network,NN) π ( a ∣ s ; θ ) \pi(a|s;\theta) π(as;θ) π θ ( a ∣ s ) \pi_\theta(a|s) πθ(as)来表示策略网络,则我们需要优化的目标函数可以写作
PG: L P G ( θ ) = E ^ t [ log ⁡ π θ ( a t ∣ s t ) A ^ t ] \text{PG:} \quad \textcolor{blue}{L^{PG}(\theta) = \hat{\mathbb{E}}_t [\log\pi_\theta(a_t|s_t) \hat{A}_t]} PG:LPG(θ)=E^t[logπθ(atst)A^t]
其中 A ^ t \hat{A}_t A^t是对优势函数(Advantage Function)的估计,这表明已经将状态价值作为baseline b ( S ) b(S) b(S)了, L P G L^{PG} LPG表示的是policy gradient。

​ 然后再回顾一下Trust Region Policy Optimization (TRPO)的方法,在TRPO中引入了KL散度(Kullback-Leibler Divergence)的概念,KL散度是用于描述我们用概率分布Q来估计真是分布P的编码损失

KL散度的定义如下

​ 假设对随机变量 ξ \xi ξ,存在两个概率分布P和Q,其中P是真实的概率分布,Q是较容易获得的概率分布。如果 ξ \xi ξ是离散的随机变量,那么定义从P到Qd KL散度为
D K L ( P , Q ) = ∑ i P ( i ) ln ⁡ ( P ( i ) Q ( i ) ) \mathbb{D}_{KL}(P,Q) = \sum_i P(i)\ln(\frac{P(i)}{Q(i)}) DKL(P,Q)=iP(i)ln(Q(i)P(i))
如果 ξ \xi ξ是连续变量,则定义从P到Q的KL散度为
D K L ( P , Q ) = ∫ − ∞ ∞ p ( x ) ln ⁡ ( p ( x ) q ( x ) ) d x \mathbb{D}_{KL}(P,Q) = \int^\infty_{-\infty}p(\mathbb{x})\ln(\frac{p(\mathbb{x})}{q(\mathbb{x})}) d\mathbb{x} DKL(P,Q)=p(x)ln(q(x)p(x))dx
​ 在TRPO中待优化的目标函数即为
TRPO: { max ⁡ θ L T R P O ( θ ) = E ^ t [ π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) A ^ t ] subject to E ^ t [ D K L ( π θ old ( ⋅ ∣ s t ) , π θ ( ⋅ ∣ s t ) ) ] ≤ δ \text{TRPO:} \left \{ \begin{aligned} \max_\theta \quad & \textcolor{blue}{L^{TRPO}(\theta) = \hat{\mathbb{E}}_t \Big[\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\hat{A}_t \Big]} \\ \text{subject to} \quad & \textcolor{blue}{\hat{\mathbb{E}}_t \Big[\mathbb{D}_{KL} \big(\pi_{\theta_{\text{old}}}(\cdot|s_t),\pi_\theta(\cdot|s_t) \big) \Big] \le \delta} \end{aligned} \right. TRPO: θmaxsubject toLTRPO(θ)=E^t[πθold(atst)πθ(atst)A^t]E^t[DKL(πθold(st),πθ(st))]δ
其中 L T R P O L^{TRPO} LTRPO表示 trust region policy optimization, θ old \theta_{\text{old}} θold表示更新前的网络参数。

2. PPO (Clip)

​ 在介绍之前先定义几个符号,用 r t ( θ ) r_t(\theta) rt(θ)来表示概率比,即 r t ( θ ) = π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) \textcolor{blue}{r_t(\theta)=\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_\text{old}}(a_t|s_t)}} rt(θ)=πθold(atst)πθ(atst),那么则有 r t ( θ old ) = 1 r_t(\theta_\text{old})=1 rt(θold)=1,所以在TRPO中待优化的目标函数就变成了
L T R P O = E ^ t [ π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) A ^ t ] = E ^ t [ r t ( θ ) A ^ t ] L^{TRPO} = \hat{\mathbb{E}}_t \Big[ \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\hat{A}_t \Big] = \hat{\mathbb{E}}_t [r_t(\theta)\hat{A}_t] LTRPO=E^t[πθold(atst)πθ(atst)A^t]=E^t[rt(θ)A^t]
PPO clip版本就是在TRPO的基础上增加了截断(clip)的操作,截断即是将概率比限制在一定的范围之内,即 r t ( θ ) ∈ [ 1 − ϵ , 1 + ϵ ] r_t(\theta)\in[1-\epsilon, 1+\epsilon] rt(θ)[1ϵ,1+ϵ],其中 ϵ \epsilon ϵ是指定范围的超参数表示截断的范围,一般设置 ϵ = 0.2 \epsilon=0.2 ϵ=0.2。修改后的替代目标(surrogate objective)可以表示为
PPO-clip: L C L I P ( θ ) = E ^ t [ min ⁡ ( r t ( θ ) A ^ t , clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] \text{PPO-clip:} \quad \textcolor{blue}{L^{CLIP}(\theta) = \hat{\mathbb{E}}_t \Big[ \min(r_t(\theta)\hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t) \Big]} PPO-clip:LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1ϵ,1+ϵ)A^t)]
其中 L C L I P L^{CLIP} LCLIP表示PPO-clip,截断操作是 clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) clip(rt(θ),1ϵ,1+ϵ)的操作为 max ⁡ ( min ⁡ ( r t ( θ ) , 1 + ϵ ) , 1 − ϵ ) \max(\min(r_t(\theta), 1+\epsilon), 1-\epsilon) max(min(rt(θ),1+ϵ),1ϵ),即让 r t ( θ ) ∈ [ 1 − ϵ , 1 + ϵ ] r_t(\theta)\in[1-\epsilon, 1+\epsilon] rt(θ)[1ϵ,1+ϵ]

clip操作的解释如下所示

Image

3. PPO(Penalty)

​ PPO penalty版本的改进非常简单,就是在TRPO的基础上,将TRPO中的约束条件作为惩罚项(penalty)加入到目标函数中,并且使用一个自适应的学习率来更新惩罚项。数学的表示即为

PPO-penalty: L K L P E N ( θ ) = E ^ t [ π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) A ^ t − β D K L ( π θ old ( ⋅ ∣ s t ) , π θ ( ⋅ ∣ s t ) ) ] \text{PPO-penalty:} \quad \textcolor{blue}{L^{KLPEN}(\theta) = \hat{\mathbb{E}}_t \Big[ \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\hat{A}_t - \beta \mathbb{D}_{KL} \big(\pi_{\theta_{\text{old}}}(\cdot|s_t),\pi_\theta(\cdot|s_t) \big) \Big]} PPO-penalty:LKLPEN(θ)=E^t[πθold(atst)πθ(atst)A^tβDKL(πθold(st),πθ(st))]

其中 β \beta β按照以下方式进行更新

d = E ^ t [ D K L ( π θ old ( ⋅ ∣ s t ) , π θ ( ⋅ ∣ s t ) ) ] If  d < d t a r g / 1.5 , β ← β / 2 If  d > d t a r g × 1.5 , β ← β × 2 \begin{aligned} d & = \hat{\mathbb{E}}_t[\mathbb{D}_{KL} \big(\pi_{\theta_{\text{old}}}(\cdot|s_t),\pi_\theta(\cdot|s_t) \big)] \\ \text{If } d & < d_{targ} / 1.5, \beta \leftarrow \beta/2 \\ \text{If } d & > d_{targ} \times 1.5, \beta \leftarrow \beta \times 2 \end{aligned} dIf dIf d=E^t[DKL(πθold(st),πθ(st))]<dtarg/1.5,ββ/2>dtarg×1.5,ββ×2

原论文中指出,经过大量实验表明,PPO-clip的效果总是要优于PPO-penalty。

4. PPO中Advantage Function的计算

Advantage function优势函数的定义是

A ^ t = Q ( s t , a t ) − V ( s t ) \hat{A}_t = Q(s_t,a_t) - V(s_t) A^t=Q(st,at)V(st)

当我们使用类似n-step Sarsa的方式来估计 Q ( s t , a t ) Q(s_t,a_t) Q(st,at)时,那么优势函数就变为

A ^ t = r t + γ r t + 1 + ⋯ + γ T − t + 1 r T − 1 + γ T − t V ( s T ) − V ( s t ) \hat{A}_t = r_t + \gamma r_{t+1} + \cdots + \gamma^{T-t+1}r_{T-1} + \gamma^{T-t}V(s_T) - V(s_t) A^t=rt+γrt+1++γTt+1rT1+γTtV(sT)V(st)

为了方便使用,我们可以将上式再写成truncated fashion截断的形式

A ^ t = δ t + ( γ λ ) δ t + 1 + ⋯ + ( γ λ ) T − t + 1 δ T − 1 where  δ t = r t + γ V ( s t + 1 ) − V ( s t ) \begin{aligned} &\textcolor{red}{\hat{A}_t = \delta_t + (\gamma\lambda)\delta_{t+1} + \cdots + (\gamma\lambda)^{T-t+1}\delta_{T-1}} \\ \text{where } & \textcolor{red}{\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)} \end{aligned} where A^t=δt+(γλ)δt+1++(γλ)Tt+1δT1δt=rt+γV(st+1)V(st)

Pesudocode

Image

5.实现 PPO-Clip

import torch
import torch.nn as nn
import torch.nn.functional as F
from tqdm import tqdm
import gym
import numpy as np
import matplotlib.pyplot as plt

# Policy Network
class PolicyNet(nn.Module):
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(PolicyNet, self).__init__()
        self.fc1 = nn.Linear(in_features=state_dim, out_features=hidden_dim)
        self.fc2 = nn.Linear(in_features=hidden_dim, out_features=action_dim)
    
    def forward(self, observation):
        x = F.relu(self.fc1(observation))
        x = F.softmax(self.fc2(x), dim=1)
        return x

# State Value Network
class ValueNet(nn.Module):
    def __init__(self, state_dim, hidden_dim):
        super(ValueNet, self).__init__()
        self.fc1 = nn.Linear(in_features=state_dim, out_features=hidden_dim)
        self.fc2 = nn.Linear(in_features=hidden_dim, out_features=1)
    
    def forward(self, observation):
        x = F.relu(self.fc1(observation))
        return self.fc2(x)


# PPO Clip Algorithm
class PPO_clip():
    def __init__(self, state_dim, hidden_dim, action_dim, actor_lr, critic_lr, 
                lmbda, epochs, eps, gamma, device):
        self.actor = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
        self.critic = ValueNet(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)

        self.device = device
        self.lmbda = lmbda
        self.gamma = gamma
        self.epochs = epochs    # 一条序列的数据用来训练的轮数
        self.eps = eps          # PPO中截断范围的参数
    
    def calculate_advantage(self, td_delta):
        # compute advantage over td_delta
        td_delta = td_delta.detach().numpy()
        temp = 0
        advantage_list = []
        for td in td_delta[::-1]:
            temp = temp * self.gamma * self.lmbda + td
            advantage_list.append(temp)
        advantage = torch.tensor(np.array(advantage_list[::-1]), dtype=torch.float)
        return advantage.to(self.device)
    
    def choose_action(self, state):
        state = torch.tensor([state], dtype=torch.float).to(self.device)
        probs = self.actor(state)
        action_dist = torch.distributions.Categorical(probs)
        action = action_dist.sample().item()
        return action
    
    def learn(self, transition_dict):
        states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions'], dtype=torch.int64).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)

        td_target = rewards + self.gamma * self.critic(next_states) * (1-dones)
        td_delta = td_target - self.critic(states)
        advantage = self.calculate_advantage(td_delta.cpu())

        old_log_probs = torch.log(self.actor(states).gather(dim=1, index=actions)).detach()

        for _ in range(self.epochs):
            log_probs = torch.log(self.actor(states).gather(dim=1, index=actions))
            ratio = torch.exp(log_probs - old_log_probs)
            surr1 = ratio * advantage
            surr2 = torch.clamp(ratio, 1-self.eps, 1+self.eps) * advantage
            actor_loss = torch.mean(-torch.min(surr1, surr2))

            critic_loss = torch.mean(F.mse_loss(td_target.detach(), self.critic(states)))

            self.actor_optimizer.zero_grad()
            self.critic_optimizer.zero_grad()
            actor_loss.backward()
            critic_loss.backward()
            self.actor_optimizer.step()
            self.critic_optimizer.step()


def train_on_policy_agent(env, agent, num_episodes, render, seed_number):
    return_list = []
    for i in range(10):
        with tqdm(total=int(num_episodes/10), desc="Iteration:%d"%(i+1)) as pbar:
            for i_episode in range(int(num_episodes/10)):
                episode_return = 0
                done = False
                transition_dict = {
                    'states': [],
                    'actions': [],
                    'next_states': [],
                    'rewards': [],
                    'dones': []
                }
                observation, _ = env.reset(seed=seed_number)

                while not done:
                    if render:
                        env.render()
                    action = agent.choose_action(observation)
                    observation_, reward, terminated, truncated, _ = env.step(action)
                    done = terminated or truncated
                    # save one episode experience into a dict
                    transition_dict['states'].append(observation)
                    transition_dict['actions'].append(action)
                    transition_dict['rewards'].append(reward)
                    transition_dict['next_states'].append(observation_)
                    transition_dict['dones'].append(done)
                    # swap state
                    observation = observation_
                    # compute one episode return
                    episode_return += reward
                return_list.append(episode_return)
                # agent learning
                agent.learn(transition_dict)
                if((i_episode + 1) % 10 == 0):
                    pbar.set_postfix({
                        'episode': '%d'%(num_episodes / 10 * i + i_episode + 1),
                        'return': '%.3f'%(np.mean(return_list[-10:]))
                    })
                pbar.update(1)
    env.close()
    return return_list


def moving_average(a, window_size):
    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 plot_curve(return_list, mv_return, algorithm_name, env_name):
    episodes_list = list(range(len(return_list)))
    plt.plot(episodes_list, return_list, c='gray', alpha=0.6)
    plt.plot(episodes_list, mv_return)
    plt.xlabel('Episodes')
    plt.ylabel('Returns')
    plt.title('{} on {}'.format(algorithm_name, env_name))
    plt.show()


if __name__ == "__main__":

    # reproducible
    seed_number = 0
    np.random.seed(seed_number)
    torch.manual_seed(seed_number)

    num_episodes = 1000     # episodes length
    hidden_dim = 256        # hidden layers dimension
    gamma = 0.98            # discounted rate
    lmbda = 0.95            # coefficient for computing advantage 
    epochs = 10             # update parameters interval
    eps = 0.2               # clip coefficient
    actor_lr = 1e-3         # lr of actor
    critic_lr = 1e-2        # lr of critic
    device = torch.device('cuda' if torch.cuda.is_available() else 'gpu')
    env_name = 'CartPole-v1'

    render = False
    if render:
        env = gym.make(id=env_name, render_mode='human')
    else:
        env = gym.make(id=env_name)
    
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.n

    agent = PPO_clip(state_dim, hidden_dim, action_dim, actor_lr, critic_lr, lmbda, epochs, eps, gamma, device)
    return_list = train_on_policy_agent(env, agent, num_episodes, render, seed_number)
    mv_return = moving_average(return_list, 9)
    plot_curve(return_list, mv_return, 'PPO-Clip', env_name)

最后的训练回报(return)曲线如下

Image

Reference

Tutorial: Hands on RL

Paper: Proximal Policy Optimization Algorithm

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

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

相关文章

构建Actual网页客户端镜像

什么是 Actual &#xff1f; Actual 是一款超快速且注重隐私的本地优先的财务应用程序&#xff0c;用于管理您的财务。其核心是经过充分验证且深受喜爱的信封预算方法。它是 100% 免费和开源的。 Actual 具有多设备同步、可选的端到端加密等等功能。默认情况下&#xff0c;它不…

ssm医院门诊挂号系统源码和论文PPT

ssm医院门诊挂号系统源码和论文PPT008 开题报告 任务书 源码 数据库sql 论文 开发环境&#xff1a; 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 1.选题的背景和意义 …

高性能MySQL实战(二):索引

大家好&#xff0c;我是 方圆。我们在上篇 高性能MySQL实战&#xff08;一&#xff09;&#xff1a;表结构 中已经建立好了表结构&#xff0c;这篇我们则是针对已有的表结构和搜索条件为表创建索引。除此之外&#xff0c;我还会讲一些关于索引必须要了解的知识。原文收录在我的…

原来这才叫休息!——科学家揭示真正的“休息模式”

什么叫做休息&#xff1f;好好休息个周末&#xff1f;好好出去旅游一下&#xff1f;但事实上&#xff0c;往往越休息越感觉累。为什么呢&#xff1f;也许我们对休息存在误解&#xff0c;这篇文章会帮我们分析究竟该如何休息。 为什么你睡了11个小时仍然觉得疲累&#xff1f;为什…

【量化课程】06_化调仓策略

文章目录 6.1 如何衡量投资组合的收益率6.1.1 投资组合收益率的计算方法6.1.2 投资组合的绝对收益率和相对收益率 6.2 如何衡量投资组合的风险6.2.1 风险的定义6.2.2 投资组合的风险6.2.3 衡量投资组合的风险 6.3 最优化方法计算投资组合的最佳仓位6.3.1 等权重6.3.2 市值加权6…

Linux下常见的代理服务器软件介绍

在Linux系统中&#xff0c;代理服务器是我们搭建网络环境和处理网络请求的常用工具。但是&#xff0c;你知道Linux下常见的代理服务器软件有哪些吗&#xff1f;本文将为你带来对几款常见的Linux代理服务器软件的介绍&#xff0c;帮助你选择适合的代理服务器。 一、Squid&#…

根据数组中元素的位置号x,y和指定的计算规则z=f(x,y)创建数组,让x,y位置上的值是znp.fromfunction()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 根据数组中元素的位置号x,y 和指定的计算规则zf(x,y) 创建数组&#xff0c;让x,y位置上的值是z np.fromfunction() 选择题 下列说法错误的是? import numpy as np def func(i, j): return …

书单背景图片素材哪里找?这个工具赶紧用起来

好的文案是很重要的&#xff0c;但是如何找到好的文案却是一件很困难的事情。以下是一些可以寻找好的文案的方法分析&#xff0c;以及如何把书单文章转到视频的操作分享。 1.书籍和杂志&#xff1a;阅读关于广告、营销、文案和创意的书籍和杂志可以帮助你了解不同的文案类型和风…

《零基础实践深度学习》(第2版)学习笔记,(二)机器学习和深度学习综述

文章目录 1. 人工智能、机器学习、深度学习的关系2. 机器学习2.1 实现原理2.2 如何实施 3. 深度学习神经网络核心概念 1. 人工智能、机器学习、深度学习的关系 **人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;**是研发用于模拟、延伸和扩展人的智能…

【MySQL--->数据类型】

文章目录 [TOC](文章目录) 一、数据类型分类二、整型类型三、bit(位)类型四、float类型五、decimal类型六、char和varchar类型1.char类型2.varchar3.char与varchar的区别 七、日期与时间类型八、enum和set 一、数据类型分类 二、整型类型 数值类型有数据存储上限,而且每个类型都…

Crond计划任务和用户权限提升

目录 前言 一、一次性任务 1、at实现&#xff0c;atd服务 2、查看atd服务的状态&#xff1a;systemctl status atd 二、周期性任务 1.在/etc/crontab文件中写入计划任务 2、crontab文件的含义&#xff1a; 3、操作设置 三、用户权限提升 3.1 su 3.2 sudo提权 总结 前言…

【灵商课堂】知识的结束就是智慧的开始

心无挂碍&#xff0c;心无恐惧 1、知识不会通向智慧。 我们累积了关于很多事情的大量知识&#xff0c;但是要按照学到的知识去明智地行动&#xff0c;看起来几乎是不可能的。学校、学院和大学传授有关行为、宇宙、科学和各种技术的知识&#xff0c;但是这些教育中心很少帮助一个…

油耳朵适合什么样的耳机听歌,到底有没有适合油耳的耳机?

骨传导耳机就是利用震动来传递声音的耳机&#xff0c;在运动时佩戴骨传导耳机&#xff0c;可以听歌也能听周围的声音&#xff0c;提高了运动时的安全性。目前市面上的骨传导耳机也是琳琅满目。今天就来给大家分享下目前市面上比较常见的几款骨传导耳机。希望对正在选购骨传导耳…

【Java】教你如何实现接口防刷

文章目录 前言思路分析具体实现编写 RedisUtils定义Interceptor 改进 前言 我们在浏览网站后台的时候&#xff0c;假如我们频繁请求&#xff0c;那么网站会提示 “请勿重复提交” 的字样&#xff0c;那么这个功能究竟有什么用呢&#xff0c;又是如何实现的呢&#xff1f; 其实…

张驰咨询:提高企业竞争力,六西格玛设计公司(DFSS)在行动

六西格玛设计公司(DFSS)是一种专业从事六西格玛设计的企业&#xff0c;其主要作用是为客户提供高效的六西格玛设计服务&#xff0c;以帮助客户实现高品质、低成本和高效率的产品开发过程。六西格玛设计公司通常拥有一支专业的团队&#xff0c;具有丰富的六西格玛设计经验和技术…

Mybatis 源码 ④ :TypeHandler

文章目录 一、前言二、DefaultParameterHandler1. DefaultParameterHandler#setParameters1.1 UnknownTypeHandler1.2 自定义 TypeHandler 三、DefaultResultSetHandler1. hasNestedResultMaps2. handleRowValuesForNestedResultMap2.1 resolveDiscriminatedResultMap2.2 creat…

git一次错误提交的回滚(不同分支因merge-需回滚)

场景&#xff1a;提交到sit的代码&#xff0c;结果解决冲突merge了DEV的代码&#xff0c;所以要回滚到合并之前的代码 &#xff08;原因是我再网页上处理了冲突&#xff0c;他就自动merge了,如图—所以还是idea处理冲突&#xff0c;可控&#xff09; 方式二&#xff1a; &…

【网络基础实战之路】基于BGP协议中的联邦号连接三个AS区域的实战详解

系列文章传送门&#xff1a; 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 【网络基础实战之路】基于…

Windows CMD 关闭,启动程序

Windows CMD 关闭&#xff0c;启动程序 1. Windows 通过 CMD 命令行关闭程序 示例&#xff1a;通过 taskkill 命令关闭 QQ 管家&#xff0c;但是这里有个问题&#xff0c;使用命令行关闭 QQ 管家时&#xff0c;会提示“错误: 无法终止 PID 1400 (属于 PID 22116 子进程)的进程…

CAR-T攻克实体瘤发展现状及未来挑战

CAR-T细胞疗法已经成为肿瘤治疗领域的一项重要新兴治疗方法。CAR-T&#xff08;Chimeric Antigen Receptor T-cell&#xff09;细胞疗法利用改造的T细胞来攻击患者体内的肿瘤细胞&#xff0c;具有针对性强、治疗效果显著等特点。 目前&#xff0c;在血液瘤领域&#xff0c;CAR-…