cart pole

目录

前言

本文由浅入深介绍如果使用强化学习玩cart pole游戏。

概念

在电子游戏世界(特指Atari 2600这一类的简单游戏。不包括推理解密类的游戏)中:

  • 环境指的是游戏本身,包括其内部的各种逻辑;
  • Agent指的是操作游戏的玩家,当然也可以是指操作游戏的AI算法; 状态就是指游戏在屏幕上展现的画面。游戏通过屏幕画面把状态信息传达给Agent。如果是棋类游戏,状态是离散的,状态的数量是有限的。但在动作类游戏(如打飞机)中,状态是画面中的每个物体(飞机,敌人,子弹等等)所处的位置和运动速度的组合。状态是连续的,而且数量几乎是无限的。
  • 动作是指手柄的按键组合,包括方向键和按钮的组合,当然也包括什么都不按(不做任何动作)。
  • 奖励是指游戏的得分,每击中一个敌人都可以得到一些得分的奖励。
  • 策略是Agent脑子里从状态到动作的映射。也就是说,每当Agent看到一个游戏画面(状态),就应该知道该如何操纵手柄(动作)。

Reinforcement Learning算法的任务就是找到最佳的策略。策略的表示方法可以有很多。比如: 当状态的个数是有限的情况下,我们可以给每个状态指定一个最佳动作,以后只要看到某种状态出现,就去查询这个状态对应的动作就可以了。这种表示方法类似于查询字典。用这种方法表示最佳策略是没问题的,但是却不利于我们找出最佳策略,因为这种表示法是不可微分的(non-differentiable)。 我们还可以给每个状态下所有可能的动作估一个价值(Q-value),当我们看到某个状态的时候,我们去比较这个状态下所有动作的价值,找出价值最大的那一个。这种表示方法实际上可以用一个二维的表格来表示(Tabular Representation)。 当状态的个数是无限多时,我们已经不可能用表格来表示策略了。这时我们只能用一些函数拟合(Function Approximation)的方法把状态,动作,价值这三者的关系表示出来。神经网络目前是最佳的选择。

Cart Pole游戏介绍

游戏规则

cart pole即车杆游戏,游戏如下,很简单,游戏里面有一个小车,上有竖着一根杆子。小车需要左右移动来保持杆子竖直。如果杆子倾斜的角度大于15°,那么游戏结束。小车也不能移动出一个范围(中间到两边各2.4个单位长度)。

action

  • 左移
  • 右移

state variables

  • position of the cart on the track
  • angle of the pole with the vertical
  • cart velocity
  • rate of change of the angle

分别表示车的位置,杆子的角度,车速,角度变化率

游戏奖励

在gym的Cart Pole环境(env)里面,左移或者右移小车的action之后,env都会返回一个+1的reward。到达200个reward之后,游戏也会结束。

avatar

gym介绍

简单玩法

一直左移

# coding: utf8

import gym
import time

env = gym.make("CartPole-v0")

env.reset()

# 一直左移
action = 0
sum_reward = 0

for i in range(1000):
    env.render()
    time.sleep(1)
    observation, reward, done, info = env.step(action)
    sum_reward += reward
    print(observation, reward, sum_reward, done, info)
    if done:
        break

最后输出: 注意每次输出不一定一样。

[ 0.01332186 -0.20951928 -0.01201124  0.27060169] 1.0 1.0 False {} 0
[ 0.00913147 -0.40446779 -0.0065992   0.55947214] 1.0 2.0 False {} 1
[ 0.00104212 -0.59949649  0.00459024  0.85006868] 1.0 3.0 False {} 2
[-0.01094781 -0.79468073  0.02159161  1.14419149] 1.0 4.0 False {} 3
[-0.02684143 -0.99007801  0.04447544  1.44356652] 1.0 5.0 False {} 4
[-0.04664299 -1.18571827  0.07334677  1.74980819] 1.0 6.0 False {} 5
[-0.07035735 -1.38159249  0.10834294  2.06437417] 1.0 7.0 False {} 6
[-0.0979892  -1.5776388   0.14963042  2.38850996] 1.0 8.0 False {} 7
[-0.12954198 -1.77372622  0.19740062  2.72318191] 1.0 9.0 False {} 8
[-0.1650165  -1.96963594  0.25186426  3.06899915] 1.0 10.0 True {} 9

