已经脱更好久了,最近在金山实习,白天做公司的游戏项目,晚上做自己的课题,实在是抽不出时间。
接下来几篇,我们要实现一个机械臂reach的训练,先看一下要达到的效果。
在这里插入图片描述
再看一下训练过程。
在这里插入图片描述


接下来我们开始一点点的实现起来。

这一节首先从算法开始吧,我们要用的也是目前的state-of-art算法,PPO算法。
建议大家先读读PPO的论文Proximal Policy Optimization Algorithms,论文总体来说不算很难。PPO的论文中是这样描述算法流程的
在这里插入图片描述
我们用一个更加完整点的流程图,这个流程是spinningup总结的,spinningup是openai的一个强化学习的开源项目,质量很好。
在这里插入图片描述
从算法的流程中可以总结出算法主要包含的部分:

  • 算法主要包含收集经验数据
  • 使用GAE来估计优势函数(Advantage Function)
  • 设计value function和policy function
  • 对于policy,使用PPO-clip更新policy的参数
  • 更新value function的参数

整体思路

在开始编写算法之前,我们首先要搞清楚算法中两个重要部分:actor和critic。因为PPO算法也是基于actor critic框架,所以这两个部分是较为重要的。

actor代表产生行为的函数,也就是Policy。critic代表评价行为的函数,也就是value function。actor需要根据当前的observation,来产生act,critic需要根据当前的observation,来评价当前的state的价值,而这个state是通过actor产生act之后,环境执行step得来的,所以这样critic就与actor联系了起来,critic来评价actor产生的行为的好坏。

我们举一个更加通俗易懂的例子,actor就是你自己,critic就是你的老师,state代表当前你正在做1+1=()这个数学题,假设这里是标准的MDP问题,也就是你所看到的就是当前的状态,所以 observation=state。 当actor,也就是你看到observation,也就是这道题后,产生了3这个行为,代表括号中应该填入3,critic通过当前的observation,也就是1+1=3对你刚才产生的行为进行了评价,输出了一个 -1 的值,代表你的行为价值不好,然后你知道答案不对,更新了一下自己的想法,也就是你的计算策略的参数,输出了一个2,这时候criitic通过当前的observation,1+1=2,输出了一个 +1 的值,这时候你就知道你做对了。

明确了actor和critic各自的任务后,接下来的问题就是对于机械臂的这个reach任务,observation是什么,state是什么,actor和critic怎么设计?

大家可以停下来思考一段时间,如果这个问题是自己去设计,该怎样打通env和算法之间的桥梁,怎么能让PPO算法正确的学会解决这个问题。

下面来一一回答:

  • 如果想让机械臂正确的学会reach到一个点,基本的设计思路有以下几种:
    (1)state为当前要reach的点的坐标,actor可以根据这个点的坐标决定自己的动作,critic可以根据这个点的坐标输出价值函数的值(这个地方重点看一下,有待商榷);
    (2)state为当前要reach点和机械臂末端末端两者的坐标,actor可以根据当前末端坐标和reach坐标输出动作,critic也可以更容易的根据当前末端坐标和reach坐标生成价值的判断;
    (3)state为当前工作场景的图像信息,actor根据当前的图像生成动作,critic根据当前的图像做出判断,这个是最难得,不过最后我们也会实现。

    • 那么有些小伙伴会说,除了第三种方法,前两种方法我直接机械臂求逆解过去不就是嘛。事情确实是这么个事情,道理也是这样的道理,只是在这里我们强行强化学习了。
  • 我们先从思路(1)开始,此时的state为reach点的坐标,因为是完全MDP问题,所以observation=state,此时的observation=[reach_x, reach_y, reach_z], 是一个1x3的向量。

  • 确定了observation之后,紧接着是怎样设计动作,也就是让actor输出什么? 是用离散的动作输出还是连续的动作输出? 输出关节角度? 输出末端坐标? 还是定义离散方向,让机械臂按照方向运动? 大家可以停下来思考一下。
  • 我在这里采用的是输出末端坐标。因为有了末端坐标,可以直接控制机械臂到达这个坐标点。因为输出的坐标值,所以这是一个连续的输出控制问题。
  • 现在确定了observation,也就是actor和critic的输入,也确定了actor的输出,critic的输出是固定的,就是一个数值,来表示当前的价值。所以我们就可以编写actor和critic的函数了。

critic

actor的工作流程就是
observation(1x3) -> critic -> obs value(1x1)

