從零開始強化學(xué)習(xí)(七)——DDPG

DDPG是google DeepMind團隊提出的一種用于輸出確定性動作的算法,它解決了Actor-Critic神經(jīng)網(wǎng)絡(luò)每次參數(shù)更新前后都存在相關(guān)性,導(dǎo)致神經(jīng)網(wǎng)絡(luò)只能片面的看待問題這一缺點。同時也解決了DQN不能用于連續(xù)性動作的缺點

1. DDPG簡介

Deep Deterministic Policy Gradient(DDPG)即深度確定性策略梯度算法,是一種可以解決連續(xù)性控制問題的方法,屬于model-free,off-policy,policy-based的方法

DDPG可以拆開來看,Deep是說明需要神經(jīng)網(wǎng)絡(luò);Deterministic的意思就是最終確定地只輸出一個動作。Policy Gradient是策略梯度算法。DDPG可以看成是DQN的擴展版,不同的是,以往的DQN在最終輸出的是一個動作向量,對于DDPG是最終確定地只輸出一個動作。而且,DDPG讓DQN可以擴展到連續(xù)的動作空間

提出DDPG是為了讓DQN可以擴展到連續(xù)的動作空間,比如車速、角度和電壓這種的連續(xù)值。

  • DDPG直接在DQN基礎(chǔ)上加了一個策略網(wǎng)絡(luò)來直接輸出動作值,所以DDPG需要一邊學(xué)習(xí)Q網(wǎng)絡(luò),一邊學(xué)習(xí)策略網(wǎng)絡(luò)
  • Q網(wǎng)絡(luò)的參數(shù)用w來表示。策略網(wǎng)絡(luò)的參數(shù)用\theta來表示
  • 這樣的結(jié)構(gòu)為Actor-Critic的結(jié)構(gòu)
  • 類似于DQN

    • DQN的最佳策略是想要學(xué)出一個很好的Q網(wǎng)絡(luò),學(xué)好這個網(wǎng)絡(luò)之后,希望選取的那個動作使Q值最大
    • DDPG的目的也是為了求解讓Q值最大的那個action
    • Actor只是為了迎合評委的打分而已,所以用來優(yōu)化策略網(wǎng)絡(luò)的梯度就是要最大化這個Q值,所以構(gòu)造的loss函數(shù)就是讓Q取一個負號
    • 實現(xiàn)上把loss函數(shù)投入優(yōu)化器中,它就會自動最小化loss,也就是最大化Q
  • 除了策略網(wǎng)絡(luò)要做優(yōu)化,DDPG還有一個Q網(wǎng)絡(luò)也要優(yōu)化

    • 評委在一步一步的學(xué)習(xí)當中,慢慢地去給出準確的打分。
    • 優(yōu)化Q網(wǎng)絡(luò)的方法其實跟DQN優(yōu)化Q網(wǎng)絡(luò)的方法是一樣的,用真實的reward和下一步的QQ'來去擬合未來的收益Q_target
    • 讓Q網(wǎng)絡(luò)的輸出去逼近這個Q_target
      • 所以構(gòu)造的lossfunction就是直接求這兩個值的均方誤差(MeanSquaredError,MSE)
      • 構(gòu)造好loss后,讓優(yōu)化器自動去最小化loss就好了

策略網(wǎng)絡(luò)的loss function是一個復(fù)合函數(shù),把a = \mu_\theta(s)代進去,最終策略網(wǎng)絡(luò)要優(yōu)化的是策略網(wǎng)絡(luò)的參數(shù)\theta。Q網(wǎng)絡(luò)要優(yōu)化的是Q_w(s,a)和Q_target之間的均方誤差

但是Q網(wǎng)絡(luò)的優(yōu)化存在一個和DQN一模一樣的問題就是它后面的Q_target是不穩(wěn)定的。此外后面的Q_{\bar{w}}\left(s^{\prime}, a^{\prime}\right)也是不穩(wěn)定的,因為Q_{\bar{w}}\left(s^{\prime}, a^{\prime}\right)也是一個預(yù)估值

