增強式學習

Table of Contents

Hits

1. 增強式學習

想像你正在學騎腳踏車:沒有人告訴你「左手用力 3.7 牛頓、右腳踩 45 度」這種標準答案,你只知道一件事 — 摔倒了就是做錯了,沒摔倒就繼續 。透過不斷嘗試和修正,你最終學會了騎車。這就是增強式學習(Reinforcement Learning, RL)的核心精神1

在 RL 中,有一個 Agent*(代理人)會不斷與 *環境 互動:做出一個動作(Action),環境回饋一個獎勵(Reward),Agent 再根據獎勵調整自己的策略(Policy)。如圖1所示。RL 的目標就是找到一個最佳策略,讓累積的 Reward 最多。

最常見的應用是教 AI 玩遊戲 — 在這種情境下,我們不會對某個動作貼標籤說它是「好」或「壞」,而是根據遊戲的結果(贏或輸、得分或失分)來做為回饋。

ReinforcementLearning.png

Figure 1: 強化學習流程圖

RL 的學習過程就是不斷 try-and-error:做了一個動作 → 看看結果好不好 → 好就記住、不好就避免。強化學習在過去曾被長期忽視,但在 Google DeepMind 成功用它玩 Atari 遊戲後(AI 從零開始學會打磚塊,還自己發現了把球打到磚塊後面的密技),就開始受到大量關注。

常見的 RL 應用領域:

  • 電腦遊戲(Atari、星海爭霸)
  • 棋類對弈(AlphaGo、AlphaZero)
  • 自動駕駛
  • 機器人控制

1.1.監督式學習的比較

比較項目 監督式學習 增強式學習
學習方式 有老師教(給正確答案) 自己摸索(只知道做得好不好)
圍棋的例子 看大量棋譜,模仿人類下棋 兩台 AI 互相對弈,自己發明招式
優點 有明確的標準答案,學習效率高 能突破人類的經驗限制,可能找到更好的策略
缺點 受限於人類提供的資料品質 需要大量的嘗試,訓練時間長

1.2.深度學習的比較

深度學習和強化學習不是互斥的,它們可以搭配使用。最經典的例子就是 AlphaGo 和 AlphaZero2

  • AlphaGo :用深度學習(CNN)學習三千萬盤人類棋譜 → 打敗了世界冠軍柯潔
  • AlphaZero :完全不看人類棋譜,只用 強化學習 自己和自己下棋 → 打敗了 AlphaGo

AlphaZero 的故事告訴我們:當 AI 不再被人類經驗束縛,反而可能找到人類從沒想過的策略。

1.4. 探索與利用的兩難(Exploration vs. Exploitation)

RL 有一個很有趣的兩難問題3:假設你發現了一家還不錯的餐廳,你應該:

  • *每次都去那家*(利用 Exploitation)— 穩穩拿到不錯的分數
  • *還是嘗試新餐廳*(探索 Exploration)— 可能踩雷,但也可能發現更棒的

這就是 RL 中最經典的 Exploration-Exploitation Tradeoff 。Agent 必須在「利用已知的好策略」和「探索未知的可能性」之間取得平衡。

最常見的做法叫做 $ε$-greedy:設定一個探索率 \(\epsilon\)(例如 0.1),Agent 有 10% 的機率隨機選動作(探索),90% 的機率選目前已知最好的動作(利用)。隨著訓練進行,\(\epsilon\) 會逐漸降低 — 因為 Agent 越來越有經驗了,不需要再那麼常亂試了。

2022-04-30_10-09-00.jpg

Figure 2: 增強式學習的探索與利用

2. Gymnasium(前身為 OpenAI Gym)

OpenAI Gym 曾經是強化學習界最受歡迎的工具包,提供許多現成的測試環境(遊戲),讓大家可以專心開發 RL 演算法,不用自己寫遊戲。2022 年後,Gym 由 Farama Foundation 接手維護,改名為 Gymnasium ,API 也做了些調整。

安裝方式很簡單:

pip install gymnasium

3. CartPole:讓桿子不要倒!

Gymnasium 上有許多可用的環境,CartPole 是其中最經典也最簡單的一種。想像一台小車上面立著一根桿子(倒立擺),小車可以左右移動,你的目標就是 透過左右移動小車,讓桿子盡可能不要倒下來 。就像你小時候玩過的平衡遊戲一樣!

cartpole-sys.jpg

Figure 3: Cart-pole system

3.1. 執行測試與環境變數

