系列文章目录
强化学习
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题 。
一、强化学习是什么?
强化学习是智能体(Agent)以“试错”的方式进行学习,通过与环境进行交互获得的奖赏指导行为,目标是使智能体获得最大的奖赏,强化学习不同于连接主义学习中的监督学习,主要表现在强化信号上,强化学习中由环境提供的强化信号是对产生动作的好坏作一种评价(通常为标量信号),而不是告诉强化学习系统RLS(reinforcement learning system)如何去产生正确的动作。由于外部环境提供的信息很少,RLS必须靠自身的经历进行学习。通过这种方式,RLS在行动-评价的环境中获得知识,改进行动方案以适应环境。
理解:强化学习其实就是和人一样,一开始是什么都不懂的,所谓吃一堑长一智,他像一个新生的孩子,它在不断的试错过程中慢慢知道了做什么有奖励,做什么对得到奖励会有一定的价值,做什么会被打。在这个过程中不会像监督学习一样有个师傅带你,完全需要自己去摸索,就像修仙宗门一样,有背景的宗门弟子是继承掌门之位(监督),创立宗门的人是开山立派(强化),必须一步一个脚印去不断成长。
其实强化学习吸引我的就是因为它主要使用在游戏上,例如:
在 Flappy bird 这个游戏中,我们需要简单的点击操作来控制小鸟,躲过各种水管,飞的越远越好,因为飞的越远就能获得更高的积分奖励。
机器有一个玩家小鸟——Agent
需要控制小鸟飞的更远——目标
整个游戏过程中需要躲避各种水管——环境
躲避水管的方法是让小鸟用力飞一下——行动
飞的越远,就会获得越多的积分——奖励
二、核心算法(深度强化学习)Deep Deterministic Policy Gradient
什么是Actor-Critic ?
Actor-Critic算法分为两部分,我们分开来看actor的前身是policy gradient他可以轻松地在连续动作空间内选择合适的动作,value-based的Qlearning做这件事就会因为空间过大而爆炸,但是又因为Actor是基于回合更新的所以学习效率比较慢,这时候我们发现可以使用一个value-based的算法作为Critic就可以实现单步更新。这样两种算法相互补充就形成了Actor-Critic。
这跟深度学习中的对抗神经网络思想类似,一个生成器一个判别器对应的就是Actor与Critic。
优势:可以进行单步更新, 相较于传统的PG回合更新要快.
劣势:Actor的行为取决于 Critic 的Value,但是因为 Critic本身就很难收敛和actor一起更新的话就更难收敛了。
为了解决收敛问题, Deepmind 提出了 Actor Critic 升级版 Deep Deterministic Policy Gradient,所以DDPG可以看成Actor Critic+DQN的版本。
Actor Critic分别是两个神经网络,Actor主要是为了动作的选择存储,而Critic主要是对当前状态价值的评估,也就是Q现实-Q预测之间的误差,转递给Actor。就好像中国好声音,台上的Actor演唱完后,导师Critic告诉你某个地方唱得好,那个地方不满意。
对于网络的可视化可以看出,Critic的输入是在当前环境所处的状态,最后返回一个TD误差,Actor输入的是当前状态,选择动作,TD误差。
import numpy as np
import tensorflow as tf
import gym
np.random.seed(2)
tf.set_random_seed(2) # reproducible
# Tensorboard可视化
OUTPUT_GRAPH = True
MAX_EPISODE = 3000 #最大存储空间
DISPLAY_REWARD_THRESHOLD = 200 # renders environment if total episode reward is greater then this threshold
MAX_EP_STEPS = 1000 # 一轮最大的步数
RENDER = False # 是否显示游戏界面
GAMMA = 0.9 # TD误差的衰减率
LR_A = 0.001 # Actor的学习率
LR_C = 0.01 # Critic的学习率
env = gym.make('CartPole-v1')
env.seed(1) # reproducible
env = env.unwrapped
N_F = env.observation_space.shape[0] #获取初始环境
N_A = env.action_space.n #获取可操作的动作情况
class Actor(object):
def __init__(self, sess, n_features, n_actions, lr=0.001):
self.sess = sess
self.s = tf.placeholder(tf.float32, [1, n_features], "state")
self.a = tf.placeholder(tf.int32, None, "act")
self.td_error = tf.placeholder(tf.float32, None, "td_error") # TD_error
with tf.variable_scope('Actor'):
l1 = tf.layers.dense(
inputs=self.s,
units=20, # number of hidden units
activation=tf.nn.relu,
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='l1'
)
self.acts_prob = tf.layers.dense(
inputs=l1,
units=n_actions, # output units
activation=tf.nn.softmax, # get action probabilities
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='acts_prob'
)
with tf.variable_scope('exp_v'):
log_prob = tf.log(self.acts_prob[0, self.a])
self.exp_v = tf.reduce_mean(log_prob * self.td_error) # advantage (TD_error) guided loss
with tf.variable_scope('train'):
self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v) # minimize(-exp_v) = maximize(exp_v)
def learn(self, s, a, td):
s = s[np.newaxis, :]
feed_dict = {self.s: s, self.a: a, self.td_error: td}
_, exp_v = self.sess.run([self.train_op, self.exp_v], feed_dict)
return exp_v
def choose_action(self, s):
s = s[np.newaxis, :]
probs = self.sess.run(self.acts_prob, {self.s: s}) # get probabilities for all actions
return np.random.choice(np.arange(probs.shape[1]), p=probs.ravel()) # return a int
class Critic(object):
def __init__(self, sess, n_features, lr=0.01):
self.sess = sess
self.s = tf.placeholder(tf.float32, [1, n_features], "state")
self.v_ = tf.placeholder(tf.float32, [1, 1], "v_next")
self.r = tf.placeholder(tf.float32, None, 'r')
with tf.variable_scope('Critic'):
l1 = tf.layers.dense(
inputs=self.s,
units=20, # number of hidden units
activation=tf.nn.relu, # None
# have to be linear to make sure the convergence of actor.
# But linear approximator seems hardly learns the correct Q.
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='l1'
)
self.v = tf.layers.dense(
inputs=l1,
units=1, # output units
activation=None,
kernel_initializer=tf.random_normal_initializer(0., .1), # weights
bias_initializer=tf.constant_initializer(0.1), # biases
name='V'
)
with tf.variable_scope('squared_TD_error'):
self.td_error = self.r + GAMMA * self.v_ - self.v
self.loss = tf.square(self.td_error) # TD_error = (r+gamma*V_next) - V_eval
with tf.variable_scope('train'):
self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)
def learn(self, s, r, s_):
s, s_ = s[np.newaxis, :], s_[np.newaxis, :]
v_ = self.sess.run(self.v, {self.s: s_})
td_error, _ = self.sess.run([self.td_error, self.train_op],
{self.s: s, self.v_: v_, self.r: r})
return td_error
sess = tf.Session()
actor = Actor(sess, n_features=N_F, n_actions=N_A, lr=LR_A)
critic = Critic(sess, n_features=N_F, lr=LR_C) # we need a good teacher, so the teacher should learn faster than the actor
sess.run(tf.global_variables_initializer())
# 是否Tensorboard可视化
if OUTPUT_GRAPH:
tf.summary.FileWriter("logs/", sess.graph)
# 类似Policy Gradient的学习情况(回合制)
for i_episode in range(MAX_EPISODE):
s = env.reset()#初始化环境
t = 0
track_r = []
while True:
# 是否显示游戏界面
if RENDER: env.render()
# 选择动作
a = actor.choose_action(s)
#当前动作后的状态,返回预测下一个观测值,价值,是否结束,info信息
s_, r, done, info = env.step(a)
# 如果结束了价值为-20
if done: r = -20
# 存储当前价值
track_r.append(r)
# 得到当前状态和预测下一个状态之间的价值 TD误差
td_error = critic.learn(s, r, s_) # gradient = grad[r + gamma * V(s_) - V(s)]
# actor神经网络根据得到的误差对当前状态进行训练选择更有价值的动作
actor.learn(s, a, td_error) # true_gradient = grad[logPi(s,a) * td_error]
# 赋值为下一个观测值
s = s_
# 下一步
t += 1
# 如果当前结束或者轮次到达最大步数
if done or t >= MAX_EP_STEPS:
# 将存储价值的数组统计当前步数的总价值
ep_rs_sum = sum(track_r)
# 如果未定义running_reward,将总价值赋值
if 'running_reward' not in globals():
running_reward = ep_rs_sum
else:
# 否则获得前一轮总价值和当前轮总价值的概率发生变化
running_reward = running_reward * 0.95 + ep_rs_sum * 0.05
# 如果价值大于200,就显示游戏界面
if running_reward > DISPLAY_REWARD_THRESHOLD: RENDER = True # rendering
# 输出轮次和当前轮次下步数的总价值,最后结束死循环
print("episode:", i_episode, " reward:", int(running_reward))
break
DDPG基于Actor_Critic中改善了啥呢?
Deep指的是和DQN一样使用一个记忆库存储当前状态,动作,价值, Policy Gradient是回合制,对一回合内连续的结果做价值评估,Deterministic改变了输出动作的随机过程,让回合制只输出一个动作值,,DDPG的神经网络也是使用的Actor和Critic的神经网络。
import tensorflow as tf
import numpy as np
import gym
import time
##################### hyper parameters ####################
MAX_EPISODES = 200 #规定轮次
MAX_EP_STEPS = 200 #每轮次最大步数
LR_A = 0.001 # learning rate for actor
LR_C = 0.002 # learning rate for critic
GAMMA = 0.9 # reward discount
TAU = 0.01 # soft replacement
MEMORY_CAPACITY = 10000#记忆库最大存储空间
BATCH_SIZE = 32 #每批次进入的神经元数量(存储记忆库中的批次)
RENDER = False #游戏界面可视化
ENV_NAME = 'Pendulum-v1' #旋转让指针立起来的游戏,这是个连续性的游戏
############################### DDPG ####################################
# 建立DDPG神经网络
class DDPG(object):
def __init__(self, a_dim, s_dim, a_bound,):
# 记忆库初始化,可以试着用sumTree的方式改进
self.memory = np.zeros((MEMORY_CAPACITY, s_dim * 2 + a_dim + 1), dtype=np.float32)
self.pointer = 0 #记忆库存储内容条目计数
self.sess = tf.Session() #创建神经网络会话
# 存储该游戏的动作,初始环境状态,动作的最高值
self.a_dim, self.s_dim, self.a_bound = a_dim, s_dim, a_bound,
# 建立观测值情况占位符
self.S = tf.placeholder(tf.float32, [None, s_dim], 's')
# 建立预测观测值情况占位符
self.S_ = tf.placeholder(tf.float32, [None, s_dim], 's_')
# 建立价值占位符
self.R = tf.placeholder(tf.float32, [None, 1], 'r')
# 调用建立Actor网络的值,获得Deterministic选的动作
self.a = self._build_a(self.S,)
# 根据观测值和选择的动作获得 TD误差
q = self._build_c(self.S, self.a, )
# 将两个神经网络的变量收集起来
a_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='Actor')
c_params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='Critic')
# 调节指数加权平均衰减值
ema = tf.train.ExponentialMovingAverage(decay=1 - TAU) # soft replacement
def ema_getter(getter, name, *args, **kwargs):
return ema.average(getter(name, *args, **kwargs))
# 调整指数加权平均更新参数和保留历史参数
target_update = [ema.apply(a_params), ema.apply(c_params)] # soft update operation
a_ = self._build_a(self.S_, reuse=True, custom_getter=ema_getter) # replaced target parameters
q_ = self._build_c(self.S_, a_, reuse=True, custom_getter=ema_getter)
a_loss = - tf.reduce_mean(q) # maximize the q
# 添加损失和优化器
self.atrain = tf.train.AdamOptimizer(LR_A).minimize(a_loss, var_list=a_params)
with tf.control_dependencies(target_update): # soft replacement happened at here
# 获取q现实
q_target = self.R + GAMMA * q_
# 获取q现实和q预测之间的价值误差
td_error = tf.losses.mean_squared_error(labels=q_target, predictions=q)
self.ctrain = tf.train.AdamOptimizer(LR_C).minimize(td_error, var_list=c_params)
self.sess.run(tf.global_variables_initializer())
# 动作选取,deterministic 直接选择
def choose_action(self, s):
return self.sess.run(self.a, {self.S: s[np.newaxis, :]})[0]
# 学习并随机抽取记忆库参数学习
def learn(self):
indices = np.random.choice(MEMORY_CAPACITY, size=BATCH_SIZE)
bt = self.memory[indices, :]
bs = bt[:, :self.s_dim]
ba = bt[:, self.s_dim: self.s_dim + self.a_dim]
br = bt[:, -self.s_dim - 1: -self.s_dim]
bs_ = bt[:, -self.s_dim:]
self.sess.run(self.atrain, {self.S: bs})
self.sess.run(self.ctrain, {self.S: bs, self.a: ba, self.R: br, self.S_: bs_})
# 记忆库存储内容
def store_transition(self, s, a, r, s_):
transition = np.hstack((s, a, [r], s_))
index = self.pointer % MEMORY_CAPACITY # replace the old memory with new memory
self.memory[index, :] = transition
self.pointer += 1
# actor神经网络
def _build_a(self, s, reuse=None, custom_getter=None):
trainable = True if reuse is None else False
with tf.variable_scope('Actor', reuse=reuse, custom_getter=custom_getter):
net = tf.layers.dense(s, 30, activation=tf.nn.relu, name='l1', trainable=trainable)
a = tf.layers.dense(net, self.a_dim, activation=tf.nn.tanh, name='a', trainable=trainable)
return tf.multiply(a, self.a_bound, name='scaled_a')
# critic神经网络
def _build_c(self, s, a, reuse=None, custom_getter=None):
trainable = True if reuse is None else False
with tf.variable_scope('Critic', reuse=reuse, custom_getter=custom_getter):
n_l1 = 30
w1_s = tf.get_variable('w1_s', [self.s_dim, n_l1], trainable=trainable)
w1_a = tf.get_variable('w1_a', [self.a_dim, n_l1], trainable=trainable)
b1 = tf.get_variable('b1', [1, n_l1], trainable=trainable)
net = tf.nn.relu(tf.matmul(s, w1_s) + tf.matmul(a, w1_a) + b1)
return tf.layers.dense(net, 1, trainable=trainable) # Q(s,a)
############################### training ####################################
env = gym.make(ENV_NAME) #创建指针游戏
env = env.unwrapped #解压
env.seed(1) #为此环境的随机数生成器设置种子
s_dim = env.observation_space.shape[0] #得到当前初始化环境状态值
a_dim = env.action_space.shape[0]#获取动作可选值
a_bound = env.action_space.high #获得动作的最高值
ddpg = DDPG(a_dim, s_dim, a_bound)#调用DDPG
var = 3 # control exploration
t1 = time.time()
# policy gradient 回合制框架
for i in range(MAX_EPISODES):
s = env.reset() #初始化环境
ep_reward = 0 #该回合价值
for j in range(MAX_EP_STEPS):
# 游戏界面可视化
if RENDER:
env.render()
# Add exploration noise
# 神经网络选择动作
a = ddpg.choose_action(s)
# 为探索的动作选择添加随机性
a = np.clip(np.random.normal(a, var), -2, 2)
# 获得该动作的下一步状态,价值,是否结束,无用信息
s_, r, done, info = env.step(a)
# 开始存储记忆库
ddpg.store_transition(s, a, r / 10, s_)
#当前记忆库的存储下标限制
if ddpg.pointer > MEMORY_CAPACITY:
var *= .9995 # decay the action randomness
# 存得差不多就开始学习了
ddpg.learn()
# 下一步观测值赋值
s = s_
# 累计这一回合的价值
ep_reward += r
# 如果到了最后一步就结束当前回合
if j == MAX_EP_STEPS-1:
print('Episode:', i, ' Reward: %i' % int(ep_reward), 'Explore: %.2f' % var, )
# if ep_reward > -300:RENDER = True
break
# 记录运行时间
print('Running time: ', time.time() - t1)