pytorch笔记:policy gradient

该博客介绍了如何使用PyTorch实现策略梯度(Policy Gradient)算法解决CartPole游戏。文章详细讲解了强化学习的基础理论,并展示了代码实现,包括环境设置、网络结构、动作选择、参数更新等关键步骤。代码中采用自定义的损失函数和折扣回报,并在每个episode结束后更新模型参数,最终实现了稳定的学习效果。
摘要由CSDN通过智能技术生成

本文参考了 策略梯度PG( Policy Gradient) 的pytorch代码实现示例 cart-pole游戏_李莹斌XJTU的博客-CSDN博客_策略梯度pytorch

在其基础上添加了注释和自己的一些理解

1 理论部分

强化学习笔记:Policy-based Approach_UQI-LIUWJ的博客-CSDN博客

我们使用其中的框架(在我们后面的实验中,我们认为每次N取1就对参数进行一次更新)

同时奖励R不是使用R(\tau^n),而是使用折扣回报 

 2  代码部分

2.1 导入库 & 参数处理

import argparse
import numpy as np
import gym
from itertools import count
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical
parser = argparse.ArgumentParser(description='Pytorch REINFORCE example')
parser.add_argument('--gamma', type=float, default=0.99)
parser.add_argument('--seed',type=int, default=543)
parser.add_argument('--render',action='store_false')
parser.add_argument('--log-interval', type=int, default=10)
parser.add_argument('--episodes', type=int, default=10)
parser.add_argument('--steps_per_episode', type=int, default=10)


args = parser.parse_args()

python 笔记:argparse_UQI-LIUWJ的博客-CSDN博客

2.2 gym环境创建

python 笔记 :Gym库 (官方文档笔记)_UQI-LIUWJ的博客-CSDN博客

env = gym.make('CartPole-v1')
#创建一个推车杆的gym环境
env.seed(args.seed)
#设置随机种子
torch.manual_seed(args.seed)
# 策略梯度算法方差很大,设置随机以保证复现性


print('observation space:',env.observation_space)
'''
Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38],
    [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38],
    (4,),
    float32)
'''

print('action space:',env.action_space)
#Discrete(2)

cartpole 的state是一个4维向量,分别是位置,速度,杆子的角度,加速度;

action是二维、离散,即向左/右推杆子

2.3 创建Policy类

输入某一时刻的状态(也就是一个四维向量),输出采取各个动作的概率(二维向量)

class Policy(nn.Module):
    ##  离散空间采用了 softmax policy 来参数化策略
    def __init__(self):
        super(Policy,self).__init__()
        self.fc1 = nn.Linear(4,128)
        #一开始是一个[1,4]维的Tensor,表示状态
        #先使用一个全连接层将维度升至128维

        self.dropout = nn.Dropout(p=0.6)

        self.fc2 = nn.Linear(128,2)
        # 两种动作 (取决于action_space,Discrete(2))
        # 再降维至每一个动作一个维度

        self.saved_log_probs = []
        #一个数组,记录每个时刻的log p(a|s)

        self.rewards = []
        #一个数组,记录每个时刻做完动作后的reward

    def forward(self, x):
        x = self.fc1(x)
        x = self.dropout(x)
        x = F.relu(x)
        action_scores = self.fc2(x)
        return F.softmax(action_scores,dim=1)
        #a[..][0],a[..][1],...a[..][n] 这些进行softmax
        #求得在状态x下各个action被执行的概率

policy = Policy()

optimizer = optim.Adam(policy.parameters(),lr=1e-2)

eps = np.finfo(np.float32).eps.item()  
# 非负的最小值,使得归一化时分母不为0

numpy 笔记:finfo_UQI-LIUWJ的博客-CSDN博客

2.4 选择动作

def select_action(state):
    ## 选择动作,这个动作不是根据Q值来选择,而是使用softmax生成的概率来选
    #  在policy gradient中,不需要epsilon-greedy,因为概率本身就具有随机性


    state = torch.from_numpy(state).float().unsqueeze(0)
    #print(state.shape)   
    #torch.size([1,4])
    #通过unsqueeze操作变成[1,4]维的向量
    
    probs = policy(state)
    #Policy的返回结果,在状态x下各个action被执行的概率
    
    m = Categorical(probs)      
    # 生成分布

    action = m.sample()           
    # 从分布中采样(根据各个action的概率)
    
    #print(m.log_prob(action))
    # m.log_prob(action)相当于probs.log()[0][action.item()].unsqueeze(0)
    #换句话说,就是选出来的这个action的概率,再加上log运算

    policy.saved_log_probs.append(m.log_prob(action))
    # 即 logP(a_t|s_t,θ)

    return action.item()         
    # 返回一个元素值

    '''
    所以每一次select_action做的事情是,选择一个合理的action,返回这个action;
    同时我们当前policy中添加在当前状态下选择这个action的概率的log结果
    '''

2.5 Policy 中参数的更新

