DQN——深度强化学习的理解以及keras实现

1. 起源

Q-learing是一种经典的时序差分离线控制算法,与之相对的SARSA算法是时序差分在线控制算法的代表。

所谓的在线,是一直使用一个策略来更新价值函数和选择新的动作。而离线是使用两个控制策略,一个策略用于选择新的动作,另一个策略用于更新价值函数。

①SARSA算法流程为:

  1. 起初,我们使用 ϵ − \epsilon- ϵ贪婪法在当前状态S选择一个动作A,这样系统会转到一个新的状态 S ′ S^\prime S, 同时给我们一个即时奖励 R R R, 在新的状态 S ′ S^\prime S
  2. 然后,我们使用 ϵ − \epsilon- ϵ贪婪法在状态 S ′ S^\prime S选择一个动作 A ′ A^\prime A,但是注意这时候我们并不执行这个动作A′,只是用来更新的我们的价值函数:
    Q ( S , A ) = Q ( S , A ) + α ( R + γ Q ( S ′ , A ′ ) − Q ( S , A ) ) Q(S,A)=Q(S,A)+\alpha(R+\gamma Q(S^\prime,A^\prime)-Q(S,A)) Q(S,A)=Q(S,A)+α(R+γQ(S,A)Q(S,A))
  3. S = S ′ S=S^\prime S=S A = A ′ A=A^\prime A=A

注释:

  1. Q ( S , A ) Q(S,A) Q(S,A)更新时使用的 A ′ A^\prime A将会作为下一阶段开始时候的执行动作 A A A
  2. 选择的动作 A ′ A^\prime A只会参与价值函数的更新,不会真正的执行。

②Q-learing算法流程为:

  1. 起初,我们使用 ϵ − \epsilon- ϵ贪婪法在当前状态S选择一个动作A,这样系统会转到一个新的状态 S ′ S^\prime S, 同时给我们一个即时奖励 R R R, 在新的状态 S ′ S^\prime S
  2. 然后,我们使用贪婪法在状态 S ′ S^\prime S选择一个动作 A ′ A^\prime A,但是注意这时候我们并不执行这个动作A′,只是用来更新的我们的价值函数:
    Q ( S , A ) = Q ( S , A ) + α ( R + γ m a x a Q ( S ′ , A ′ ) − Q ( S , A ) ) Q(S,A)=Q(S,A)+\alpha(R+\gamma \mathop{max}\limits_{a}Q(S^\prime,A^\prime)-Q(S,A)) Q(S,A)=Q(S,A)+α(R+γamaxQ(S,A)Q(S,A))
  3. S = S ′ S=S^\prime S=S

注释:
1.选择的动作 A ′ A^\prime A只会参与价值函数的更新,不会真正的执行。
2. Q ( S , A ) Q(S,A) Q(S,A)更新后,下一轮迭代新的执行动作 A A A需要基于状态S′,用ϵ−贪婪法重新选择得到。

2. DQN算法解读

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值的更新。

DQN算法流程为:

  1. 基于状态 S S S,可得到对应得特征向量 h ( S ) h(S) h(S)
  2. h ( S ) h(S) h(S)作为神经网络的输入。神经网络输出得到所有动作对应的 Q Q Q值输出。用 ϵ − \epsilon- ϵ贪婪法在当前 Q Q Q值输出中选择对应的动作 A A A
  3. 在状态 S S S执行当前动作 A A A,得到新状态 S ′ S^\prime S对应的特征向量 h ( S ′ ) h(S^\prime) h(S)和奖励 R R R
  4. [ h ( S ) , A , R , h ( S ′ ) , d o n e ] [h(S),A,R,h(S^\prime),done] [h(S),A,R,h(S),done]这个5元组存入经验回放集合 D D D
  5. S = S ′ S=S\prime S=S
  6. 从经验回放集合 D D D中采样 m m m个样本 [ h ( S j ) , A j , R j , h ( S j ′ ) , d o n e ] [h(S_j),A_j,R_j,h(S_j^\prime),done] [h(Sj),Aj,Rj,h(Sj),done] j = 1 , 2. , , , m j=1,2.,,,m j=1,2.,,,m,计算当前目标 Q Q Q值:
    y j = R j + Q ( h ( S j ′ ) , A j ′ , w ) y_j=R_j+Q(h(S_j^\prime),A^\prime_j,w) yj=Rj+Q(h(Sj),Aj,w)
  7. 使用mse损失函数更新网络参数:
    L o s s = 1 m ( y j − Q ( h ( S j ) , A j , w ) ) Loss=\frac{1}{m}(y_j-Q(h(S_j),A_j,w)) Loss=m1(yjQ(h(Sj),Aj,w))
    注:使用神经网络计算 Q ( h ( S j ) , A j , w ) Q(h(S_j),A_j,w) Q(h(Sj),Aj,w) Q ( h ( S j ′ ) , A j ′ , w ) Q(h(S_j^\prime),A^\prime_j,w) Q(h(Sj),Aj,w)的值。

此外,平时阅读各类文献出现的名词:目标 Q Q Q值(target Q − Q- Qvalue)为 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+1St=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函数的来龙去脉。

3. keras实现DQN

这里使用Gym进行模拟。

Gym 是 OpenAI 发布的用于开发和比较强化学习算法的工具包。使用它我们可以让 AI 智能体做很多事情,比如行走、跑动,以及进行多种游戏。在这个Demo中,我们使用的是车杆游戏(Cart-Pole)这个小游戏。

游戏规则:游戏里面有一个小车,上有竖着一根杆子。小车需要左右移动来保持杆子竖直。如果杆子倾斜的角度大于15°,那么游戏结束。小车也不能移动出一个范围(中间到两边各2.4个单位长度)。

环境

  • Python 3.6
  • Tensorflow-gpu 1.1.0
  • Keras 2.1.1
  • Gym 0.10.8
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()

你可能感兴趣的:(人工智能,神经网络,强化学习)