零、题记

这篇博客一方面为了记录当前的知识点,另一方面PPO算法实在是太重要了,不但要从理论上理解它到底是怎样实现的,还需要从代码方面进行学习和记录,这里我就通俗的将这个知识点进行简单的记录,用来日后我自己的巩固和大家的交流学习。

下面均是我自己个人见解,如有不对之处,欢迎评论区指出错误!

一. PPO背景引入

这里简要交代PPO的算法原理及思想过程,主要记录自己的笔记,记录比较详细,可以查看,后面代码会紧紧贴合前面的内容,并且会再次提到一些细节。



 好到这里就是PPO的基本思想和RL的前期铺垫工作

二 代码理解

代码选择的是 动手学强化学习

下面我将逐一刨析整个代码的全部过程,之后大家可以将这些过程迁移到Issac Gym中进行学习和思考。

1 task_PPO.py文件

点击查看代码
import gym
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
import rl_utils class PolicyNet(torch.nn.Module):
def __init__(self, state_dim, hidden_dim, action_dim):
super(PolicyNet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, action_dim) def forward(self, x):
x = F.relu(self.fc1(x))
return F.softmax(self.fc2(x), dim=1) class ValueNet(torch.nn.Module):
def __init__(self, state_dim, hidden_dim):
super(ValueNet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, 1) def forward(self, x):
x = F.relu(self.fc1(x))
return self.fc2(x) class PPO:
''' PPO算法,采用截断方式 '''
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.gamma = gamma
self.lmbda = lmbda
self.epochs = epochs # 一条序列的数据用来训练轮数
self.eps = eps # PPO中截断范围的参数
self.device = device def take_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()
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)
td_target = rewards + self.gamma * self.critic(next_states) * (1 -
dones)
td_delta = td_target - self.critic(states)
advantage = rl_utils.compute_advantage(self.gamma, self.lmbda,
td_delta.cpu()).to(self.device)
old_log_probs = torch.log(self.actor(states).gather(1,
actions)).detach() for _ in range(self.epochs):
log_probs = torch.log(self.actor(states).gather(1, 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)) # PPO损失函数
critic_loss = torch.mean(
F.mse_loss(self.critic(states), td_target.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() if __name__ == "__main__":
#初始化参数
actor_lr = 1e-3
critic_lr = 1e-2
num_episodes = 500
hidden_dim = 128
gamma = 0.98
lmbda = 0.95
epochs = 10
eps = 0.2
device = torch.device("cpu")
env_name = "CartPole-v1"
env = gym.make(env_name)
env.reset(seed=0)
torch.manual_seed(0)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
#初始化智能体
agent = PPO(state_dim, hidden_dim, action_dim, actor_lr, critic_lr, lmbda,
epochs, eps, gamma, device)
#开始训练
return_list = rl_utils.train_on_policy_agent(env, agent, num_episodes) episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('PPO on {}'.format(env_name))
plt.show() mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('PPO on {}'.format(env_name))
plt.show()

2 rl_utils.py文件

点击查看代码
from tqdm import tqdm
import numpy as np
import torch
import collections
import random class ReplayBuffer:
def __init__(self, capacity):
self.buffer = collections.deque(maxlen=capacity) def add(self, state, action, reward, next_state, done):
self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size):
transitions = random.sample(self.buffer, batch_size)
state, action, reward, next_state, done = zip(*transitions)
return np.array(state), action, reward, np.array(next_state), done def size(self):
return len(self.buffer) 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 train_on_policy_agent(env, agent, num_episodes):
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()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = 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(done)
state = next_state
episode_return += reward
return_list.append(episode_return)
agent.update(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)
return return_list def train_off_policy_agent(env, agent, num_episodes, replay_buffer, minimal_size, batch_size):
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
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
replay_buffer.add(state, action, reward, next_state, done)
state = next_state
episode_return += reward
if replay_buffer.size() > minimal_size:
b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
transition_dict = {'states': b_s, 'actions': b_a, 'next_states': b_ns, 'rewards': b_r,
'dones': b_d}
agent.update(transition_dict)
return_list.append(episode_return)
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)
return return_list def compute_advantage(gamma, lmbda, td_delta):
td_delta = td_delta.detach().numpy()
advantage_list = []
advantage = 0.0
for delta in td_delta[::-1]:
advantage = gamma * lmbda * advantage + delta
advantage_list.append(advantage)
advantage_list.reverse()
return torch.tensor(advantage_list, dtype=torch.float)

3 开始刨析

(1)先从主函数运行开始,其他的遇到了我们再来分析



