使用Policy network和Value network实现CartPole

我们知道,著名的AlphaGo的基本组成是由策略网络(Policy network)估值网络(Value network),蒙特卡洛搜索树(Monte Carlo Tree Search)来共同完成,value network用于评估局面,policy network用于决策:

而Monte Carlo Tree Search作为一种解决多轮序贯博弈问题的策略,我们会在今后进行研究,今天首先要做的是对两个网络进行基本地使用:使用其来实现CartPole,CartPole是一个简单的游戏,游戏策略即为如图所示的模型,为模型施与一个向右或者向左的力,如果小车偏离中心超过2.4个单位距离,或者杆的倾斜度超过15度则视为游戏结束。

这里我们借助OpenAI Gym来实现。
下面进入正题:

Policy network

策略网络即一个神经网络模型,可以通过观察当前的环境状态,来直接预测出一个最佳的行动策略,使这个策略可以获得最大的期望收益。得到每个行动方案所对应的概率。
所以解决CartPole问题,我们就有了方案:根据输入的环境参数state,来得到对应的每个action的概率。
在这里使用一个隐藏层来实现:

H = 50

observate = tf.placeholder(tf.float32, [None, 4], name="input_x")
W1 = tf.get_variable("w1", shape=[4, H],
                     initializer=tf.contrib.layers.xavier_initializer())
layer1 = tf.nn.relu(tf.matmul(observate, W1))
W2 = tf.get_variable("w2", shape=[H, 1],
                     initializer=tf.contrib.layers.xavier_initializer())
score = tf.matmul(layer1, W2)
probability = tf.nn.sigmoid(score)

其中H为隐藏层的层数,环境信息值observate并不是像素值,而是记录小车速度,位置,杆的角度,速度等信息的有四个值的数组。
我们设力向左为0,向右为1,probability为Action为1的概率。

而训练的方向,则是基于一个环境,获取价值越高的action所对应的probability应该越大。
我们设置每做出一个action之后,如果游戏没有结束,则reward为1,否则为0,那么每个环境都有一个对应reward为1的action。
我们当前的学习目标期望的价值,则为当前的Reward加上未来潜在的可获取的reward。

设置gamma为0~1的数,防止目标发散:

def discount_reward(r):
    # 根据每个reward:r和gamma来求每次的潜在价值
    discount_r = np.zeros_like(r)
    running_add = 0
    for t in reversed(range(r.size)):
        running_add = running_add * gamma + r[t]
        discount_r[t] = running_add
    return discount_r

由此得到每个action的潜在价值。
我们所要得到的训练结果为价值越大概率越大,价值越小概率越小。那么我们将loss设置为

当前的action对应的probability与其相应的价值取反。可以扩大当前的probability,而由于当前的action为使游戏顺利进行的action,故可以得到目标结果。

对以上理论进行整合和实现,得到Policy network实现的代码:

import numpy as np
import tensorflow as tf
import gym
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
env = gym.make('CartPole-v0')

env.reset()
H = 50
batch_size = 25
learning_rate = 1e-1
D = 4
gamma = 0.99
xs, ys, drs = [], [], []
reward_sum = 0
episode_number = 1
total_episodes = 1000

# 根据当前的环境状态根据隐藏节点求action为1的概率
observate = tf.placeholder(tf.float32, [None, D], name="input_x")
W1 = tf.get_variable("w1", shape=[D, H],
                     initializer=tf.contrib.layers.xavier_initializer())
layer1 = tf.nn.relu(tf.matmul(observate, W1))
W2 = tf.get_variable("w2", shape=[H, 1],
                     initializer=tf.contrib.layers.xavier_initializer())
score = tf.matmul(layer1, W2)
probability = tf.nn.sigmoid(score)

# 根据概率来求损失和梯度
input_y = tf.placeholder(tf.float32, [None, 1], name="input_y")
advantages = tf.placeholder(tf.float32, name="reward_signal")
loglik = tf.log(input_y * (input_y - probability) +
                (1 - input_y) * (input_y + probability))
loss = -tf.reduce_mean(loglik * advantages)

tvars = tf.trainable_variables()
newGrads = tf.gradients(loss, tvars)


# 根据梯度优化训练两层神经网络
adam = tf.train.AdamOptimizer(learning_rate=learning_rate)
W1grad = tf.placeholder(tf.float32, name="batch_grad1")
W2grad = tf.placeholder(tf.float32, name="batch_grad2")
batchGrad = [W1grad, W2grad]
updateGrads = adam.apply_gradients(zip(batchGrad, tvars))


def discount_reward(r):
    # 根据每个reward:r和gamma来求每次的潜在价值
    discount_r = np.zeros_like(r)
    running_add = 0
    for t in reversed(range(r.size)):
        running_add = running_add * gamma + r[t]
        discount_r[t] = running_add
    return discount_r