根据杆子角度调整

如果杆子角度为负,则右移;否则左移。

# coding: utf8

import gym
import time

env = gym.make("CartPole-v0")

env.reset()

# 一直往左移
action = 0
sum_reward = 0

for i in range(1000):
    env.render()
    time.sleep(0.1)
    observation, reward, done, info = env.step(action)
    if observation[1] < 0:
        action = 1
    else:
        action = 0
    sum_reward += reward
    print(observation, reward, sum_reward, done, info)
    if done:
        break

 

最后输出

[-0.01882535 -0.17022571 -0.03011459  0.27344505] 1.0 1.0 False {}
[-0.02222987  0.0253127  -0.02464569 -0.02858193] 1.0 2.0 False {}
[-0.02172361 -0.16944731 -0.02521733  0.25622426] 1.0 3.0 False {}
[-0.02511256  0.02602544 -0.02009284 -0.04430474] 1.0 4.0 False {}
[-0.02459205 -0.16880272 -0.02097894  0.2419716 ] 1.0 5.0 False {}
[-0.0279681   0.02661253 -0.0161395  -0.05725412] 1.0 6.0 False {}
[-0.02743585 -0.16827434 -0.01728459  0.2302933 ] 1.0 7.0 False {}
[-0.03080134  0.02709028 -0.01267872 -0.06779128] 1.0 8.0 False {}
[-0.03025953 -0.16784762 -0.01403455  0.22086463] 1.0 9.0 False {}
...
[-0.10049214 -0.18592199  0.11853794  0.62351417] 1.0 57.0 False {}
[-0.10421058  0.00736289  0.13100822  0.37038998] 1.0 58.0 False {}
[-0.10406332 -0.18935337  0.13841602  0.70134211] 1.0 59.0 False {}
[-0.10785039  0.00360618  0.15244286  0.45523554] 1.0 60.0 False {}
[-0.10777827 -0.19330536  0.16154758  0.79182232] 1.0 61.0 False {}
[-0.11164438 -0.00072631  0.17738402  0.55400362] 1.0 62.0 False {}
[-0.1116589   0.19151957  0.18846409  0.32203753] 1.0 63.0 False {}
[-0.10782851 -0.00571617  0.19490485  0.66773352] 1.0 64.0 False {}
[-0.10794283  0.18623823  0.20825952  0.44219351] 1.0 65.0 False {}
[-0.10421807 -0.01112806  0.21710339  0.79263264] 1.0 66.0 True {}

Random Guessing Algorithm

observation是一个四维向量,如果对这个向量求它的加权和,就可以得到一个值,那么就可以根据加权和的符号来决定action,同样可以用sigmoid函数当成二分类问题。这样就可以通过改变权重值来改变policy。

代码如下, 包含了hill climbing算法的代码。

# coding: utf8

import numpy as np
import gym
import time

def get_action(weights, observation):
    wxb = np.dot(weights[:4], observation) + weights[4]
    if wxb >= 0:
        return 1
    else:
        return 0

def get_sum_reward_by_weights(env, weights):
    observation = env.reset()
    sum_reward = 0
    for t in range(1000):
        # time.sleep(0.01)
        # env.render()
        action = get_action(weights, observation)
        observation, reward, done, info = env.step(action)
        sum_reward += reward
        # print(sum_reward, action, observation, reward, done, info)
        if done:
            break
    return sum_reward


def get_weights_by_random_guess():
    return np.random.rand(5)

def get_weights_by_hill_climbing(best_weights):
    return best_weights + np.random.normal(0, 0.1, 5)

