使用 PyTorch 和 Gymnasium 的近端策略優化

Proximal Policy Optimization(PPO)是解決強化學習(RL)問題的首選算法之一。它由OpenAI的聯合創始人約翰·舒曼於2017年開發。

PPO在OpenAI廣泛應用於訓練模型以模擬類似人類行為。它改進了Trust Region Policy Optimization(TRPO)等早期方法,因為它是一種堅固且高效的算法而變得流行。

在本教程中,我們深入探討PPO。我們涵蓋了理論並演示了如何使用PyTorch實施它。

理解Proximal Policy Optimization(PPO)

傳統的監督式學習算法沿著最陡峭梯度的方向更新參數。如果此更新過大,將在後續互不相關的訓練示例中進行更正。

然而,強化學習中的訓練示例包括代理的行動和回報。因此,訓練示例彼此相關。代理探索環境以找出最優策略。因此,對梯度進行大幅更改可能導致策略陷入次優獎勵的不良區域。由於代理需要探索環境,大幅度的策略更改使訓練過程不穩定。

基於信賴區域的方法旨在避免這個問題,確保政策更新在信賴區域內。這個信賴區域是政策空間內的一個人工限制區域,允許更新的範圍。更新後的政策只能在舊政策的信賴區域內。確保政策更新是增量的,可以防止不穩定性。

信賴區域政策更新(TRPO)

信賴區域政策更新(TRPO)算法於2015年由約翰·舒爾曼(John Schulman)提出(他也於2017年提出PPO)。為了衡量舊政策和更新政策之間的差異,TRPO使用KL散度(Kullback-Leibler divergence)。KL散度用於衡量兩個概率分布之間的差異。TRPO被證明在實現信賴區域方面是有效的。

TRPO的問題在於與KL散度相關的計算複雜性。應用KL散度必須使用數值方法(如泰勒展開)擴展到二階。這是計算昂貴的。PPO被提出作為TRPO的簡單且更有效的替代方案。PPO對政策比率進行剪切以近似信賴區域,而不涉及涉及KL散度的複雜計算。

這就是為什麼在解決強化學習問題時,PPO已經成為比TRPO更受青睞的原因。由於更有效的信任區域估計方法,PPO有效地平衡了性能和穩定性。

Proximal policy approximation (PPO)

PPO通常被視為演員-評論者方法的一個子類,該方法根據值函數更新策略梯度。優勢演員-評論者(A2C)方法使用一個稱為優勢的參數。這衡量了評論者預測的回報與實現策略的回報之間的差異。

要理解PPO,您需要了解其組成部分:

  1. 演員執行策略。它實現為神經網絡。給定一個狀態作為輸入,它輸出要採取的動作。
  2. 評論家是另一個神經網絡。 它將狀態作為輸入並輸出該狀態的預期值。因此,評論家表示狀態價值函數。
  3. 基於策略梯度的方法可以選擇使用不同的目標函數。 特別是,PPO 使用優勢函數。優勢函數衡量了累積獎勵(基於演員實施的策略)超出預期基準獎勵(由評論家預測)的量。PPO 的目標是增加選擇具有高優勢的動作的可能性。PPO 的優化目標使用基於此優勢函數的損失函數。
  4. 在 PPO 中,修剪后的目标函数是主要创新。它防止在单个训练迭代中进行大幅度的策略更新。它限制了在单个迭代中策略更新的数量。为了衡量增量策略更新,基于策略的方法使用新策略对旧策略的概率比。
  5. 替代损失是 PPO 中的目标函数,它考虑了前面提到的创新。计算如下:
    1. 计算实际比率(如前所述),并将其乘以优势。
    2. 將比率修剪至所需範圍內。將修剪後的比率乘以優勢。
    3. 取上述兩個量中的最小值。
  6. 在實踐中,還會添加一個熵項到替代損失中。這被稱為熵獎勵。它基於行動概率的數學分佈。熵獎勵背後的想法是以受控方式引入一些額外的隨機性。這樣做鼓勵優化過程探索行動空間。高熵獎勵促進探索而非開發。

理解修剪機制

假設在舊政策πold下,在狀態a採取行動的機率是sπold(a|s)。在新政策下,從相同狀態採取相同行動的機率更新為a,機率為sπnew(a|s)。這些機率的比率作為政策參數θ的函數是r(θ)。當新政策使行動更可能發生(在相同狀態下)時,比率大於1,反之亦然。