# Session执行
with tf.Session() as sess:
    rendering = False
    init = tf.global_variables_initializer()
    sess.run(init)
    observation = env.reset()
    gradBuff = sess.run(tvars)
    for ix, grad in enumerate(gradBuff):
        gradBuff[ix] = grad * 0
    while episode_number <= total_episodes:

        if reward_sum / batch_size > 100 or rendering == True:
            rendering = True
            env.render()

        x = np.reshape(observation, [1, D])

        tfprob = sess.run(probability, feed_dict={observate: x})
        action = 1 if np.random.uniform() < tfprob else 0
        xs.append(x)
        y = 1 - action
        ys.append(y)

        observation, reward, done, info = env.step(action)
        reward_sum += reward
        drs.append(reward)

        if done:
            episode_number += 1
            epx = np.vstack(xs)
            epy = np.vstack(ys)
            epr = np.vstack(drs)
            xs, ys, drs = [], [], []
            discount_epr = discount_reward(epr)
            discount_epr -= np.mean(discount_epr)
            discount_epr /= np.std(discount_epr)

            tGrad = sess.run(newGrads, feed_dict={observate:epx,
                                                  input_y:epy,
                                                  advantages: discount_epr})
            for ix, grad in enumerate(tGrad):
                gradBuff[ix] += grad

            if episode_number % batch_size == 0:
                sess.run(updateGrads, feed_dict={W1grad:gradBuff[0],
                                                 W2grad:gradBuff[1]})
                for ix, grad in enumerate(gradBuff):
                    gradBuff[ix] = grad * 0
                print('Average reward for episode %d: %f.' % \
                      (episode_number, reward_sum/batch_size))

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

                reward_sum = 0

            observation = env.reset()

观察结果:

只进行了一百多次实验,平均reward便已经可以达到100了。

Value network

与策略网络不同的是,估值网络则是学习action对应的期望价值,成为Q-learning,期望价值指的是从当前的这一步到后续的所有步骤总共可以获得的期望的最大值,用Q表示。
关于Q-learning的简单实用: https://www.jianshu.com/p/1c0d5e83b066

可以知道 ,Q矩阵记录的内容为在某一个state下所有的action对应的Q值,但是 在稍微复杂的环境中,如CartPole游戏,state是有非常多的,我们不可能把所有的state都用一个Q矩阵来记录,所以我们引入DQN,即较深层的神经网络,DQN与普通的Q-learning不同的在于相较于简单的矩阵记录方法,我们使用神经网络来对输入的state和每个action的Q值来进行训练

如上图,输入state,经过神经网络的处理以后 得到每个action的value。根据最大的value来选择action。
我们使用两个神经网络来对state进行处理得到每个value的价值。

    W1 = tf.Variable(tf.truncated_normal([STATE, HIDDEN_SIZE]))
    b1 = tf.Variable(tf.constant(0.01, shape = [HIDDEN_SIZE]))
    W2 = tf.Variable(tf.truncated_normal([HIDDEN_SIZE, ACTION]))
    b2 = tf.Variable(tf.constant(0.01, shape=[ACTION]))

    state_input = tf.placeholder("float",[None,STATE])
    h_layer = tf.nn.relu(tf.matmul(state_input,W1) + b1)
    Q_value = tf.matmul(h_layer,W2) + b2

然后使用一个buffer缓存来储存若干个数据,每次从中随机取出batch_size个数据来进行训练,为了使数据可变,当数据数量超出规定个数以后使用新数据替换掉较老的数据

buffer = deque()

def add(state,action,reward,next_state,done):
    if len(buffer) > 100:
        buffer.popleft()
    buffer.append((state,action,reward,next_state,done))

规定loss值为step得出的reward值与求解出来的R值来做差平方后求均值,使得两者的值更加接近即可。

action_input = tf.placeholder("float",[None, ACTION])
y_input = tf.placeholder("float",[None])
Q_action = tf.reduce_sum(tf.multiply(Q_value, action_input),reduction_indices=1)
cost = tf.reduce_mean(tf.square(y_input - Q_action))
optimizer = tf.train.AdamOptimizer(0.001).minimize(cost)

接下来我们使用类将它们封装后一起代入,可以得到经过训练后的结果

import gym
import tensorflow as tf
import numpy as np
import random
from collections import deque

GAMMA = 0.9
INITIAL_EPSILON = 0.5
FINAL_EPSILON = 0.01
REPLAY_SIZE = 10000
BATCH_SIZE = 32
HIDDEN_SIZE = 20

