《PaddlePaddle從入門到煉丹》七——強化學習

原文博客:Doi技術團隊
鏈接地址:https://blog.doiduoyi.com/authors/1584446358138
初心:記錄優秀的Doi技術團隊學習經歷

前言

本章介紹使用PaddlePaddle實現強化學習,通過自我學習,完成一個經典控制類的遊戲,相關遊戲介紹可以在Gym官網上了解。我們這次玩的是一個CartPole-v1遊戲,操作就是通過控制滑塊的左右移動,不讓豎着的柱子掉下來。利用強化學習的方法,不斷自我學習,通過在玩遊戲的過程中獲取到獎勵或者懲罰,學習到一個模型。在王者榮耀中的超強人機使用的AI技術也類似這樣。
在這裏插入圖片描述

PaddlePaddle程序

創建一個DQN.py的Python文件。導入項目所需的依賴庫,如果還沒安裝gym的話,可以通過命令pip3 install gym安裝。

import numpy as np
import paddle.fluid as fluid
import random
import gym
from collections import deque
from paddle.fluid.param_attr import ParamAttr

定義一個簡單的網絡,這個網絡只是由4個全連接層組成,併爲每個全連接層指定參數的名稱。指定參數的作用是爲了之後更新模型參數使用的,因爲之後會通過這個網絡生成兩個模型,而且沒有模型參數更新不一樣。

# 定義一個深度神經網絡,通過指定參數名稱,用於之後更新指定的網絡參數
def DQNetWork(ipt, variable_field):
    fc1 = fluid.layers.fc(input=ipt,
                          size=24,
                          act='relu',
                          param_attr=ParamAttr(name='{}_fc1'.format(variable_field)),
                          bias_attr=ParamAttr(name='{}_fc1_b'.format(variable_field)))
    fc2 = fluid.layers.fc(input=fc1,
                          size=24,
                          act='relu',
                          param_attr=ParamAttr(name='{}_fc2'.format(variable_field)),
                          bias_attr=ParamAttr(name='{}_fc2_b'.format(variable_field)))
    out = fluid.layers.fc(input=fc2,
                          size=2,
                          param_attr=ParamAttr(name='{}_fc3'.format(variable_field)),
                          bias_attr=ParamAttr(name='{}_fc3_b'.format(variable_field)))
    return out

定義一個更新參數的函數,這個函數是通過指定的參數名稱,通過修剪參數的方式完成模型更新。

# 定義更新參數程序
def _build_sync_target_network():
    # 獲取所有的參數
    vars = list(fluid.default_main_program().list_vars())
    # 把兩個網絡的參數分別過濾出來
    policy_vars = list(filter(lambda x: 'GRAD' not in x.name and 'policy' in x.name, vars))
    target_vars = list(filter(lambda x: 'GRAD' not in x.name and 'target' in x.name, vars))
    policy_vars.sort(key=lambda x: x.name)
    target_vars.sort(key=lambda x: x.name)

    # 從主程序中克隆一個程序用於更新參數
    sync_program = fluid.default_main_program().clone()
    with fluid.program_guard(sync_program):
        sync_ops = []
        for i, var in enumerate(policy_vars):
            sync_op = fluid.layers.assign(policy_vars[i], target_vars[i])
            sync_ops.append(sync_op)
    # 修剪第二個玩了個的參數,完成更新參數
    sync_program = sync_program._prune(sync_ops)
    return sync_program

定義5個數據輸出層,state_data是當前遊戲狀態的數據輸入層,action_data是對遊戲操作動作的數據輸入層,只有兩個動作0和1,reward_data是當前遊戲給出的獎勵的數據輸入層,next_state_data是遊戲下一個狀態的數據輸入層,done_data是遊戲是否結束的數據輸入層。

# 定義輸入數據
state_data = fluid.layers.data(name='state', shape=[4], dtype='float32')
action_data = fluid.layers.data(name='action', shape=[1], dtype='int64')
reward_data = fluid.layers.data(name='reward', shape=[], dtype='float32')
next_state_data = fluid.layers.data(name='next_state', shape=[4], dtype='float32')
done_data = fluid.layers.data(name='done', shape=[], dtype='float32') 

定義一些必要的訓練參數,比如epsilon-greedy 探索策略參數。

# 定義訓練的參數
batch_size = 32
num_episodes = 300
num_exploration_episodes = 100
max_len_episode = 1000
learning_rate = 1e-3
gamma = 1.0
initial_epsilon = 1.0
final_epsilon = 0.01

創建一個遊戲,通過指定遊戲的名稱CartPole-v1就可以獲取前言部分所說的遊戲。也可以創建其他更多的有些,具體可以參照官方的遊戲名稱。

# 實例化一個遊戲環境,參數爲遊戲名稱
env = gym.make("CartPole-v1")
replay_buffer = deque(maxlen=10000)

獲取第一個網絡模型,並指定參數名稱內包含policy字符串。

# 獲取網絡
state_model = DQNetWork(state_data, 'policy')

這裏從主程序中克隆一個預測程序,這個預測程序是之後預測遊戲的下一個動作的,也就是說它在操作遊戲。

# 克隆預測程序
predict_program = fluid.default_main_program().clone()

這裏定義損失函數,強化學習中的損失函數跟之後我們使用的損失函數有點不一樣。雖然最終還是使用平方差損失函數,但是輸入的不只是普通的輸入數據和標籤。

# 定義損失函數
action_onehot = fluid.layers.one_hot(action_data, 2)
action_value = fluid.layers.elementwise_mul(action_onehot, state_model)
pred_action_value = fluid.layers.reduce_sum(action_value, dim=1)