為了穩(wěn)定這個Q_target,DDPG分別給Q網(wǎng)絡(luò)和策略網(wǎng)絡(luò)都搭建了target network:

  • target_Q網(wǎng)絡(luò)就為了來計算Q_target里面的Q_{\bar{w}}\left(s^{\prime},a^{\prime}\right)
  • Q_{\bar{w}}\left(s^{\prime},a^{\prime}\right)里面的需要的next action a′就是通過target_P網(wǎng)絡(luò)來去輸出,即a^{\prime}=\mu_{\bar{\theta}}\left(s^{\prime}\right)
  • 為了區(qū)分前面的Q網(wǎng)絡(luò)和策略網(wǎng)絡(luò)以及后面的target_Q網(wǎng)絡(luò)和target_P策略網(wǎng)絡(luò),前面的網(wǎng)絡(luò)的參數(shù)是w,后面的網(wǎng)絡(luò)的參數(shù)是\bar{w}
  • DDPG有四個網(wǎng)絡(luò),策略網(wǎng)絡(luò)的target網(wǎng)絡(luò)和Q網(wǎng)絡(luò)的target網(wǎng)絡(luò),它是為了讓計算Q_target的時候能夠更穩(wěn)定一點,因為這兩個網(wǎng)絡(luò)也是固定一段時間的參數(shù)之后再跟評估網(wǎng)絡(luò)同步一下最新的參數(shù)

這里面訓(xùn)練需要用到的數(shù)據(jù)就是s,a,r,s′,只需要用到這四個數(shù)據(jù)。我們就用Replay Memory把這些數(shù)據(jù)存起來,然后再sample進來訓(xùn)練。這個經(jīng)驗回放的技巧跟DQN是一模一樣的。因為DDPG使用了經(jīng)驗回放這個技巧,所以DDPG是一個off-policy的算法

2. Exploration vs. Exploitation

DDPG通過off-policy的方式來訓(xùn)練一個確定性策略。因為策略是確定的,如果agent使用同策略來探索,在一開始的時候,很可能不會嘗試足夠多的action來找到有用的學(xué)習(xí)信號。為了讓DDPG的策略更好地探索,在訓(xùn)練的時候action加了噪音。DDPG的原作者推薦使用時間相關(guān)的OUnoise,但最近的結(jié)果表明不相關(guān)的、均值為0的Gaussian noise的效果非常好,由于后者更簡單,因此更喜歡使用它。為了便于獲得更高質(zhì)量的訓(xùn)練數(shù)據(jù),可以在訓(xùn)練過程中把噪聲變小

在測試的時候,為了查看策略利用它學(xué)到的東西的表現(xiàn),不會在action中加噪音

雖然DDPG表現(xiàn)很好,但它在超參數(shù)和其他類型的調(diào)整方面經(jīng)常很敏感。DDPG常見的問題是已經(jīng)學(xué)習(xí)好的Q函數(shù)開始顯著地高估Q值,然后導(dǎo)致策略被破壞了,因為它利用了Q函數(shù)中的誤差??梢阅脤嶋H的Q值跟這個Q-network輸出的Q值進行對比。實際的Q值可以用MC來算。根據(jù)當前的policy采樣1000條軌跡,得到G后取平均,得到實際的Q值

雙延遲深度確定性策略梯度(Twin Delayed DDPG,簡稱 TD3)通過引入三個關(guān)鍵技巧來解決這個問題:

  • 截斷的雙Q學(xué)習(xí)(Clipped Dobule Q-learning): TD3學(xué)習(xí)兩個Q-function(因此名字中有twin)。TD3通過最小化均方誤差來同時學(xué)習(xí)兩個Q-function:Q_{\phi_1}Q_{\phi_2}。兩個Q-function都使用一個目標,兩個Q-function中給出較小的值會被作為如下的Q-target:
    y\left(r, s^{\prime}, d\right)=r+\gamma(1-d) \min _{i=1,2} Q_{\phi_{i, t a r g}}\left(s^{\prime}, a_{T D 3}\left(s^{\prime}\right)\right)\tag{1}

  • 延遲的策略更新(“Delayed” Policy Updates):相關(guān)實驗結(jié)果表明,同步訓(xùn)練動作網(wǎng)絡(luò)和評價網(wǎng)絡(luò),卻不使用目標網(wǎng)絡(luò),會導(dǎo)致訓(xùn)練過程不穩(wěn)定;但是僅固定動作網(wǎng)絡(luò)時,評價網(wǎng)絡(luò)往往能夠收斂到正確的結(jié)果。因此TD3算法以較低的頻率更新動作網(wǎng)絡(luò),較高頻率更新評價網(wǎng)絡(luò),通常每更新兩次評價網(wǎng)絡(luò)就更新一次策略

  • 目標策略平滑(Target Policy smoothing): TD3引入了smoothing的思想,TD3在目標動作中加入噪音,通過平滑Q沿動作的變化,使策略更難利用Q函數(shù)的誤差

這三個技巧加在一起,使得性能相比基線DDPG有了大幅的提升

