Q-learning是一种强化学习算法。强化学习指的是让计算机在什么都不懂的情况下,通过不断的和环境进行互动得到反馈,来改善自身的行动策略,最终找到规律并达到学习目的的方法。
所以强化学习的过程只涉及两个对象,一个是智能体agent,它可以发出行动action,接受反馈并评估;另一个是环境environment,它是agent行动的前提,并能对agent的行动做出反馈。
Q-learning的核心在于,它有一个记录着不同状态下采取行动所对应价值的表,即Q table。随着不断地行动,得到反馈去更新Q表中的值。所以,一般称Q-learning算法是基于价值即Value-based的。
行动1 | 行动2 | |
---|---|---|
状态1 | ||
状态2 | ||
状态3 |
让我们设想这样一个情景,你正在写一篇技术博客,共有5个章节(states)。你可以采取两种行动(actions),一是写,二是躺着休息。
在前四章选择写时,你获得了烦躁和微妙的成就感(reward),可以设定为0;在第五章选择写时,你达到了最终状态完成这篇文章,得到了内心的平和以及好心人的点赞,可以设为1;当任一状态选择老子不写了可以获得如释重负的愉悦,依旧设定为0。
可以看出,从reward的角度来看,如果我们只关心当下的感受,写不写都是一样的。Q-learning的高明之处在于,状态-行动产生的Q值(Q table对应值)和之后的状态-行动相关,这样就可以达到延迟满足的目的。
首先随机生成一个Q表。假设我们处在第三个状态s3即写完第三章,我们可以选择a1写,a2休息。此时我们观察一下Q table:
a1 | a2 | |
---|---|---|
s3 | 0.1 | 0.2 |
此时Q表表明选择a2更好,得到预估的Q(s3,a2)。但在实际编程中为了避免探索-利用困境(exploration-exploitation dilemma),即总是利用已知选最优解而失去了探索新的有可能的最优解。此处使用贪心算法(greedy)设定了一个概率阈值 ϵ \epsilon ϵ,即有 1 − ϵ 1-\epsilon 1−ϵ不选Q最大的行动而随机选择。
此处实际Q值仅和下一状态-行动Q值及其奖励有关。先假设 Q ( s 1 ) = R 2 + Q ( s 2 ) Q(s1)=R2+Q(s2) Q(s1)=R2+Q(s2)。但我们往往会在下一状态-行动的Q值先增加一个衰减系数 γ \gamma γ,即 Q ( s 1 ) = R 2 + γ Q ( s 2 ) Q(s1)=R2+\gamma Q(s2) Q(s1)=R2+γQ(s2)。当我们把式子从 Q ( s 1 ) Q(s1) Q(s1)展开,我们可以发现:
Q ( s 1 ) = r 2 + γ Q ( s 2 ) = r 2 + γ [ r 3 + γ Q ( s 3 ) ] = r 2 + γ [ r 3 + γ r 4 + γ Q ( s 4 ) ] ] = … \mathbf{Q}(\mathbf{s} \mathbf{1})=\mathbf{r} \mathbf{2}+\gamma \mathbf{Q}(\mathbf{s} \mathbf{2})=\mathbf{r} \mathbf{2}+\gamma[\mathbf{r} \mathbf{3}+\gamma \mathbf{Q}(\mathbf{s} \mathbf{3})]=\mathbf{r} \mathbf{2}+\gamma[\mathbf{r} \mathbf{3}+\gamma \mathbf{r}\mathbf{4}+\gamma \mathbf{Q} \mathbf{( s 4})] \mathbf{]}=\ldots Q(s1)=r2+γQ(s2)=r2+γ[r3+γQ(s3)]=r2+γ[r3+γr4+γQ(s4)]]=…
Q ( s 1 ) = r 2 + γ r 3 + γ 2 r 4 + γ 3 r 5 + γ 4 r 6 + … Q(s 1)=r 2+\gamma r 3+\gamma^{2} r 4+\gamma^{3} r 5+\gamma^{4} r 6+\ldots Q(s1)=r2+γr3+γ2r4+γ3r5+γ4r6+…
此时,实际Q值仅和各个状态的奖励有关。衰减系数等于1意味着我们能清清楚楚地看到之后所有步的全部价值,而小于1意味着,越往后的选择带来的奖励对当前行动产生的影响越小。
回到s3,我们选择了a2,到达了s4。此时我们发现,s4-a2对应Q值更大,故假设选择 m a x Q ( s 4 ) = Q ( s 4 , a 2 ) maxQ(s4)=Q(s4,a2) maxQ(s4)=Q(s4,a2),(注意:此处因为贪心算法对行动的选择具有随机性,是假设而不是真的选择。接下来操作目的是对Q(s3,a2)进行修正(learn),可以使后续选择对当前状态-行动组合产生影响)。同时通过不断的写到达终止状态s5,得到了 R 4 = 1 R4=1 R4=1的奖励。
a1 | a2 | |
---|---|---|
s4 | 0.1 | 0.3 |
让我们重新设定Q(s3,a2)的实际值。此时只考虑下一步的奖励和最大Q值,可以用 R + m a x Q ( s 4 ) R+maxQ(s4) R+maxQ(s4)作为新的Q值。
在Q-learning算法中,Q值不是一下就改变的,因为后续Q值是未完全修正的,它值作为一种参考意见。我们往往在预估Q的基础上进行修改。 Q 预 估 = Q 预 估 + α ( Q 实 际 − Q 预 估 ) Q预估 = Q预估 + \alpha(Q实际-Q预估) Q预估=Q预估+α(Q实际−Q预估),其中 α \alpha α是学习率(learning-rate),越大表明更新越快,等于1时意味着直接替换为Q实际。
这样我们就明白了Q Learning的核心内容Q值的更新(update):
Q ( s , a ) ← Q ( s , a ) + α [ r + γ max a ′ Q ( s ′ , a ′ ) − Q ( s , a ) ] Q(s, a) \leftarrow Q(s, a)+\alpha\left[r+\gamma \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime}\right)-Q(s, a)\right] Q(s,a)←Q(s,a)+α[r+γa′maxQ(s′,a′)−Q(s,a)]
一个episode是一个回合,包含了初始状态到终止状态的全过程。每一个step是做出行动的一步(状态不一定会变)。每一步查看预估Q值采取行动得到新的状态,假想新状态的行动得到实际Q值,并修正。
Initialize Q ( s , a ) arbitrarily Repeat (for each episode): Initialize s Repeat (for each step of episode): Choose a from s using policy derived from Q (e.g., ε -greedy) Take action a , observe r , s ′ Q ( s , a ) ← Q ( s , a ) + α [ r + γ max a ′ Q ( s ′ , a ′ ) − Q ( s , a ) ] s ← s ′ ; until s is terminal \begin{aligned} &\text { Initialize } Q(s, a) \text { arbitrarily } \\ &\text { Repeat (for each episode): } \\ &\quad\text { Initialize } s \\ &\quad\text { Repeat (for each step of episode): } \\ &\quad\quad\text { Choose } a \text { from } s \text { using policy derived from } Q \text { (e.g., } \varepsilon \text {-greedy) } \\ &\quad\quad\text { Take action } a, \text { observe } r, s^{\prime} \\ &\quad\quad Q(s, a) \leftarrow Q(s, a)+\alpha\left[r+\gamma \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime}\right)-Q(s, a)\right] \\ &\quad\quad s \leftarrow s^{\prime} ; \\ &\quad \text { until } s \text { is terminal } \end{aligned} Initialize Q(s,a) arbitrarily Repeat (for each episode): Initialize s Repeat (for each step of episode): Choose a from s using policy derived from Q (e.g., ε-greedy) Take action a, observe r,s′Q(s,a)←Q(s,a)+α[r+γa′maxQ(s′,a′)−Q(s,a)]s←s′; until s is terminal
参考Github;课程见小例子 - 强化学习 (Reinforcement Learning) | 莫烦Python
# -*- coding: utf-8 -*-
"""
Created on Wed Dec 1 10:21:54 2021
@author: z
"""
import numpy as np
import pandas as pd
import time
#parameters 参数设置
N_STATES = 6 #states
ACTIONS = ['left','right'] #actions
EPSILON = 0.9 #greedy rate
ALPHA = 0.1 #learning rate
GAMMA = 0.9 #discount factor 奖励衰减值
MAX_EPISODES = 13 #maximum episodes
FRESH_TIME = 0.1 #移动时间间隔 fresh time for one move
#initialize Q 初始化Q表
def build_Q_table(n_states, actions):
table = pd.DataFrame(np.zeros((n_states,len(actions))),
columns = actions)
return table
#take action 根据Q表选择行动
def choose_action(state, Q_table):
s_a = Q_table.iloc[state,:] #?iloc 行号获取数据
if (np.random.uniform() > EPSILON) or (s_a.all() == 0):#?all parameters
action_name = np.random.choice(ACTIONS)
else:
action_name = s_a.argmax() #?默认索引
if action_name==1:
action_name = 'right'
elif action_name==0:
action_name = 'left'
return action_name
#learn 更新Q表
def update(S, A, S_, R, Q_table, GAMMA):
Q_predict = Q_table.loc[S,A]
if S_ == 'terminal':
Q_real = R
is_terminated = True #!true关键字
else:
Q_real = R + GAMMA*Q_table.iloc[S_,:].max()
is_terminated = False
# modify after taking action
Q_table.loc[S,A] += ALPHA * (Q_real - Q_predict)
return is_terminated
#set environment 设置环境 可以用Tkinter或gym
def update_env(S, episode, step_counter):
# This is how environment be updated
env_list = ['-']*(N_STATES-1) + ['T'] # '---------T' our environment
if S == 'terminal':
interaction = 'Episode %s: total_steps = %s' % (episode+1, step_counter)
print('\r{}'.format(interaction), end='')
time.sleep(2)
print('\r ', end='')
else:
env_list[S] = 'o'
interaction = ''.join(env_list)
print('\r{}'.format(interaction), end='')
time.sleep(FRESH_TIME)
#get feedback 得到反馈包括下一状态和奖励
def get_env_feedback(S,A):
if A == 'right':
if S == N_STATES - 2:
S_ = 'terminal'
R = 1 #rewards = 1
else:
S_ = S+1
R = 0
else:
if S == 0:
S_ = S
R = 0
else:
S_ = S-1
R = 0
return S_, R
#main RL
def rl():
Q_table = build_Q_table(N_STATES, ACTIONS)
step = 1000 #记录最终步数,设定比较大的初值
#循环(回合数结束 Q整体更新次数)
for episode in range(MAX_EPISODES):
S = 0 #对象参数化
step_counter = 0
update_env(S, episode, step_counter)
#循环(知道达到终止状态)
is_terminated = False
while not is_terminated:
A = choose_action(S, Q_table)
S_,R = get_env_feedback(S, A)
is_terminated = update(S, A, S_, R, Q_table, GAMMA)
S = S_ #!不要忘记更新状态
update_env(S, episode, step_counter)
step_counter +=1
#保存当前最优策略(Q)
if step>=step_counter:
step = step_counter
Q_table_final = Q_table
EP = episode+1
return EP,Q_table_final
if __name__ == "__main__":
EP,q_table = rl()
print('\r\nEpisode:%s'%(EP))
print('\r\nQ-table:\n')
print(q_table)