首先是对函数的一些变量进行初始化,都包括Adam优化器的学习率,episode的长度,隐藏层的维数,奖励函数的系数\(\gamma\)值,GAE的\(\lambda\)值,epoch的长度(表示一条序列数据用来训练的轮数),eps表示PPO中截断范围clip函数的参数,device是什么设备(源代码是cuda,这里大家可以更改下,因为我是用cpu跑的),然后下面的就是代码的一些初始化了,我这里不再赘述。



这哥俩就表述的是前者是观测值的数量(比如读取的电机的角度、力矩、电机速度、欧拉角、线速度等),后者是动作空间的数量(比如输出的电机的角度,一只A1有12个电机,那么输出的action的维数就是12,代表着每一个电机最后应该转多少角度)

(2)初始化智能体

这时我们调用PPO函数,然后我们来看一下PPO函数具体都干了些什么。(这里就是起了这个名字,不要误会它在这个函数里把所有PPO算法都i实现了)

这个地方就是对PPO算法的实现了。我们下面一步一步来看。

a.第一个地方

就是对Actor-Critic算法的复现,定义了两个神经网络,这两个神经网络具体长什么样子呢?我们来看看:

PolicyNet是Actor,相当于前面我们提到的Policy Improvement,简称PI,是策略改善的网络,最终我们希望输入一个状态,我们就知道该使用什么样子的action,黑盒明白吧。

ValueNet是Critic,对Actor的动作做评估,进而更新state value或者action value的值。网络结构我也大致画一下:



其实也就相当于前面去掉了softmax,至于为什么呢?

你看哈,actor网络的作用是选择一个动作进行输出,所以我们使用softmax进行分类,理论上选择让累计奖励更大的动作作为较大概率的输出(greedy action),所以这里用了一个分类器。

b.第二个地方

由于需要对两个网络进行反向传播,所以这里定义了两个Adam优化器进行。

普通咱一般用的是这种:

Adam优化器是这样子:(如果比较感兴趣的话,这篇博客个人感觉讲的比较好!)

代码里的lr就是咱前面提到的学习率,一般都是10的负几次方。

c.第三个地方就没啥说的了

(3)开始训练



转到对应的函数文件



可以说这个函数是这个代码最核心的部分,也是PPO算法最核心的部分。

a.



首先循环10次,每一轮咱训练50个episodes,一个episode就包含着很多个\(<state,action>\)对,最后10x50等于500。num_episodes = 500

然后是用tqdm定义一个进度条(这个不是重点)

初始化变量\(transition_dict\),这个变量包含5个值:现在的状态states,执行的动作actions,执行完到下一个时刻的状态next_states,奖励函数rewards,以及这一时刻执行完的结束标志done(结束为1,否则为0,记住后面代码里要用到)

b.

接着上面的代码再继续分析

i)take actions



可以看到首先将输入的state转换为tensor,然后因为actor网络的最后一层是softmax函数,所以通过actor网络输出两个执行两个动作可能性的大小,然后通过action_dist = torch.distributions.Categorical(probs) action = action_dist.sample()根据可能性大小进行采样最后得到这次选择动作1进行返回。

ii)env.step(action)

按照采取的动作,和环境进行交互,返回到达下一时刻状态的值、奖励函数以及一个episode是否到达最大长度episode_max的结束标志done。

这个长度是在这里定义的:

iii)transition_dist

然后将这些返回的值装进这个字典里面,有点像replay buffer我个人感觉。

然后更新下一时刻的状态值,这个episode的奖励函数也将累加episode_return += reward

然后通过return_list.append(episode_return)记录下这一整个episode的总的累计奖励R

iv)agent.update(...)

agent.update(transition_dict)到了这个函数。

这个函数说明了我们如何利用这些变量进行更新呢?

首先先从transition_dict取出变量存入tsnsor中便于计算

然后定义了TD Target:





然后是TD error





最后这俩就一起说了



前一个是计算GAE

advantage表示的是某状态下采取某动作的优势值,也就是咱前面提到的PPO算法公式中的A:





old_log_probs则表示的是旧策略下某个状态下采取某个动作的概率值的对数值。对应下面PPO算法公式中的clip函数中分母:



然后这个函数下面就老有意思了:



首先第一部分: log_probs = torch.log(self.actor(states).gather(1, actions))

求对数值,然后下面新旧相减:

ratio = torch.exp(log_probs - old_log_probs)

这不就是\(e^{ln(a-b)} = \frac{b}{a}嘛\)



就是在计算这个:

然后下面的surr1和surr2起初我也看不懂,后来看看算法自然而然就明白是干嘛的了:



这个其实就是用前面的ratio相乘来作为下图的两个值



