Q-learing是一种经典的时序差分离线控制算法,与之相对的SARSA算法是时序差分在线控制算法的代表。
所谓的在线,是一直使用一个策略来更新价值函数和选择新的动作。而离线是使用两个控制策略,一个策略用于选择新的动作,另一个策略用于更新价值函数。
注释:
注释:
1.选择的动作 A ′ A^\prime A′只会参与价值函数的更新,不会真正的执行。
2. Q ( S , A ) Q(S,A) Q(S,A)更新后,下一轮迭代新的执行动作 A A A需要基于状态S′,用ϵ−贪婪法重新选择得到。
与Q-learning相比,DQN的 Q ( S , A ) Q(S,A) Q(S,A)不是直接通过状态值 S S S和动作 A A A来计算,而是通过一个神经网络 Q Q Q网络来计算。
如果想用神经网络取而代之,就需要一个技巧取代先前用来存放 Q Q Q值表格,这里用到了经验回放(experience replay)。其作用是将每次和环境交互得到的奖励 R R R与状态 S S S得更新情况都保存起来,用于后面目标 Q Q Q值的更新。
此外,平时阅读各类文献出现的名词:目标 Q Q Q值(target Q − Q- Q−value)为 R t + 1 + Q ( S t + 1 , A t + 1 ) R_{t+1}+Q(S_{t+1},A_{t+1}) Rt+1+Q(St+1,At+1)。再根据贝尔曼方程(Bellman Equation)的推导,价值函数 V π ( S ) : V_\pi(S): Vπ(S):
V π ( S ) = E ( R t + 1 + γ R t + 2 + γ 2 R t + 3 + . . . ∣ S t = s ) = E ( R t + 1 + γ ( R t + 2 + γ R t + 3 + . . . ) ∣ S t = s ) = E ( R t + 1 + γ G t + 1 ∣ S t = s ) = E ( R t + 1 + γ V ( S t + 1 ) ∣ S t = s ) \begin{aligned} V_\pi(S) & = E(R_{t+1}+\gamma R_{t+2}+\gamma ^2R_{t+3}+...|S_t=s) \\ & =E(R_{t+1}+\gamma (R_{t+2}+\gamma R_{t+3}+...)|S_t=s)\\ & = E(R_{t+1}+\gamma G_{t+1}|S_t=s) \\ & = E(R_{t+1}+\gamma V(S_{t+1})|S_t=s) \end{aligned} Vπ(S)=E(Rt+1+γRt+2+γ2Rt+3+...∣St=s)=E(Rt+1+γ(Rt+2+γRt+3+...)∣St=s)=E(Rt+1+γGt+1∣St=s)=E(Rt+1+γV(St+1)∣St=s)
其中 G t + 1 G_{t+1} Gt+1能写成 V ( S t + 1 ) V(S_{t+1}) V(St+1) 的原因是 收获的期望等于收获的期望的期望: E ( G t + 1 ) = V π ( S t + 1 ) = E ( V π ( S t + 1 ) ) E(G_{t+1})=V_\pi(S_{t+1})=E(V_\pi(S_{t+1})) E(Gt+1)=Vπ(St+1)=E(Vπ(St+1));进一步地将价值函数 V π ( S ) V_\pi(S) Vπ(S)换成动作价值函数 Q π ( S , A ) Q_\pi(S,A) Qπ(S,A)的话,可得到动作价值函数数 Q π ( S , A ) Q_\pi(S,A) Qπ(S,A)的贝尔曼方程
Q π ( S , A ) = E ( R t + 1 + Q ( S t + 1 , A t + 1 ) ∣ S t = s , A t = a ) Q_\pi(S,A)=E(R_{t+1}+Q(S_{t+1},A_{t+1})|S_t=s,A_t=a) Qπ(S,A)=E(Rt+1+Q(St+1,At+1)∣St=s,At=a)
我们可以发现 Q π ( S , A ) Q_\pi(S,A) Qπ(S,A) 是 Q ( S t , A t ) Q(S_{t},A_{t}) Q(St,At)的估计值,这也就解释了神经网络损失函数 L o s s Loss Loss函数的来龙去脉。
这里使用Gym进行模拟。
Gym 是 OpenAI 发布的用于开发和比较强化学习算法的工具包。使用它我们可以让 AI 智能体做很多事情,比如行走、跑动,以及进行多种游戏。在这个Demo中,我们使用的是车杆游戏(Cart-Pole)这个小游戏。
游戏规则:游戏里面有一个小车,上有竖着一根杆子。小车需要左右移动来保持杆子竖直。如果杆子倾斜的角度大于15°,那么游戏结束。小车也不能移动出一个范围(中间到两边各2.4个单位长度)。
from DRL import DRL
from collections import deque
import os
import random
from keras.layers import Dense,Input
from keras import Model
from keras.optimizers import Adam
import numpy as np
class DQN(DRL):
def __init__(self):
super(DQN, self).__init__()
self.model = self.build_model()
self.memory_buffer = deque(maxlen=200)
self.gamma = 0.95
self.epsilon = 1.0
self.epsilon_decay = 0.995
self.epsilon_min = 0.01
def load(self):
if os.path.exists("model/dqn.h5"):
self.model.load_weights("model/dqn.h5")
def build_model(self):
inputs = Input(shape=(4,))
x = Dense(16, activation='relu')(inputs)
x = Dense(16, activation= 'relu')(x)
x = Dense(2, activation='linear')(x)
model = Model(inputs=inputs, outputs=x)
model.compile(loss='mse', optimizer=Adam(1e-3))
return model
def egreedy_action(self, state):
if np.random.rand() >= self.epsilon:
return random.randint(0,1)
else:
q_values = self.model.predict(state)
return np.argmax(q_values)
def remember(self, state, action, reward, next_state, done):
item = (state, action, reward, next_state, done)
self.memory_buffer.append(item)
def update_epsilon(self):
if self.epsilon >= self.epsilon_min:
self.epsilon *= self.epsilon_decay
def process_batch(self,batch):
data = random.sample(self.memory_buffer, batch)
# 这里取元组的第1个值,即当前state
states = np.array([d[0] for d in data])
# 这里取元组的第4个值,即下一个state
next_states = np.array([d[3] for d in data])
y = self.model.predict(states)
q = self.model.predict(next_states)
print("y value {}, and q value {}".format(y, q))
for i, (_, action, reward, _, done) in enumerate(data):
target = reward
if not done:
target += self.gamma * np.amax(q[i])
y[i][action] = target
return states, y
def train(self, episode, batch):
history = {'episode': [], 'Episode_reward': [], 'Loss': []}
count = 0
for i in range(episode):
observation = self.env.reset()
reward_sum = 0
loss = np.infty
done = False
while not done:
# 一个参数为-1时,reshape函数会根据另一个参数的维度计算出数组的另外一个shape属性值
x = observation.reshape(-1, 4)
action = self.egreedy_action(x)
observation, reward, done, _ = self.env.step(action)
reward_sum += reward
self.remember(x[0], action, reward, observation, done)
if len(self.memory_buffer) > batch:
X, y = self.process_batch(batch)
loss = self.model.train_on_batch(X, y)
count += 1
self.update_epsilon()
if i % 5 == 0:
history['episode'].append(i)
history['Episode_reward'].append(reward_sum)
history['Loss'].append(loss)
print('Episode: {} | Episode reward: {} | loss: {:.3f} | e:{:.2f}'.format(i, reward_sum, loss, self.epsilon))
self.model.save_weights('model/dqn.h5')
return history
if __name__ == '__main__':
model = DQN()
history = model.train(600, 32)
model.save_history(history, 'dqn.csv')
model.load()
model.play()