剪裁機制限制了這個機率比率,使得新的動作機率必須落在舊的動作機率的某個百分比範圍內。例如,r(θ)可以被限制在0.8和1.2之間。這可以防止大幅度跳躍,進而確保穩定的訓練過程。

在本文的其餘部分,您將學習如何組裝用於使用PyTorch實現PPO的簡單組件。

在實現PPO之前,我們需要安裝先決軟件庫並選擇適合應用策略的環境。

安裝PyTorch和所需庫。

我們需要安裝以下軟件:

  • PyTorch和其他軟件庫,如numpy(用於數學和統計功能)和matplotlib(用於繪製圖形)。
  • 從 OpenAI 的開源 Gym 軟體套件是一個 Python 函式庫,模擬不同的環境和遊戲,可以使用強化學習來解決。您可以使用 Gym API 讓您的演算法與環境互動。 由於 gym 功能有時會在升級過程中變動,這個例子中,我們將其版本凍結為 0.25.2

要在伺服器或本機安裝,執行:

$ pip install torch numpy matplotlib gym==0.25.2

要使用像Google Colab或DataLab這樣的筆記本進行安裝,請使用:

!pip install torch numpy matplotlib gym==0.25.2

創建CartPole環境

使用OpenAI Gym創建CartPole環境的兩個實例(一個用於訓練,另一個用於測試):

env_train = gym.make('CartPole-v1') env_test = gym.make('CartPole-v1')

現在,讓我們使用PyTorch實現PPO。

定義策略網絡

如前所述,PPO被實現為一個actor-critic模型。actor實現策略,critic預測其估計值。actor和critic神經網絡都接受相同的輸入-每個時間步的狀態。因此,actor和critic模型可以共享一個通用的神經網絡,這被稱為骨幹架構。actor和critic可以用額外的層擴展骨幹架構。

定義骨幹網絡

以下步驟描述了骨幹網絡:

  • 實現一個具有3層的網絡-一個輸入層、一個隱藏層和一個輸出層。
  • 在輸入和隱藏層之後,我們使用激活函數。在本教程中,我們選擇ReLU因為它在計算上是有效的。
  • 我們還在輸入和隱藏層之後引入了一個輸出功能,以獲得一個強健的網絡。輸出功能會隨機將一些神經元設為零。這減少了對特定神經元的依賴,防止過度擬合,從而使網絡更加強健。

以下代碼實現了骨幹:

class BackboneNetwork(nn.Module): def __init__(self, in_features, hidden_dimensions, out_features, dropout): super().__init__() self.layer1 = nn.Linear(in_features, hidden_dimensions) self.layer2 = nn.Linear(hidden_dimensions, hidden_dimensions) self.layer3 = nn.Linear(hidden_dimensions, out_features) self.dropout = nn.Dropout(dropout) def forward(self, x): x = self.layer1(x) x = f.relu(x) x = self.dropout(x) x = self.layer2(x) x = f.relu(x) x = self.dropout(x) x = self.layer3(x) return x

定義演員-評論家網絡

現在,我們可以使用這個網絡來定義演員-評論家類ActorCritic。演員模擬策略並預測動作。評論家模擬值函數並預測值。它們都將狀態作為輸入。

class ActorCritic(nn.Module): def __init__(self, actor, critic): super().__init__() self.actor = actor self.critic = critic def forward(self, state): action_pred = self.actor(state) value_pred = self.critic(state) return action_pred, value_pred

實例化演員和評論家網絡

我們將使用上面定義的網絡來創建一個演員和一個評論家。然後,我們將創建一個代理,包括演員和評論家。

在創建代理之前,初始化網絡的參數:

  • 隱藏層的維度H,這是一個可配置的參數。隱藏層的大小和數量取決於問題的複雜性。我們將使用一個64 x 64維度的隱藏層。
  • 輸入特徵N,其中N是狀態數組的大小。輸入層有N x H維度。在CartPole環境中,狀態是一個包含4個元素的數組。因此N為4。
  • 演員網絡的輸出特徵O,其中O是環境中動作的數量。演員的輸出層有H x O維度。CartPole環境有2個動作。
  • 評論家網絡的輸出特徵。由於評論家網絡僅預測預期值(給定輸入狀態),輸出特徵的數量為1。
  • 將輟學率表示為一個分數。

以下的程式碼顯示如何基於主幹網路來宣告演員和評論家網路:

def create_agent(hidden_dimensions, dropout): INPUT_FEATURES = env_train.observation_space.shape[0] HIDDEN_DIMENSIONS = hidden_dimensions ACTOR_OUTPUT_FEATURES = env_train.action_space.n CRITIC_OUTPUT_FEATURES = 1 DROPOUT = dropout actor = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, ACTOR_OUTPUT_FEATURES, DROPOUT) critic = BackboneNetwork( INPUT_FEATURES, HIDDEN_DIMENSIONS, CRITIC_OUTPUT_FEATURES, DROPOUT) agent = ActorCritic(actor, critic) return agent

計算回報

環境根據代理人的行動,從每個步驟到下一個步驟給予獎勵。獎勵R表示為:

回報被定義為預期未來獎勵的累積價值。遠未來時間步的獎勵比即時獎勵更不具價值。因此,回報通常被計算為折扣回報G,定義為:

在這個教程(以及許多其他參考資料)中,回報指的是折扣回報。

要計算回報:

  • 從所有未來狀態中獲得預期獎勵開始。
  • 將每個未來獎勵乘以折扣因子的指數。例如,從現在起2個時間步之後的預期獎勵乘以2。
  • 將所有折扣未來獎勵加總以計算回報。
  • 對回報的值進行歸一化。

函數calculate_returns()執行這些計算,如下所示:

def calculate_returns(rewards, discount_factor): returns = [] cumulative_reward = 0 for r in reversed(rewards): cumulative_reward = r + cumulative_reward * discount_factor returns.insert(0, cumulative_reward) returns = torch.tensor(returns) # 歸一化回報 returns = (returns - returns.mean()) / returns.std() return returns

實現優勢函數

優勢是按照評論家預測的值和演員根據政策選擇的行動預期回報之間的差異計算的。對於給定的行動,優勢表達了採取該特定行動相對於任意(平均)行動的好處。

原始 PPO 論文(方程式 10)中,往前看到時間步 T 的優勢被表示為:

在編寫算法時,通過批次大小來強制往前看到一組時間步的限制。因此,上述方程可以簡化為價值和預期回報之間的差異。預期回報在狀態-動作值函數 Q 中量化。

因此,下面的簡化公式表達了在特定狀態下在特定策略下在特定時間步中選擇的優勢:

  • 選擇特定動作
  • 在給定狀態下
  • 在特定策略下
  • 在特定時間步中的優勢被表達為:

這被表示為:

OpenAI 也使用這個公式來實現 RL。下面的calculate_advantages() 函數顯示了計算優勢:

def calculate_advantages(returns, values): advantages = returns - values # 正規化優勢 advantages = (advantages - advantages.mean()) / advantages.std() return advantages

替代損失和剪切機制

在沒有像 PPO 這樣的特殊技術的情況下,政策損失將是標準政策梯度損失。標準政策梯度損失的計算方法是:

  • 政策行動概率
  • 優勢函數,計算方式為:
    • 政策回報
    • 預期值

標準政策梯度損失無法對突然的政策變化進行修正。替代損失修改了標準損失,限制了政策在每次迭代中的變化量,這是兩個量中的最小值:

  • 乘積:
    • 政策比率。此比率表示舊動作機率和新動作機率之間的差異。
    • 優勢函數
  • 乘積:
    • 政策比率的夾取值。此比率被截斷,使得更新後的政策在舊政策的一定百分比之內。
    • 優勢函數

在優化過程中,替代損失用作實際損失的代理。

剪切機制

策略比率,R,是新舊策略之間的差異,並且是在新舊參數下該策略的對數概率比率:

被剪切的策略比率,R’,受到約束,使得:

給定優勢,At,如前一節所示,以及如上所示的政策比率,代理損失計算如下:

下面的代碼顯示了如何實現剪切機制和代理損失。

def calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages): advantages = advantages.detach() policy_ratio = ( actions_log_probability_new - actions_log_probability_old ).exp() surrogate_loss_1 = policy_ratio * advantages surrogate_loss_2 = torch.clamp( policy_ratio, min=1.0-epsilon, max=1.0+epsilon ) * advantages surrogate_loss = torch.min(surrogate_loss_1, surrogate_loss_2) return surrogate_loss

現在,讓我們訓練代理。

計算政策和價值損失

