在之前的文章中,我們做了如下工作:
- 如何設計一個類flappy-bird小遊戲:【python實戰】使用pygame寫一個flappy-bird類小遊戲 | 設計思路+項目結構+代碼詳解|新手向
- DFS 算法是怎麼回事,我是怎麼應用於該小遊戲的:【深度優先搜索】一個實例+兩張動圖徹底理解DFS|DFS與BFS的區別|用DFS自動控制我們的小遊戲
- BFS 算法是怎麼回事,我是怎麼應用於該小遊戲的:【廣度優先搜索】一個實例+兩張動圖徹底理解BFS|思路+代碼詳解|用DFS自動控制我們的小遊戲
- 強化學習爲什麼有用?其基本原理:無需公式或代碼,用生活實例談談AI自動控制技術“強化學習”算法框架
- 構建一個簡單的卷積神經網絡,使用DRL框架tianshou匹配DQN算法
- 構造一個簡單的神經網絡,以DQN方式實現小遊戲的自動控制
構造一個輸入速度的神經網絡,實現 DQN
本文涉及的 .py
文件有:
DQN_train/gym_warpper.py
DQN_train/dqn_train3.py
DQN_train/dqn_render3.py
requirements
tianshou
pytorch > 1.40
gym
繼續訓練與測試
在本項目地址中,你可以使用如下文件對我訓練的模型進行測試,或者繼續訓練。
繼續訓練該模型
python DQN_train/dqn_train3.py
我已經訓練了 40 次(每次5個epoch),輸入上述命令,你將開始第 41 次訓練,如果不使用任務管理器強制停止,計算機將一直訓練下去,並自動保存每一代的權重。
查看效果
python DQN_train/dqn_render3.py 3
注意參數 3 ,輸入 3 代表使用訓練 3 次後的權重。
效果如圖:
我保留了該模型的所有歷史權重。你還可以輸入參數:1-40,查看歷代神經網絡的表現。如果你繼續訓練了模型,你可以輸入更大的參數,如 41 。
輸入 10 則代表使用訓練 10 次後的權重:
python DQN_train/dqn_render3.py 25
效果如圖:
輸入 30 則代表使用訓練 30 次後的權重:
python DQN_train/dqn_render3.py 30
效果如圖:
封裝交互環境
上一個模型的效果並不好,這個模型的表現卻很值得稱道。我對這個模型做出了什麼改進呢?
事件 | 獎勵 |
---|---|
動作後碰撞障礙物、牆壁 | -1 |
動作後無事發生 | 0.0001 |
動作後得分 | 1 |
在第一層滯留過久(超過500步) | -10 |
可以看出,我將動作後無事發生
的獎勵從 0.1 降低到了 -1 ,是爲了:
- 突出
動作後得分
這項的獎勵; - 如此,智能體第一次得分後,會很“欣喜地發現”上升一層的快樂遠遠大於在第一層苟命的快樂。
此外,如果智能體在第一層滯留過久
,也是會受到 -10 的懲罰的:
- 這是爲了告訴智能體,在第一層過久是不被鼓勵的;
- 因爲狀態是鏈式的,因此最後的懲罰會回溯分配到之前的“苟命”策略上。
封裝代碼在 gym_wrapper.py 中,使用類 AmazingBrickEnv3
。
強化學習機制與神經網絡的構建
上節中,我們將 2 幀的數據輸入到線性層中,效果並不理想。我進一步幫助機器提取了信息
,並且預處理了數據
:
- 不再將巨大的 2 幀數據輸入到網絡中;
- 取而代之的是,當前狀態的速度向量
(velx, vely)
; - 再加上
玩家xy座標
、左障礙物右上頂點xy座標
、右障礙物左上頂點xy座標
、4個障礙方塊的左上頂點的xy座標
(共14個數); - 如此,輸入層只有 16 個神經網即可,且每 1 幀做一次決策。
我還放慢了 epsilon (探索概率)的收斂速度,讓智能體更多地去探索動作,不侷限在局部最優解中。
此外,我對輸入數據進行了歸一化處理比如,玩家的座標 x, y 分別除以了屏幕的 寬、高。從結果和訓練所需的代數更少來看,我認爲這對於機器學習有極大的幫助。
線性神經網絡的構建
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(16, 128)
self.fc2 = nn.Linear(128, 256)
self.fc3 = nn.Linear(256, 128)
self.fc4 = nn.Linear(128, 3)
def forward(self, obs, state=None, info={}):
if not isinstance(obs, torch.Tensor):
obs = torch.tensor(obs, dtype=torch.float)
x = F.relu(self.fc1(obs))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
x = self.fc4(x)
return x, state
如上,共四層線性網絡。
記錄訓練的微型框架
爲了保存訓練好的權重,且在需要時可以暫停並繼續訓練,我新建了一個.json
文件用於保存訓練數據。
dqn2_path = osp.join(path, 'DQN_train/dqn_weights/')
if __name__ == '__main__':
try:
with open(dqn3_path + 'dqn3_log.json', 'r') as f:
jlist = json.load(f)
log_dict = jlist[-1]
round = log_dict['round']
policy.load_state_dict(torch.load(dqn3_path + 'dqn3round_' + str(int(round)) + '.pth'))
del jlist
except FileNotFoundError as identifier:
print('\n\nWe shall train a bright new net.\n')
# 第一次訓練時,新建一個 .json 文件
# 聲明一個列表
# 以後每次寫入 json 文件,向列表新增一個字典對象
with open(dqn3_path + 'dqn3_log.json', 'a+') as f:
f.write('[]')
round = 0
while True:
round += 1
print('\n\nround:{}\n\n'.format(round))
result = ts.trainer.offpolicy_trainer(
policy, train_collector, test_collector,
max_epoch=max_epoch, step_per_epoch=step_per_epoch,
collect_per_step=collect_per_step,
episode_per_test=30, batch_size=64,
# 如下,每新一輪訓練才更新 epsilon
train_fn=lambda e: policy.set_eps(0.1 / round),
test_fn=lambda e: policy.set_eps(0.05 / round), writer=None)
print(f'Finished training! Use {result["duration"]}')
torch.save(policy.state_dict(), dqn3_path + 'dqn3round_' + str(int(round)) + '.pth')
policy.load_state_dict(torch.load(dqn3_path + 'dqn3round_' + str(int(round)) + '.pth'))
log_dict = {}
log_dict['round'] = round
log_dict['last_train_time'] = datetime.datetime.now().strftime('%y-%m-%d %I:%M:%S %p %a')
log_dict['best_reward'] = result['best_reward']
with open(dqn3_path + 'dqn3_log.json', 'r') as f:
"""dqn3_log.json should be inited as []"""
jlist = json.load(f)
jlist.append(log_dict)
with open(dqn3_path + 'dqn3_log.json', 'w') as f:
json.dump(jlist, f)
del jlist
DQN
import os.path as osp
import sys
dirname = osp.dirname(__file__)
path = osp.join(dirname, '..')
sys.path.append(path)
from amazing_brick.game.wrapped_amazing_brick import GameState
from amazing_brick.game.amazing_brick_utils import CONST
from DQN_train.gym_wrapper import AmazingBrickEnv3
import tianshou as ts
import torch, numpy as np
from torch import nn
import torch.nn.functional as F
import json
import datetime
train_env = AmazingBrickEnv3()
test_env = AmazingBrickEnv3()
state_shape = 16
action_shape = 1
net = Net()
optim = torch.optim.Adam(net.parameters(), lr=1e-3)
'''args for rl'''
estimation_step = 3
max_epoch = 5
step_per_epoch = 300
collect_per_step = 50
policy = ts.policy.DQNPolicy(net, optim,
discount_factor=0.9, estimation_step=estimation_step,
use_target_network=True, target_update_freq=320)
train_collector = ts.data.Collector(policy, train_env, ts.data.ReplayBuffer(size=2000))
test_collector = ts.data.Collector(policy, test_env)
採用這種方式獲得了不錯的效果,在第 40 代訓練後(共 40 * 5 * 300 = 6000 個 step),智能體已經能走 10 層左右。
相信繼續的迭代會獲得更好的成績。
項目地址:https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL
本項目的說明文件到此結束。感謝你的閱讀,歡迎提交更好的方案與意見!