def get_best_result(algo="random_guess"):
    env = gym.make("CartPole-v0")
    np.random.seed(10)
    best_reward = 0
    best_weights = np.random.rand(5)

    for iter in range(10000):
        cur_weights = None

        if algo == "hill_climbing":
            # print(best_weights)
            cur_weights = get_weights_by_hill_climbing(best_weights)
        else:
            cur_weights = get_weights_by_random_guess()

        cur_sum_reward = get_sum_reward_by_weights(env, cur_weights)

        # print(cur_sum_reward, cur_weights)
        if cur_sum_reward > best_reward:
            best_reward = cur_sum_reward
            best_weights = cur_weights

        if best_reward >= 200:
            break

    print(iter, best_reward, best_weights)
    return best_reward, best_weights

print(get_best_result("hill_climbing"))

# env = gym.make("CartPole-v0")
# get_sum_reward_by_weights(env, [0.22479665, 0.19806286, 0.76053071, 0.16911084, 0.08833981])

 

Hill Climbing Algorithm

Hill Climbing Algorithm会给当前最好的权重加上一组随机值,如果加上这组值持续时间变长了那么就更新最好的权重,如果没有变的更好就不更新。

refer: https://www.fashici.com/tech/836.html

 

 

# -*- coding: utf-8 -*-
#载入库
import numpy as np
import tensorflow as tf
import gym

env = gym.make('CartPole-v0')
#创建CartPole问题的环境env

env.reset()
#初始化环境

random_episodes = 0
reward_sum = 0#奖励
while random_episodes < 10:
    env.render()#将CartPole问题的图像渲染出来

    observation, reward, done, _ = env.step(np.random.randint(0, 2))
    #使用np.random.randint(0, 2)产生随机的Action
    #然后使用env.step()执行随机的Action,并获取返回值
    #如果done标记为True,则表示这次试验结束,即倾角超过15度或者偏离中心过远导致任务失败

    reward_sum += reward
    if done:#如果试验结束
        random_episodes += 1
        print("game over,Reward for this episode was:", reward_sum)
        #输出这次试验累计的奖励
        reward_sum = 0 #奖励重新置为0
        env.reset()#重启环境


print "随机测试结束"

# 超参数
H = 50  # 隐含的节点数
batch_size = 25  #
learning_rate = 1e-1  # 学习率
gamma = 0.99  # Reward的discount比例设为0.99,该值必须小于1.
#防止Reward被无损耗地不断累加导致发散,这样也能区分当前Reward和未来的Reward的价值
#当前Action直接带来的Reward不需要discount,而未来的Reward因存在不确定性,所以需要discount

D = 4  # 环境信息observation的维度D为4

tf.reset_default_graph()

#策略网络的具体结构。
#该网络将接受observation作为信息输入,最后输出一个概率值用以选择Action
#这里只有两个Action,向左施加力或者向右施加力,因此可以通过一个概率值决定

observations = tf.placeholder(tf.float32, [None, D], name="input_x")
#创建输入信息observations的placeholder其维度为D

#使用tf.contrib.layers.xavier_initializer方法初始化隐含层的权重W1,其维度为[D,H]
W1 = tf.get_variable("W1", shape=[D, H],
                     initializer=tf.contrib.layers.xavier_initializer())

layer1 = tf.nn.relu(tf.matmul(observations, W1))
#接着使用tf.matmul将环境信息observations乘上W1再使用relu激活函数处理得到隐含层的输出layer1

#使用tf.contrib.layers.xavier_initializer方法初始化隐含层的权重W2,其维度为[H,1]
W2 = tf.get_variable("W2", shape=[H, 1],
                     initializer=tf.contrib.layers.xavier_initializer())
score = tf.matmul(layer1, W2)
probability = tf.nn.sigmoid(score)
#将隐含层输出layer1乘以W2后,使用Sigmoid激活函数处理得到最后的输出概率


# From here we define the parts of the network needed for learning a good policy.
tvars = tf.trainable_variables()#获取策略网络中全部可训练的参数tvars
input_y = tf.placeholder(tf.float32, [None, 1], name="input_y")
advantages = tf.placeholder(tf.float32, name="reward_signal")
#定义人工设置的虚拟label的占位符input_y
#以及每个Action的潜在价值的占位符

