什么是强化学习?
强化学习是机器学习的一大分支。强化学习是让计算机实现不断尝试来从错误中学习从而找到规律达成目标的一种算法的统称。比如 阿尔法狗等。就是让计算机不断地从新环境中学习来更新自己的准则,不断进步的过程。(有点类似监督学习的神经网络,但是强化学习是没有label标签的)具有分数导向性。
RL 常用算法
价值导向 | 直接选择 | 想象环境学习 |
---|---|---|
Q learning | Policy Gradients | model based RL |
Sarsa | ||
Deep Q network |
model based 相较于 model free 需要为虚拟世界建模,拥有“想象力”,而常见的model free 算法有 Q learning ,sarsa 和 policy gradient。
基于概率的模型适合连续性,且具有选择性(依靠概率分布选择),即概率最大的key不一定被选到(policy gradients)
但是基于价值则具有唯一性,拥有都选择value 最大的key。(Q learning ,sarsa)
每一轮进行更新(类似于批处理)
policy gradients
monte-carlo learning
每一步都要更新(边游戏边学习)(现在更常用)
Q learning
sarsa
在线学习
sarsa
sarsa(λ)
离线学习
Q Learning
deep q network
在机器学习领域,更多的人倾向于基于Python来完成算法,我们需要的知识贮备有:
numpy,pandas:数据处理分析
matplotlib:绘图误差
tkinter/openai gym:编写模拟环境
pytorch:神经网络
Q学习中,对于常见的倒立摆任务中,智能体的状态是对四个变量分别进行离散化转化为数值,动作价值是在时刻t,状态 s t s_t st下采取动作是将获得的折扣奖励总和。但是Q表的问题就是当状态变量的类型数量增加,每个变量都要被精准的离散化,会导致表格的行数十分巨大,钦此,用表格表示的强化学习很难解决大量状态的任务。于是我们提出了深度神经网络表示动作价值函数。
神经网络的输入是每个状态变量的值。因此神经网络输入层中的神经元数量也状态变量的数量相同,这样就不需要考虑对连续的状态变量进行离散化,输出层的神经元数量就是动作类型的数量。输出层中神经元输出的值是动作价值函数 Q ( s t , a t ) Q\left( s_t,a_t \right) Q(st,at)的值。因此他会输出采用这个动作之后所得到的的折扣奖励总和。然后比较来确定选择的行动。
对于Q-learning而言:
Q ( s t , a t ) = Q ( s t , a t ) + η ∗ ( R t + 1 + γ max a Q ( s t + 1 + a ) − Q ( s t , a t ) ) Q\left(s_{t}, a_{t}\right)=Q\left(s_{t}, a_{t}\right)+\eta^{*}\left(R_{t+1}+\gamma \max _{a} Q\left(s_{t+1}+a\right)-Q\left(s_{t}, a_{t}\right)\right) Q(st,at)=Q(st,at)+η∗(Rt+1+γamaxQ(st+1+a)−Q(st,at))
使用这个更新公式的原因是我们想要构造一个这样的表达式:
Q ( s t , a t ) = R t + 1 + γ max a Q ( s t + 1 , a ) Q\left(s_{t}, a_{t}\right)=R_{t+1}+\gamma \max _{a} Q\left(s_{t+1}, a\right) Q(st,at)=Rt+1+γamaxQ(st+1,a)
因为一个神经网络中我们需要计算实际输出与想要的值之间的差值,将其平方值作为误差来进行训练。在这个问题中,在时间t的状态 s t s_t st下采用动作 a t a_t at,则输出层的神经元输出的值是 Q ( s t , a t ) Q\left(s_{t}, a_{t}\right) Q(st,at), 经过学习和训练,我们使得输出值和 R t + 1 + γ max a Q ( s t + 1 , a ) R_{t+1}+\gamma \max _{a} Q\left(s_{t+1}, a\right) Rt+1+γmaxaQ(st+1,a)接近。 因为 这两个值本来应该是相当的,t时刻的奖励与t+1时刻的奖励只差 R t + 1 R_{t+1} Rt+1.所以误差函数是:
E ( s t , a t ) = ( R t + 1 + γ max a Q ( s t + 1 , a ) − Q ( s t , a t ) ) 2 E\left(s_{t}, a_{t}\right)=\left(R_{t+1}+\gamma \max _{a} Q\left(s_{t+1}, a\right)-Q\left(s_{t}, a_{t}\right)\right)^{2} E(st,at)=(Rt+1+γamaxQ(st+1,a)−Q(st,at))2
但是,由于状态 s t + 1 s_{t+1} st+1实际上是由 s t s_t st状态下采取动作 a t a_t at后取得的,所以要通过将状态 s t + 1 s_{t+1} st+1输入神经网络来获得 max a Q ( s t + 1 , a ) \max _{a} Q\left(s_{t+1}, a\right) maxaQ(st+1,a)的值。
这就是深度学习来表示强化学习的动作价值函数的基本方法,也就是DQN(deep-q-network).
DQN不像Q-learning一样,并不是每一步都学习该步的内容,而是将内容储存在经验池中并随机从中提取(replay)内容进行学习,每个步骤的内容也成为transition。我们这样做的原因是因为如果每一步都学习那么t时刻和t+1时刻的时间相关性太高,参数学习难以稳定,所以采用经验回放使用小批量进行训练神经网络。
有两种类型的神经网络:确定动作的主网络和计算误差函数时确定动作价值的目标网络。因为我们在更新价值函数时需要下一个时刻的状态的价值函数,也就是要用相同的价值函数来更新,然而相同的函数会出现Q函数更新学习不稳定的情况,所以我们在求 max a Q ( s t + 1 , a ) \max _{a} Q\left(s_{t+1}, a\right) maxaQ(st+1,a)的值时,使用一段时间之前的另一个Q函数(固定目标Q网络)来计算。
为了简便,我们可以在每个步骤中的奖励固定为-1,0,1.这样无论学习任务如何,都可以使用相同的超参数
我们不采用平方误差函数,因为当误差很大时,平方误差会导致误差函数的输出过大。
Huber损失对数据中的异常点没有平方误差损失那么敏感。
本质上,Huber损失是绝对误差,只是在误差很小时,就变为平方误差。误差降到多小时变为二次误差由超参数δ(delta)来控制。当Huber损失在[0-δ,0+δ]之间时,等价为MSE,而在[-∞,δ]和[δ,+∞]时为MAE。
L δ ( y , f ( x ) ) = { 1 2 ( y − f ( x ) ) 2 for ∣ y − f ( x ) ∣ ≤ δ δ ∣ y − f ( x ) ∣ − 1 2 δ 2 otherwise L_{\delta}(y, f(x))=\left\{\begin{array}{ll} \frac{1}{2}(y-f(x))^{2} & \text { for }|y-f(x)| \leq \delta \\ \delta|y-f(x)|-\frac{1}{2} \delta^{2} & \text { otherwise } \end{array}\right. Lδ(y,f(x))={21(y−f(x))2δ∣y−f(x)∣−21δ2 for ∣y−f(x)∣≤δ otherwise
Huber损失结合了MSE和MAE的优点,对异常点更加鲁棒。
代码如下(示例)建议使用jupyter notebook:
# cartpole文件在 环境文件夹\Lib\site-packages\gym\envs\classic_control
# gym安装:pip install gym matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple
import random
import torch
import torch.nn as nn
import numpy as np
import gym
class Network(nn.Module):
def __init__(self):
super(Network, self).__init__()
self.fc = nn.Sequential(
nn.Linear(4, 24),
nn.ReLU(),
nn.Linear(24, 24),
nn.ReLU(),
nn.Linear(24, 2)
)
self.mls = nn.MSELoss()
self.opt = torch.optim.Adam(self.parameters(), lr = 0.001)
def forward(self, inputs):
return self.fc(inputs)
env = gym.envs.make('CartPole-v1')
env = env.unwrapped
net = Network() # 学习的网络
net2 = Network() # 延迟的网络
memory_count = 0 # 经验池中已经遇到的情况
memory_size = 2000 # 记忆库大小
epsilon = 0.5 # 贪婪系数
gama = 0.9 # 时间折扣
b_size = 500 # batch size每次从记忆库中提取的数据
learn_time = 0
update_time = 10 # 每10步更新一次网络
memory = np.zeros((memory_size, 10)) # s=4,a=1,s=4,r=1,
start_study = False
for i in range(50000):
s = env.reset()
while True:
if random.randint(0,100) < 100*(epsilon**learn_time):
a = random.randint(0,1)
else:
totalreward = net(torch.Tensor(s))
a = torch.argmax(totalreward).data.item()
s_, r, done, info = env.step(a)
r = ( env.theta_threshold_radians - abs(s_[2]) ) / env.theta_threshold_radians * 0.7 + ( env.x_threshold - abs(s_[0]) ) / env.x_threshold * 0.3 # (杆子角度为0最好,在中间也很好,比例定为7/3)
memory[memory_count % memory_size][0:4] = s
memory[memory_count % memory_size][4:5] = a
memory[memory_count % memory_size][5:9] = s_
memory[memory_count % memory_size][9:10] = r
memory_count += 1
s = s_
if memory_count > memory_size:
if learn_time % update_time == 0:
net2.load_state_dict(net.state_dict()) # 延迟更新
index = random.randint(0, memory_size - b_size -1)
b_s = torch.Tensor(store[index:index + b_size, 0:4])
b_a = torch.Tensor(store[index:index + b_size, 4:5]).long()
b_s_ = torch.Tensor(store[index:index + b_size, 5:9])
b_r = torch.Tensor(store[index:index + b_size, 9:10])
q = net(b_s).gather(1, b_a)
q_next = net2(b_s_).detach().max(1)[0].reshape(b_size, 1)
tq = b_r + gama * q_next
loss = net.mls(q, tq) # 让总收益等于下一步收益加之后的收益
net.opt.zero_grad()
loss.backward()
net.opt.step()
learn_time += 1
if not start_study:
print('start study')
start_study = True
break
if done:
break
env.render()