RL 六

第一篇的代码,经过调试,运行没得问题了,我们来看看作者想让我们了解一些什么。

启动一个车竿实验

env_id = "CartPole-v0"  
env = gym.make(env_id)  
epsilon_start = 1.0
epsilon_final = 0.01
epsilon_decay = 500

epsilon_by_frame = lambda frame_idx: epsilon_final + (epsilon_start - epsilon_final) * math.exp(-1. * frame_idx / epsilon_decay)

lambda相当于就是一个小函数,参数为epsilon_final,冒号后面的部分计算了并得到返回值。

这里我们详细介绍下其中 e p s i l o n _ f i n a l + ( e p s i l o n _ s t a r t − e p s i l o n _ f i n a l ) ∗ m a t h . e x p ( − 1. ∗ f r a m e i d x / e p s i l o n _ d e c a y ) epsilon\_final + (epsilon\_start - epsilon\_final) * math.exp(-1. * frame_idx / epsilon\_decay) epsilon_final+(epsilon_startepsilon_final)math.exp(1.frameidx/epsilon_decay)
将代码整理为我们平时看见的公式的形式:
e p s i l o n f r a m e = e p s i l o n f i n a l + ( e p s i l o n s t a r t − e p s i l o n f i n a l ) × e ( − 1 × f r a m e i d x / e p s i l o n d e c a y ) epsilon_{frame}= epsilon_{final} + (epsilon_{start} - epsilon_{final}) \times e^{(-1 \times frame_{idx} / epsilon_{decay})} epsilonframe=epsilonfinal+(epsilonstartepsilonfinal)×e(1×frameidx/epsilondecay)
将全局变量替换了,然后公式简化为:
e p s i l o n f r a m e = 0.01 + 0.99 × 1 / e ( f r a m e i d x / 500 ) epsilon_{frame}= 0.01 + 0.99 \times 1/{e^{(frame_{idx} / 500)}} epsilonframe=0.01+0.99×1/e(frameidx/500)
在这里插入图片描述

折腾半天就是一个指数函数的变形,简单的分析一波。

  • f r a m e i d x frame_{idx} frameidx等于五百的时候, e p s i l o n f r a m e epsilon_{frame} epsilonframe等于1
  • f r a m e i d x frame_{idx} frameidx无限大的时候,这个函数无限接近于0.01

恩恩,记住这个,后面有用。

好吧,感觉有点运算的感觉了,但是还没得难度,大概深度还不够,我们继续。

class DQN(nn.Module):
    def __init__(self, num_inputs, num_actions):
        super(DQN, self).__init__()
        
        self.layers = nn.Sequential(
            nn.Linear(env.observation_space.shape[0], 128),
            nn.ReLU(),
            nn.Linear(128, 128),
            nn.ReLU(),
            nn.Linear(128, env.action_space.n)
        )
        
    def forward(self, x):
        return self.layers(x)
    
    def act(self, state, epsilon):
        if random.random() > epsilon:
            with torch.no_grad():
                state   = Variable(torch.FloatTensor(state).unsqueeze(0))
            q_value = self.forward(state)
            action  = q_value.max(1)[1].data[0]
        else:
            action = random.randrange(env.action_space.n)
        return action

这里就涉及到深度学习的计算了,DQN是自定义的类,在初始化的时候会定义一个self.layers的变量。

这个变量用到nn.Sequential函数就是申明一个所谓的神经网络,其实就是一个计算的集合。

也算是一个计算结构,其中,每个 nn.Linear 里面有个 m × n 的权重(weights)矩阵和一个 1 × n 的一个偏移(bias)矩阵,这些就是网络的核心部分。

