强化学习DQN 入门小游戏 最简单的Pytorch代码

本文目的是用最简单的代码,展示DQN玩游戏的效果,不涉及深度学习原理讲解。

毕竟,入门如此艰难,唯一的动力不过是看个效果,装个biu……

安装OpenAI的游戏库gym

pip install gym

看一下运行效果

import gym

env = gym.make('CartPole-v1')
print('State shape:', env.observation_space.shape)
print('Number of actions:', env.action_space.n)
for _ in range(20):
    observation = env.reset()   # 初始状态
    for t in range(500):
        env.render()    # 显示图像
        action = env.action_space.sample()  # 随机选择一个动作
        observation, reward, done, info = env.step(action)  # 状态,回报,是否结束,信息
        print(observation, reward, done, info)
        if done:
            print("Episode finished after {} timesteps".format(t + 1))
            break
env.close()

在这里插入图片描述
这是个手推车平衡游戏,我们可以采取两个动作(action),向左推或向右推,保持杆子不倒的时间越长,分数越高。游戏的每个时刻都有一个状态(observation),这个游戏中状态是由4个数值描述的,我们其实不必了解这四个值的含义,规律反正是交给网络学习的,这是深度学习很爽的地方,写出AlphaGo的程序员不需要懂围棋规则。每采取一个动作,除了得到下一时刻的状态,最重要的是得到一个回报(reward),在这个游戏中,无论什么动作,回报都是+1,意思是只要活着,就给你加分,直到杆子倒下,游戏结束。

代码

import random
import gym
import torch
from torch import nn, optim

class QNet(nn.Sequential):
    def __init__(self):
        super(QNet, self).__init__(
            nn.Linear(4, 256),
            nn.ReLU(),
            nn.Linear(256, 128),
            nn.ReLU(),
            nn.Linear(128, 2)
        )

class Game:
    def __init__(self, exp_pool_size, explore):
        self.env = gym.make('CartPole-v1')
        self.exp_pool = []
        self.exp_pool_size = exp_pool_size
        self.q_net = QNet()
        self.explore = explore
        self.loss_fn = nn.MSELoss()
        self.opt = optim.Adam(self.q_net.parameters())

    def __call__(self):
        is_render = False
        avg = 0
        while True:
            # 数据采样
            state = self.env.reset()
            R = 0
            while True:
                if is_render:
                    self.env.render()
                if len(self.exp_pool) >= self.exp_pool_size:
                    self.exp_pool.pop(0)
                    self.explore += 1e-7
                    if torch.rand(1) > self.explore:
                        action = self.env.action_space.sample()
                    else:
                        _state = torch.tensor(state, dtype=torch.float32)
                        Qs = self.q_net(_state[None, ...])
                        action = torch.argmax(Qs, 1)[0].item()
                else:
                    action = self.env.action_space.sample()

                next_state, reward, done, _ = self.env.step(action)
                R += reward
                self.exp_pool.append([state, reward, action, next_state, done])
                state = next_state

                if done:
                    avg = 0.95 * avg + 0.05 * R
                    print(avg, R)
                    if avg > 400:
                        is_render = True
                    break
            # 训练
            if len(self.exp_pool) >= self.exp_pool_size:
                exps = random.choices(self.exp_pool, k=100)
                _state = torch.tensor([exp[0] for exp in exps]).float()
                _reward = torch.tensor([[exp[1]] for exp in exps])
                _action = torch.tensor([[exp[2]] for exp in exps])
                _next_state = torch.tensor([exp[3] for exp in exps]).float()
                _done = torch.tensor([[int(exp[4])] for exp in exps])

                # 预测值
                _Qs = self.q_net(_state)
                _Q = torch.gather(_Qs, 1, _action)
                # 目标值
                _next_Qs = self.q_net(_next_state)
                _max_Q = torch.max(_next_Qs, dim=1, keepdim=True)[0]
                _target_Q = _reward + (1 - _done) * 0.9 * _max_Q

                loss = self.loss_fn(_Q, _target_Q.detach())
                self.opt.zero_grad()
                loss.backward()
                self.opt.step()


if __name__ == '__main__':
    g = Game(10000, 0.9)
    g()

代码很简练,慢慢读都能懂,我解释一下几个重点。

  1. 整个流程是先采样,将样本存入经验池self.exp_pool,当样本足够时,从经验池中随机选取100条样本进行训练,而后边更新经验池边训练。
  2. QNet就是我们学习的网络,状态到动作的映射,根据输入状态建议采取的动作。
  3. self.explore探索值,很容易可以看明白它是一个概率,控制动作是随机选取还是由网络推荐。探索可以让我们发现新鲜样本,但随着训练进行,我们见过的样本越来越多,应该逐渐减少探索,也就是降低随机动作的概率。
  4. R是每一局游戏的得分,因为这个值在训练中变化非常大,所以加了个avg滑动平均的操作,这样可以更清晰地看出训练效果。由于游戏限制,每局游戏最多到500分就会结束,所以我们设置当avg>400就开始显示图像。
  5. 其实强化学习DQN真正的精髓是在训练中目标值的确定,所以这块我们跳过~[666]

没错,就这点代码,训练2分钟,DQN就能学会玩这个平衡游戏啦!

在这里插入图片描述

再附带一个小车爬坡的小游戏

在这里插入图片描述
游戏名:MountainCar-v0。小车想到达最高峰,但其引擎强度不足以单程通过,所以要在两个山坡间反复横跳,积蓄力量,一鸣惊人~

这个游戏的区别是reward始终为-1,意思是只有到达终点才有奖励,其他打酱油行为都要扣分,拿到高分的办法就是尽快到达终点。状态有2个值:水平位置和速度。动作有3个值:左、右、不动。所以要把QNet的输入和输出维度改一下。

训练难点在于:游戏只持续200个动作,在随机选择动作的情况下,小车很难靠运气到达终点,也就很少有成功的经验,不容易学习。

next_state, reward, done, _ = self.env.step(action)
position, velocity = next_state
reward = (position + 0.5) ** 2
R += reward

于是我根据状态把reward给改了,position + 0.5的原因是,起始最低点的水平坐标值为-0.5,改完之后的reward含义就是:离最低点越远奖励越高,而且奖励还是成平方增长。

经过这通骚操作,就可以训练了,也只需要训练几分钟。avg的阈值我给的30,同学们看着给。

在这里插入图片描述

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值