那么这里的critic里面是什么呢?对于深度强化学习来说,一种数据的输入,映射到另一种数据的输出,我们基本是采用神经网络实现。所以这里呢critic也是一个神经网络。那么是哪种神经网路呢? 这个也很简单,矩阵或者向量的输入,对应矩阵或者向量的输出,这个典型的符合多层感知机的结构,所以critic在这里就是一个MLP。

那么我们就来编程实现一下:

因为actor和critic的输出维度不一样,所以actor和critic的网络结构并不相同,所以呢,为了方便复用和降低耦合,我们不对他俩写两个mlp,而是通过传递参数给他们直接生成不同的mlp网络。

我们首先来编写实现一下一个抽象的MLP.

import torch
import torch.nn as nn

def mlp(sizes, activation, output_activation=nn.Identity):
    layers = []
    for j in range(len(sizes) - 1):
        act = activation if j < len(sizes) - 2 else output_activation
        layers += [nn.Linear(sizes[j], sizes[j + 1]), act()]
    return nn.Sequential(*layers)

mlp的编写很简单,逻辑也很清楚,可以通过

obs_dim=3
hidden_sizes=[64,64]
activation=nn.Tanh

net=mlp([obs_dim] + list(hidden_sizes) + [1], activation)
print(net)

来看一下生成的网络结构是怎样的。

下面来编写critic

class MLPCritic(nn.Module):

    def __init__(self, obs_dim, hidden_sizes, activation):
        super().__init__()
        self.v_net = mlp([obs_dim] + list(hidden_sizes) + [1], activation)

    def forward(self, obs):
        return torch.squeeze(self.v_net(obs), -1)  # Critical to ensure v has right shape.

我们通过下面的测试用例来看一下critic到底做了啥。

import torch
import torch.nn as nn

def mlp(sizes, activation, output_activation=nn.Identity):
    layers = []
    for j in range(len(sizes) - 1):
        act = activation if j < len(sizes) - 2 else output_activation
        layers += [nn.Linear(sizes[j], sizes[j + 1]), act()]
    return nn.Sequential(*layers)

class MLPCritic(nn.Module):

    def __init__(self, obs_dim, hidden_sizes, activation):
        super().__init__()
        self.v_net = mlp([obs_dim] + list(hidden_sizes) + [1], activation)

    def forward(self, obs):
        return torch.squeeze(self.v_net(obs), -1)  # Critical to ensure v has right shape.


obs_dim=3
observation=torch.as_tensor([0.5, 0.1, 0],dtype=torch.float32)
hidden_sizes=[64,64]
activation=nn.Tanh

critic=MLPCritic(obs_dim,hidden_sizes,activation)
print('v_net={}'.format(critic.v_net))
print('v_net(obs)={}'.format(critic.v_net(observation)))
print('v_net forward={}'.format(critic.forward(observation)))

输出为:
>

v_net=Sequential(
(0): Linear(in_features=3, out_features=64, bias=True)
(1): Tanh()
(2): Linear(in_features=64, out_features=64, bias=True)
(3): Tanh()
(4): Linear(in_features=64, out_features=1, bias=True)
(5): Identity()
)
v_net(obs)=tensor([0.1236], grad_fn=)
v_net forward=0.12357600778341293

输出符合我们的预期。
以上就是critic的实现了,critic的实现较为简单,就是从多维输出映射到一个值的输出。

下面我们来实现actor和其他部分。


猜你想看:

  1. Ubuntu助手 — 一键自动安装软件,一键进行系统配置
  2. 深度强化学习专栏 —— 1.研究现状
  3. 深度强化学习专栏 —— 2.手撕DQN算法实现CartPole控制
  4. 深度强化学习专栏 —— 3.实现一阶倒立摆
  5. 深度强化学习专栏 —— 4. 使用ray做分布式计算
  6. 深度强化学习专栏 —— 5. 使用ray的tune组件优化强化学习算法的超参数
  7. pybullet杂谈 :使用深度学习拟合相机坐标系与世界坐标系坐标变换关系(一)
  8. pybullet杂谈 :使用深度学习拟合相机坐标系与世界坐标系坐标变换关系(二)
  9. pybullet电机控制总结
  10. Part 1 - 自定义gym环境
  11. Part 1.1 - 注册自定义Gym环境
  12. Part 1.2 - 实现一个井字棋游戏的gym环境
  13. Part 1.3 - 熟悉PyBullet
  14. Part 1.4 - 为PyBullet创建Gym环境