目標策略平滑化的工作原理如下:
a_{T D 3}\left(s^{\prime}\right)=\operatorname{clip}\left(\mu_{\theta, t a r g}\left(s^{\prime}\right)+\operatorname{clip}(\epsilon,-c, c), a_{\text {low }}, a_{\text {high }}\right)\tag{2}
其中\epsilon本質(zhì)上是一個噪聲,是從正態(tài)分布中取樣得到的,即\epsilon \sim N(0,\sigma),目標策略平滑化是一種正則化方法

3. 算法流程

偽代碼如下:

  1. 初始化Actor和Critic以及其各自的目標網(wǎng)絡(luò)共4個網(wǎng)絡(luò)以及經(jīng)驗池replay buffer R
  2. 在Actor網(wǎng)絡(luò)輸出動作時,DDPG通過添加隨機噪聲的方式實現(xiàn)exploration,可以讓智能體更好的探索潛在的最優(yōu)策略,之后是采取經(jīng)驗回放的技巧。把智能體與環(huán)境交互的數(shù)據(jù)(s_t,a_t,r_t,s_{t+1})存儲到R。隨后每次訓(xùn)練從中隨機采樣一個minibatch
  3. 在參數(shù)更新上,先利用Critic的目標網(wǎng)絡(luò)Q'來計算目標值y_i,利用y_i與當前Q值的均方誤差構(gòu)造損失函數(shù),進行梯度更新。對于Actor的策略網(wǎng)絡(luò),其實就是把Actor的確定性動作函數(shù)代進Q-function的a,然后求梯度,最后是更新目標網(wǎng)絡(luò)

4. 總結(jié)

簡單來說DQN+Actor-Critic=>Deep Deterministic Policy Gradient(DDPG)。實際上DDPG其實更接近DQN,只是采用了類似Actor-Critic的結(jié)構(gòu)。DDPG吸收了Actor-Critic中策略梯度單步更新的優(yōu)點,同時還吸收了DQN對Q值估計的技巧。DDPG 最大的優(yōu)勢就是能夠在連續(xù)動作上更有效地學(xué)習(xí)

5. 代碼

代碼主要看DDPG算法主要幾個模塊:

5.1 Actor

Actor作用是接收狀態(tài)描述,輸出一個action,由于DDPG中的動作空間要求是連續(xù)的,所以使用了一個tanh

class Actor(nn.Module):
    def __init__(self, n_obs, n_actions, hidden_size, init_w=3e-3):
        super(Actor, self).__init__()  
        self.linear1 = nn.Linear(n_obs, hidden_size)
        self.linear2 = nn.Linear(hidden_size, hidden_size)
        self.linear3 = nn.Linear(hidden_size, n_actions)
        self.linear3.weight.data.uniform_(-init_w, init_w)
    self.linear3.bias.data.uniform_(-init_w, init_w)
def forward(self, x):
    x = F.relu(self.linear1(x))
    x = F.relu(self.linear2(x))
    x = F.tanh(self.linear3(x))
    return x

實現(xiàn)方面,就是用了幾個全連接層來設(shè)計的網(wǎng)絡(luò),輸出的結(jié)果是一個連續(xù)的值

5.2 Critic

Critic批評者,在DDPG中接受來自Actor的一個Action值和當前的狀態(tài),輸出的是當前狀態(tài)下,采用Action動作以后得到的關(guān)于Q的期望

class Critic(nn.Module):
    def __init__(self, n_obs, n_actions, hidden_size, init_w=3e-3):
        super(Critic, self).__init__()
        self.linear1 = nn.Linear(n_obs + n_actions, hidden_size)
        self.linear2 = nn.Linear(hidden_size, hidden_size)
        self.linear3 = nn.Linear(hidden_size, 1)
        # 隨機初始化為較小的值
        self.linear3.weight.data.uniform_(-init_w, init_w)
        self.linear3.bias.data.uniform_(-init_w, init_w)
    def forward(self, state, action):
        # 按維數(shù)1拼接
        x = torch.cat([state, action], 1)
        x = F.relu(self.linear1(x))
        x = F.relu(self.linear2(x))
        x = self.linear3(x)
        return x

5.3 Replay Buffer

Replay Buffer就是用來存儲一系列等待學(xué)習(xí)的SARS片段。

class ReplayBuffer:
    def __init__(self, capacity):
        self.capacity = capacity
        self.buffer = []
        self.position = 0
    def push(self, state, action, reward, next_state, done):
        if len(self.buffer) < self.capacity:
            self.buffer.append(None)
        self.buffer[self.position] = (state, action, reward, next_state, done)
        self.position = (self.position + 1) % self.capacity
    def sample(self, batch_size):
        batch = random.sample(self.buffer, batch_size)
        state_batch, action_batch, reward_batch, next_state_batch, done_batch = map(np.stack, zip(*batch))
        return state_batch, action_batch, reward_batch, next_state_batch, done_batch
    def __len__(self):
        return len(self.buffer)

