【强化学习】倒立摆-PPO算法

先从最简单的开始入门吧

主要参考:

阿里云强化学习训练营

主要改动:

  1. 因为原代码使用Categorical,训练效果不佳,改成了比较简单的动作选择,效果改善了。
  2. 添加一部分函数的说明[Categorical, gather, clamp]

强化学习

类似巴普洛夫的狗

环境=>观测=>动作=>环境改变=>观测

通过奖励,训练模型采取更好的策略。

倒立摆

在倒立摆中,环境的观测值就是

  • 小车在轨道上的位置
  • 杆子与竖直方向的夹角
  • 小车速度
  • 角度变化率

动作为:

  • 0 左
  • 1 右

训练模型,让其学会左右移动,使得杆子与数值方向夹角为0

代码

#导入gym和torch相关包
import gym
import random
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

超参数设置

#Hyperparameters
learning_rate = 0.0005  #学习率
gamma = 0.98  #
lmbda = 0.95 #
eps_clip = 0.1 #
K_epoch = 3    # 
T_horizon = 20 # 

1.定义PPO架构


class PPO(nn.Module):
    def __init__(self):
        super(PPO, self).__init__()
        self.data = []  #用来存储交互数据
        self.fc1 = nn.Linear(4, 256)  #由于倒立摆环境简单,这里仅用一个线性变换来训练数据
        self.fc_pi = nn.Linear(256, 2)  #policy函数, 动作只有左和右,因此输出是2
        self.fc_v = nn.Linear(256, 1)  #value函数(输出v),值只有1,因此输出为1
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)  #优化器

    #policy函数
    #输入观测值x
    #输出动作空间概率,从而选择最优action
    def pi(self, x, softmax_dim=0):
        x = F.relu(self.fc1(x))
        x = self.fc_pi(x)
        prob = F.softmax(x, dim=softmax_dim)
        # x形状20*2
        # 这里的dim取0,代表获取同一样本的动作空间softmax
        # 概率越大代表选择的可能性越大,在每次观测时,95%选择概率更大的动作,5%选择概率更小的动作。
        # 下面有个地方dim取1,代表获取一个批次(例如20次尝试)的softmax,不同状态时的动作概率
        return prob

    #value函数
    #输入观测值x
    #输出x状态下value的预测值(reward),提供给policy函数作为参考值
    def v(self, x):
        x = F.relu(self.fc1(x))
        v = self.fc_v(x)
        return v

    #把交互数据存入buffer
    def put_data(self, transition):
        self.data.append(transition)

    #这里实际上做的是类型转换,把gym中env的observation,action等转换成torch Tensor
    def make_batch(self):
        s_lst, a_lst, r_lst, s_prime_lst, prob_a_lst, done_lst = [], [], [], [], [], []
        for transition in self.data:
            s, a, r, s_prime, prob_a, done = transition

            s_lst.append(s)
            a_lst.append([a])
            r_lst.append([r])
            s_prime_lst.append(s_prime)
            prob_a_lst.append([prob_a])
            done_mask = 0 if done else 1 # done表示游戏结束
            done_lst.append([done_mask])

        s,a,r,s_prime,done_mask, prob_a = torch.tensor(s_lst, dtype=torch.float), torch.tensor(a_lst), \
                                          torch.tensor(r_lst), torch.tensor(s_prime_lst, dtype=torch.float), \
                                          torch.tensor(done_lst, dtype=torch.float), torch.tensor(prob_a_lst)
        self.data = []
        return s, a, r, s_prime, done_mask, prob_a

    #训练模型
    def train_net(self):
        s, a, r, s_prime, done_mask, prob_a = self.make_batch()
        for i in range(K_epoch):  #K_epoch:训练多少个epoch
            #计算td_error 误差,value模型的优化目标就是尽量减少td_error
            td_target = r + gamma * self.v(s_prime) * done_mask # 游戏结束了,长期期望为0
            delta = td_target - self.v(s)
            delta = delta.detach().numpy()

            #计算advantage:
            #即当前策略比一般策略(baseline)要好多少
            #可能为正可能为负,进行调整
            #policy的优化目标就是让当前策略比baseline尽量好
            advantage_lst = []
            advantage = 0.0
            for delta_t in delta[::-1]:
                advantage = gamma * lmbda * advantage + delta_t
                advantage_lst.append([advantage])
            advantage_lst.reverse()
            advantage = torch.tensor(advantage_lst, dtype=torch.float)

            #防止更新偏离太多
            pi = self.pi(s, softmax_dim=1)
            pi_a = pi.gather(1, a)
            ratio = torch.exp(torch.log(pi_a) - torch.log(prob_a))  
            surr1 = ratio * advantage
            surr2 = torch.clamp(ratio, 1 - eps_clip, 1 + eps_clip) * advantage
            #这里简化ppo,把policy loss和value loss放在一起计算
            loss = -torch.min(surr1, surr2) + F.smooth_l1_loss(
                self.v(s), td_target.detach())
            #梯度优化
            self.optimizer.zero_grad()
            loss.mean().backward()
            self.optimizer.step()

