強化學習DQN 入門小遊戲 最簡單的Pytorch程式碼
本文目的是用最簡單的程式碼,展示DQN玩遊戲的效果,不涉及深度學習原理講解。
畢竟,入門如此艱難,唯一的動力不過是看個效果,裝個biu……
安裝OpenAI的遊戲庫gym
pip install gym
看一下執行效果
import gym
env = gym.make('CartPole-v1')
print('State shape:', env.observation_space.shape)
print('Number of actions:', env.action_space.n)
for _ in range(20):
observation = env.reset() # 初始狀態
for t in range(500):
env.render() # 顯示影象
action = env.action_space.sample() # 隨機選擇一個動作
observation, reward, done, info = env.step(action) # 狀態,回報,是否結束,資訊
print(observation, reward, done, info)
if done:
print("Episode finished after {} timesteps" .format(t + 1))
break
env.close()
這是個手推車平衡遊戲,我們可以採取兩個動作(action),向左推或向右推,保持杆子不倒的時間越長,分數越高。遊戲的每個時刻都有一個狀態(observation),這個遊戲中狀態是由4個數值描述的,我們其實不必瞭解這四個值的含義,規律反正是交給網路學習的,這是深度學習很爽的地方,寫出AlphaGo的程式設計師不需要懂圍棋規則。每採取一個動作,除了得到下一時刻的狀態,最重要的是得到一個回報(reward),在這個遊戲中,無論什麼動作,回報都是+1,意思是隻要活著,就給你加分,直到杆子倒下,遊戲結束。
程式碼
import random
import gym
import torch
from torch import nn, optim
class QNet(nn.Sequential):
def __init__(self):
super(QNet, self).__init__(
nn.Linear(4, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 2)
)
class Game:
def __init__(self, exp_pool_size, explore):
self.env = gym.make('CartPole-v1')
self.exp_pool = []
self.exp_pool_size = exp_pool_size
self.q_net = QNet()
self.explore = explore
self.loss_fn = nn.MSELoss()
self.opt = optim.Adam(self.q_net.parameters())
def __call__(self):
is_render = False
avg = 0
while True:
# 資料取樣
state = self.env.reset()
R = 0
while True:
if is_render:
self.env.render()
if len(self.exp_pool) >= self.exp_pool_size:
self.exp_pool.pop(0)
self.explore += 1e-7
if torch.rand(1) > self.explore:
action = self.env.action_space.sample()
else:
_state = torch.tensor(state, dtype=torch.float32)
Qs = self.q_net(_state[None, ...])
action = torch.argmax(Qs, 1)[0].item()
else:
action = self.env.action_space.sample()
next_state, reward, done, _ = self.env.step(action)
R += reward
self.exp_pool.append([state, reward, action, next_state, done])
state = next_state
if done:
avg = 0.95 * avg + 0.05 * R
print(avg, R)
if avg > 400:
is_render = True
break
# 訓練
if len(self.exp_pool) >= self.exp_pool_size:
exps = random.choices(self.exp_pool, k=100)
_state = torch.tensor([exp[0] for exp in exps]).float()
_reward = torch.tensor([[exp[1]] for exp in exps])
_action = torch.tensor([[exp[2]] for exp in exps])
_next_state = torch.tensor([exp[3] for exp in exps]).float()
_done = torch.tensor([[int(exp[4])] for exp in exps])
# 預測值
_Qs = self.q_net(_state)
_Q = torch.gather(_Qs, 1, _action)
# 目標值
_next_Qs = self.q_net(_next_state)
_max_Q = torch.max(_next_Qs, dim=1, keepdim=True)[0]
_target_Q = _reward + (1 - _done) * 0.9 * _max_Q
loss = self.loss_fn(_Q, _target_Q.detach())
self.opt.zero_grad()
loss.backward()
self.opt.step()
if __name__ == '__main__':
g = Game(10000, 0.9)
g()
程式碼很簡練,慢慢讀都能懂,我解釋一下幾個重點。
- 整個流程是先採樣,將樣本存入經驗池self.exp_pool,當樣本足夠時,從經驗池中隨機選取100條樣本進行訓練,而後邊更新經驗池邊訓練。
- QNet就是我們學習的網路,狀態到動作的對映,根據輸入狀態建議採取的動作。
- self.explore探索值,很容易可以看明白它是一個概率,控制動作是隨機選取還是由網路推薦。探索可以讓我們發現新鮮樣本,但隨著訓練進行,我們見過的樣本越來越多,應該逐漸減少探索,也就是降低隨機動作的概率。
- R是每一局遊戲的得分,因為這個值在訓練中變化非常大,所以加了個avg滑動平均的操作,這樣可以更清晰地看出訓練效果。由於遊戲限制,每局遊戲最多到500分就會結束,所以我們設定當avg>400就開始顯示影象。
- 其實強化學習DQN真正的精髓是在訓練中目標值的確定,所以這塊我們跳過~[666]
沒錯,就這點程式碼,訓練2分鐘,DQN就能學會玩這個平衡遊戲啦!
再附帶一個小車爬坡的小遊戲
遊戲名:MountainCar-v0。小車想到達最高峰,但其引擎強度不足以單程通過,所以要在兩個山坡間反覆橫跳,積蓄力量,一鳴驚人~
這個遊戲的區別是reward始終為-1,意思是隻有到達終點才有獎勵,其他打醬油行為都要扣分,拿到高分的辦法就是儘快到達終點。狀態有2個值:水平位置和速度。動作有3個值:左、右、不動。所以要把QNet的輸入和輸出維度改一下。
訓練難點在於:遊戲只持續200個動作,在隨機選擇動作的情況下,小車很難靠運氣到達終點,也就很少有成功的經驗,不容易學習。
next_state, reward, done, _ = self.env.step(action)
position, velocity = next_state
reward = (position + 0.5) ** 2
R += reward
於是我根據狀態把reward給改了,position + 0.5的原因是,起始最低點的水平座標值為-0.5,改完之後的reward含義就是:離最低點越遠獎勵越高,而且獎勵還是成平方增長。
經過這通騷操作,就可以訓練了,也只需要訓練幾分鐘。avg的閾值我給的30,同學們看著給。