深度强化学习方法(DQN)玩转Atari游戏(pong)

Atari Pong

简介

        Pong是起源于1972年美国的一款模拟两个人打乒乓球的游戏,近几年常用于测试强化学习算法的性能。这篇文章主要记录如何用DQN实现玩Atari游戏中的Pong,希望对和我一样的小白有所帮助,文章最后附本文代码及参考代码。

环境介绍

torch = 1.8.0+cu111
Python = 3.8.5
环境配置见另一篇博客https://blog.csdn.net/libenfan/article/details/116396388?spm=1001.2014.3001.5502

代码详解

        代码主要包含四个部分:经验重放区ReplayMemory、DQN网络、DQNagent、训练器Trainer。

ReplayMemory

        用于DQN的经验重放,包含的采样、存储、计算经验重放区长度三个方法。

# 定义一个元组表征经验存储的格式
Transition = namedtuple('Transion', 
                        ('state', 'action', 'next_state', 'reward'))

class ReplayMemory(object):
    def __init__(self, capacity):
        self.capacity = capacity
        self.memory = []
        self.position = 0
     
    def push(self, *args):
        if len(self.memory) < self.capacity:
            self.memory.append(None)
        self.memory[self.position] = Transition(*args)
        self.position = (self.position + 1) % self.capacity #移动指针,经验池满了之后从最开始的位置开始将最近的经验存进经验池
        
    def sample(self, batch_size):
        return random.sample(self.memory, batch_size)# 从经验池中随机采样
    
    def __len__(self):
        return len(self.memory)

DQN网络

        三层卷积层,两层线性连接层,(这里要注意卷积层输出的大小要能够与线性层的输入大小相匹配)。
        由于需要对pong环境进行重写,因此DQN网络的输入大小在后面介绍pong环境重写的时候会说明。

class DQN(nn.Module):
    def __init__(self, in_channels=4, n_actions=14):
        """
        Initialize Deep Q Network
        Args:
            in_channels (int): number of input channels
            n_actions (int): number of outputs
        """
        super(DQN, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, 32, kernel_size=8, stride=4)
        # self.bn1 = nn.BatchNorm2d(32)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=4, stride=2)
        # self.bn2 = nn.BatchNorm2d(64)
        self.conv3 = nn.Conv2d(64, 64, kernel_size=3, stride=1)
        # self.bn3 = nn.BatchNorm2d(64)
        self.fc4 = nn.Linear(7 * 7 * 64, 512)
        self.head = nn.Linear(512, n_actions)
        
    def forward(self, x):
        x = x.float() / 255
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = F.relu(self.fc4(x.view(x.size(0), -1)))
        return self.head(x)

DQNagent

        定义一个自己的agent,agent需要包含动作选择、策略学习的方法。

class DQN_agent():
    def __init__(self,in_channels=1, action_space=[], learning_rate=1e-4, memory_size=10000, epsilon=1):
        
        self.in_channels = in_channels        
        self.action_space = action_space
        self.action_dim = self.action_space.n     
                
        self.memory_buffer = ReplayMemory(memory_size)
        self.stepdone = 0
        self.DQN = DQN(self.in_channels, self.action_dim).cuda()
        self.target_DQN = DQN(self.in_channels, self.action_dim).cuda()
        # 加载之前训练好的模型,没有预训练好的模型时可以注释
        self.DQN.load_state_dict(torch.load(madel_path))
        
        self.target_DQN.load_state_dict(self.DQN.state_dict())
        self.optimizer = optim.RMSprop(self.DQN.parameters(),lr=learning_rate, eps=0.001, alpha=0.95)
        
        
        
    def select_action(self, state):
        
        self.stepdone += 1
        state = state.to(device)
        epsilon = EPS_END + (EPS_START - EPS_END)* \
            math.exp(-1. * self.stepdone / EPS_DECAY)            # 随机选择动作系数epsilon 衰减,也可以使用固定的epsilon
        # epsilon-greedy策略选择动作
        if random.random()

训练器Trainer


        用于训练DQNagent,包含获取当前状态、训练和绘制奖励曲线三个方法,输入参数包含环境(env)、agent(DQNagent)、训练的代数(n_episode)。