2.训练

observation => model => probability => action

get_action函数通过argmax获取概率最大的选项;这里只有两个动作,0和1,左和右

Categorical一般用于复杂动作,这里一开始用Categorical效果并不好,所以改成95%概率使用概率最大的动作,5%概率使用概率较小的动作。

使用random.uniform生成均匀分布

def get_action(prob):
    # m = Categorical(prob)
    # a = m.sample().item()
    pp = random.uniform(0, 1)
    a = int(torch.argmax(prob).numpy())
    if pp < 0.95:
        return 1 - a
    return a

训练过程

#主函数:简化ppo 这里先交互T_horizon个回合然后停下来学习训练,再交互,这样循环10000次
def train():
    #创建倒立摆环境
    env = gym.make('CartPole-v1')
    # from gym import wrappers
    # env = wrappers.Monitor(env,"./cartpole", force=True)
    model = PPO()
    score = 0.0
    print_interval = 20

    #主循环
    max_score = 0
    for n_epi in range(2000):
        s = env.reset()
        done = False
        while not done:
            for t in range(T_horizon):
                #由当前policy模型输出最优action
                prob = model.pi(torch.from_numpy(s).float())
                a = get_action(prob)

                #用最优action进行交互
                s_prime, r, done, info = env.step(a)

                #存储交互数据,等待训练
                model.put_data(
                    (s, a, r / 100.0, s_prime, prob[a].item(), done))
                s = s_prime

                score += r
                if done:
                    break

            #模型训练
            model.train_net()
        #打印每轮的学习成绩
        if n_epi % print_interval == 0 and n_epi != 0:
            print("# of episode :{}, avg score : {:.1f}".format(
                n_epi, score / print_interval))
            if score > max_score:
                torch.save(model.state_dict(), 'pp.pt')
                max_score = score
            score = 0.0

    env.close()

测试过程

from gym import wrappers

model = PPO()
model.load_state_dict(torch.load('pp.pt'))
env = gym.make('CartPole-v1')
env = wrappers.Monitor(env, "./cartpole", force=True)
#主循环
cnt = 0
for n_epi in range(100):
    s = env.reset()
    done = False
    while not done:
        #由当前policy模型输出最优action
        prob = model.pi(torch.from_numpy(s).float())
        a = get_action(prob)
        #用最优action进行交互
        s_prime, r, done, info = env.step(a)
        # env.render()
        cnt += 1
        if cnt > 1000:
            break
env.close()

3.部分函数介绍

  • Categorical函数

以给定概率probs创建一个分布

dist = Categorical(probs)
index = dist.sample()

sample就是按照上面分布采样

eg:

probs = torch.FloatTensor([0.2, 0.8])
dist = Categorical(probs)

cnt = 0
for _ in range(100):
    index = dist.sample()
    if index == 0:
        cnt += 1
print('0出现次数{},概率{:.2%}'.format(cnt,cnt / 100))
print('1出现次数{},概率{:.2%}'.format(100-cnt,1-cnt / 100))
0出现次数26,概率26.00%
1出现次数74,概率74.00%
  • clamp函数

y = torch.clamp(x, min, max)


    | min , x < min
y = | x   , min < x < max
    | max , x > max 
  • gather函数

gather(x, dim, index)

在x的dim维度上,选择索引index的数据

x = torch.Tensor([
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,16]
])

对于这个二维数组

我们知道,获取一个二维数组的值需要行列两个索引。

dim为0时,

  • 行索引为index的值

  • 列索引为index对应的列

例如下方:

index = torch.LongTensor([[0, 0, 0, 0]])
print(x.gather(0, index))

会得到

tensor([[1., 2., 3., 4.]])
  行为index的值,  0,0,0,0
  列为index的列,  1,2,3,4

dim为1时

  • 行索引为index对应的行

  • 列索引为index的值

例如下方:

index = torch.LongTensor([[0, 1, 2, 3]])
print(x.gather(1, index))

会得到

tensor([[1., 2., 3., 4.]])
   行:index对应行:   0 0 0 0
   列:index的值:     1 2 3 4

你可能感兴趣的:(强化学习,人工智能,深度学习,python)