以下是一段最基本的測試程式,讓 Agent 完全隨機地操控小車:

 1: import gymnasium as gym
 2: 
 3: # 建立執行環境
 4: env = gym.make('CartPole-v1')
 5: # 將執行環境初始化(回傳初始觀測值和額外資訊)
 6: observation, info = env.reset()
 7: for _ in range(100):
 8:     # 隨機從action_space中挑選下一動作(action)丟入step執行
 9:     observation, reward, terminated, truncated, info = env.step(env.action_space.sample())
10:     if terminated or truncated:
11:         break
12: env.close()
13: print("隨機亂動,遊戲結束!")

如下圖所示,增強式學習的核心就是:Agent 採取 Action,採取行動後環境可能會改變,而環境會給 Agent 一個 Reward,讓 Agent 知道這個 Action 好不好。其中 action 有 0 或 1 兩種可能值,代表將車子向左或向右推動。

cartpole-cycle.jpg

Figure 4: Cartpole cycle

就 CartPole 來說,action 丟入環境執行後,可以得到幾個相關的環境資訊(由 step 函式傳回),這些變數可由以下方式取得:

 1: import gymnasium as gym
 2: 
 3: env = gym.make('CartPole-v1')
 4: observation, info = env.reset()
 5: rewards = 0
 6: for _ in range(100):
 7:     action = env.action_space.sample()  # 仍然隨機產生 action
 8:     # action丟入環境執行,傳回環境資訊(5個回傳值)
 9:     observation, reward, terminated, truncated, info = env.step(action)
10:     rewards += reward
11:     print(observation)
12:     if terminated or truncated:  # 回合結束,可能柱子太傾斜或車子跑遠
13:         print("Rewards:", rewards)
14:         break
15: env.close()

每次執行的結果都不同(因為是隨機動作),但大致上會看到類似這樣的輸出:

[-0.00943879  0.18031594 -0.0417659  -0.35468228]
[-0.00583247  0.37600609 -0.04885955 -0.66023713]
...
Rewards: 11.0

雖然程式指定跑 100 次動作,但因為隨機亂動,桿子很快就倒了,所以通常 10 幾步就結束了。

3.2. 重要環境變數

在 Gymnasium 的環境中,有兩個重要的空間:動作空間 action_space 和觀測空間 observation_space ,分別描述了 Agent 能做什麼動作、以及能看到什麼資訊。

1: import gymnasium as gym
2: env = gym.make('CartPole-v1')
3: print("動作空間:", env.action_space)
4: print("觀測空間:", env.observation_space)
動作空間: Discrete(2)
觀測空間: Box([-4.8 ...], [4.8 ...], (4,), float32)