这其实就已经写完了actor的损失函数了,也就是策略的损失函数了。至于critic的损失函数,是求td target和critic(states)的MSE,均方误差。



然后清零adam,梯度反向传播,更新。。

接下来我们就要进入一个循环中,循环epoch=10次进行参数的神经网络参数的更新,每一次相当于收集了50个episodes。

c.

                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)

更新进度条,跟咱要学的PPO没关系。

d.

return return_list返回这个奖励函数表。

(4)记录

大家可能对整个流程比较糊涂,这里简单的做一个梳理:

下面引用自其他佬的博客:

三、Acknowledge

感谢这些大佬的博客和思路理解:

鸣谢1

鸣谢2

鸣谢3

【论文系列】PPO知识点梳理 (尽我可能细致通俗理解!)的更多相关文章

  1. [独孤九剑]Oracle知识点梳理(十)%type与%rowtype及常用函数

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  2. [独孤九剑]Oracle知识点梳理(九)数据库常用对象之package

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  3. [独孤九剑]Oracle知识点梳理(八)常见Exception

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  4. [独孤九剑]Oracle知识点梳理(七)数据库常用对象之Cursor

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  5. [独孤九剑]Oracle知识点梳理(六)数据库常用对象之Procedure、function、Sequence

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  6. [独孤九剑]Oracle知识点梳理(五)数据库常用对象之Table、View

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  7. [独孤九剑]Oracle知识点梳理(四)SQL语句之DML和DDL

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  8. [独孤九剑]Oracle知识点梳理(三)导入、导出

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  9. [独孤九剑]Oracle知识点梳理(二)数据库的连接

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

  10. [独孤九剑]Oracle知识点梳理(一)表空间、用户

    本系列链接导航: [独孤九剑]Oracle知识点梳理(一)表空间.用户 [独孤九剑]Oracle知识点梳理(二)数据库的连接 [独孤九剑]Oracle知识点梳理(三)导入.导出 [独孤九剑]Oracl ...

随机推荐

  1. MyBatis——案例——查询-多条件查询-动态条件查询(关键字 if where)

    动态条件查询   SQL语句会随着用户的输入或外部条件的变化而变化,我们称为 动态SQL   MyBatis 对动态SQL有很强大的支撑:   if   choose(when,otherwise) ...

  2. Maven 依赖项管理&&依赖范围

    依赖管理   使用坐标导入jar包     1.在pom.xml 中编写 <dependencies> 标签     2.在 <dependencies> 标签中使用 < ...

  3. JavaScript——基础语法

    书写语法    输出语句    变量    数据类型    运算符        == 与 === 区别:       ==:         1.判断类型是否一样,如果不一样,则进行类型转换     ...

  4. 一条 SQL 语句在 MySQL 中是如何执行的?

    本篇文章会分析下一个 SQL 语句在 MySQL 中的执行流程,包括 SQL 的查询在 MySQL 内部会怎么流转,SQL 语句的更新是怎么完成的. 在分析之前我会先带着你看看 MySQL 的基础架构 ...

  5. HDLC报文简单分析

    最近在学习HDLC协议,从刚开始的一窍不通到现在的懵懵懂懂,下面分享一段报文解析,给初学者一点点经验的分析. 报文:7E A0 57 03 02 B8 4B 5B E6 E7 00 C4 01 C1 ...

  6. k8s StorageClass 存储类

    目录 一.概述 1.StorageClass 对象定义 2.StorageClass YAML 示例 二.StorageClass 字段 1.provisioner(存储制备器) 1.1.内置制备器 ...

  7. TLB一致性维护

    TLB 是页表项的物理 cache,用于加速虚拟地址到物理地址的转换.CPU 在访问一个虚拟地址时,首先会在 TLB 中查找,如果找不到对应的表项,那么就称之为 TLB miss,此时就需要去内存里查 ...

  8. 4.2.2 等差数列的前n项和公式

    ${\color{Red}{欢迎到学科网下载资料学习 }}$ [ [基础过关系列]高二数学同步精品讲义与分层练习(人教A版2019)] ( https://www.zxxk.com/docpack/2 ...

  9. 一些OI常用小技巧啊

    1.卡常 \[---总有人以为自己比编译器聪明 \;\;\;by\;\;bezel \] 我们可能确实没有编译器聪明,但是,为了防止CCF的老人机出现什么问题,卡一卡常还是有必要的. 如果实在被逼无奈 ...

  10. Pytorch 实现 GAN 网络

    Pytorch 实现 GAN 网络 原理 GAN的基本原理其实非常简单,假设我们有两个网络,G(Generator)和D(Discriminator).它们的功能分别是: G 是一个生成网络,它接收一 ...