#当Action取值为1的概率为probability(即策略网络输出的概率)
#当Action取值为0的概率为1-probability
#label取值与Action相反,即label=1-Action。
#当Action为1时,label为0,此时loglik=tf.log(probability),Action取值为1的概率的对数
#当Action为0时,label为1,此时loglik=tf.log(1-probability),Action取值为0的概率的对数
#所以,loglik其实就是当前Action对应的概率的对数
loglik = tf.log(input_y * (input_y - probability) + (1 - input_y) * (input_y + probability))

loss = -tf.reduce_mean(loglik * advantages)
#将loglik与潜在价值advanages相乘,并取负数作为损失,即优化目标
newGrads = tf.gradients(loss, tvars)
#使用tf.gradients求解模型参数关于loss的梯度

# Once we have collected a series of gradients from multiple episodes, we apply them.
# We don't just apply gradeients after every episode in order to account for noise in the reward signal.
#模型的优化器使用Adam算法
adam = tf.train.AdamOptimizer(learning_rate=learning_rate)  # Our optimizer
W1Grad = tf.placeholder(tf.float32, name="batch_grad1")  # Placeholders to send the final gradients through when we update.
W2Grad = tf.placeholder(tf.float32, name="batch_grad2")
#我们分别设置两层神经网络参数的梯度的placeholder

batchGrad = [W1Grad, W2Grad]
updateGrads = adam.apply_gradients(zip(batchGrad, tvars))
#并使用adam.apply_gradients定义我们更新模型参数的操作updateGrads
#之后计算参数的梯度,当积累到一定样本量的梯度,就传入W1Grad和W2Grad,并执行updateGrads更新模型参数
#注意:
#深度强化学习的训练和其他神经网络一样,也使用batch training的方式。
#我们不逐个样本的更新参数,而是累计到一个batch_size的样本的梯度在更新参数
#防止单一样本随机扰动的噪声对模型带来不良影响。


#用来估算每一个Action对应的潜在价值discount_r
#因为CartPole问题中每次获得的Reward都和前面的Action有关,属于delayed reward
#因此需要比较精确地衡量每一个Action实际带来的价值,不能只看当前这一步的Reward,而要考虑后面的Delayed Reward
#哪些能让Pole长时间保持在空中竖直的Action应该拥有较大的值,而哪些最终导致pole倾倒的Action,应该拥有较小的期望价值。
#我们判断越靠后的Aciton的期望价值越小,因为它们更可能是导致Pole倾倒的原因,并且判断越考前的期望价值约大,因为它们
#长时间保持了Pole的竖直,和整倾倒的原因没那么大
#在CartPole问题中,除了最后结束时刻的Action为0,其余的均为1,。
def discount_rewards(r):
    """ take 1D float array of rewards and compute discounted reward """
    discounted_r = np.zeros_like(r)
    running_add = 0
    #定义每个Action除直接获得的Reward外的潜在价值running_add
    #running_add是从后向前累计的,并且需要经过discount衰减。
    #每一个Action的潜在价值,即为后一个Action的前在价值乘以衰减系数gamma,再加上它直接获得的reward
    #即running_add * gamma + r[t]
    #这样从最后一个Action不断向前累计计算,即可得到全部Action的潜在价值。
    for t in reversed(range(r.size)):
        running_add = running_add * gamma + r[t]
        discounted_r[t] = running_add
    return discounted_r

#xs为环境信息observation的列表
#ys为我们定义的label的列表
#drs为我们记录的每一个Action的Reward
xs, ys, drs = [], [], []
# running_reward = None
reward_sum = 0  #累计的Reward
episode_number = 1
total_episodes = 10000 #总试验次数
init = tf.global_variables_initializer()

