響站怎么建設(shè)網(wǎng)站營(yíng)業(yè)推廣是什么
大家好,今天和各位講解一下深度強(qiáng)化學(xué)習(xí)中的基礎(chǔ)模型 DQN,配合 OpenAI 的 gym 環(huán)境,訓(xùn)練模型完成一個(gè)小游戲,完整代碼可以從我的 GitHub 中獲得:
https://github.com/LiSir-HIT/Reinforcement-Learning/tree/main/Model
1. 算法原理
1.1 基本原理
DQN(Deep Q Network)算法由 DeepMind 團(tuán)隊(duì)提出,是深度神經(jīng)網(wǎng)絡(luò)和 Q-Learning 算法相結(jié)合的一種基于價(jià)值的深度強(qiáng)化學(xué)習(xí)算法。
Q-Learning 算法構(gòu)建了一個(gè)狀態(tài)-動(dòng)作值的 Q 表,其維度為 (s,a),其中 s 是狀態(tài)的數(shù)量,a 是動(dòng)作的數(shù)量,根本上是 Q 表將狀態(tài)和動(dòng)作映射到 Q 值。此算法適用于狀態(tài)數(shù)量能夠計(jì)算的場(chǎng)景。但是在實(shí)際場(chǎng)景中,狀態(tài)的數(shù)量可能很大,這使得構(gòu)建 Q 表難以解決。為破除這一限制,我們使用 Q 函數(shù)來(lái)代替 Q 表的作用,后者將狀態(tài)和動(dòng)作映射到 Q 值的結(jié)果相同。
由于神經(jīng)網(wǎng)絡(luò)擅長(zhǎng)對(duì)復(fù)雜函數(shù)進(jìn)行建模,因此我們用其當(dāng)作函數(shù)近似器來(lái)估計(jì)此 Q 函數(shù),這就是 Deep Q Networks。此網(wǎng)絡(luò)將狀態(tài)映射到可從該狀態(tài)執(zhí)行的所有動(dòng)作的 Q 值。即只要輸入一個(gè)狀態(tài),網(wǎng)絡(luò)就會(huì)輸出當(dāng)前可執(zhí)行的所有動(dòng)作分別對(duì)應(yīng)的 Q 值。如下圖所示,它學(xué)習(xí)網(wǎng)絡(luò)的權(quán)重,以此輸出最佳 Q 值。
1.2 模型結(jié)構(gòu)
DQN 體系結(jié)構(gòu)主要包含:Q 網(wǎng)絡(luò)、目標(biāo)網(wǎng)絡(luò),以及經(jīng)驗(yàn)回放組件。.Q 網(wǎng)絡(luò)是經(jīng)過(guò)訓(xùn)練以生成最佳狀態(tài)-動(dòng)作值的 agent。經(jīng)驗(yàn)回放單元的作用是與環(huán)境交互,生成數(shù)據(jù)以訓(xùn)練 Q 網(wǎng)絡(luò)。目標(biāo)網(wǎng)絡(luò)與 Q 網(wǎng)絡(luò)在初始時(shí)是完全相同的。DQN 工作流程圖如下
1.2.1? 經(jīng)驗(yàn)回放
經(jīng)驗(yàn)回放從當(dāng)前狀態(tài)中以貪婪策略??選擇一個(gè)動(dòng)作,執(zhí)行后從環(huán)境中獲得獎(jiǎng)勵(lì)和下一步的狀態(tài),如下圖所示。
然后將此觀測(cè)值另存為用于訓(xùn)練數(shù)據(jù)的樣本,如下圖所示。
與 Q Learning 算法不同,經(jīng)驗(yàn)回放組件的存在有其必須性。神經(jīng)網(wǎng)絡(luò)通常接受一批數(shù)據(jù),如果我們用單個(gè)樣本去訓(xùn)練它,每個(gè)樣本和相應(yīng)的梯度將具有很大的方差,并且會(huì)導(dǎo)致網(wǎng)絡(luò)權(quán)重永遠(yuǎn)不會(huì)收斂。
當(dāng)我們訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí),最好的做法是在隨機(jī)打亂的訓(xùn)練數(shù)據(jù)中選擇一批樣本。這確保了訓(xùn)練數(shù)據(jù)有足夠的多樣性,使網(wǎng)絡(luò)能夠?qū)W習(xí)有意義的權(quán)重,這些權(quán)重可以很好地泛化并且可以處理一系列數(shù)據(jù)值。如果我們以順序動(dòng)作傳遞一批數(shù)據(jù),則不會(huì)達(dá)到此效果。
所以可得出結(jié)論:順序操作彼此高度相關(guān),并且不會(huì)像網(wǎng)絡(luò)所希望的那樣隨機(jī)洗牌。這導(dǎo)致了一個(gè) “災(zāi)難性遺忘” 的問(wèn)題,網(wǎng)絡(luò)忘記了它不久前學(xué)到的東西。
以上是引入經(jīng)驗(yàn)回放組件的原因。智能體在內(nèi)存容量范圍內(nèi)從一開(kāi)始就執(zhí)行的所有動(dòng)作和觀察都將被存儲(chǔ)。然后從此存儲(chǔ)器中隨機(jī)選擇一批樣本。這確保了批次是經(jīng)過(guò)打亂,并且包含來(lái)自舊樣品和較新樣品的足夠多樣性,這樣能保證訓(xùn)練過(guò)的網(wǎng)絡(luò)具有能處理所有場(chǎng)景的權(quán)重。
# --------------------------------------- #
# 經(jīng)驗(yàn)回放池
# --------------------------------------- #class ReplayBuffer():def __init__(self, capacity):# 創(chuàng)建一個(gè)先進(jìn)先出的隊(duì)列,最大長(zhǎng)度為capacity,保證經(jīng)驗(yàn)池的樣本量不變self.buffer = collections.deque(maxlen=capacity)# 將數(shù)據(jù)以元組形式添加進(jìn)經(jīng)驗(yàn)池def add(self, state, action, reward, next_state, done):self.buffer.append((state, action, reward, next_state, done))# 隨機(jī)采樣batch_size行數(shù)據(jù)def sample(self, batch_size):transitions = random.sample(self.buffer, batch_size) # list, len=32# *transitions代表取出列表中的值,即32項(xiàng)state, action, reward, next_state, done = zip(*transitions)return np.array(state), action, reward, np.array(next_state), done# 目前隊(duì)列長(zhǎng)度def size(self):return len(self.buffer)
1.2.2 Q 網(wǎng)絡(luò)預(yù)測(cè) Q 值
所有之前的經(jīng)驗(yàn)回放都將保存為訓(xùn)練數(shù)據(jù)?,F(xiàn)在從此訓(xùn)練數(shù)據(jù)中隨機(jī)抽取一批樣本,以便它包含較舊樣本和較新樣本的混合。隨后將這批訓(xùn)練數(shù)據(jù)輸入到兩個(gè)網(wǎng)絡(luò)。Q 網(wǎng)絡(luò)從每個(gè)數(shù)據(jù)樣本中獲取當(dāng)前狀態(tài)和操作,并預(yù)測(cè)該特定操作的 Q 值,這是“預(yù)測(cè) Q 值”。如下圖所示。
1.2.3 目標(biāo)網(wǎng)絡(luò)預(yù)測(cè)目標(biāo) Q 值
目標(biāo)網(wǎng)絡(luò)從每個(gè)數(shù)據(jù)樣本中獲取下一個(gè)狀態(tài),并可以從該狀態(tài)執(zhí)行的所有操作中預(yù)測(cè)最佳 Q 值,這是“目標(biāo) Q 值”。如下圖所示。
DQN 同時(shí)用到兩個(gè)結(jié)構(gòu)相同參數(shù)不同的神經(jīng)網(wǎng)絡(luò),區(qū)別是一個(gè)用于訓(xùn)練,另一個(gè)不會(huì)在短期內(nèi)得到訓(xùn)練,這樣設(shè)置是從考慮實(shí)際效果出發(fā)的必然需求。
如果構(gòu)建具有單個(gè) Q 網(wǎng)絡(luò)且不存在目標(biāo)網(wǎng)絡(luò)的 DQN,假設(shè)此網(wǎng)絡(luò)應(yīng)該如下工作:通過(guò) Q 網(wǎng)絡(luò)執(zhí)行兩次傳遞,首先輸出 “預(yù)測(cè) Q 值”,然后輸出 “目標(biāo) Q 值”。這可能會(huì)產(chǎn)生一個(gè)潛在的問(wèn)題:Q 網(wǎng)絡(luò)的權(quán)重在每個(gè)時(shí)間步長(zhǎng)都會(huì)更新,從而改進(jìn)了對(duì)“預(yù)測(cè) Q 值”的預(yù)測(cè)。但是,由于網(wǎng)絡(luò)及其權(quán)重相同,因此它也改變了我們預(yù)測(cè)的“目標(biāo) Q 值”的方向。它們不會(huì)保持穩(wěn)定,在每次更新后可能會(huì)波動(dòng),類似一直追逐一個(gè)移動(dòng)著的目標(biāo)。
通過(guò)采用第二個(gè)未經(jīng)訓(xùn)練的網(wǎng)絡(luò),可以確保 “目標(biāo) Q 值” 至少在短時(shí)間內(nèi)保持穩(wěn)定。但這些“目標(biāo) Q 值”畢竟只是預(yù)測(cè)值,這是為改善它們的數(shù)值做出的妥協(xié)。所以在經(jīng)過(guò)預(yù)先配置的時(shí)間步長(zhǎng)后,需將 Q 網(wǎng)絡(luò)中更新的權(quán)重復(fù)制到目標(biāo)網(wǎng)絡(luò)。
可以得出,使用目標(biāo)網(wǎng)絡(luò)可以帶來(lái)更穩(wěn)定的訓(xùn)練。
1.2.2 和 1.2.3 代碼對(duì)應(yīng)如下:
# -------------------------------------- #
# 構(gòu)造深度學(xué)習(xí)網(wǎng)絡(luò),輸入狀態(tài)s,得到各個(gè)動(dòng)作的reward
# -------------------------------------- #class Net(nn.Module):# 構(gòu)造只有一個(gè)隱含層的網(wǎng)絡(luò)def __init__(self, n_states, n_hidden, n_actions):super(Net, self).__init__()# [b,n_states]-->[b,n_hidden]self.fc1 = nn.Linear(n_states, n_hidden)# [b,n_hidden]-->[b,n_actions]self.fc2 = nn.Linear(n_hidden, n_actions)# 前傳def forward(self, x): # [b,n_states]x = self.fc1(x)x = self.fc2(x)return x# -------------------------------------- #
# 構(gòu)造深度強(qiáng)化學(xué)習(xí)模型
# -------------------------------------- #class DQN:#(1)初始化def __init__(self, n_states, n_hidden, n_actions,learning_rate, gamma, epsilon,target_update, device):# 屬性分配self.n_states = n_states # 狀態(tài)的特征數(shù)self.n_hidden = n_hidden # 隱含層個(gè)數(shù)self.n_actions = n_actions # 動(dòng)作數(shù)self.learning_rate = learning_rate # 訓(xùn)練時(shí)的學(xué)習(xí)率self.gamma = gamma # 折扣因子,對(duì)下一狀態(tài)的回報(bào)的縮放self.epsilon = epsilon # 貪婪策略,有1-epsilon的概率探索self.target_update = target_update # 目標(biāo)網(wǎng)絡(luò)的參數(shù)的更新頻率self.device = device # 在GPU計(jì)算# 計(jì)數(shù)器,記錄迭代次數(shù)self.count = 0# 構(gòu)建2個(gè)神經(jīng)網(wǎng)絡(luò),相同的結(jié)構(gòu),不同的參數(shù)# 實(shí)例化訓(xùn)練網(wǎng)絡(luò) [b,4]-->[b,2] 輸出動(dòng)作對(duì)應(yīng)的獎(jiǎng)勵(lì)self.q_net = Net(self.n_states, self.n_hidden, self.n_actions)# 實(shí)例化目標(biāo)網(wǎng)絡(luò)self.target_q_net = Net(self.n_states, self.n_hidden, self.n_actions)# 優(yōu)化器,更新訓(xùn)練網(wǎng)絡(luò)的參數(shù)self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=self.learning_rate)#(3)網(wǎng)絡(luò)訓(xùn)練def update(self, transition_dict): # 傳入經(jīng)驗(yàn)池中的batch個(gè)樣本# 獲取當(dāng)前時(shí)刻的狀態(tài) array_shape=[b,4]states = torch.tensor(transition_dict['states'], dtype=torch.float)# 獲取當(dāng)前時(shí)刻采取的動(dòng)作 tuple_shape=[b],維度擴(kuò)充 [b,1]actions = torch.tensor(transition_dict['actions']).view(-1,1)# 當(dāng)前狀態(tài)下采取動(dòng)作后得到的獎(jiǎng)勵(lì) tuple=[b],維度擴(kuò)充 [b,1]rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1,1)# 下一時(shí)刻的狀態(tài) array_shape=[b,4]next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float)# 是否到達(dá)目標(biāo) tuple_shape=[b],維度變換[b,1]dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1,1)# 輸入當(dāng)前狀態(tài),得到采取各運(yùn)動(dòng)得到的獎(jiǎng)勵(lì) [b,4]==>[b,2]==>[b,1]# 根據(jù)actions索引在訓(xùn)練網(wǎng)絡(luò)的輸出的第1維度上獲取對(duì)應(yīng)索引的q值(state_value)q_values = self.q_net(states).gather(1, actions) # [b,1]# 下一時(shí)刻的狀態(tài)[b,4]-->目標(biāo)網(wǎng)絡(luò)輸出下一時(shí)刻對(duì)應(yīng)的動(dòng)作q值[b,2]--># 選出下個(gè)狀態(tài)采取的動(dòng)作中最大的q值[b]-->維度調(diào)整[b,1]max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1,1)# 目標(biāo)網(wǎng)絡(luò)輸出的當(dāng)前狀態(tài)的q(state_value):即時(shí)獎(jiǎng)勵(lì)+折扣因子*下個(gè)時(shí)刻的最大回報(bào)q_targets = rewards + self.gamma * max_next_q_values * (1-dones)# 目標(biāo)網(wǎng)絡(luò)和訓(xùn)練網(wǎng)絡(luò)之間的均方誤差損失dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))# PyTorch中默認(rèn)梯度會(huì)累積,這里需要顯式將梯度置為0self.optimizer.zero_grad()# 反向傳播參數(shù)更新dqn_loss.backward()# 對(duì)訓(xùn)練網(wǎng)絡(luò)更新self.optimizer.step()# 在一段時(shí)間后更新目標(biāo)網(wǎng)絡(luò)的參數(shù)if self.count % self.target_update == 0:# 將目標(biāo)網(wǎng)絡(luò)的參數(shù)替換成訓(xùn)練網(wǎng)絡(luò)的參數(shù)self.target_q_net.load_state_dict(self.q_net.state_dict())self.count += 1
DQN 模型偽代碼:
2. 實(shí)例演示
接下來(lái)我們用 GYM 庫(kù)中的車桿穩(wěn)定小游戲來(lái)驗(yàn)證一下我們構(gòu)建好的 DQN 模型,導(dǎo)入最基本的庫(kù),設(shè)置參數(shù)。有關(guān) GYM 強(qiáng)化學(xué)習(xí)環(huán)境的內(nèi)容可以查看官方文檔:
https://www.gymlibrary.dev/#
環(huán)境的狀態(tài) state 包含四個(gè):位置、速度、角度、角速度;動(dòng)作 action 包含 2 個(gè):小車左移和右移;目的是保證桿子豎直。環(huán)境交互與模型訓(xùn)練如下:
import gym
from RL_DQN import DQN, ReplayBuffer
import torch
from tqdm import tqdm
import matplotlib.pyplot as plt# GPU運(yùn)算
device = torch.device("cuda") if torch.cuda.is_available() \else torch.device("cpu")# ------------------------------- #
# 全局變量
# ------------------------------- #capacity = 500 # 經(jīng)驗(yàn)池容量
lr = 2e-3 # 學(xué)習(xí)率
gamma = 0.9 # 折扣因子
epsilon = 0.9 # 貪心系數(shù)
target_update = 200 # 目標(biāo)網(wǎng)絡(luò)的參數(shù)的更新頻率
batch_size = 32
n_hidden = 128 # 隱含層神經(jīng)元個(gè)數(shù)
min_size = 200 # 經(jīng)驗(yàn)池超過(guò)200后再訓(xùn)練
return_list = [] # 記錄每個(gè)回合的回報(bào)# 加載環(huán)境
env = gym.make("CartPole-v1", render_mode="human")
n_states = env.observation_space.shape[0] # 4
n_actions = env.action_space.n # 2# 實(shí)例化經(jīng)驗(yàn)池
replay_buffer = ReplayBuffer(capacity)
# 實(shí)例化DQN
agent = DQN(n_states=n_states,n_hidden=n_hidden,n_actions=n_actions,learning_rate=lr,gamma=gamma,epsilon=epsilon,target_update=target_update,device=device,)# 訓(xùn)練模型
for i in range(500): # 100回合# 每個(gè)回合開(kāi)始前重置環(huán)境state = env.reset()[0] # len=4# 記錄每個(gè)回合的回報(bào)episode_return = 0done = False# 打印訓(xùn)練進(jìn)度,一共10回合with tqdm(total=10, desc='Iteration %d' % i) as pbar:while True:# 獲取當(dāng)前狀態(tài)下需要采取的動(dòng)作action = agent.take_action(state)# 更新環(huán)境next_state, reward, done, _, _ = env.step(action)# 添加經(jīng)驗(yàn)池replay_buffer.add(state, action, reward, next_state, done)# 更新當(dāng)前狀態(tài)state = next_state# 更新回合回報(bào)episode_return += reward# 當(dāng)經(jīng)驗(yàn)池超過(guò)一定數(shù)量后,訓(xùn)練網(wǎng)絡(luò)if replay_buffer.size() > min_size:# 從經(jīng)驗(yàn)池中隨機(jī)抽樣作為訓(xùn)練集s, a, r, ns, d = replay_buffer.sample(batch_size)# 構(gòu)造訓(xùn)練集transition_dict = {'states': s,'actions': a,'next_states': ns,'rewards': r,'dones': d,}# 網(wǎng)絡(luò)更新agent.update(transition_dict)# 找到目標(biāo)就結(jié)束if done: break# 記錄每個(gè)回合的回報(bào)return_list.append(episode_return)# 更新進(jìn)度條信息pbar.set_postfix({'return': '%.3f' % return_list[-1]})pbar.update(1)# 繪圖
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('DQN Returns')
plt.show()
我簡(jiǎn)單訓(xùn)練了100輪,每回合的回報(bào) returns 繪圖如下。若各位發(fā)現(xiàn)代碼有誤,請(qǐng)及時(shí)反饋。