强烈推荐先过一遍科科科老师的dqn
首先说一下为什么会出现DQN
在 Q learning 或者 SARSA 中
我们可以用一个 Q 表 来存储我们的行为与状态
但是当state的状态为连续空间的时候
就会出现无数个state
如果把连续的state离散化会导致精度丢失
如果有一个东西丢给它一个state就会给出一个action就好了
而神经网络恰好就符合我们的需求
2013年发表的论文
Playing Atari with Deep Reinforcement Learning
这是第一次成功的强化学习和深度学习的结合
知道你们不想看英文,上面的链接是中文的
凡事都是有利有弊
引入神经网络的弊端:
弊端1. 用神经网络训练的时候所有状态都是连续的,具有高度相关性
弊端2. 训练神经网络时,Q_target是在一直变化的,
因为画圈圈的部分也是经过神经网络得到的,所以很难拟合Q_target
举个例子
监督学习的终点就在前方五十米,你只需要慢慢接近终点就行了
dqn的终点在前方五十米,你前进了二十米,终点又移动到了你后方五十米
解决1的办法:经验回放
搞一个经验池,随机抽取经验训练
解决2的办法:固定目标
使用两个神经网络,一个网络参数每次都更新,一个网络固定次数更新参数
用这个每次都更新参数的网络 拟合 固定次数更新参数的网络
这个地方你或许会有疑问,固定次数更新参数会有什么不一样?
直接看图吧
分模块代码解析
基本的神经网络框架
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 在CartPole-v0中状态是用四个浮点数表示
# N_STATES就是4
self.l1 = nn.Linear(N_STATES, 64)
self.l2 = nn.Linear(64, 32)
# 在CartPole-v0中动作是用两个数表示
# N_ACTIONS就是2
self.predict = nn.Linear(32, N_ACTIONS)
def forward(self, x):
out = self.l1(x)
out = F.relu(out)
out = self.l2(out)
out = F.relu(out)
return self.predict(out)
经验回放所使用的类
class ReplayMemory():
def __init__(self, max_size, batch_size):
# 存放经验的最大容量
self.max_size = max_size
# 每次从经验池中随机取多少条经验
self.batch_size = batch_size
# 定义一个双向队列存放经验,坑1,会讲一下这个双向队列
self.buffer = collections.deque(maxlen=max_size)
def append(self, exp):
# 把一条经验放入经验池
self.buffer.append(exp)
def sample(self):
# 从经验池随机抽样得到batch_size条经验
mini_batch = random.sample(self.buffer, self.batch_size)
# 声明4个空数组分别存放状态、动作、奖励、下次的状态
# s 表示 state,a 表示 action,r 表示 reward,ss 表示 下次的state
s_mini_batch, a_mini_batch, r_mini_batch, ss_mini_batch = [], [], [], []
for exp in mini_batch:
s, a, r, ss = exp
s_mini_batch.append(s)
a_mini_batch.append(a)
r_mini_batch.append(r)
ss_mini_batch.append(ss)
# 坑2,会讲一下返回的都是什么东西
return torch.FloatTensor(np.array(s_mini_batch)), \
# 坑3,这个地方真的很坑,我自己搞了好久
torch.LongTensor(np.array(a_mini_batch)).view(-1,1), \
torch.FloatTensor(np.array(r_mini_batch)).view(-1,1), \
torch.FloatTensor(np.array(ss_mini_batch))
def __len__(self):
return len(self.buffer)
坑1:
既然队列有max_size,那么达到max_size后,经验该怎么存放
看下面的代码
当你追加经验超过它的max_size后,它会自动把最先进入队列的经验扔掉
这种机制可以保证在经验池里的都是最新的经验
也不用担心当经验池满了之后如何覆盖
坑2:
这里就是把list转成了我们想要形状的张量
view(-1,1)的参数可以看作行和列
上面这个就是要把形状变成(-1行,1列)
其中-1就是我不确定,让程序自己算
坑3:
为什么其他的都是float类型的张量,而这个是long类型的张量
torch.LongTensor(np.array(a_mini_batch)).view(-1,1)
说到这就不得不提 torch.gather(input,dim,index)
其中index只接收int64类型也就是long类型,
你直接给它1都不行,必须是long(1)
一般报错是这样的
至于gather()的作用下面会再讲
最主要的类DQN
class DQN():
def __init__(self):
# 创建两个神经网络,为什么是两个,见弊端2
self.eval_net, self.target_net = Net(), Net()
self.learn_step_counter = 0 # 记录学习的次数
self.Target_replace_iter = 100 # 每学习100次更新target网络的参数
# 优化器
self.optimizer = optim.Adam(self.eval_net.parameters(), lr=lr)
# 均方差
self.loss_func = nn.MSELoss()
def choose_action(self, s):
# 这是是把s转换成了张量
s = torch.FloatTensor(s)
# 随机产生一个0-1之间的数,如果小于greedy就选择价值最高的行为,greedy是全局参数,没有定义在这个类中
if np.random.uniform() < greedy:
# 给eval_net一个状态,得到各个行为的价值
actions_value = self.eval_net.forward(s)
# 选择其中价值最大的动作
action = torch.max(actions_value, 0)[1].data.numpy()
else:
# 随机选择行为(探索精神)
action = np.random.randint(0, N_ACTIONS)
return action
def learn(self):
# 每隔Target_replace_iter步更新一下target网络的参数
if self.learn_step_counter % self.Target_replace_iter == 0:
self.target_net.load_state_dict(self.eval_net.state_dict())
# 记录学习的次数
self.learn_step_counter += 1
# 随机抽样得到batch_size条经验
s, a, r, ss = memory.sample()
# 强化学习的精髓,这三行下面详细讲
q_eval = self.eval_net(s).gather(1, a)
q_next = self.target_net(ss).detach()
q_target = r + gamma * q_next.max(1)[0].view(batch_size, 1)
# 计算q_eval与q_target值的差距
loss = self.loss_func(q_eval, q_target)
# 梯度归零
self.optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新权重参数
self.optimizer.step()
坑3那里说的gather()的作用也在这里讲了
q_eval = self.eval_net(s).gather(1, a)
self.eval_net(s)就是使用当前的神经网络eval_net进行一次前向传播得到各个行为的价值
在这里我们使用的是CartPole-v0的环境
为了方便观察我这里把batch_size调成4看一下效果
下面就是动作0和动作1分别对应的价值
那后面的gather(1, a)是干什么的呢
a就是在状态s下所采取的行为
那把行为所对应的价值取出来是不是就得到了在状态s下采取行为a的价值
gather(1, a)就是干这个的
看一下gather(dim,index)是如何做到的
最终q_eval就是在状态s下采取行为a的价值
q_next = self.target_net(ss).detach()
这行代码是target_net进行一次前向传播得到下一个state的价值
而后面的.detach()就是不进行反向传播
因为我们不是要固定target_net的参数吗
这两行代码我们已经得到了
在状态s下采取行为a的价值
下一个状态各个行为对应的价值
看最后一行核心代码
q_target = r + gamma * q_next.max(1)[0].view(batch_size, 1)
q_next.max(1)[0].view(batch_size, 1)这一部分就是把下一个状态中行为的最大值取出来
q_target = r + gamma * max(q_next)
q_target 就是我们这一步的奖励加上打折的未来可能获取的最大奖励
类比
今天我说现在给你100块
今天我说一年后给你100块
你的心情肯定是不一样的,这就是他们的奖励相同但价值不同
gamma 就是对未来的奖励做了一个折扣
这种方法会先更新得到奖励或惩罚的那一步
然后更新得到奖励或惩罚的那一步的前一步
然后更新前前一步
完整代码地址
https://gitee.com/VVeaker/ai/blob/master/DeepReinforcementLearning/DQN_CartPole_v0.py