targetQ_predict_value = DQNetWork(next_state_data, 'target')
best_v = fluid.layers.reduce_max(targetQ_predict_value, dim=1)
best_v.stop_gradient = True
target = reward_data + gamma * best_v * (1.0 - done_data)

cost = fluid.layers.square_error_cost(pred_action_value, target)
avg_cost = fluid.layers.reduce_mean(cost)

這裏獲取一個更新參數的程序,用於之後執行更新參數。

# 獲取更新參數程序
_sync_program = _build_sync_target_network()

定義一個優化方法,這裏還是用AdamOptimizer,筆者也是比較喜歡使用這個優化方法。

# 定義優化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=learning_rate, epsilon=1e-3)
opt = optimizer.minimize(avg_cost)

開始創建執行器

# 創建執行器並進行初始化
place = fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
epsilon = initial_epsilon

這個循環有點大,不過因爲是一個整體,不好拆分出來,所以就一起介紹吧。

  • 在每次循環開始,就開始獲取遊戲的狀態,這個是遊戲結束之後再執行的。
  • 定義一個epsilon-greedy探索策略,這個策略是根據訓練的進度,開始選擇自動操作的動作或者是模型預測的動作的概率。
  • 接下來就是一局遊戲的的循環,在這裏可以顯示遊戲的界面
  • 下面就是通過使用epsilon-greedy探索策略,決定使用隨機生成動作,還是預測生成動作,使用隨機動作可以增加數據的多樣性,通過使用模型預測就是讓模型根據當前的遊戲狀態來預測下一步動作是怎麼纔是正確的,隨着模型的不斷訓練,這個預測也是越來越正確。
  • 然後更加隨機生成的動作,或者模型預測的動作,傳遞個遊戲,得到遊戲的相關輸出,比如遊戲的下一個狀態,遊戲的獎勵,遊戲是否結束等等。
  • 如果遊戲結束了,應當給遊戲一個負獎勵,懲罰它做出了一個錯誤的操作。
  • 然後把這些數據存儲起來,用於之後訓練使用。
  • 當存儲的數據大於或等於Batch size,就可以開始訓練。
update_num = 0
# 開始玩遊戲
for epsilon_id in range(num_episodes):
    # 初始化環境,獲得初始狀態
    state = env.reset()
    epsilon = max(initial_epsilon * (num_exploration_episodes - epsilon_id) /
                  num_exploration_episodes, final_epsilon)
    for t in range(max_len_episode):
        # 顯示遊戲界面
        # env.render()
        state = np.expand_dims(state, axis=0)
        # epsilon-greedy 探索策略
        if random.random() < epsilon:
            # 以 epsilon 的概率選擇隨機下一步動作
            action = env.action_space.sample()
        else:
            # 使用模型預測作爲結果下一步動作
            action = exe.run(predict_program,
                             feed={'state': state.astype('float32')},
                             fetch_list=[state_model])[0]
            action = np.squeeze(action, axis=0)
            action = np.argmax(action)

        # 讓遊戲執行動作,獲得執行完 動作的下一個狀態,動作的獎勵,遊戲是否已結束以及額外信息
        next_state, reward, done, info = env.step(action)

        # 如果遊戲結束,就進行懲罰
        reward = -10 if done else reward
        # 記錄遊戲輸出的結果,作爲之後訓練的數據
        replay_buffer.append((state, action, reward, next_state, done))
        state = next_state

        # 如果遊戲結束,就重新玩遊戲
        if done:
            print('Pass:%d, epsilon:%f, score:%d' % (epsilon_id, epsilon, t))
            break

        # 如果收集的數據大於Batch的大小,就開始訓練
        if len(replay_buffer) >= batch_size:
            batch_state, batch_action, batch_reward, batch_next_state, batch_done = \
                [np.array(a, np.float32) for a in zip(*random.sample(replay_buffer, batch_size))]

            # 更新參數
            if update_num % 200 == 0:
                exe.run(program=_sync_program)
            update_num += 1

            # 調整數據維度
            batch_action = np.expand_dims(batch_action, axis=-1)
            batch_next_state = np.expand_dims(batch_next_state, axis=1)

            # 執行訓練
            exe.run(program=fluid.default_main_program(),
                    feed={'state': batch_state,
                          'action': batch_action.astype('int64'),
                          'reward': batch_reward,
                          'next_state': batch_next_state,
                          'done': batch_done})

輸出訓練信息:

......
Pass:70, epsilon:0.300000, score:234
Pass:71, epsilon:0.290000, score:272
Pass:72, epsilon:0.280000, score:254
Pass:73, epsilon:0.270000, score:148
Pass:74, epsilon:0.260000, score:147
Pass:75, epsilon:0.250000, score:342
Pass:76, epsilon:0.240000, score:295
Pass:77, epsilon:0.230000, score:290
Pass:78, epsilon:0.220000, score:276
Pass:79, epsilon:0.210000, score:279
......

關於通過使用PaddlePaddle實現強化學習,並玩一個小遊戲就介紹完成了。強化學習還有很多好玩的地方,比如應用於機器人的避障等一些智能控制上。

同步到百度AI Studio平臺:http://aistudio.baidu.com/aistudio/projectdetail/31310
同步到科賽網K-Lab平臺:https://www.kesci.com/home/project/5c3eaac54223d9002bfef5ae
項目代碼GitHub地址:https://github.com/yeyupiaoling/LearnPaddle2/tree/master/note7

注意: 最新代碼以GitHub上的爲準


上一章:《PaddlePaddle從入門到煉丹》六——生成對抗網絡
下一章:《PaddlePaddle從入門到煉丹》八——模型的保存與使用

參考資料

  1. https://github.com/PaddlePaddle/models/blob/develop/fluid/DeepQNetwork/README_cn.md
  2. https://github.com/snowkylin/TensorFlow-cn
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章