Linear的计算: Q = x × w e i g h t s T + b i a s Q = x \times weights^T + bias Q=x×weightsT+bias
Rule的计算相对要简单一些,就是将小于零的变成0: Q = { x x > 0 0 o t h e r Q=\left\{ \begin{array}{rcl} x & & x>0 \\ 0 & & other \end{array}\right. Q={x0x>0other

然后怎么让这个跑起来,我们继续看 DQN 中的 act 函数,我再复制一次。

    def act(self, state, epsilon):
        if random.random() > epsilon:
            with torch.no_grad():
                state   = Variable(torch.FloatTensor(state).unsqueeze(0))
            q_value = self.forward(state)
            action  = q_value.max(1)[1].data[0]
        else:
            action = random.randrange(env.action_space.n)
        return action

这个函数里面,对 epsilon 进行了判断:

random.random() 是输出 [0, 1)的数,

所以

  • e p s i l o n > = 1 epsilon>=1 epsilon>=1 的时候,就运行else的算式,
  • e p s i l o n < 1 epsilon<1 epsilon<1的时候才有机会运行if中的算式。
    通过上面的方法就可以得到action,也就是预测的动作。

虽然得到预测的动作了,但是好像还没有讲到机器学习的学习部分,为啥机器学习的自动学习在哪儿呢, 恩恩,我们继续来看, 我对自己说,坚持一下,写完这段,我们就分一下。

训练的话,主要看这一段:

num_frames = 10000
batch_size = 32
gamma      = 0.99

losses = []
all_rewards = []
episode_reward = 0

state = env.reset()
for frame_idx in range(1, num_frames + 1):
    epsilon = epsilon_by_frame(frame_idx)
    action = model.act(state, epsilon)
    
    next_state, reward, done, _ = env.step(action)
    replay_buffer.push(state, action, reward, next_state, done)
    
    state = next_state
    episode_reward += reward
    
    if done:
        state = env.reset()
        all_rewards.append(episode_reward)
        episode_reward = 0
        
    if len(replay_buffer) > batch_size:
        loss = compute_td_loss(batch_size)
        losses.append(loss.item())
        
    if frame_idx % 200 == 0:
        plot(frame_idx, all_rewards, losses)

这里会就得到的训练数据保存在replay_buffer里面, 然后通过这个变量将 训练的数据 传递给训练的核心函数 – compute_td_loss ,这个是在前面实现的,我粘下来:

def compute_td_loss(batch_size):
    state, action, reward, next_state, done = replay_buffer.sample(batch_size)

    with torch.no_grad():
        state      = Variable(torch.FloatTensor(np.float32(state)))
        next_state = Variable(torch.FloatTensor(np.float32(next_state)))
        action     = Variable(torch.LongTensor(action))
        reward     = Variable(torch.FloatTensor(reward))
        done       = Variable(torch.FloatTensor(done))

    q_values      = model(state)
    next_q_values = model(next_state)

    q_value          = q_values.gather(1, action.unsqueeze(1)).squeeze(1)
    next_q_value     = next_q_values.max(1)[0]
    expected_q_value = reward + gamma * next_q_value * (1 - done)
    
    loss = (q_value - Variable(expected_q_value.data)).pow(2).mean()
        
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    return loss

这里面很大一部分都是算 loss 值, 该样例中 loss 值是环境的一个打分, 通过loss值来执行backward完成对之前的参数的修改, 修改过错中,其实就是用微分, 主要计算量在导数上。

怎么理解这个过程呢,我个人认为可以这样想, 就是loss值算出来大了,说明没达到理想效果,还记得前面的乘加计算嘛,就是通过导数的方式去减小或者加大前面的值,这样就可以少加一点或者多加一点达到我们理想的效果。

我们还记得之前有个函数包含 e ( f r a m e i d x / 500 ) e^{(frame_{idx} / 500)} e(frameidx/500) 这个项嘛, 感觉也算是一个铺垫, 因为 e x e^x ex的导数是它的本身, 后面可能经常碰到 e e e 这个常量。

对于怎么反向传导,怎么优化, 这里好像没有展开,不过可以看看torch.optim优化算法理解之optim.Adam()

loss是通过 q_value 和 expected_q_value 的差方值获取到的, 这两个值分别表示当前的打分和下一个理想状态的打分。

loss有点像是偏差的意思,偏差越小当然越好,我们通过这个计算方法,可以看出,作者是想将这两个值的差距尽量减小。

为啥作者有这个想法,我们看看下面的cartPole-v0图就知道了

cartPole-v0.gif

可能作者认为,只有是稳态,才能达到两个状态值基本相近的情况。所以作者最终目的就是让这个状态稳定。

当然,大家也可以根据自己的实际情况来完成这个loss的计算。


参考

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值