提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
强化学习:Reinforcement Learning,又称再励学习或者评价学习。也是机器学习的技术之一。所谓强化学习就是智能系统从环境到行为映射的学习,以使奖励信号(强化信号)函数值最大,由于外部给出的信息很少,强化学习系统必须依靠自身的经历进行自我学习。通过这种学习获取知识,改进行动方案以适应环境。强化学习最关键的三个因素是状态,行为和环境奖励。关于强化学习和深度学习的实例,最典型的莫过于谷歌的AlphaGo和AlphaZero两位了,前者通过深度学习中的深度卷积神经网络,在训练了大约三千万组人类的下棋数据,无数度电的情况下才搞出来的模型,而后者使用强化学习的方式,通过自己和自己下棋的方式搞出来的模型。而最终的实验结果也很让人震撼。AlphaGo干败了人类围棋顶尖高手,而AlphaZero干败了AlphaGo.
提示:以下是本篇文章正文内容,下面案例可供参考
一、强化学习
1.1 强化学习方法
1.Model-Free RL(等待反馈后采取行动)
Qleanning
Sarsa
Policy Gradients
2.Model-Based RL (多了个虚拟,能够想象来得到对应的行为并挑选最好的)
QLearning
Sarsa
Policy Gradients
1.基于概率(Policy-Based RL)
根据概率选择行动
Policy Gradients
2.基于价值(Value-Based RL)
根据最高价值选择动作
QLearning
Sarsa
Actor-Critic (以上两种结合)
1.回合更新
基础班Plicy Gradients
Monte-Carlo Learning
2.单步更新
Q learning
Sarsa
Policy Gradients
1.在线学习(On-Policy)
Sarsa
Sarsa(lambda)
2.离线学习 (Off-Policy)
Q learning
Deep Q Network
二、Q Learning
2.1 例子1 一维寻宝
import numpy as np
import pandas as pd
import time
np.random.seed(2) #产生伪随机数列
N_STATES=6 # 状态
ACTIONS=['LEFT','RIGHT'] #可用的动作
EPSILON=0.9 #选择动作概率 0.9概率选择最优,0.1选择其他
alpha=0.1 #学习效率
LMBDA=0.9 #奖励衰减值
MAX_EPISODES=13 #迭代次数
FRESH_TIME=0.3 #走一步的时间
def build_q_table(n_states,actions):
##初始化Q_table
##n_state 即s1 s2...
##actions 动作
table = pd.DataFrame(
np.zeros((n_states, len(actions))), # q_table initial values
columns=actions, # actions's name
)
print(table)
return table
def choseAction(state,q_table):
#选择动作
state_actions=q_table.iloc[state,:] ##类似于matlab 将q_table 的state这一行赋值给变量
if(np.random.uniform()>EPSILON)or (state_actions.all()==0):
##np.random.uniform() 产生一个随机数(0-1)
action_name=np.random.choice(ACTIONS) #随机选择一个
else:
action_name=state_actions.argmax() #选择其中大的一个
return action_name
##环境的反馈,采取动作后的状态,以及奖励
def get_env_feedback(S,A):
if A=='right':
if S==N_STATES-2: ##采取下一个动作更新后状态就为终止,到达最右边
S_='terminal'
R=1 ##得到奖励
else:
S_=S+1
R=0
else:
R=0
if S==0:
S_=S
else:
S_=S-1
return S_,R
def rl():
q_table=build_q_table(N_STATES,ACTIONS)
for episode in range(MAX_EPISODES):
step_counter=0
S=0
is_terminated=False
while not is_terminated:
A=choseAction(S,q_table)
S_,R=get_env_feedback(S,A) #获取下一个状态和奖励
q_predict=q_table[S,A] #预估得到的奖励
if S_ !='terminal':
q_target=R+LMBDA*q_table.iloc[S_,:].max() ##真实值
else:
q_target=R
is_terminated=True
q_table.ix[S,A]+=alpha*(q_target-q_predict)
S=S_
##updateEnv
return q_table
2.2 例子2 迷宫寻宝
import numpy as np
import pandas as pd
class QLearningTable:
def __init__(self,actions,learning_rate,reward_decay,e_greedy):
self.action=actions
self.lr=learning_rate
self.gamma=reward_decay
self.epsilon=e_greedy
self.q_table=pd.DataFrame(columns=self.action)
def choose_action(self,observation):
self.check_state_exist(observation)#检验观测值是否在qtable中
##随机选择主要是为了以防qtable中的两个值一样
if(np.random.uniform<self.epsilon):
state_action=self.q_table.iloc[observation,:]
action=state_action.argmax()
else:
action=np.random.choice(self.action)
return action
def learn(self,s,a,r,s_):
self.check_state_exist(s_)
q_predict=self.q_table[s,a]
if s_!='terminal':
q_target=r+self.gamma*self.q_table[s_,:].max()
else:
q_target=r
self.q_table[s,a]+=self.lr*(q_target-q_predict)
def check_state_exist(self,state):
if state not in self.q_table.index:
#append,初始化为0
三、Sarsa
类似于QLearning,Sarsa也是用qtable来完成。
不同之处在于Sarsa 不再是预估计下一个状态动作的最大值(它实际上不一定采取该动作),而是直接选取最大的动作。
具体伪代码如下:
3.1 例子1 迷宫寻宝
import numpy as np
import pandas as pd
class SarsaTable():
def __init__(self, actions, learning_rate, reward_decay, e_greedy):
self.action = actions
self.lr = learning_rate
self.gamma = reward_decay
self.epsilon = e_greedy
self.q_table = pd.DataFrame(columns=self.action)
def choose_action(self, observation):
self.check_state_exist(observation) # 检验观测值是否在qtable中
##随机选择主要是为了以防qtable中的两个值一样
if (np.random.uniform < self.epsilon):
state_action = self.q_table.iloc[observation, :]
action = state_action.argmax()
else:
action = np.random.choice(self.action)
return action
def learn(self, s, a, r, s_,a_):
self.check_state_exist(s_)
q_predict = self.q_table.ix[s, a]
if s_ != 'terminal':
q_target = r + self.gamma * self.q_table[s_,a_]
else:
q_target = r
self.q_table[s, a] += self.lr * (q_target - q_predict)
def check_state_exist(self, state):
if state not in self.q_table.index:
# append,初始化为0
while True:
## 更新环境
observation_,reward,done=env.step(action) #获取环境的反馈,包括下一个observation和reward以及是否完成
action_=RL.choose_action(observation_)##选择动作
RL.learn(observation,action,reward,observation_,action_) ##sarsa 学习方法
obervation=observation ##更新observation 和 action
action=action_
if done:
break
3.2 Sarsa(Lambda)
可知当lambda=0时,为单步更新,只更新距离宝藏最近的一步。
当lambda=1时,为回合更新,更新找到宝藏路径上的所有步骤。
当lambda<1&&lambda>0 时,则介于两者之间
伪代码如下:
import numpy as np
import pandas as pd
class SarsaTable():
def __init__(self, actions, learning_rate, reward_decay, e_greedy,trace_decay):
self.action = actions
self.lr = learning_rate
self.gamma = reward_decay
self.epsilon = e_greedy
self.lambda_=trace_decay
self.q_table = pd.DataFrame(columns=self.action)
self.eligibility_trace=self.q_table.copy() #记录路径
def choose_action(self, observation):
self.check_state_exist(observation) # 检验观测值是否在qtable中
##随机选择主要是为了以防qtable中的两个值一样
if (np.random.uniform < self.epsilon):
state_action = self.q_table.iloc[observation, :]
action = state_action.argmax()
else:
action = np.random.choice(self.action)
return action
def learn(self, s, a, r, s_,a_):
self.check_state_exist(s_)
q_predict = self.q_table.ix[s, a]
if s_ != 'terminal':
q_target = r + self.gamma * self.q_table[s_,a_]
else:
q_target = r
error=q_target - q_predict
#Method1 路径标记值不封顶
self.eligibility_trace[s,a]+=1
#Method2 路径标记值封顶为1
self.eligibility_trace.ix[s,:]=0
self.eligibility_trace.ix[s,a]=1
#q_table更新
self.q_table[s,a]+=self.lr*error*self.eligibility_trace
self.eligibility_trace*=self.gamma*self.lambda_ #路径标记衰减
def check_state_exist(self, state):
if state not in self.q_table.index:
# append,初始化为0 both q_table and eligibility_trace
四、Deeq Q Network(DQN)
传统的表格形式有着存储容量,查询问题。
由此我们可以使用
1.将状态和动作作为神经网络的输入,并得到动作的q值
2.将状态s输入,输出所有的动作值,并选择最大值的动作来作为下一步的动作。
记忆库:DQN能学习当前经历着的, 也能学习过去经历过的, 甚至是学习别人的经历,所以每次 DQN 更新的时候, 我们都可以随机抽取一些之前的经历进行学习. 随机抽取这种做法打乱了经历之间的相关性, 也使得神经网络更新更有效率
Fixed Q-targets :也是一种打乱相关性的机理, 如果使用 fixed Q-targets, 我们就会在 DQN 中使用到两个结构相同但参数不同的神经网络, 预测 Q 估计 的神经网络具备最新的参数, 而预测 Q 现实 的神经网络使用的参数则是很久以前的.
伪代码如下:
4.1 例子:迷宫
import numpy as np
import pandas as pd
import time
import tensorflow as tf
class DQN:
def __init__(self,n_actions,n_features,learning_rate,reward_decay,e_graddy,replace_target_iter,memory_size,batch_size,e_greedy_increment,output_graph):
self.n_actions=n_actions
self.n_feature=n_features #特征数
self.lr=learning_rate
self.gamma=reward_decay
self.epsilon_max=e_graddy
self.replace_target_iter=replace_target_iter #隔了多少步后将target net 参数更新
self.memory_size=memory_size ##记忆的容量
self.epsilon_increment=e_greedy_increment
self.learn_step_counter=0 #学习的步数
self.memory=pd.DataFrame(np.zeros(self.memory_size,n_features*2+2)) #记忆
self.createNetwork()
self.cost_his=[] #记录每一步的误差
##记录memory
def store_transition(self,s,a,r,s_):
trainsition=np.hstack(s,[a,r],s_) #记录
#append to self.memory
#选取动作
def choose_action(self,observation):
actions_value=self.sess.run() ##获取神经网络得到的动作的值
#90%选取最大的,其他的随机选取
## 创建两个神经网络已完成暂时冻结q_target
def createNetwork:
self.s=tf.placeholder(tf.float32,[None,self.n_feature],name='s') #输入
self.q_target=tf.placeholder(tf.float32,[None,self.n_actions],name='Q_target') #计算error
with tf.variable_scope('eval_net'):
#c_name 所有的参数的集合
c_name,n_l1,w_initializer,b_initializer=['eval_net_params',tf.GraphKeys.GLOBAL_VARIABLES],10,
tf.random_normal_initializer(0,0.3),tf.constant_initializer(0.1) #layer 配置
#nl1 为每层的神经元
#第一层,
with tf.variable_creator_scope('l1'):
w1=tf.get_variable('w1',[self.n.features,n_l1],initializer=w_initializer,collections=c_name)
b1=tf.get_variable('b1',[1,n_l1],b_initializer,c_name)
l1=tf.nn.relu(tf.matmul(self.s,w1)+b1)
#第二层:
with tf.variable_creator_scope('l2'):
w2 = tf.get_variable('w2', [n_l1 self.n_actions], initializer=w_initializer, collections=c_name)
b2 = tf.get_variable('b2', [1, sekf.n_actions], b_initializer, c_name)
self.q_eval=tf.matmul(l1,w2)+b2
with tf.name_scope('loss'):
self.loss=tf.reduce_sum(tf.squared_difference(self.q_target,self.q_eval))
with tf.name_scope('train'):
self._train_op=tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)
#target Net
self.s_=tf.placeholder(tf.float32,[None,self.n_feature],name='s_') #input
with tf.variable_scope('target_net'):
# c_name 所有的参数的集合
c_name, n_l1, w_initializer, b_initializer = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10,
tf.random_normal_initializer(0, 0.3), tf.constant_initializer(0.1) # layer 配置
# nl1 为每层的神经元
# 第一层,
with tf.variable_creator_scope('l1'):
w1 = tf.get_variable('w1', [self.n.features, n_l1], initializer=w_initializer, collections=c_name)
b1 = tf.get_variable('b1', [1, n_l1], b_initializer, c_name)
l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)
# 第二层:
with tf.variable_creator_scope('l2'):
w2 = tf.get_variable('w2', [n_l1 self.n_actions], initializer=w_initializer, collections=c_name)
b2 = tf.get_variable('b2', [1, sekf.n_actions], b_initializer, c_name)
self.q_eval = tf.matmul(l1, w2) + b2
def _replace_target_param(self):
## 更新target net的参数
def learn(self):
#检查是否需要更新target net参数
#抽取记忆
batch_memory=self.memory.sample(self.batch_size)
#计算q估计和q现实
q_next,q_eval=self.sess.run([self.q_next,self.q_eval],
fead_dict={self.s_:batch_memory.iloc[:,-self.n_feature],
self.s:batch_memory.ilos[:,self.n_feature]
})
q_target=q_eval.copy()
#训练
_,self.cost=self.sess.run([self._train_op,self.loss],
feed_dict={self.s:batch_memory.iloc[:,:self.n_feature],self.q_target:q_target})
self.cost_his.append(self.cost)
#更新epsilon,平衡探索和勘测
self.learn_step_counter+=1
#get environment
while True:
#refresh environment
#获取动作
action=RL.choose_action(observation)
#获取外界环境反馈
observation_,reward,done=env.step(action)
#存储相关信息
RL.store_transmition(observation,action,reward,observation_)
#学习
if(step>200 and step%5==0):
RL.learn()
#更新观测点
observation=observation_
if done:
break
step+=1
补充:gym?
4.2 DDQN(Double DQN)
主要解决over estimating
(估计值过大)
可知DQN的q现实部分如下
可知神经网络获得的q值有误差,同时由于我们选择了最大的值,所以我们引入了最大的误差,由此导致了over estimating
(q现实来选择动作)
Double DQN
主要想法是引入另一个神经网络来减少误差。
它利用Q估计的神经网络来估计现实中的Qmax)(s’,a’)并用这个q估计出来的动作来选择q现实的qs’
(用q估计来选择对应的动作)
#qeval4next q现实中用q估计来得到的下一个动作
q_next, q_eval4next = self.sess.run(
[self.q_next, self.q_eval],
feed_dict={self.s_: batch_memory[:, -self.n_features:], # next observation
self.s: batch_memory[:, -self.n_features:]}) # next observation
q_eval = self.sess.run(self.q_eval, {self.s: batch_memory[:, :self.n_features]})
q_target = q_eval.copy()
batch_index = np.arange(self.batch_size, dtype=np.int32)
eval_act_index = batch_memory[:, self.n_features].astype(int)
reward = batch_memory[:, self.n_features + 1]
if self.double_q: # 如果是 Double DQN
max_act4next = np.argmax(q_eval4next, axis=1) # q_eval 得出的最高奖励动作
selected_q_next = q_next[batch_index, max_act4next] # Double DQN 选择 q_next 依据 q_eval 选出的动作
else: # 如果是 Natural DQN
selected_q_next = np.max(q_next, axis=1) # natural DQN
q_target[batch_index, eval_act_index] = reward + self.gamma * selected_q_next
4.3 DQN Prioritized Experience Replay
随机抽样的优化。
这一套算法重点就在我们 batch 抽样的时候并不是随机抽样, 而是按照 Memory 中的样本优先级来抽. 所以这能更有效地找到我们需要学习的样本.
原来我们可以用到 TD-error, 也就是 Q现实 - Q估计 来规定优先学习的程度. 如果 TD-error 越大, 就代表我们的预测精度还有很多上升空间, 那么这个样本就越需要被学习, 也就是优先级 p 越高.
伪代码:
补充:排序方法:sumTree
def store_transition(self, s, a, r, s_):
if self.prioritized: # prioritized replay
transition = np.hstack((s, [a, r], s_))
#计算q_next,q_eval 估计和现实值
error=r+np.max(q_next.max(),q_eval)
self.memory.store(abs(error),transition)
else: # random replay
if not hasattr(self, 'memory_counter'):
self.memory_counter = 0
transition = np.hstack((s, [a, r], s_))
index = self.memory_counter % self.memory_size
self.memory[index, :] = transition
self.memory_counter += 1
def learn(self):
...
# 相对于 DQN 代码, 改变的部分
#抽取样本按照优先级
if self.prioritized:
tree_idx, batch_memory, ISWeights = self.memory.sample(self.batch_size)
else:
sample_index = np.random.choice(self.memory_size, size=self.batch_size)
batch_memory = self.memory[sample_index, :]
...
if self.prioritized:
#abs_errors 新的误差(新的priority)
_, abs_errors, self.cost = self.sess.run([self._train_op, self.abs_errors, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_features],
self.q_target: q_target,
self.ISWeights: ISWeights})
self.memory.batch_update(tree_idx, abs_errors) # update priority
else:
_, self.cost = self.sess.run([self._train_op, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_features],
self.q_target: q_target})
...
4.4 Dueling DQN
它分成了这个 state 的值, 加上每个动作在这个 state 上的 advantage. 因为有时候在某种 state, 无论做什么动作, 对下一个 state 都没有多大影响
def _build_net(self):
def build_layers(s, c_names, n_l1, w_initializer, b_initializer):
with tf.variable_scope('l1'): # 第一层, 两种 DQN 都一样
w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
l1 = tf.nn.relu(tf.matmul(s, w1) + b1)
if self.dueling:
# Dueling DQN
with tf.variable_scope('Value'): # 专门分析 state 的 Value
w2 = tf.get_variable('w2', [n_l1, 1], initializer=w_initializer, collections=c_names)
b2 = tf.get_variable('b2', [1, 1], initializer=b_initializer, collections=c_names)
self.V = tf.matmul(l1, w2) + b2
with tf.variable_scope('Advantage'): # 专门分析每种动作的 Advantage
w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
self.A = tf.matmul(l1, w2) + b2
with tf.variable_scope('Q'): # 合并 V 和 A, 为了不让 A 直接学成了 Q, 我们减掉了 A 的均值
out = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keep_dims=True)) # Q = V(s) + A(s,a)
else:
with tf.variable_scope('Q'): # 普通的 DQN 第二层
w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
out = tf.matmul(l1, w2) + b2
return out
五、Policy Gradients
Policy Gradients 直接输出动作,优势在于能够在一个连续区间挑选动作
更新步骤:通过奖惩信息来降低或者增加动作被选中的增大幅度。
伪代码:
基于回合,在回合结束后更新。
我们利用神经网络输出动作的采取概率:
选择行为也不再是用 Q value 来选定的, 而是用概率来选定. 即使不用 epsilon-greedy, 也具有一定的随机性.
对于学习:
首先我们要对这回合的所有 reward 动动手脚, 使他变得更适合被学习. 第一就是随着时间推进, 用 gamma 衰减未来的 reward, 然后为了一定程度上减小 policy gradient 回合 variance, 我们标准化回合的 state-action value
(vt = 本reward + 衰减的未来reward) 引导参数的梯度下降,如果vt是负的,就认为该方向是错误的,(可以对vt进行标准化)
六、Actor Critic
结合了 Policy Gradient (Actor) 和 Function Approximation (Critic) 的方法. Actor 基于概率选行为, Critic 基于 Actor 的行为评判行为的得分, Actor 根据 Critic 的评分修改选行为的概率.
它的优势在于能够进行单步更新,提高了学习效率。
Actor 想要最大化期望的 reward, 在 Actor Critic 算法中, 我们用 比平时好多少 (TD error) 来当做 reward, 所以就是:
actor 学习需要当前状态s,施加的行为a,以及对应的tderror(是否加大或者减少该动作的概率)
with tf.variable_scope('exp_v'):
log_prob = tf.log(self.acts_prob[0, self.a]) # log 动作概率
self.exp_v = tf.reduce_mean(log_prob * self.td_error) # log 概率 * TD 方向
with tf.variable_scope('train'):
# 因为我们想不断增加这个 exp_v (动作带来的额外价值),
# 所以我们用过 minimize(-exp_v) 的方式达到
# maximize(exp_v) 的目的
self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v)
Critic 的更新很简单, 就是像 Q learning 那样更新现实和估计的误差 (TD error) 就好了.
critic 学习时需要当前状态s和下一个状态s’
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)
主流程如下:
#选择动作
a=actor.choose_action(s)
#获取环境反馈
s_,r,done,info=env.step(a)
#获取tderror
td_error=critic.learn(s,r,s_)
#actor学习
actor.learn(s,a,td_error)
#状态更新
s=s_
七、DDPG ?
DDPG: Google DeepMind 提出的一种使用 Actor Critic 结构, 但是输出的不是行为的概率, 而是具体的行为, 用于连续动作 (continuous action) 的预测. DDPG 结合了之前获得成功的 DQN 结构, 提高了 Actor Critic 的稳定性和收敛性.
Policy Gradient 这边, 我们有估计网络和现实网络, 估计网络用来输出实时的动作, 供 actor 在现实中实行. 而现实网络则是用来更新价值网络系统的. 所以我们再来看看价值系统这边, 我们也有现实网络和估计网络, 他们都在输出这个状态的价值, 而输入端却有不同, 状态现实网络这边会拿着从动作现实网络来的动作加上状态的观测值加以分析, 而状态估计网络则是拿着当时 Actor 施加的动作当做输入.
actor:
需要两个参数: grad q 有critic得到, gradu 有actor得到
这两个参数分别是对应的梯度,即从获取大Q的方向来修改动作参数
actor中也有两个网络,一个实时更新。
critic:
它由两个计算Q的神经网络(借鉴DQN),Q_target依据下一状态用Actor来选取动作
更新的伪代码:
actor:
with tf.variable_scope('policy_grads'):
# 这是在计算 (dQ/da) * (da/dparams)
self.policy_grads = tf.gradients(
ys=self.a, xs=self.e_params, # 计算 ys 对于 xs 的梯度
grad_ys=a_grads # 这是从 Critic 来的 dQ/da
)
with tf.variable_scope('A_train'):
opt = tf.train.AdamOptimizer(-self.lr) # 负的学习率为了使我们计算的梯度往上升, 和 Policy Gradient 中的方式一个性质
self.train_op = opt.apply_gradients(zip(self.policy_grads, self.e_params)) # 对 eval_net 的参数更新
对于a_grad 的计算如下:(该值来源于critic,其中a来自Actor根据s计算得到)
with tf.variable_scope('a_grad'):
self.a_grads = tf.gradients(self.q, self.a)[0] # dQ/da
critic:
他需要从actor中得到的动作a_
# 计算 target Q
with tf.variable_scope('target_q'):
self.target_q = R + self.gamma * self.q_ # self.q_ 根据 Actor 的 target_net 来的
# 计算误差并反向传递误差
with tf.variable_scope('TD_error'):
self.loss = tf.reduce_mean(tf.squared_difference(self.target_q, self.q)) # self.q 又基于 Actor 的 target_net
with tf.variable_scope('C_train'):
self.train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)
actor = Actor(...)
critic = Critic(..., actor.a, actor.a_) # 将 actor 同它的 eval_net/target_net 产生的 a/a_ 传给 Critic
actor.add_grad_to_graph(critic.a_grads) # 将 critic 产出的 dQ/da 加入到 Actor 的 Graph 中去
八、A3A
A3C 的算法实际上就是将 Actor-Critic 放在了多个线程中进行同步训练.
我们认为中央大脑拥有 global net 和他的参数, 每位玩家有一个 global net 的副本 local net, 可以定时向 global net 推送更新, 然后定时从 global net 那获取综合版的更新.
同时中央大脑只保存对应的参数,而不进行训练。
对于worker,可以用push和pull来更新获取对应的中央大脑的参数,同时在本地环境中进行学习。
对于actor:
我们使用了 Normal distribution 来选择动作, 所以在搭建神经网络的时候, actor 这边要输出动作的均值和方差. 然后放入 Normal distribution 去选择动作. 计算 actor loss 的时候我们还需要使用到 critic 提供的 TD error 作为 gradient ascent 的导向.(通过均值和方差来采样,得到对应的动作)
对于critic:
计算TD error。
对应的伪代码:
# 这个 class 可以被调用生成一个 global net.
# 也能被调用生成一个 worker 的 net, 因为他们的结构是一样的,
# 所以这个 class 可以被重复利用.
class ACNet(object):
def __init__(self, globalAC=None):
# 当创建 worker 网络的时候, 我们传入之前创建的 globalAC 给这个 worker
if 这是 global: # 判断当下建立的网络是 local 还是 global
with tf.variable_scope('Global_Net'):
self._build_net()
else:
with tf.variable_scope('worker'):
self._build_net()
# 接着计算 critic loss 和 actor loss
# 用这两个 loss 计算要推送的 gradients
with tf.name_scope('sync'): # 同步
with tf.name_scope('pull'):
# 更新去 global
with tf.name_scope('push'):
# 获取 global 参数
def _build_net(self):
# 在这里搭建 Actor 和 Critic 的网络
return 均值, 方差, state_value
def update_global(self, feed_dict):
# 进行 push 操作
def pull_global(self):
# 进行 pull 操作
def choose_action(self, s):
# 根据 s 选动作
worker:类似于之前的(?!)
# s, a, r 的缓存, 用于 n_steps 更新 buffer_s, buffer_a, buffer_r = [], [], [] while not COORD.should_stop() and GLOBAL_EP < MAX_GLOBAL_EP: s = self.env.reset() for ep_t in range(MAX_EP_STEP): a = self.AC.choose_action(s) s_, r, done, info = self.env.step(a) buffer_s.append(s) # 添加各种缓存 buffer_a.append(a) buffer_r.append(r) # 每 UPDATE_GLOBAL_ITER 步 或者回合完了, 进行 sync 操作 if total_step % UPDATE_GLOBAL_ITER == 0 or done: # 获得用于计算 TD error 的 下一 state 的 value if done: v_s_ = 0 # terminal else: v_s_ = SESS.run(self.AC.v, {self.AC.s: s_[np.newaxis, :]})[0, 0] buffer_v_target = [] # 下 state value 的缓存, 用于算 TD for r in buffer_r[::-1]: # 对于未来的步骤进行r的递减操作 v_s_ = r + GAMMA * v_s_ buffer_v_target.append(v_s_) buffer_v_target.reverse() buffer_s, buffer_a, buffer_v_target = np.vstack(buffer_s), np.vstack(buffer_a), np.vstack(buffer_v_target) feed_dict = { self.AC.s: buffer_s, self.AC.a_his: buffer_a, self.AC.v_target: buffer_v_target, } self.AC.update_global(feed_dict) # 推送更新去 globalAC buffer_s, buffer_a, buffer_r = [], [], [] # 清空缓存 self.AC.pull_global() # 获取 globalAC 的最新参数 s = s_ if done: GLOBAL_EP += 1 # 加一回合 break # 结束这回合 ```
九、PPO/DPPO
PPO: OpenAI 提出的一种解决 Policy Gradient 不好确定 Learning rate (或者 Step size) 的问题.
因为如果 step size 过大, 学出来的 Policy 会一直乱动, 不会收敛, 但如果 Step Size 太小, 对于完成训练, 我们会等到绝望. PPO 利用 New Policy 和 Old Policy 的比例, 限制了 New Policy 的更新幅度, 让 Policy Gradient 对稍微大点的 Step size 不那么敏感.
更新Actor:
补充: Actor在旧策略上依据Advantage(TDerror) 来修改新策略。 当对应的Advantage大时,修改的幅度变大。同时附加一个惩罚项来让新策略和旧策略相差不太多。如果差太多,那么就相当于应用了一个非常大的学习率。这样难以收敛。
结构:
pi 就是我们的 Actor 了. 每次要进行 PPO 更新 Actor 和 Critic 的时候, 我们有需要将 pi 的参数复制给 oldpi. 这就是 update_oldpi 这个 operation 在做的事. 这里的 Actor 使用了 normal distribution 正态分布输出动作.
更新(?!):
def __init__(self):
self.tfa = tf.placeholder(tf.float32, [None, A_DIM], 'action')
self.tfadv = tf.placeholder(tf.float32, [None, 1], 'advantage')
with tf.variable_scope('loss'):
with tf.variable_scope('surrogate'):
ratio = pi.prob(self.tfa) / oldpi.prob(self.tfa)
surr = ratio * self.tfadv # surrogate objective
if METHOD['name'] == 'kl_pen': # 如果用 KL penatily
self.tflam = tf.placeholder(tf.float32, None, 'lambda')
kl = kl_divergence(oldpi, pi)
self.kl_mean = tf.reduce_mean(kl)
self.aloss = -(tf.reduce_mean(surr - self.tflam * kl))
else: # 如果用 clipping 的方式
self.aloss = -tf.reduce_mean(tf.minimum(
surr,
tf.clip_by_value(ratio, 1.-METHOD['epsilon'], 1.+METHOD['epsilon'])*self.tfadv))
with tf.variable_scope('atrain'):
self.atrain_op = tf.train.AdamOptimizer(A_LR).minimize(self.aloss)
与环境的交互?