我們現在準備計算政策和價值損失:

  • 政策損失是替代損失和熵奖励的总和。
  • 值損失基于評論家預測的值和政策生成的回報(累計獎勵)之間的差異。值損失計算使用平滑L1損失函數。這有助於平滑損失函數,使其對異常值不敏感。上述兩種損失都是張量。梯度下降基於標量值。使用.sum()函數將張量元素相加,得到表示損失的單一標量值。下面的函數展示了如何實現此功能:

定義訓練循環

def calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred): entropy_bonus = entropy_coefficient * entropy policy_loss = -(surrogate_loss + entropy_bonus).sum() value_loss = f.smooth_l1_loss(returns, value_pred).sum() return policy_loss, value_loss

在開始訓練過程之前,創建一組空數組作為緩衝區。訓練算法將使用這些緩衝區來存儲有關代理動作、環境狀態和每個時間步驟的獎勵的信息。下面的函數初始化這些緩衝區:

def init_training(): states = [] actions = [] actions_log_probability = [] values = [] rewards = [] done = False episode_reward = 0 return states, actions, actions_log_probability, values, rewards, done, episode_reward

每個訓練迭代都會使用該迭代的策略參數運行代理。代理與環境在循環中以時間步驟的方式互動,直到達到終止條件。

在每個時間步驟之後,代理的動作、獎勵和值將被添加到相應的緩衝區中。當揭示結束時,函數將返回更新後的緩衝區集,這些緩衝區總結了該揭示的結果。

在運行訓練循環之前:

  • 使用agent.train()將模型設置為訓練模式。
  • 使用env.reset()將環境重置為隨機狀態。這是該訓練迭代的起始狀態。

以下步驟解釋了訓練循環中每個時間步驟中發生的事情:

  • 將狀態傳遞給代理。
  • 代理返回:
    • 根據策略(actor)給出的狀態,返回預測的動作。通過softmax函數將此預測動作張量傳遞以獲得動作概率集。
    • 基於評論家的預測值來預測狀態。
  • 代理選擇要採取的行動:
    • 使用行動概率來估計概率分佈。
    • 通過從此分佈中選取樣本隨機選擇一個行動。dist.sample() 函數實現此功能。
  • 使用 env.step() 函數將此行動傳遞給環境,模擬環境對此時間步的響應。根據代理的行動,環境生成:
    • 新的狀態
    • 獎勵
    • 布林返回值done(這表示環境是否已達到終端狀態)
  • 將代理動作、獎勵、預測值和新狀態的值附加到相應的緩衝區中。

env.step()函數對done的布林返回值返回true時,訓練集結束。

在情節結束後,使用從每個時間步驟累積的值來計算該情節的累積回報,方法是將每個時間步驟的獎勵相加。我們使用先前描述的calculate_returns()函數來執行此操作。該函數的輸入是折扣因子和包含每個時間步驟的獎勵的緩衝區。我們使用這些回報和從每個時間步驟累積的值來使用calculate_advantages()函數來計算優勢。

以下是實現這些步驟的Python函數:

def forward_pass(env, agent, optimizer, discount_factor): states, actions, actions_log_probability, values, rewards, done, episode_reward = init_training() state = env.reset() agent.train() while not done: state = torch.FloatTensor(state).unsqueeze(0) states.append(state) action_pred, value_pred = agent(state) action_prob = f.softmax(action_pred, dim=-1) dist = distributions.Categorical(action_prob) action = dist.sample() log_prob_action = dist.log_prob(action) state, reward, done, _ = env.step(action.item()) actions.append(action) actions_log_probability.append(log_prob_action) values.append(value_pred) rewards.append(reward) episode_reward += reward states = torch.cat(states) actions = torch.cat(actions) actions_log_probability = torch.cat(actions_log_probability) values = torch.cat(values).squeeze(-1) returns = calculate_returns(rewards, discount_factor) advantages = calculate_advantages(returns, values) return episode_reward, states, actions, actions_log_probability, advantages, returns

更新模型參數

每個訓練迭代運行模型通過完整的情節,其中包含許多時間步驟(直到達到終止條件)。在每個時間步驟中,我們存儲策略參數、代理動作、回報和優勢。在每次迭代之後,我們根據該迭代中所有時間步驟中策略的表現來更新模型。

CartPole環境中的最大時間步數為500。在更複雜的環境中,時間步數可能會更多,甚至達到百萬級別。在這種情況下,訓練結果數據集必須分成批次。每個批次中的時間步數稱為優化批次大小。