class DQN():
    def __init__(self, env):
        self.replay_buffer = deque()
        self.time_step = 0
        self.epsilon = INITIAL_EPSILON
        self.state_dim = env.observation_space.shape[0]
        self.action_dim = env.action_space.n

        self.create_Q_network()
        self.create_training_method()

        self.session = tf.InteractiveSession()
        self.session.run(tf.initialize_all_variables())

    def create_Q_network(self):
        W1 = self.weight_variable([self.state_dim, HIDDEN_SIZE])
        b1 = self.bias_variable([HIDDEN_SIZE])
        W2 = self.weight_variable([HIDDEN_SIZE,self.action_dim])
        b2 = self.bias_variable([self.action_dim])

        self.state_input = tf.placeholder("float",[None,self.state_dim])

        h_layer = tf.nn.relu(tf.matmul(self.state_input,W1) + b1)

        self.Q_value = tf.matmul(h_layer,W2) + b2

    def create_training_method(self):
        self.action_input = tf.placeholder("float",[None,self.action_dim]) # one hot presentation
        self.y_input = tf.placeholder("float",[None])
        Q_action = tf.reduce_sum(tf.multiply(self.Q_value,self.action_input),reduction_indices = 1)
        self.cost = tf.reduce_mean(tf.square(self.y_input - Q_action))
        self.optimizer = tf.train.AdamOptimizer(0.0001).minimize(self.cost)

    def perceive(self,state,action,reward,next_state,done):
        one_hot_action = np.zeros(self.action_dim)
        one_hot_action[action] = 1
        self.replay_buffer.append((state,one_hot_action,reward,next_state,done))
        if len(self.replay_buffer) > REPLAY_SIZE:
            self.replay_buffer.popleft()

        if len(self.replay_buffer) > BATCH_SIZE:
            self.train_Q_network()

    def train_Q_network(self):
        self.time_step += 1
        minibatch = random.sample(self.replay_buffer,BATCH_SIZE)
        state_batch = [data[0] for data in minibatch]
        action_batch = [data[1] for data in minibatch]
        reward_batch = [data[2] for data in minibatch]
        next_state_batch = [data[3] for data in minibatch]
        print(reward_batch)
        y_batch = []
        Q_value_batch = self.Q_value.eval(feed_dict={self.state_input:next_state_batch})
        for i in range(0,BATCH_SIZE):
            done = minibatch[i][4]
            if done:
                y_batch.append(reward_batch[i])
            else :
                y_batch.append(reward_batch[i] + GAMMA * np.max(Q_value_batch[i]))
        #print(state_batch)
        #print(action_batch)
        #print(y_batch)
        self.optimizer.run(feed_dict={
            self.y_input:y_batch,
            self.action_input:action_batch,
            self.state_input:state_batch
        })

    def egreedy_action(self,state):
        value = self.Q_value.eval(feed_dict = {
            self.state_input:[state]
        })
        self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON)/10000
        Q_value = value[0]
        if random.random() <= self.epsilon:
            return random.randint(0,self.action_dim - 1)
        else:
            return np.argmax(Q_value)


    def action(self,state):
        value = self.Q_value.eval(feed_dict = {
        self.state_input:[state]
        })

        return np.argmax(value[0])

    def weight_variable(self,shape):
        initial = tf.truncated_normal(shape)
        return tf.Variable(initial)

    def bias_variable(self,shape):
        initial = tf.constant(0.01, shape = shape)
        return tf.Variable(initial)

ENV_NAME = 'CartPole-v0'
EPISODE = 10000
STEP = 300
TEST = 10


def main():
    env = gym.make(ENV_NAME)
    agent = DQN(env)

    for episode in range(EPISODE):

        state = env.reset()

        for step in range(STEP):
            action = agent.egreedy_action(state)
            next_state,reward,done,_ = env.step(action)

            agent.perceive(state,action,reward,next_state,done)
            state = next_state
            if done:
                break

        if episode % 100 == 0:
            total_reward = 0
            for i in range(TEST):
                state = env.reset()
                for j in range(STEP):
                    if total_reward/TEST >= 160:
                        env.render()
                    action = agent.action(state)
                    state,reward,done,_ = env.step(action)
                    total_reward += reward
                    if done:
                        break
            ave_reward = total_reward/TEST
            print ('episode: ',episode,'Evaluation Average Reward:',ave_reward)


if __name__ == '__main__':
    main()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 男神的新书里说 在很久的时间里,黄光单纯地来自文字,图像几乎完全没有 顿时 脑洞大开 每天 看着这些坚硬的壳被略施...
    蚝宝宝阅读 308评论 0 0
  • 你认识我的时候我已经是这个样子 是个符合或者不符合你想法的成品 你也无法参与我的成长 不能看到我从不懂事到懂事 从...
    哀慕熙荣阅读 90评论 0 1
  • 文:少女 图片来源:网络 都说生孩子对女人来说是一次“脱胎换骨”,生产后能否及时、科学地对自己的健康进行管理往往成...
    原子妈咪阅读 408评论 0 0
  • 当你什么都还不是的时候,你的很多忙别人多半不会来帮的 就像网上流传很广已久的的一句话“当你成功了,什么好资源都自然...
    睡过坟堆的人阅读 220评论 0 1
  • 我的恩师——张伟 我想,曾经我最恨的老师就是她了吧。这个有着男性名字的女老师。她可真能豁出去啊,甭管是早上凌晨五点...
    乱雪飘阅读 436评论 5 2