1. 程式人生 > 其它 >強化學習DQN 入門小遊戲 最簡單的Pytorch程式碼

強化學習DQN 入門小遊戲 最簡單的Pytorch程式碼

技術標籤:AI強化學習深度學習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()

程式碼很簡練,慢慢讀都能懂,我解釋一下幾個重點。

  1. 整個流程是先採樣,將樣本存入經驗池self.exp_pool,當樣本足夠時,從經驗池中隨機選取100條樣本進行訓練,而後邊更新經驗池邊訓練。
  2. QNet就是我們學習的網路,狀態到動作的對映,根據輸入狀態建議採取的動作。
  3. self.explore探索值,很容易可以看明白它是一個概率,控制動作是隨機選取還是由網路推薦。探索可以讓我們發現新鮮樣本,但隨著訓練進行,我們見過的樣本越來越多,應該逐漸減少探索,也就是降低隨機動作的概率。
  4. R是每一局遊戲的得分,因為這個值在訓練中變化非常大,所以加了個avg滑動平均的操作,這樣可以更清晰地看出訓練效果。由於遊戲限制,每局遊戲最多到500分就會結束,所以我們設定當avg>400就開始顯示影象。
  5. 其實強化學習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,同學們看著給。

在這裡插入圖片描述