因此,更新模型參數的步驟如下:

  • 將訓練結果數據集分成批次。
  • 對於每個批次:
    • 獲取代理的動作和每個狀態的預測值。
    • 使用這些預測動作來估計新的動作概率分佈。
    • 使用此分佈來計算熵。
    • 使用此分佈來獲取訓練結果數據集中操作的對數概率。這是訓練結果數據集中操作的新對數概率集。這些相同操作的舊對數概率是在前一節中解釋的訓練循環中計算的。
    • 使用操作的舊和新概率分佈計算替代損失。
    • 使用替代損失、熵和優勢計算策略損失和價值損失。
    • 分別在策略和價值損失上運行.backward()。這會更新損失函數上的梯度。
    • 運行.step()在優化器上以更新策略參數。在這種情況下,我們使用Adam優化器來平衡速度和穩健性。
    • 累積策略和價值損失。
  • 根據參數PPO_STEPS的值,在每個批次上重複執行向後傳遞(上述操作)幾次。在每個批次上重複執行向後傳遞在計算上是有效的,因為它有效地增加了訓練數據集的大小,而無需運行額外的向前傳遞。在採樣和優化之間的每個輪換中的環境步數稱為迭代批次大小。
  • 返回平均策略損失和價值損失。

下面的代碼實現了這些步驟:

def update_policy( agent, states, actions, actions_log_probability_old, advantages, returns, optimizer, ppo_steps, epsilon, entropy_coefficient): BATCH_SIZE = 128 total_policy_loss = 0 total_value_loss = 0 actions_log_probability_old = actions_log_probability_old.detach() actions = actions.detach() training_results_dataset = TensorDataset( states, actions, actions_log_probability_old, advantages, returns) batch_dataset = DataLoader( training_results_dataset, batch_size=BATCH_SIZE, shuffle=False) for _ in range(ppo_steps): for batch_idx, (states, actions, actions_log_probability_old, advantages, returns) in enumerate(batch_dataset): # 獲取所有輸入狀態的動作的新對數機率 action_pred, value_pred = agent(states) value_pred = value_pred.squeeze(-1) action_prob = f.softmax(action_pred, dim=-1) probability_distribution_new = distributions.Categorical( action_prob) entropy = probability_distribution_new.entropy() # 使用舊動作估算新的對數機率 actions_log_probability_new = probability_distribution_new.log_prob(actions) surrogate_loss = calculate_surrogate_loss( actions_log_probability_old, actions_log_probability_new, epsilon, advantages) policy_loss, value_loss = calculate_losses( surrogate_loss, entropy, entropy_coefficient, returns, value_pred) optimizer.zero_grad() policy_loss.backward() value_loss.backward() optimizer.step() total_policy_loss += policy_loss.item() total_value_loss += value_loss.item() return total_policy_loss / ppo_steps, total_value_loss / ppo_steps

最後讓我們運行 PPO 代理。

評估性能

為了評估代理的性能,創建一個新的環境,計算在這個新環境中運行代理所獲得的累積獎勵。您需要使用.eval()函數將代理設置為評估模式。步驟與訓練迴圈相同。下面的程式碼片段實現了評估函數:

def evaluate(env, agent): agent.eval() rewards = [] done = False episode_reward = 0 state = env.reset() while not done: state = torch.FloatTensor(state).unsqueeze(0) with torch.no_grad(): action_pred, _ = agent(state) action_prob = f.softmax(action_pred, dim=-1) action = torch.argmax(action_prob, dim=-1) state, reward, done, _ = env.step(action.item()) episode_reward += reward return episode_reward

可視化訓練結果

我們將使用 Matplotlib 庫來可視化訓練過程的進展。下面的函數顯示了如何繪製來自訓練和測試迴圈的獎勵:

