DQN 笔记 State-action Value Function(Q-function)_UQI-LIUWJ的博客-CSDN博客
强化学习笔记 experience replay 经验回放_UQI-LIUWJ的博客-CSDN博客
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import gym
BATCH_SIZE = 32
# 从experience buffer 每一次提取,用于训练的batch大小
LR = 0.01
# 学习率
EPSILON = 0.9
# 贪婪策略指数,Q-learning的一个指数,用于指示是探索还是利用。
GAMMA = 0.9
# 折扣回报
TARGET_REPLACE_ITER = 100
# target network的更新频率
#在100轮以内固定target network,每100轮更新一次
MEMORY_CAPACITY = 2000
#replay buffer 大小
env = gym.make('CartPole-v0')
N_ACTIONS = env.action_space.n
#2
#每一个state时可以有几个动作
N_STATES = env.observation_space.shape[0]
#4
#每一个state是几维的向量
Net的作用是输入某一时刻的state向量,输出是各个action的值
这里相比于policy network,这里输出的时候不用softmax,因为这里是需要Q最大的action就可以了
class Net(nn.Module):
def __init__(self, ):
super(Net, self).__init__()
self.fc1 = nn.Linear(N_STATES, 50)
self.out = nn.Linear(50, N_ACTIONS)
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
actions_value = self.out(x)
return actions_value
# 创建Q-learning的模型
class DQN(object):
def __init__(self):
# 两张网结构上是一样的,不过就是target_net是每100次更新一次,eval_net每次都更新
self.eval_net, self.target_net = Net(), Net()
#eval_net:实时更新的Q的network
#target_net:每TARGET_REPLACE_ITER轮更新一次的target network
self.learn_step_counter = 0
# 如果次数到了,更新target_net,那么target_net更新的轮数重置
self.memory_counter = 0
# relay buffer 计数器
self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2))
# relay buffer
self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)
self.loss_func = nn.MSELoss()
# 选择动作
def choose_action(self, x):
x = torch.unsqueeze(torch.FloatTensor(x), 0)
#print(state.shape)
#torch.size([1,4])
#当前状态s对应的向量,通过unsqueeze操作变成[1,4]维的向量
#之前设置了EPSILON,用于控制是使用还是探索
if np.random.uniform() < EPSILON:
# 贪婪策略
actions_value = self.eval_net(x)
#eval_net是一个Net 类
#actions_value 是每个action的Q值 (即Q(s,a)))
action = torch.max(actions_value, 1)[1].data.numpy()
#表示Q值最大的那个action的index
action = action[0]
# return the argmax index,在本例中,就是0或者1
else:
# random
action = np.random.randint(0, N_ACTIONS)
#此时使用的是探索
return action
将transition的结果存入experience buffer中
def store_transition(self, s, a, r, s_):
#s和s_是两个ndarray(当前状态,使用了某个action之后的状态)
# a和r是两个数,action和reward
transition = np.hstack((s, [a, r], s_))
# 将参数打包起来,成为一个长一元ndarray
index = self.memory_counter % MEMORY_CAPACITY
#当前的transition需要存入/覆盖的位置
self.memory[index, :] = transition
self.memory_counter += 1
#更新memory
def learn(self):
# target parameter update
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
self.target_net.load_state_dict(self.eval_net.state_dict())
#如果到了该更新target network的时候了,那么把eval_net此时的参数赋给target_net
self.learn_step_counter += 1
# target network的计数
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)
#从relay buffer中选择一个batch的样本index(可以重复)
b_memory = self.memory[sample_index, :]
#通过上一行的index,从relay buffer中提取出这一个batch的记录
b_s = torch.FloatTensor(b_memory[:, :N_STATES])
b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int))
b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2])
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])
#分别对应了这一个batch中的原始状态,该状态采取的动作,该状态的reward,该状态采取该动作之后的状态
q_eval = self.eval_net(b_s).gather(1, b_a)
# (batch, 1)
# self.eval_net(b_s)的输出是一个(batch,2)的tensor,相应的Q(s,a)
# 这里gather的操作是根据b_a是0还是1选择每一行的0还是1,也就是选择maxQ(s,a)
q_next = self.target_net(b_s_).detach()
# detach的作用就是不反向传播去更新,因为target的更新在前面定义好了的,每100轮更新一次
#q_nexts 是Q(s_t+1,a),由于target network不进行更新,所以将q_next detach出来
q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)
#r_t+Γ max Q(s_t+1,a)
# shape (batch, 1)
loss = self.loss_func(q_eval, q_target)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
#pytorch老三样
dqn = DQN()
#创建一个DQN network
print('\nCollecting experience...')
for i_episode in range(400):
#采集400个序列
s = env.reset()
# 重置当前环境状态。
#s表示初始化这一个episode的环境
#array([ 2.3993547 , 0.35676894, 0.00279927, -0.2451453 ], dtype=float32)
ep_r = 0
# ep_r表示每个episode中的reward
for t in range(1000):
#一个epsiode里面有1000步
#env.render()
#渲染环境,如果你是再服务器上跑的,只想出结果,不想看动态推杆过程的话,可以注释掉
a = dqn.choose_action(s)
#根据当前的状态s,选择当前回合合适的action
s_, r, done, info = env.step(a)
# 四个返回的内容是state,reward,done(是否重置环境),info
x, x_dot, theta, theta_dot = s_
#s的四个状态
r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
# r1代表车的 x水平位移 与 x最大边距 的距离差的得分
r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
# r2代表杆的 与垂直线之间的夹角
r = r1 + r2
#修改reward,因为如果使用默认的reward的话可能不好训练,所以这里使用了修改后的reward
#杆越竖直,越靠近中心,奖励应该越大
dqn.store_transition(s, a, r, s_)
#状态,该状态使用的action,该状态使用这个action之后的奖励,之后的状态
#这些信息存储起来,放入relay buffer中
ep_r += r
#当前这轮episode的reward++
if dqn.memory_counter > MEMORY_CAPACITY:
#表明relay buffer中已经满了,也即是已经采样了一些样本,之后再进行training,那么就可以一边采样一边训练了
dqn.learn()
if done:
print('Ep: ', i_episode, '| Ep_r: ', round(ep_r, 2))
if done:
break
s = s_
'''
Collecting experience...
Ep: 203 | Ep_r: 1.29
Ep: 204 | Ep_r: 1.67
Ep: 205 | Ep_r: 1.36
Ep: 206 | Ep_r: 8.36
Ep: 207 | Ep_r: 3.23
Ep: 208 | Ep_r: 2.25
Ep: 209 | Ep_r: 1.12
Ep: 210 | Ep_r: 1.82
Ep: 211 | Ep_r: 3.1
Ep: 212 | Ep_r: 4.04
Ep: 213 | Ep_r: 1.26
'''
loss function 如下: