## 强化学习 Actor-critic
# 和PG比起来主要的变化:
# 评估点由状态价值变成了TD_error,网络形式变了
# learn函数长得不一样
# action有一个优化函数,优化的是价值函数,希望最大化期望的reward,Critic网络也有一个reward,希望最小化现实和估计的误差(即td——error)
# Actor使用我们上一节讲到的策略函数,负责生成动作(Action)并和环境交互。而Critic使用我们之前讲到了的价值函数,负责评估Actor的表现,并指导Actor下一阶段的动作
import gym
# import tensorflow as tf
import tensorflow.compat.v1 as tf
tf.compat.v1.disable_eager_execution()
import numpy as np
import random
from collections import deque
# Hyper Parameters
GAMMA = 0.95 # discount factor 衰减因子
LEARNING_RATE = 0.01 # 探索率
class Actor():
def __init__(self, env, sess): # 初始化
# init some parameters
self.time_step = 0 # 某个地方需要用的步数
self.state_dim = env.observation_space.shape[0] # 状态维度
self.action_dim = env.action_space.n # 动作维度
self.create_softmax_network() # 创建softmax网络
# Init session 初始化tensorflow参数
self.session = tf.InteractiveSession()
self.session.run(tf.global_variables_initializer()) # 初始化 tensorflow 参数。
def create_softmax_network(self): # 创建softmax网络
# network weights
W1 = self.weight_variable([self.state_dim, 20]) # w1 权重,4*20的网络
b1 = self.bias_variable([20]) # b1权重,y = w1*x + b1
W2 = self.weight_variable([20, self.action_dim])
b2 = self.bias_variable([self.action_dim])
# input layer
self.state_input = tf.placeholder("float", [None, self.state_dim]) # 状态输入层占位,多少组不知道,每组有4个状态
self.tf_acts = tf.placeholder(tf.int32, [None, 2], name="actions_num") # 给他的值对应于依据概率选择出来的动作
self.td_error = tf.placeholder(tf.float32, None, "td_error") # TD_error PG中基于状态价值,这里评估点发生了一点变化
# hidden layers
h_layer = tf.nn.relu(tf.matmul(self.state_input, W1) + b1) # 进行 y = w1*x + b1 的运算 ,并激活成可输出的状态
# softmax layer
# matmul返回两个数组的矩阵乘积,结果还是一个矩阵
self.softmax_input = tf.matmul(h_layer, W2) + b2 # #进行 y = w2*x + b2 的运算,输出是两个是数(不确定)TODO
# softmax output
self.all_act_prob = tf.nn.softmax(self.softmax_input, name='act_prob') # softmax输出层,输出每个动作的概率
# 计算logits 和 labels 之间的softmax 交叉熵
# 函数先对 logits 进行 softmax 处理得到归一化的概率,将lables向量进行one-hot处理,然后求logits和labels的交叉熵:
self.neg_log_prob = tf.nn.softmax_cross_entropy_with_logits(logits=self.softmax_input,
labels=self.tf_acts)
# TODO softmax_cross_entropy_with_logits 和 sparse_softmax_cross_entropy_with_logits 的区别是啥
# 这句是在算损失函数了,定义为softmax交叉熵损失函数和TD_error的乘积
self.exp = tf.reduce_mean(self.neg_log_prob * self.td_error) # 策略梯度函数
# 创建优化器 这里需要最大化当前策略的价值,因此需要最大化self.exp,即最小化-self.exp
# 由于tensorflow要minimize误差 但是我们希望这个概率变大所以要加个负号
# 利用tensorflow中的Adam优化算法最小化loss函数
# Adam优化算法:是一个寻找全局最优点的优化算法,引入了二次方梯度校正。
self.train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(-self.exp)
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)
def choose_action(self, observation): # 依据概率选择动作
"""
选择动作 :这里的observation其实就是状态,当前的状态先传入state_input(也就相当于softmax网络的入口),
softmax网络的输出是针对当前状态每个动作的概率,第一句就是运行了一个会话进行这个过程。
#TODO prob_weights 应该是一个动作对应概率的矩阵,怎么查看数据类型来着忘了
下一句就是依据概率选择动作了,选择概率最大的动作
"""
# np.newaxis功能:增加一个维度,具体见印象笔记
prob_weights = self.session.run(self.all_act_prob, feed_dict={self.state_input: observation[np.newaxis, :]})
# 这个range表示这个action的大小,后面的p表示概率分布, .ravel的意思是将数组维度拉成一维数组,也就是将矩阵向量化,见印象笔记
action = np.random.choice(range(prob_weights.shape[1]), p=prob_weights.ravel())
return action
def learn(self, state, action, td_error):
"""
s,a 用于产生梯度上升法的方向,这时候的action是上面这个函数依据概率选择出来的动作
td 来自Critic,用于告诉Actor这个方向对不对
"""
s = state[np.newaxis, :] # 把state变成(4,1)的形状
one_hot_action = np.zeros(self.action_dim) # 初始化one_hot 形式的action
one_hot_action[action] = 1 # action是数字几就把第几个位置上的数变成1
a = one_hot_action[np.newaxis, :] # 然后再把它变成横向向量的形式
# train on episode
self.session.run(self.train_op, feed_dict={
self.state_input: s,
self.tf_acts: a, # 把动作传给了tf_acts
self.td_error: td_error,
})
# critic网络中会用到的一些超级参数
EPSILON = 0.01 # final value of epsilon epsilon 的最小值,当 epsilon 小于该值时,将不再随机选择行为。
REPLAY_SIZE = 10000 # experience replay buffer size 经验回放缓冲区大小
BATCH_SIZE = 32 # size of minibatch
REPLACE_TARGET_FREQ = 10 # frequency to update target Q network
class Critic():
def __init__(self, env, sess):
# init some parameters
self.time_step = 0
self.epsilon = EPISODE
self.state_dim = env.observation_space.shape[0] # 状态维度
self.action_dim = env.action_space.n # 动作维度 TODO .n是什么意思?
self.create_Q_network() # 创建Q网络
self.create_training_method() # 创建训练方法
# Init session 初始化会话
self.session = sess
self.session.run(tf.global_variables_initializer())
def create_Q_network(self): # critic网络,使用类似于DQN的三层神经网络,但是只有一维输出值
# network weights
W1q = self.weight_variable([self.state_dim, 20])
b1q = self.bias_variable([20])
W2q = self.weight_variable([20, 1])
b2q = self.bias_variable([1])
self.state_input = tf.placeholder(tf.float32, [1, self.state_dim], "state") # 应该是指只输入了一组?
# hidden layers
h_layerq = tf.nn.relu(tf.matmul(self.state_input, W1q) + b1q) # #进行 y = w1*x + b1 的运算 ,从线性状态激活成非线性状态
# Q Value layer
self.Q_value = tf.matmul(h_layerq, W2q) + b2q # 进行 y = w2*x + b2 的运算,输出是两个是数(不确定)TODO
def create_training_method(self): # 创建训练方法
self.next_value = tf.placeholder(tf.float32, [1, 1], "v_next")
self.reward = tf.placeholder(tf.float32, None, 'reward')
# https://blog.csdn.net/tian_jiangnan/article/details/105047745
# tf.variable_scope是一个变量管理器,下面的东东即使变量名一样,作用域不一样,引用的时候就不会出现穿插问题了
with tf.variable_scope('squared_TD_error'): # 在作用域名为squared_TD_error的作用域里面
self.td_error = self.reward + GAMMA * self.next_value - self.Q_value # 计算TD_error
self.loss = tf.square(self.td_error) # tf.square是对td_error里面每一个元素求平方
with tf.variable_scope('train'): # 在作用域名为train的作用域里面
# 利用tensorflow中的Adam优化算法最小化loss函数
# Adam优化算法:是一个寻找全局最优点的优化算法,引入了二次方梯度校正。
self.train_op = tf.train.AdamOptimizer(self.epsilon).minimize(self.loss)
def train_Q_network(self, state, reward, next_state): # 训练Q网络
s, s_ = state[np.newaxis, :], next_state[np.newaxis, :] # 当前状态和下一个状态
# 由输入状态和Q_value计算状态价值函数
v_ = self.session.run(self.Q_value, {self.state_input: s_})
# 运行会话输出td_error
td_error, _ = self.session.run([self.td_error, self.train_op],
{self.state_input: s, self.next_value: v_, self.reward: reward}) # 得到td误差
return td_error
def weight_variable(self, shape): # 权重变量
initial = tf.truncated_normal(shape) # 从一个正态分布片段中输出平均数值 shape:决定输出张量的形状
return tf.Variable(initial) # 更新参数,变量存在内存中
def bias_variable(self, shape): # 偏执变量
initial = tf.constant(0.01, shape=shape) # 生成常量矩阵
return tf.Variable(initial)
# Hyper Parameters
ENV_NAME = 'CartPole-v0'
EPISODE = 3000 # Episode limitation
STEP = 3000 # Step limitation in an episode
TEST = 10 # The number of experiment test every 100 episode 每训练100幕数据就做一次效果测试,测试10次取平均
def main():
# initialize OpenAI Gym env and dqn agent
sess = tf.InteractiveSession() # 开启会话
env = gym.make(ENV_NAME) # 导入环境
actor = Actor(env, sess) # 定义AC网络
critic = Critic(env, sess)
for episode in range(EPISODE):
# initialize task
# a) 初始化S为当前状态序列的第一个状态, 拿到其特征向量ϕ(S)
state = env.reset() # 初始化第一个状态
# Train
for step in range(STEP): # 这部分actor网络和critic网络进行交互
# b) 在Actor网络中使用ϕ(S)作为输入,输出动作A,基于动作A得到新的状态S′,反馈R。
action = actor.choose_action(state) # e-greedy action for train 输入状态,得到动作A
# c) 在Critic网络中分别使用ϕ(S),ϕ(S‘′)作为输入,得到Q值输出V(S),V(S′)
next_state, reward, done, _ = env.step(action) # 基于动作A得到新的状态next_state,回报reward
# 由train_Q_network计算得到TD误差
td_error = critic.train_Q_network(state, reward, next_state) # gradient = grad[r + gamma * V(s_) - V(s)]
# 更新Actor网络参数θ
actor.learn(state, action, td_error) # true_gradient = grad[logPi(s,a) * td_error] 最大化价值函数
state = next_state # 为下一步做准备,下一个状态即为下一步的当前状态
if done: # 达到终止条件就退出循环
break
# Test every 100 episodes
if episode % 100 == 0:
total_reward = 0 # 初始化总回报
for i in range(TEST):
state = env.reset() # 初始化环境
for j in range(STEP):
env.render() # env.render()函数用于渲染出当前的智能体以及环境的状态
action = actor.choose_action(state) # # 根据状态选择动作
state, reward, done, _ = env.step(action) # 根据action执行step,得到三状态
total_reward += reward # 为了十次取一次平均,先加后除
if done: # 如果达到了终止条件,则退出
break
ave_reward = total_reward / TEST # 求平均
print('episode: ', episode, 'Evaluation Average Reward:', ave_reward)
if __name__ == '__main__':
main()
【强化学习】AC注释版本
最新推荐文章于 2024-01-31 17:08:11 发布