1. 什么是Q-Learning
Q-Learning算法是一种使用时序差分求解强化学习控制问题的方法。通过当前的状态 S S S,动作 A A A,即时奖励 R R R,衰减因子 γ γ γ,探索率 ϵ ϵ ϵ,就最最优的动作价值函数 Q Q Q和最有策略 π π π。
- S S S: 表示环境的状态,在 t t t时刻环境的状态 S t S_t St
- A A A:agent的动作,在 t t t时刻采取的动作 A t A_t At
- R R R:环境的奖励,在 t t t时刻agent在状态St采取动作 A t A_t At对应的奖励 R t + 1 R_{t+1} Rt+1会在 t + 1 t+1 t+1时刻得到
- γ \gamma γ:折扣因子, 当前延时奖励的权重
- ϵ ϵ ϵ:探索率,在Q-learning我们会选取当前轮迭代价值最大的动作,可能会导致有的动作重来都没被执行过,在agent选择动作时,有小概率不是选取当前迭代价值最大的动作。
1.1 Q-Learning算法简介
首先我们基于状态 S S S,用 ϵ − g r e e d ϵ−greed ϵ−greed(贪心)选择到动作 A A A, 然后执行动作 A A A,得到奖励 R R R,并进入状态 S ′ S^′ S′, Q Q Q值的更新公式如下: Q ( S , A ) = Q ( S , A ) + α ( R + γ m a x Q ( S ′ , a ) − Q ( S , A ) ) Q(S,A)=Q(S,A)+\alpha(R+\gamma maxQ(S^′,a)-Q(S,A)) Q(S,A)=Q(S,A)+α(R+γmaxQ(S′,a)−Q(S,A))
1.2 Q-learning的算法流程
- 随机初始化状态和动作价值对应的价值。(初始化 Q Q Q表格)
- for i from 1 to T(T:迭代的总轮数)
a)初始化 S S S为当前状态的序列的第一个状态
b)用 ϵ ϵ ϵ−贪婪法在当前状态 S S S选择出动作 A A A
c)在状态 S S S执行当前动作 A A A,得到新状态 S ′ S^′ S′和奖励 R R R
d)更新价值函数 Q ( S , A ) Q(S,A) Q(S,A):
Q ( S , A ) = Q ( S , A ) + α ( R + γ m a x Q ( S ′ , a ) − Q ( S , A ) ) Q(S,A)=Q(S,A)+\alpha(R+\gamma maxQ(S^′,a)-Q(S,A)) Q(S,A)=Q(S,A)+α(R+γmaxQ(S′,a)−Q(S,A))
e) S = S ′ S=S^′ S=S′
f) if d o n e done done 完成当前迭代
1.3 关于Q_table举个例子
(1)游戏地图
- 黑色的框为陷阱
- 黄色的框为出口(奖励点)
(2) 这是一个训练模型之后的Q表格
(3) 举个简单的例子
- 如果agent在“1”的位置进入迷宫,会更具Q表格,向下走的Q值最大为0.59,那么agent就会走到“5”的位置。
- agent在“5”的位置之后,更具Q表格,向下走的Q值最大为0.66,依然是向下走,那么agent就走到了“9”的位置。
- agent在“9”的位置之后,更具Q表格,向右走的Q值最大为0.73,依然是向下走,那么agent就走到了“10”的位置。
- agent在“10”的位置之后,更具Q表格,向下走的Q值最大为0.81,依然是向下走,那么agent就走到了“14”的位置。
- agent在“14”的位置之后,更具Q表格,向右走的Q值最大为0.9,依然是向下走,那么agent就走到了“15”的位置。
- agent在“15”的位置之后,更具Q表格,向右走的Q值最大为1,依然是向下走,那么agent就走到了“16”的位置,到达终点。
最后agent的动作路线为1–>5–>9–>10–>14–>15–>16
每跑一次, Q Q Q表格的值都会有所改变,但是原理不变。
如果想看到更加直观的视觉戳这里
2. DQN(Deep Q Network)
前面讲过Q-Learning的决策是根据Q表格的值,执行那个动作后得到的奖励更多,就选取那个动作执行。前面所讲的状态空间和动作空间都很小,如果状态空间和动作空间变得很大很大,那我们还能用一个Q表格来表示吗?显然不可以,就引入了价值函数近似。
2.1 价值函数近似
由于在实际问题中,一个问题的状态规模很大,一个可行的解决办法就是使用价值函数近似。我们引入一个状态价值函数
v
^
\hat v
v^,由权重
ω
\omega
ω描述,以状态
s
s
s作为输入,计算得到状态
s
s
s的价值:
v
^
(
s
,
w
)
≈
v
π
(
s
)
\hat v(s,w)\approx v_\pi(s)
v^(s,w)≈vπ(s)
上面我们提到的
ω
\omega
ω就相当于我们神经网络中的参数,通过输入的状态
s
s
s,采用MC(蒙特卡洛)/TD(时序差分)计算出价值函数作为输出,然后对权重
ω
\omega
ω进行训练,直到收敛。事实上,所谓的DQN就是将神经网络和Q-Learning结合,将Q表格变成了Q网络。
2.2 Deep Q-Learning算法思路
DQN是一种Off-Policy算法,用李宏毅老师的话讲,可以看着别人学习,那么DQN为什么能够看着别人学习呢?DQN采用了一种经验回放的方式进行学习。每次agent和环境交互得到的奖励,当前状态和下一个状态等数据保存起来,用于后面Q网络的更新。
下面我们看下Nature DQN,其实Nature DQN为DQN第二代了,DQN NIPS为最原始的DQN,在这之上的还有很多的DQN的版本,比如Double DQN,Dueling DQN等等。之所以在这儿给大家介绍Nature DQN呢!个人觉得这个版本的DQN,应该是最经典的了。接下来我们看看DQN是如何进行强化学习的吧。
2.3 算法流程图
输入:总迭代轮数
T
T
T,状态特征维度
n
n
n,动作维度
A
A
A, 步长
a
a
a,衰减因子
γ
\gamma
γ, 探索率
ϵ
\epsilon
ϵ, 当前Q网络
Q
Q
Q,目标Q网络
Q
′
Q^′
Q′, 批量梯度下降的样本数
m
m
m,目标Q网络参数更新频率
P
P
P。
输出:Q网络参数
- 随机初始化所有的状态和动作对应的价值 Q Q Q,随机初始化当前 Q Q Q网络的所有参数 ω \omega ω,初始化目标Q网络 Q ′ Q^′ Q′的参数 ω ′ \omega′ ω′ = = = ω \omega ω,清空经验池 D D D
- for i from 1 to T(不断地迭代)
a)初始化环境,获取第一个状态 s s s,获取特征向量 ϕ \phi ϕ ( S ) (S) (S)
b)在 Q Q Q网络中使用 ϕ \phi ϕ ( S ) (S) (S)作为输入,得到 Q Q Q网络所有动作的Q值,使用 ϵ \epsilon ϵ-贪婪法在当前的Q值输出中选择对应的动作 A A A
c)在状态 S S S下执行动作 A A A,得到新的状态 S ′ S^′ S′,以及其对应的 ϕ \phi ϕ ( S ′ ) (S^′) (S′)和奖励 R R R,是否为结束状态 i s d o n e isdone isdone
d)将{ ϕ ( S ) \phi(S) ϕ(S), A A A, R R R, ϕ ( S ′ ) \phi(S^′) ϕ(S′), i s d o n e isdone isdone }将这5个元素放入经验池 D D D
e) S = S ′ S=S^′ S=S′
f)从经验池 D D D中采取 m m m个样本,{ ϕ ( S j ) \phi(S_j) ϕ(Sj), A j A_j Aj, R j R_j Rj, ϕ ( S j ′ \phi(S^′_j ϕ(Sj′, i s d o n e j isdone_j isdonej), j = 1 , 2 , 3 , 4.... m j=1,2,3,4....m j=1,2,3,4....m,计算当前 Q Q Q值 y j y_j yj:
f ( n ) { R j , i s d o n e j i s t r u e R j + γ m a x a ′ Q ′ ( ϕ ( S j ′ ) , A j ′ , ω ′ ) , i s d o n e j i s f a l s e f(n) \begin{cases} R_j, &isdone_j\ is\ true\\ R_j+\gamma max_a′ Q^′ (\phi(S^′_j),A_j^′,\omega^′) , &isdone_j\ is \ false \end{cases} f(n){Rj,Rj+γmaxa′Q′(ϕ(Sj′),Aj′,ω′),isdonej is trueisdonej is false
g)使用均方差损失函数 ( 1 m ) \left(\frac{1}{m}\right) (m1) ∑ j = 1 m \sum_{j=1}^m ∑j=1m( y j − Q ( ϕ ( S j ) , A j , ω ) ) 2 y_j-Q(\phi (S_j),A_j,\omega))^2 yj−Q(ϕ(Sj),Aj,ω))2通过神经网络梯度下降反向传播更新参数 ω \omega ω
h)如果i%P=0,跟新目标 Q Q Q网络的参数 ω ′ = ω \omega^′=\omega ω′=ω
i)如果 S ′ S^′ S′为终止状态,则当前迭代完毕,否则跳转到步骤(2)
2.4 DQN实现代码
(1) 网络结构
class Net(nn.Module):
def __init__(self, ):
super(Net, self).__init__()
self.fc1 = nn.Linear(N_STATES, 50)
self.fc1.weight.data.normal_(0, 0.1) # initialization
self.out = nn.Linear(50, N_ACTIONS)
self.out.weight.data.normal_(0, 0.1) # initialization
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
actions_value = self.out(x)
return actions_value
两个网络的结构一样,但是里面的参数权重有区别,一个是实时更新,一个是隔一段时间在更新
(2) 动作的选取
def choose_action(self, x): #x为当前状态的4个值
x = torch.unsqueeze(torch.FloatTensor(x), 0) #在数据的第0维处增加一维
# input only one sample
if np.random.uniform() < EPSILON: # greedy #贪婪取法
actions_value = self.eval_net.forward(x) ##传入eval_net获取下一个的动作
action = torch.max(actions_value, 1)[1].data.numpy() ##返回这一行中最大值的索引
action = action[0] if ENV_A_SHAPE == 0 else action.reshape(ENV_A_SHAPE) # return the argmax index
else: # random
action = np.random.randint(0, N_ACTIONS)
# action = random.sample(N_ACTIONS)
action = action if ENV_A_SHAPE == 0 else action.reshape(ENV_A_SHAPE)
return action
加入了一个探索值 ( ϵ ) (\epsilon) (ϵ),在即小的可能性是随机选择动作
(3) 经验池
def store_transition(self, s, a, r, s_): #s和s_都为4个值,分别为 位置 移动速度 角度 移动角度
transition = np.hstack((s, [a, r], s_))
# replace the old memory with new memory #更新经验
index = self.memory_counter % MEMORY_CAPACITY
self.memory[index, :] = transition #将第index经验替换为transition
self.memory_counter += 1
(4) 更新网络参数
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()) ## 每学习200步将eval_net的参数赋值给target_net
self.learn_step_counter += 1
# sample batch transitions #选取过渡
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE) #从MEMORY_CAPACITY随机选取BATCH_SIZE个
b_memory = self.memory[sample_index, :]
b_s = torch.FloatTensor(b_memory[:, :N_STATES]) #第一个状态
b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int)) #动作
print("--------")
print(b_a)
print("-----")
b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2]) #得分
b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:]) #下一个状态
# q_eval w.r.t the action in experience
q_eval = self.eval_net(b_s).gather(1, b_a) # shape (batch, 1) 当前状态的Q值使用eval_net计算
# print("++++++")
# print(self.eval_net(b_s))
# print(self.eval_net(b_s).gather(1,b_a))
# print("+++++++")
q_next = self.target_net(b_s_).detach() #使用target_net计算下一步Q值 # detach from graph, don't backpropagate detach防止targent——net反向传播
q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1) # shape (batch, 1)
loss = self.loss_func(q_eval, q_target)
self.optimizer.zero_grad() #zer——grad设置所有优化器的梯度为0
loss.backward() #反向传播
self.optimizer.step() #执行下个优化
DQN是深度强化学习的门槛,只要踏进了大门,后面的学习就会很轻松
源码请看我的github