# Launch the graph
with tf.Session() as sess:#创建sess
    rendering = False #render的标志关闭,因为会带来较大的延迟
    sess.run(init)#初始化全部参数

    observation = env.reset()  # Obtain an initial observation of the environment
    #先初始化CartPole的环境并获得初始状态

    # Reset the gradient placeholder. We will collect gradients in
    # gradBuffer until we are ready to update our policy network.
    gradBuffer = sess.run(tvars)
    #获取所有模型参数,用来创建储存参数梯度的缓冲器gradBuffer

    for ix, grad in enumerate(gradBuffer):
        gradBuffer[ix] = grad * 0
        #将gradBuffer全部初始化为0

    #接下来,每次试验中,我们将收集参数的梯度存储到gradBuffer中,直到完成了一个batch_size的试验
    #再将汇总的梯度更新到模型参数
    while episode_number <= total_episodes:

        # Rendering the environment slows things down,
        # so let's only look at it once our agent is doing a good job.
        # 当某个batch的平均Reward达到100以上时,即Agent表现良好。
        if reward_sum / batch_size > 100 or rendering == True:
            #调用env.render()对试验环境进行展示
            env.render()
            rendering = True

        # Make sure the observation is in a shape the network can handle.
        # 先使用tf.reshape将observation变形为策略网络的输入的格式
        x = np.reshape(observation, [1, D])

        # Run the policy network and get an action to take.
        # 然后传入网络中
        tfprob = sess.run(probability, feed_dict={observations: x})
        #获得网络输出的概率tfprob,即Action取值为1的概率

        action = 1 if np.random.uniform() < tfprob else 0
        #接下来,我们在0到1之间随机抽样,若随机值小于tfprob,则令Action取值为1,
        #否则令Action取值为0,

        xs.append(x)  # 将输入的环境信息observation添加到列表xs中
        y = 1 if action == 0 else 0  # a "fake label" 虚拟的label
        ys.append(y)#添加到列表ys中

        # step the environment and get new measurements
        observation, reward, done, info = env.step(action)
        #使用env.step执行一次Action,获取observation,reward,done和info

        reward_sum += reward
        #将reward累加到reward_sum

        drs.append(reward)  # record reward (has to be done after we call step() to get reward for previous action)
        #并将reward添加到列表drs中

        if done:#为True时,表示一次试验结束
            episode_number += 1
            # stack together all inputs, hidden states, action gradients, and rewards for this episode
            epx = np.vstack(xs)
            epy = np.vstack(ys)
            epr = np.vstack(drs)
            #使用np.vstack将几个列表xs,ys,drs中的元素纵向堆叠起来

            xs, ys, drs = [], [], []  # 清空,以备下次使用


            discounted_epr = discount_rewards(epr)
            discounted_epr -= np.mean(discounted_epr)
            discounted_epr /= np.std(discounted_epr)
            # 使用前面定义好的discount_rewards函数计算每一步Action的潜在价值
            #并进行标准化,得到一个零均值,标准差为1的分布
            #discount_reward会参与到模型损失的计算,分布稳定的discount_reward有利于训练的稳定

            # Get the gradient for this episode, and save it in the gradBuffer
            #将epx,epy,discounted_epr输入网络,并求解梯度
            tGrad = sess.run(newGrads, feed_dict={observations: epx, input_y: epy, advantages: discounted_epr})
            for ix, grad in enumerate(tGrad):
                gradBuffer[ix] += grad
                #将获得的梯度累加到gradBuffer中去

            # If we have completed enough episodes, then update the policy network with our gradients.
            if episode_number % batch_size == 0:
                #当进行试验的次数达到batch_size的整数倍时,gradBuffer中就积累了足够多的梯度。
                #因此使用updateGrads操作将gradBuffer中的梯度更新到策略网络的模型参数中去
                sess.run(updateGrads, feed_dict={W1Grad: gradBuffer[0], W2Grad: gradBuffer[1]})
                for ix, grad in enumerate(gradBuffer):
                    gradBuffer[ix] = grad * 0
                    #清空gradBuffer为计算下一个batch的梯度做准备

                # Give a summary of how well our network is doing for each batch of episodes.
                # running_reward = reward_sum if running_reward is None else running_reward * 0.99 + reward_sum * 0.01
                print('Average reward for episode %d : %f.' % (episode_number, reward_sum / batch_size))

                if reward_sum / batch_size > 200:
                    print("Task solved in", episode_number, 'episodes!')
                    break

                reward_sum = 0

            observation = env.reset()
            #每次试验结束后,将任务环境env重置,方便下一次试验

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值