def finish_episode(ep_reward):
    R = 0
    policy_loss = []
    returns = []

    for r in policy.rewards[::-1]:
        R = r + args.gamma * R
        #相当于是对时刻i而言 Σ(j ∈[i,t)) R^(j-i),也就是之后考虑衰减的奖励和
        returns.insert(0,R)        
    # 将R插入到指定的位置0处(折扣奖励)

    returns = torch.tensor(returns)
    #所以returns的位数也和policy一样

    print(policy.saved_log_probs)
    '''
    一个类似于
    [tensor([-0.9295], grad_fn=<SqueezeBackward1>),
     tensor([-0.5822], grad_fn=<SqueezeBackward1>)]
     的list
    '''

    print(returns)
    #一个类似于tensor([8.6483, 7.7255])的tensor
    
    returns = (returns - returns.mean()) / (returns.std() + eps)
    # 归一化

    for log_prob, R in zip(policy.saved_log_probs,returns):
        #相当于 对 i ∈[0,len(returns)) 每次取saved_log_probs和returns相同下标的元素
        policy_loss.append(-log_prob * R)
        # 折扣奖励*logP(a|s)

    print(policy_loss)
    '''
    也是一个类似于
    [tensor([-0.9295], grad_fn=<SqueezeBackward1>),
     tensor([-0.5822], grad_fn=<SqueezeBackward1>)]
     的list
    '''

    
    policy_loss = torch.cat(policy_loss).sum()
    #torch.cat之后,变成tensor([-0.9295,-0.5822],grad_fn=<CatBackward>)的形式
    #sum求和,也就是这个episode的总loss

    optimizer.zero_grad()
    policy_loss.backward()
    optimizer.step()
    #pytorch深度学习老三样

    del policy.rewards[:]          
    # 清空episode 数据
    del policy.saved_log_probs[:]

2.6 main函数

def main():
    running_reward = 10
    for i_episode in range(args.episodes):
        # 采集(训练)最多1000个序列
        
        state, ep_reward = env.reset(),0
        # ep_reward表示每个episode中的reward
        #state表示初始化这一个episode的环境

        state
        #array([-0.00352001,  0.01611176, -0.00538757, -0.00544052], dtype=float32)
        
        
        for t in range(1,args.steps_per_episode):
            #一个epsiode里面有几步

            action = select_action(state)
            #根据当前state的结果,按照概率选择下一步的action
            #同时我们当前policy中添加在当前状态下选择这个action的概率的log结果(后来的梯度上升中用)
            
            
            state, reward, done, _ = env.step(action)
            #四个返回的内容是state,reward,done(是否重置环境),info
            
            if args.render:
                env.render()
            #渲染环境,如果你是再服务器上跑的,只想出结果,不想看动态推杆过程的话,可以设置为False
                
            policy.rewards.append(reward)
            #选择这个action后的奖励,也添加到policy对应的数组中
            
            ep_reward += reward
            #这一个episode总的reward
            
            if done:
                break

        '''
        结束一个episode后,policy中会有两个等长的数组,一个是奖励,一个是概率的log结果
        它们两两对齐
        '''

        running_reward = 0.05 * ep_reward + (1-0.05) * running_reward
        #通过这种方式计算加权平均reward
        
        finish_episode(ep_reward)
        if i_episode % args.log_interval == 0:
            print('Episode {}\tLast reward: {:.2f}\tAverage reward: {:.2f}'.format(
                i_episode, ep_reward, running_reward))
        if running_reward > env.spec.reward_threshold:   # 大于游戏的最大阈值475时,退出游戏
            print("Solved! Running reward is now {} and "
                  "the last episode runs to {} time steps!".format(running_reward, t))
            break


if __name__ == '__main__':
    main()

3 和pytorch  深度学习的区别

可以看出来,主题框架和pytorch 深度学习(如pytorch笔记——简易回归问题_UQI-LIUWJ的博客-CSDN博客)是几乎一样的,不同的是,那里损失函数使用torch.nn中的一个直接调用的,这里相当于是自己设定,设定在一定程度上依赖于强化学习的奖励。

然后这里使用了折扣回报代替整体回报,同时我们每采一个episode就进行更新。我们也可以采样多个episode再进行更新,那样的话就是我一次性存储多个episode的奖励和概率,然后统一计算损失函数

梯度是随着P(a|s)传递的,反向传播也逆之更新模型各参数

4 结果

做了两组实验

一个的更新过程就是一个episode 全是这个episode的奖励 


可以看到他即使到了1000次也没有收敛

另一种更新的方法是折扣回报 

 可以看到它在进行700个episode的时候就结束了(平均reward大于475) 

 4 补充说明:离散动作 & 连续动作

  • 要输出离散动作的话,可以加一层 softmax 层来确保说所有的输出是动作概率,而且所有的动作概率加和为 1。
  • 要输出连续动作的话,一般可以在输出层这里加一层 tanh,把输出先限制到[-1,1]之间。拿到这个输出后,可以根据实际动作的范围再做缩放
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UQI-LIUWJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值