可以設(shè)置Replay Buffer的容量,push函數(shù)是向buffer中添加一個SARS片段;sample代表從buffer中采樣batch size個片段

5.4 DDPG

DDPG用到了以上的所有對象,包括Critic、Target Critic、Actor、Target Actor、memory

init函數(shù)如下:

def __init__(self, n_states, n_actions, hidden_dim=30, device="cpu", critic_lr=1e-3,
                actor_lr=1e-4, gamma=0.99, soft_tau=1e-2, memory_capacity=100000, batch_size=128):
    self.device = device
    self.critic = Critic(n_states, n_actions, hidden_dim).to(device)
    self.actor = Actor(n_states, n_actions, hidden_dim).to(device)
    self.target_critic = Critic(n_states, n_actions, hidden_dim).to(device)
    self.target_actor = Actor(n_states, n_actions, hidden_dim).to(device)
    for target_param, param in zip(self.target_critic.parameters(), self.critic.parameters()):
        target_param.data.copy_(param.data)
    for target_param, param in zip(self.target_actor.parameters(), self.actor.parameters()):
        target_param.data.copy_(param.data)
    self.critic_optimizer = optim.Adam(
        self.critic.parameters(),  lr=critic_lr)
    self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=actor_lr)
    self.memory = ReplayBuffer(memory_capacity)
    self.batch_size = batch_size
    self.soft_tau = soft_tau
    self.gamma = gamma

其中核心的函數(shù)就是update函數(shù):

def update(self):
    if len(self.memory) < self.batch_size:
        return
    state, action, reward, next_state, done = self.memory.sample(
        self.batch_size)
    # 將所有變量轉(zhuǎn)為張量
    state = torch.FloatTensor(state).to(self.device)
    next_state = torch.FloatTensor(next_state).to(self.device)
    action = torch.FloatTensor(action).to(self.device)
    reward = torch.FloatTensor(reward).unsqueeze(1).to(self.device)
    done = torch.FloatTensor(np.float32(done)).unsqueeze(1).to(self.device)
    # 注意critic將(s_t,a)作為輸入
    policy_loss = self.critic(state, self.actor(state))
    
    policy_loss = -policy_loss.mean()

    next_action = self.target_actor(next_state)
    target_value = self.target_critic(next_state, next_action.detach())
    expected_value = reward + (1.0 - done) * self.gamma * target_value
    expected_value = torch.clamp(expected_value, -np.inf, np.inf)

    value = self.critic(state, action)
    value_loss = nn.MSELoss()(value, expected_value.detach())
    
    self.actor_optimizer.zero_grad()
    policy_loss.backward()
    self.actor_optimizer.step()

    self.critic_optimizer.zero_grad()
    value_loss.backward()
    self.critic_optimizer.step()
    for target_param, param in zip(self.target_critic.parameters(), self.critic.parameters()):
        target_param.data.copy_(
            target_param.data * (1.0 - self.soft_tau) +
            param.data * self.soft_tau
        )
    for target_param, param in zip(self.target_actor.parameters(), self.actor.parameters()):
        target_param.data.copy_(
            target_param.data * (1.0 - self.soft_tau) +
            param.data * self.soft_tau
        )

整體流程如下:

  • 從memory中采樣一個batch的數(shù)據(jù)
  • policy_loss = self.critic(state, self.actor(state))
    • 將state放到actor對象得到action
    • 將state,action放到critic對象得到policy loss
next_action = self.target_actor(next_state)
target_value = self.target_critic(next_state, next_action.detach())
  • 然后target actor和target critic也按照以上過程得到target value

  • 根據(jù)target value 計算expected value:
    r + γ Q r+\gamma Q\tag{3}

實現(xiàn)如下:

expected_value = reward + (1.0 - done) * self.gamma * target_value
expected_value = torch.clamp(expected_value, -np.inf, np.inf)

如果done為1,代表已經(jīng)結(jié)束了,也就不需要這個系數(shù)了。第二行對expected value進行了數(shù)值上的限制

  • 接下來計算根據(jù)數(shù)據(jù)集中action得到的value值

    value = self.critic(state, action)
    
  • 計算優(yōu)化Q網(wǎng)絡(luò)的loss, 采用的是MSEloss

    value_loss = nn.MSELoss()(value, expected_value.detach())
    
  • 對policy loss和value loss進行梯度回傳,更新訓(xùn)練參數(shù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容