由結果可以看出:

  • action_space 是離散型 Discrete(2) ,代表只有兩個動作:0(向左推)和 1(向右推)。
  • observation_space 是連續型 Box ,代表觀測值是一個長度為 4 的浮點數陣列,每個元素都有上下界。
  1. Observation(觀測值):

    Type: Box(4)4

    Num Observation Min Max
    0 Cart Position -4.8 4.8
    1 Cart Velocity -Inf Inf
    2 Pole Angle -0.418 rad (-24 deg) 0.418 rad (24 deg)
    3 Pole Angular Velocity -Inf Inf
  2. Actions(動作空間,離散空間)

    Type: Discrete(2)

    Num Action
    0 Push cart to the left
    1 Push cart to the right

    註:施加的力大小是固定的,但減小或增大的速度不是固定的,它取決於當時桿子與豎直方向的角度。角度不同,產生的速度和位移也不同。

  3. Reward(獎勵)

    每撐過一步就得到 1 分獎勵(包括結束那一步)。在 CartPole-v1 中,最高可以撐 500 步,所以滿分是 500 分。

  4. 初始狀態:

    初始狀態所有觀測值都從[-0.05, 0.05]中隨機取值。

  5. 達到下列條件之一即結束一回合:
    1. 桿子與豎直方向角度超過 12 度( terminated=True
    2. 小車位置距離中心超過 2.4(小車跑出畫面, terminated=True
    3. 回合步數達到 500 步( truncated=True ,恭喜你撐到滿分!)

3.3. 小練習

上述程式只執行了一回合的模擬,請你修改上述程式,進行 200 回合的模擬,記錄每回合隨機運作的 reward 結果,並將結果畫成折線圖(x 軸為回合數,y 軸為每回合的 reward)。你覺得隨機亂動平均能撐幾步?

4. 直覺反應的 CartPole

前面的程式用隨機方式來操控小車,效果當然很差。但就算是最笨的人,也知道「桿子往左倒,車就往左推」這種直覺反應。讓我們把這個簡單策略寫成程式:

 1: import gymnasium as gym
 2: 
 3: env = gym.make('CartPole-v1')
 4: observation, info = env.reset()
 5: rewards = 0
 6: for t in range(500):
 7:     # 取得目前狀態
 8:     pos, v, ang, rot = observation
 9:     # 直覺策略:桿子往左傾就往左推,往右傾就往右推
10:     if ang < 0:
11:         action = 0  # 車往左推
12:     else:
13:         action = 1  # 車往右推
14:     observation, reward, terminated, truncated, info = env.step(action)
15:     rewards += reward
16:     if terminated or truncated:
17:         print('Rewards:', rewards)
18:         break
19: 
20: env.close()

這個策略沒有在「學習」,只是一個固定的 if-else 規則。但比起隨機亂動,已經好很多了 — 大概能撐 40~60 步。

Rewards: 51.0

不過離滿分 500 還差得遠。為什麼?因為這個策略只看了「桿子角度」一個特徵,完全忽略了速度、位置等資訊。

4.1. 小練習

上述程式只看了桿子角度,你能否再想出更好的策略?提示:觀察 observation 的 4 個數值,思考哪些資訊也該納入決策。目標是讓 reward 超過 100!

5. Hill Climbing:讓電腦自己找最佳策略 5

前面的「直覺策略」是我們人類自己寫的規則,但如果讓電腦 自己去找 最好的策略呢?這就是「爬山演算法」(Hill Climbing)的概念。

想像你在一座山上,被蒙住眼睛,你要爬到最高點。你的策略很簡單:往四周摸一摸,哪邊比較高就往哪邊走一步。這就是爬山演算法的精神 — 每次微調一點點,如果變好就保留,變差就放棄

具體來說,我們建立一個簡單的模型:把觀測值(4個數字)分別乘上一組權重,求加權和,如果和為正就往右推、為負就往左推。模型如下圖所示:

hillclimbing.png

Figure 5: Hill Climbing Model

這個模型的決策公式為:
\[H_{sum} = w_1 \cdot x + w_2 \cdot \theta + w_3 \cdot \dot{x} + w_4 \cdot \dot{\theta} + b\]
如果 \(H_{sum} \geq 0\),就往右推(action=1);否則往左推(action=0)。

爬山演算法的流程很簡單:

  1. 隨機生成一組權重
  2. 用這組權重跑一回合遊戲,記錄得分
  3. 在當前最佳權重上加入一點隨機微調
  4. 如果微調後得分更高 → 更新最佳權重
  5. 如果沒有改善 → 保持原來的權重
  6. 重複步驟 3~5,直到找到滿分的權重

5.1. 程式碼

 1: import numpy as np
 2: import gymnasium as gym
 3: import matplotlib.pyplot as plt
 4: 
 5: def run_one_episode(env, weights):
 6:     """用指定的權重跑一回合,回傳總 reward"""
 7:     observation, info = env.reset()
 8:     rewards = 0
 9:     for t in range(500):
10:         # 加權和決定動作
11:         action = 1 if np.dot(weights[:4], observation) + weights[4] >= 0 else 0
12:         observation, reward, terminated, truncated, info = env.step(action)
13:         rewards += reward
14:         if terminated or truncated:
15:             break
16:     return rewards
17: 
18: # 爬山演算法主程式
19: env = gym.make("CartPole-v1")
20: np.random.seed(10)
21: 
22: best_reward = 0
23: best_weights = np.random.rand(5)  # 初始權重隨機
24: reward_history = []
25: 
26: for i in range(300):
27:     # 在最佳權重上加入微小隨機擾動
28:     new_weights = best_weights + np.random.normal(0, 0.1, 5)
29:     r = run_one_episode(env, new_weights)
30:     reward_history.append(r)
31:     if r > best_reward:
32:         best_reward = r
33:         best_weights = new_weights
34:     if best_reward >= 500:  # CartPole-v1 滿分 500
35:         print(f"第 {i} 次迭代就找到滿分策略!")
36:         break
37: 
38: print(f"最佳 reward: {best_reward}")
39: env.close()
40: 
41: # 畫出學習過程
42: plt.figure(figsize=(10, 4))
43: plt.plot(reward_history)
44: plt.xlabel("Iteration")
45: plt.ylabel("Reward")
46: plt.title("Hill Climbing: Reward per Iteration")
47: plt.grid(True)
48: plt.tight_layout()
49: plt.savefig("images/hill_climbing_result.png", dpi=150)

爬山演算法本質上是一種「局部搜尋」的方法 — 效率很高(通常幾十次就能找到不錯的解),但因為不是全局搜索,找到的不一定是最佳解。就像蒙著眼睛爬山,你可能爬到一座小丘就以為到了山頂,卻不知道隔壁還有更高的山。

6. Q-Learning:讓 Agent 自己學會查表決策

前面的爬山演算法只是在「碰運氣」找好的權重,並沒有真正在學習。Q-Learning 才是強化學習中最經典的學習演算法之一6

Q-Learning 的核心概念很簡單:建立一張「作弊表」(Q-Table),記錄「在某個狀態下,做某個動作,預期能得到多少分」。Agent 每次遇到新狀態時,就查表選擇分數最高的動作。

這張表一開始是空白的(全部填 0),Agent 必須透過一次又一次的嘗試來填入正確的數值。更新公式如下:
\[Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha \Big[ R_{t+1} + \gamma \max_a Q(s_{t+1}, a) - Q(s_t, a_t) \Big]\]
其中:

  • \(\alpha\) 是學習率(每次更新幅度多大)
  • \(\gamma\) 是折扣因子(多重視未來的獎勵)
  • \(R_{t+1}\) 是這一步實際得到的獎勵
  • \(\max_a Q(s_{t+1}, a)\) 是下一步最好的預期分數

簡單來說:Agent 不只看眼前的獎勵,還會估計「到了下一個狀態後,最多還能拿多少分」,就像下棋時不只看當前這步,還要想後面幾步。

6.1. Algorithms

q-learning.png

Figure 6: Q-Learning Algorithm

用一個經典的比喻來解釋:想像你是一個小朋友,眼前有一顆棉花糖。如果 \(\gamma = 0\),你只看到眼前的棉花糖就立刻吃掉了;但如果 \(\gamma\) 接近 1,你會想:「如果我現在忍住不吃,等一下可能會有兩顆棉花糖!」。這就是 Q-Learning 厲害的地方 — 它讓 Agent 學會為了更大的未來回報,而犧牲眼前的小利。

6.2. Q-Table 實作細節

上面講了一堆理論,現在讓我們看看 Q-Table 在 CartPole 中要怎麼實作。

由於 CartPole 的 state 是連續值(角度可以是 0.123456…),這樣會有無限多種狀態,沒辦法建一個無限大的表格。所以我們要把連續值「離散化」(discretize),把它們分成幾個區間(bucket)。

例如我們把 state 的 4 個特徵 (position, velocity, angle, rotation rate) 分別離散化成 (1, 1, 6, 3) 個 bucket — 其中 6 個 bucket 代表角度的範圍被切成 6 個區間,區間中的值都對應到同一個離散值。

  1. 首先,我們要把連續的觀測值離散化,建立 Q-Table:

     1: import numpy as np
     2: import math
     3: import gymnasium as gym
     4: 
     5: env = gym.make('CartPole-v1')
     6: 
     7: # 每個特徵要切成幾個區間(bucket)
     8: n_buckets = (1, 1, 6, 3)
     9: n_actions = env.action_space.n  # = 2
    10: 
    11: # 建立 Q-table(所有值初始為 0)
    12: q_table = np.zeros(n_buckets + (n_actions,))
    13: print(f"Q-table 大小: {q_table.shape}")  # (1, 1, 6, 3, 2)
    14: 
    15: # 設定每個特徵的上下界
    16: state_bounds = list(zip(env.observation_space.low, env.observation_space.high))
    17: state_bounds[1] = [-0.5, 0.5]  # 速度範圍限制
    18: state_bounds[3] = [-math.radians(50), math.radians(50)]  # 角速度範圍限制
    19: 
    20: def get_state(observation, n_buckets, state_bounds):
    21:     """將連續的觀測值轉換成離散的 bucket 索引"""
    22:     state = [0] * len(observation)
    23:     for i, s in enumerate(observation):
    24:         l, u = state_bounds[i][0], state_bounds[i][1]
    25:         if s <= l:
    26:             state[i] = 0
    27:         elif s >= u:
    28:             state[i] = n_buckets[i] - 1
    29:         else:
    30:             state[i] = int(((s - l) / (u - l)) * n_buckets[i])
    31:     return tuple(state)
    

state_bounds 初始內容為

[(-4.8, 4.8),
 [-0.5, 0.5],
 (-0.41887903, 0.41887903),
 [-0.8726646259971648, 0.8726646259971648]]

q_table(1, 1, 6, 3, 2) 的 ndarray,初始值全為 0,代表 Agent 一開始對所有狀態-動作的組合完全沒有任何經驗。

  1. 再來是 $ε$-greedy 策略。選動作時,有 \(\epsilon\) 的機率隨機選(exploration,探索未知),其他時間照著 Q-Table 選最高分的動作(exploitation,利用已知):

    1: def choose_action(state, q_table, action_space, epsilon):
    2:     if np.random.random_sample() < epsilon:  # 探索:隨機選
    3:         return action_space.sample()
    4:     else:  # 利用:選 Q 值最大的動作
    5:         return np.argmax(q_table[state])
    
  2. 做出 action 後,收到 observation 和 reward,就可以更新 Q-Table:

    1: # 算出下個 state 的離散值
    2: next_state = get_state(observation, n_buckets, state_bounds)
    3: 
    4: # Q-learning 更新公式
    5: q_next_max = np.amax(q_table[next_state])
    6: q_table[state + (action,)] += lr * (reward + gamma * q_next_max - q_table[state + (action,)])
    7: 
    8: # 移動到下個 state
    9: state = next_state
    
  3. 訓練過程中,\(\epsilon\) 和學習率 \(\alpha\) 會隨著訓練逐漸降低。一開始 Agent 什麼都不知道,所以多探索(高 \(\epsilon\));後期已經學到不少經驗了,就多利用已學的知識(低 \(\epsilon\))。

    1: # epsilon 和 lr 隨回合數遞減
    2: get_epsilon = lambda i: max(0.01, min(1, 1.0 - math.log10((i+1)/25)))
    3: get_lr = lambda i: max(0.01, min(0.5, 1.0 - math.log10((i+1)/25)))
    4: 
    5: # 每回合開始時更新
    6: epsilon = get_epsilon(i_episode)
    7: lr = get_lr(i_episode)
    
  4. 成果

q-learning-result.png

7. 增強式學習有多強?7

RL 的戰績相當輝煌:

  • 圍棋 :AlphaGo/AlphaZero 擊敗世界冠軍
  • 電玩 :DeepMind 的 AI 學會玩 57 種 Atari 遊戲,其中 29 種超越人類水準
  • 超級瑪利歐 :AI 透過一次又一次的死亡,學會了跳躍、閃避怪物的時機
  • 機器人 :OpenAI 訓練機械手臂學會轉魔術方塊

那麼 RL 無敵了嗎?當然不是:

  • 訓練成本高 :要跑幾百萬、甚至幾十億次模擬才能學會,非常耗時耗能
  • 安全問題 :自動駕駛中如果 AI 誤判了紅綠燈,後果不堪設想
  • 獎勵設計困難 :如果獎勵函數設計不好,AI 可能會找到「作弊」的方式拿高分,卻完全不是我們想要的行為

RL 很強大也很有趣,但它不是萬能的。在特定領域(遊戲、棋類、機器人控制),RL 可以達到超越人類的表現!

8. 作業:強化學習大冒險

8.1. 作業一:隨機 vs 直覺 — 200 回合大對決

讓隨機策略和直覺策略各跑 200 回合,畫出兩條 reward 折線圖,看看誰比較厲害。

以下範例程式跑 200 回合的「隨機策略」:

 1: import gymnasium as gym
 2: import matplotlib.pyplot as plt
 3: import numpy as np
 4: 
 5: def run_episodes(strategy, n_episodes=200):
 6:     """跑 n_episodes 回合,回傳每回合的 reward 列表"""
 7:     env = gym.make('CartPole-v1')
 8:     rewards_list = []
 9:     for _ in range(n_episodes):
10:         obs, info = env.reset()
11:         total_reward = 0
12:         for t in range(500):
13:             if strategy == 'random':
14:                 action = env.action_space.sample()
15:             elif strategy == 'intuition':
16:                 # 直覺策略:桿子往左傾就往左推
17:                 action = 0 if obs[2] < 0 else 1
18:             obs, reward, terminated, truncated, info = env.step(action)
19:             total_reward += reward
20:             if terminated or truncated:
21:                 break
22:         rewards_list.append(total_reward)
23:     env.close()
24:     return rewards_list
25: 
26: # 只跑隨機策略
27: random_rewards = run_episodes('random')
28: print(f"隨機策略平均 reward: {np.mean(random_rewards):.1f}")
  1. 你的任務
    1. 修改上面的程式,再跑一次「直覺策略」( intuition )的 200 回合
    2. 把兩條折線畫在同一張圖上(用不同顏色),加上圖例(legend)
    3. 在圖的標題中顯示兩種策略的平均 reward
  2. 預期輸出
    隨機策略平均 reward: 22.x
    直覺策略平均 reward: 42.x
    
    (一張折線圖,藍色=隨機、橘色=直覺,直覺策略明顯比隨機高)
    

8.2. 作業二:爬山高手 — 微調的藝術

在教材的爬山演算法中,我們用 np.random.normal(0, 0.1, 5) 來產生微調的隨機值,其中 0.1 是「步幅」(step size)。步幅太大,權重會亂跳;步幅太小,搜尋速度太慢。

以下範例程式用步幅 0.1 來跑爬山演算法:

 1: import numpy as np
 2: import gymnasium as gym
 3: 
 4: def hill_climb(step_size, max_iter=300, seed=42):
 5:     """用指定步幅跑爬山演算法,回傳找到的最佳 reward 和歷史紀錄"""
 6:     env = gym.make('CartPole-v1')
 7:     np.random.seed(seed)
 8:     best_reward = 0
 9:     best_weights = np.random.rand(5)
10:     history = []
11: 
12:     for i in range(max_iter):
13:         new_weights = best_weights + np.random.normal(0, step_size, 5)
14:         obs, info = env.reset()
15:         total = 0
16:         for _ in range(500):
17:             action = 1 if np.dot(new_weights[:4], obs) + new_weights[4] >= 0 else 0
18:             obs, reward, terminated, truncated, info = env.step(action)
19:             total += reward
20:             if terminated or truncated:
21:                 break
22:         history.append(total)
23:         if total > best_reward:
24:             best_reward = total
25:             best_weights = new_weights
26:     env.close()
27:     return best_reward, history
28: 
29: best, hist = hill_climb(step_size=0.1)
30: print(f"步幅 0.1 → 最佳 reward: {best}")
  1. 你的任務

    測試三種不同的步幅:0.01、0.1、0.5,各跑一次爬山演算法(300 次迭代),比較哪個步幅最快找到好的策略。

    提示:用迴圈呼叫 hill_climb() 三次就好。

  2. 預期輸出
    步幅 0.01 → 最佳 reward: xxx
    步幅 0.10 → 最佳 reward: xxx
    步幅 0.50 → 最佳 reward: xxx
    
    結論:步幅 0.1 左右效果最好
    

8.3. 作業三:自創策略挑戰賽 — 你能打敗直覺嗎?

直覺策略只看了桿子角度( obs[2] ),但 observation 其實有 4 個數值。如果我們把更多資訊加入決策,應該能做得更好!

以下是直覺策略的程式,平均大概能撐 40~60 步:

 1: import gymnasium as gym
 2: import numpy as np
 3: 
 4: def my_strategy(obs):
 5:     """直覺策略:只看桿子角度"""
 6:     pos, vel, angle, ang_vel = obs
 7:     if angle < 0:
 8:         return 0  # 往左推
 9:     else:
10:         return 1  # 往右推
11: 
12: # 測試策略
13: env = gym.make('CartPole-v1')
14: rewards = []
15: for _ in range(100):
16:     obs, info = env.reset()
17:     total = 0
18:     for _ in range(500):
19:         action = my_strategy(obs)
20:         obs, reward, terminated, truncated, info = env.step(action)
21:         total += reward
22:         if terminated or truncated:
23:             break
24:     rewards.append(total)
25: env.close()
26: print(f"直覺策略平均 reward: {np.mean(rewards):.1f}")
  1. 你的任務

    修改 my_strategy() 函式,設計你自己的策略,目標是讓平均 reward 超過 100

    提示:

    • obs[0] 是車子位置,太偏左偏右都不好
    • obs[1] 是車子速度,跑太快要煞車
    • obs[2] 是桿子角度,快倒了要趕快救
    • obs[3] 是桿子角速度,正在加速倒的時候更緊急
    • 你可以組合多個條件,例如:「如果桿子角度 + 角速度 < 0,就往左推」
  2. 預期輸出
    我的策略平均 reward: 1xx.x (超過 100 就算過關!)
    

Footnotes:

Author: Yung-Chin Yen

Created: 2026-02-14 Sat 22:29