def plot_train_rewards(train_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(train_rewards, label='Training Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Training Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(train_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()
def plot_test_rewards(test_rewards, reward_threshold): plt.figure(figsize=(12, 8)) plt.plot(test_rewards, label='Testing Reward') plt.xlabel('Episode', fontsize=20) plt.ylabel('Testing Reward', fontsize=20) plt.hlines(reward_threshold, 0, len(test_rewards), color='y') plt.legend(loc='lower right') plt.grid() plt.show()

在下面的示例图中,我们展示了应用策略在训练和测试环境中获得的训练和测试奖励。请注意,每次运行代码时,这些图的形状会有所不同。这是由于训练过程中固有的随机性。

训练奖励(在训练环境中应用策略获得)。作者提供的图片。

测试奖励(在测试环境中应用策略获得)。作者提供的图片。

在上面显示的输出图中,观察训练过程的进展:

  • 獎勵從低值開始。隨著訓練的進展,獎勵增加。
  • 獎勵在增加的同時隨機波動。這是因為代理人在探索策略空間。
  • 訓練終止,測試獎勵在許多迭代中穩定在閾值(475)周圍。
  • 獎勵上限為500。這些是環境(Gym CartPole v1)強加的限制。

同樣,您可以通過迭代繪製值和策略損失:

def plot_losses(policy_losses, value_losses): plt.figure(figsize=(12, 8)) plt.plot(value_losses, label='Value Losses') plt.plot(policy_losses, label='Policy Losses') plt.xlabel('Episode', fontsize=20) plt.ylabel('Loss', fontsize=20) plt.legend(loc='lower right') plt.grid() plt.show()

下面的示例圖顯示了訓練情節中跟踪的損失。

通過培訓過程中的價值和策略損失。圖片由作者提供

觀察圖表並注意:

  • 損失似乎是隨機分佈的,並且沒有遵循任何模式。
  • 這是強化學習訓練的典型特徵,目標不是最小化損失,而是最大化獎勵。

運行PPO算法

現在您擁有使用PPO訓練代理的所有組件。要將所有內容組合起來,您需要:

  • 聲明超參數,如折扣因子、批大小、學習率等。
  • 將緩衝區實例化為空數組,以存儲每次迭代的獎勵和損失。
  • 使用create_agent()函數創建一個代理實例。
  • 使用forward_pass()update_policy()函數迭代運行前向和後向傳遞。
  • 使用evaluate()函數測試策略的性能。
  • 將訓練和評估函數的策略、價值損失和獎勵添加到相應的緩衝區中。
  • 計算最近幾個時間步的獎勵和虧損的平均值。以下示例將獎勵和虧損平均值計算在最後40個時間步上。
  • 每隔一段時間打印評估結果。以下示例每隔10個步驟打印一次。
  • 當平均獎勵超過一定閾值時終止流程。

以下代碼展示了如何在Python中聲明一個執行此操作的函數:

def run_ppo(): MAX_EPISODES = 500 DISCOUNT_FACTOR = 0.99 REWARD_THRESHOLD = 475 PRINT_INTERVAL = 10 PPO_STEPS = 8 N_TRIALS = 100 EPSILON = 0.2 ENTROPY_COEFFICIENT = 0.01 HIDDEN_DIMENSIONS = 64 DROPOUT = 0.2 LEARNING_RATE = 0.001 train_rewards = [] test_rewards = [] policy_losses = [] value_losses = [] agent = create_agent(HIDDEN_DIMENSIONS, DROPOUT) optimizer = optim.Adam(agent.parameters(), lr=LEARNING_RATE) for episode in range(1, MAX_EPISODES+1): train_reward, states, actions, actions_log_probability, advantages, returns = forward_pass( env_train, agent, optimizer, DISCOUNT_FACTOR) policy_loss, value_loss = update_policy( agent, states, actions, actions_log_probability, advantages, returns, optimizer, PPO_STEPS, EPSILON, ENTROPY_COEFFICIENT) test_reward = evaluate(env_test, agent) policy_losses.append(policy_loss) value_losses.append(value_loss) train_rewards.append(train_reward) test_rewards.append(test_reward) mean_train_rewards = np.mean(train_rewards[-N_TRIALS:]) mean_test_rewards = np.mean(test_rewards[-N_TRIALS:]) mean_abs_policy_loss = np.mean(np.abs(policy_losses[-N_TRIALS:])) mean_abs_value_loss = np.mean(np.abs(value_losses[-N_TRIALS:])) if episode % PRINT_INTERVAL == 0: print(f'Episode: {episode:3} | \ Mean Train Rewards: {mean_train_rewards:3.1f} \ | Mean Test Rewards: {mean_test_rewards:3.1f} \ | Mean Abs Policy Loss: {mean_abs_policy_loss:2.2f} \ | Mean Abs Value Loss: {mean_abs_value_loss:2.2f}') if mean_test_rewards >= REWARD_THRESHOLD: print(f'Reached reward threshold in {episode} episodes') break plot_train_rewards(train_rewards, REWARD_THRESHOLD) plot_test_rewards(test_rewards, REWARD_THRESHOLD) plot_losses(policy_losses, value_losses)

運行程序:

run_ppo()

輸出應該類似於下面的示例:

Episode: 10 | Mean Train Rewards: 22.3 | Mean Test Rewards: 30.4 | Mean Abs Policy Loss: 0.37 | Mean Abs Value Loss: 0.39 Episode: 20 | Mean Train Rewards: 38.6 | Mean Test Rewards: 69.8 | Mean Abs Policy Loss: 0.46 | Mean Abs Value Loss: 0.37 . . . Episode: 100 | Mean Train Rewards: 289.5 | Mean Test Rewards: 427.3 | Mean Abs Policy Loss: 1.73 | Mean Abs Value Loss: 0.21 Episode: 110 | Mean Train Rewards: 357.7 | Mean Test Rewards: 461.4 | Mean Abs Policy Loss: 1.86 | Mean Abs Value Loss: 0.22 Reached reward threshold in 116 episodes

您可以在這個 DataLab 筆記本上查看並運行工作中的程序

在機器學習中,超參數控制訓練過程。下面,我將解釋一些在 PPO 中使用的重要超參數:

  • 學習率: 學習率決定每次迭代中策略參數可以變化的程度。在隨機梯度下降中,策略參數在每次迭代中更新的量由學習率和梯度的乘積決定。
  • 剪切参数: 这也被称为epsilon,ε。它决定了政策比例被剪切的程度。允许新旧政策的比例在范围 [1-ε, 1+ε]内变化。当超出此范围时,将被人为剪切至该范围内。
  • 批量大小:这是指每次梯度更新考虑的步数。在PPO中,批量大小是需要应用策略并计算替代损失来更新策略参数所需的时间步数。在本文中,我们使用了批量大小为64。
  • 迭代步数:这是每个批次被重复使用来运行反向传播的次数。本文中的代码将其称为PPO_STEPS。在复杂环境中,多次运行正向传播是计算密集型的。一个更高效的替代方案是多次重新运行每个批次。通常建议使用5到10之间的值。
  • 折扣因子: 這也被稱為伽瑪,γ。 它表達了即時獎勵比未來獎勵更有價值的程度。這與計算金錢時間價值中的利率概念類似。當 靠近0時,意味著未來獎勵較不重要,代理應優先考慮即時獎勵。當 靠近1時,意味著未來獎勵很重要。
  • 熵系数:熵系数决定熵奖励,其计算方式为熵系数与分布的熵的乘积。熵奖励的作用是为政策引入更多的随机性,从而鼓励代理探索政策空间。然而,当这种随机性过高时,训练无法收敛到最佳政策。
  • 培訓的成功標準:您需要設定判定培訓成功的標準。一種常見的方法是設定最近N次試驗(回合)的平均獎勵高於特定閾值的條件。在上面的示例代碼中,這是通過變量N_TRIALS來表示的。當將其設置為較高值時,培訓時間會變長,因為策略必須在更多回合中達到閾值獎勵。這也會導致更穩健的策略,但計算上會更昂貴。請注意,PPO是一種隨機策略,代理有時可能無法突破閾值。因此,如果N_TRIALS的值設置得太高,培訓可能無法終止。

優化PPO性能的策略

優化訓練PPO算法的性能涉及通過試驗和錯誤以及嘗試不同的超參數值。然而,有一些廣泛的指南:

  • 折扣因子:當長期獎勵很重要時,比如在CartPole環境中,竿子需要保持穩定一段時間,可以從一個中等的gamma值開始,例如0.99。
  • 熵獎勵:在複雜的環境中,代理必須探索動作空間以找到最優策略。熵獎勵促進探索。熵獎勵添加到替代損失中。在決定熵係數之前,檢查替代損失的大小和分布的熵。在本文中,我們使用了一個熵係數為0.01。
  • 剪切参数: 剪切参数决定更新策略与当前策略之间的差异程度。较大的剪切参数值鼓励更好地探索环境,但也存在训练不稳定的风险。您需要一个允许逐渐探索同时又能防止不稳定更新的剪切参数。在本文中,我们使用了剪切参数为0.2。
  • 学习率: 当学习率过高时,策略会以较大的步长进行更新,每次迭代训练过程可能变得不稳定。当学习率过低时,训练时间太长。本教程使用了学习率为0.001,在该环境下表现良好。在许多情况下,建议使用学习率为1e-5。

在PPO中的挑战和最佳实践

在解釋了PPO的概念和實施細節之後,讓我們來討論挑戰和最佳實踐。

培訓PPO時常見的挑戰

即使PPO被廣泛使用,您需要注意潛在的挑戰,以成功使用這種技術解決實際問題。一些這樣的挑戰包括:

  • 收斂緩慢:在複雜環境中,PPO可能效率低下,需要與環境進行許多交互才能收斂到最優策略。這使得訓練變得緩慢且昂貴。
  • 對超參數的敏感性:PPO 依賴於有效地探索策略空間。訓練過程的穩定性和收斂速度對於超參數的取值非常敏感。這些超參數的最佳取值通常只能通過試誤來確定。
  • 過度擬合: RL 環境通常是使用隨機參數初始化的。PPO 訓練是基於找到基於代理環境的最佳策略。有時,訓練過程會收斂到一組特定環境的最佳參數,但對於任何隨機化的環境都不是最優的。通常通過進行許多迭代來解決這個問題,每個迭代使用不同的隨機化訓練環境。
  • 動態環境: 簡單的RL環境,如CartPole環境,是靜態的 – 規則在整個時間內保持不變。許多其他環境,如機器人在不穩定的移動表面上學習行走,是動態的 – 環境的規則隨著時間而變化。要在這樣的環境中表現良好,PPO通常需要進行額外的微調。
  • 探索與開發: PPO的剪輯機制確保政策更新在可信區域內。然而,這也阻止了代理探索動作空間。這可能導致在複雜環境中特別收斂到局部最優解。另一方面,允許代理過度探索可能會阻止其收斂到任何最優策略。

訓練PPO模型的最佳實踐

要獲得使用 PPO 的良好結果,我建議一些最佳做法,例如:

  • 標準化輸入特徵:對回報和優勢的值進行標準化可以減少數據的變異性,並導致穩定的梯度更新。將數據標準化將所有值帶到一致的數值範圍。這有助於減少異常值和極端值的影響,否則可能扭曲梯度更新並減慢收斂速度。
  • 使用適當大小的批次:小批次可加快更新和訓練速度,但可能導致收斂到局部最優解和訓練過程的不穩定性。較大的批次大小可讓代理學習到強健的策略,從而帶來穩定的訓練過程。然而,過大的批次大小也是次優的。除了增加計算成本外,它們使策略更新對值函數的反應變得較不靈敏,因為梯度更新是基於大批次估計的平均值。此外,這可能導致對特定批次的過度擬合更新。
  • 迭代步驟:通常建議對每個批次重複使用5-10次迭代。這可以使訓練過程更有效率。對同一批次重複使用太多次會導致過度擬合。代碼中將此超參數稱為PPO_STEPS
  • 定期進行評估:為了檢測過度配適,定期監控策略的有效性至關重要。如果在某些情況下策略被證明是無效的,可能需要進行進一步的訓練或微調。
  • 調整超參數: 如前所述,PPO訓練對超參數的值很敏感。嘗試不同的超參數值,以確定您特定問題的正確值設置。
  • 共享骨幹網路: 正如本文所述,使用共享骨幹可防止演員和評論家網路之間的不平衡。在演員和評論家之間共享骨幹網路有助於共享特徵提取和對環境的共同理解。這使得學習過程更有效和穩定。它還有助於減少算法的計算空間和時間複雜度。
  • 隱藏層的數量和大小: 增加隱藏層的數量和維度以應對更複雜的環境。像 CartPole 這樣的簡單問題可以用單個隱藏層解決。本文中使用的隱藏層具有 64 個維度。將網路設計得比必要的要大很多是在計算上浪費,並可能導致不穩定。
  • 提早停止:當滿足評估指標時停止訓練有助於防止過度訓練並避免資源浪費。一個常見的評估指標是當代理在過去N次事件中超過閾值獎勵。

結論

在本文中,我們討論了PPO作為解決RL問題的方法。然後,我們詳細介紹了使用PyTorch實現PPO的步驟。最後,我們提出了一些PPO的性能技巧和最佳實踐。

最好的學習方法是自己實現代碼。您也可以修改代碼以適應Gym中的其他經典控制環境。要了解如何使用Python和OpenAI的Gymnasium實現RL代理,請參加課程Python中的Gymnasium強化學習

Source:
https://www.datacamp.com/tutorial/proximal-policy-optimization