class Trainer():
    def __init__(self, env, agent, n_episode):
        self.env = env
        self.n_episode = n_episode
        self.agent = agent
        # self.losslist = []
        self.rewardlist = []
        
    # 获取当前状态,将env返回的状态通过transpose调换轴后作为状态
    def get_state(self,obs):
        state = np.array(obs)
        state = state.transpose((2, 0, 1)) #将2轴放在0轴之前
        state = torch.from_numpy(state)
        return state.unsqueeze(0)    # 转化为四维的数据结构
    
 # 训练智能体
    def train(self):
        for episode in range(self.n_episode):
            
            obs = self.env.reset()
            state = self.get_state(obs)
            episode_reward = 0.0
            
            # print('episode:',episode)
            for t in count():  
                # print(state.shape)                              
                action = self.agent.select_action(state)
                if RENDER:
                    self.env.render()
                
                
                obs,reward,done,info = self.env.step(action)
                episode_reward += reward
                
                if not done:
                    next_state = self.get_state(obs)
                else:
                    next_state = None
                # print(next_state.shape)
                reward = torch.tensor([reward], device=device)
                
                # 将四元组存到memory中
                '''
                state: batch_size channel h w    size: batch_size * 4
                action: size: batch_size * 1
                next_state: batch_size channel h w    size: batch_size * 4
                reward: size: batch_size * 1                
                '''
                self.agent.memory_buffer.push(state, action.to('cpu'), next_state, reward.to('cpu')) # 里面的数据都是Tensor
                state = next_state
                # 经验池满了之后开始学习
                if self.agent.stepdone > INITIAL_MEMORY:
                    self.agent.learn()
                    if self.agent.stepdone % TARGET_UPDATE == 0:
                        self.agent.target_DQN.load_state_dict(self.agent.DQN.state_dict())
                
                if done:
                    break
                
            if episode % 20 == 0:
                torch.save(self.agent.DQN.state_dict(), MODEL_STORE_PATH + '/' + "model/{}_episode{}.pt".format(modelname, episode))
                print('Total steps: {} \t Episode: {}/{} \t Total reward: {}'.format(self.agent.stepdone, episode, t, episode_reward))
            
            self.rewardlist.append(episode_reward)
                
            
            self.env.close()
        return
        
    #绘制奖励曲线
    def plot_reward(self):
        
        plt.plot(self.rewardlist)
        plt.xlabel("episode")
        plt.ylabel("episode_reward")
        plt.title('train_reward')
        
        plt.show()

主函数

        选择仿真环境,实例化智能体和训练器,并利用训练器对智能体进行训练 。     

if __name__ == '__main__':
   
    # create environment
    env = gym.make("PongNoFrameskip-v4")
    env = make_env(env)
    action_space = env.action_space
    state_channel = env.observation_space.shape[2]  
    
    agent = DQN_agent(in_channels = state_channel, action_space= action_space)
    
    trainer = Trainer(env, agent, n_episode)
    trainer.train()

解释

env = make_env(env)

       make_env()函数是参考代码作者对gym环境的重写,我去wrappers文件夹下研究了这个函数,其本质和baselines项目的make_atari()函数类似,用于定义自己需要的gym环境。
对于"PongNoFrameskip-v4"这个环境,下面给出参考代码中作者的make_env()函数及解析:

def make_env(env, stack_frames=True, episodic_life=True, clip_rewards=False, scale=False):
    if episodic_life:
        env = EpisodicLifeEnv(env) # 使得每次游戏玩家都只有一条命

    env = NoopResetEnv(env, noop_max=30)  # 通过随机抽取重置时的无操作数对初始状态进行采样。
    # 相邻帧的画面相似度高需要跳过
    env = MaxAndSkipEnv(env, skip=4) #跳过若干(skip)帧画面,并且取最后两帧像素值的最大值
    # 如果需要开火则需要玩家按键开始
    if 'FIRE' in env.unwrapped.get_action_meanings():
        env = FireResetEnv(env)
    # 图像处理函数,将原本的210x160x3的彩色图像编程84x84的灰度图像,符合网络结构,即线性层的输入大小对应的是84*84的图像经过卷积层后的输出
    env = WarpFrame(env)
    if stack_frames:
        # 将最后四帧当成状态,
        env = FrameStack(env, 4)
    if clip_rewards:
        # 将游戏的奖励符号化
        # 主要是因为之前deepmind团队用一个网络同时玩49个游戏,各个游戏奖励值不一样,避免混淆
        env = ClipRewardEnv(env)
    return env

        最后大家如果没有GPU,可以用Google免费的GPU平台Colab(需要VPN),使用起来比较简单,不过在Colab上面跑代码需要特别关注路径的问题。

       希望这篇文章对于初学者能够有一些参考作用,有什么问题欢迎交流,期待大家一起进步。下面附上代码链接。


参考代码:https://github.com/jmichaux/dqn-pytorch
本文代码:https://github.com/libenfan/DQN_pong

 

 

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