跳轉到

Q-learning 實作範例(ㄧ)

目標

這次我們將使用表格型 Q-learning 方法來實現一個簡單的範例。這個範例的環境是一個一維的世界,世界的最右邊有一個寶藏,探索者只要找到了這個寶藏,並嘗到了獎勵的甜頭,之後就會記住如何找到寶藏的最佳路徑,這就是他通過強化學習所掌握的行為策略。

在這個範例中,代理人 "o" 位於這個一維世界的左邊,而寶藏位於最右邊。運行這個程式,你將會看到代理人如何逐步改進他的策略,最終找到寶藏。

-o---T
# T 是寶藏的位置, o 是探索者的位置

程式碼說明

這段程式碼實現了一個簡單的強化學習範例,使用的是 Q-learning 方法。目標是讓代理人在一維世界中找到寶藏(位於最右邊的位置)。我們將逐步解析程式碼並解釋其意義:

1. 參數定義

首先定義我們的環境,包括狀態的數量、行為選項、學習參數等等。

N_STATES = 6   # 這是一維世界的長度,代表總共有6個狀態
ACTIONS = ['left', 'right']     # 可選擇的行為,向左或向右
EPSILON = 0.9   # 這是貪婪策略的參數,決定選擇行為的隨機性
ALPHA = 0.1     # 學習率,更新Q值時的步伐大小
GAMMA = 0.9    # 折扣因子,決定未來獎勵的影響程度
MAX_EPISODES = 13   # 最大的訓練回合數
FRESH_TIME = 0.3    # 每次行動後的更新間隔,讓動畫看起來更流暢

2. 建立 Q 表

對於表格型 Q-learning,我們必須將所有的 Q 值(行為值)儲存在 q_table 中,更新 q_table 其實就是在更新代理人的行為準則。q_table 的索引對應的是所有的狀態(探索者的位置),而欄位則對應的是所有的行為(探索者的動作)。

def build_q_table(n_states, actions):
    table = pd.DataFrame(
        np.zeros((n_states, len(actions))),     # 初始化 Q 表為零
        columns=actions,    # 行為名稱
    )
    return table

# q_table:
"""
   left  right
0   0.0    0.0
1   0.0    0.0
2   0.0    0.0
3   0.0    0.0
4   0.0    0.0
5   0.0    0.0
"""

Note

這段程式碼用於建立 Q 表,其中每一個狀態對應兩個行為(左和右),所有的 Q 值初始設置為 0。

3. 選擇行為

接著定義探索者如何選擇行為。這裡我們引入了 epsilon-greedy 的概念。因為在初期階段,隨機探索環境通常比固定的行為模式更有效,這是一個累積經驗的過程,我們希望探索者在這時不會過於貪婪。因此,EPSILON 這個值就是用來控制探索者的貪婪程度。隨著探索時間的增加,EPSILON 可以逐漸提高(也就是越來越貪婪),但在這個例子中,我們固定 EPSILON 為 0.9,這意味著 90% 的時間選擇最優策略,10% 的時間進行隨機探索。

def choose_action(state, q_table):
    state_actions = q_table.iloc[state, :]
    if (np.random.uniform() > EPSILON) or ((state_actions == 0).all()):
        action_name = np.random.choice(ACTIONS)  # 隨機選擇行為
    else:
        action_name = state_actions.idxmax()    # 選擇當前 Q 值最大的行為
    return action_name

Note

這段程式碼是代理人如何選擇行為的邏輯。如果代理人根據EPSILON的值進行隨機行動,則會選擇隨機行為,否則就根據當前狀態選擇最大 Q 值的行為。

4. 環境回饋

做出行為後,環境會對我們的行為給出回饋,這個回饋包含下一個狀態 (S_) 以及探索者在上一個狀態 (S) 執行動作 (A) 所獲得的獎勵 (R)。這裡的規則定義是,只有當探索者 "o" 移動到終點 T 時,才會獲得唯一的獎勵,獎勵值為 R=1,其他情況則不會有任何獎勵。

def get_env_feedback(S, A):
    if A == 'right':
        if S == N_STATES - 2:   # 當到達寶藏
            S_ = 'terminal'  # 結束
            R = 1  # 獎勵
        else:
            S_ = S + 1
            R = 0
    else:   # 向左移動
        R = 0
        if S == 0:
            S_ = S  # 撞牆不移動
        else:
            S_ = S - 1
    return S_, R

Note

這是代理人與環境互動的部分,根據代理人選擇的行為,給予相應的狀態變化和獎勵。向右移動並到達終點則獲得獎勵。

5. 更新環境

def update_env(S, episode, step_counter):
    env_list = ['-']*(N_STATES-1) + ['T']   # 環境設定,寶藏在最右邊
    if S == 'terminal':
        interaction = 'Episode %s: total_steps = %s' % (episode+1, step_counter)
        print('\r{}'.format(interaction), end='')
        time.sleep(2)
        print('\r                                ', end='')
    else:
        env_list[S] = 'o'
        interaction = ''.join(env_list)
        print('\r{}'.format(interaction), end='')
        time.sleep(FRESH_TIME)

Note

這裡負責更新並顯示環境,模擬代理人的移動。

6. 主循環

最關鍵的部分就在這裡,這張圖展示了你如何實作強化學習(RL)方法。從左圖的算法框架到右圖的程式碼,都體現了整個 Q-learning 過程的具體步驟。

首先,初始化 Q 表格,並在每個回合中,根據現有的 Q 表選擇行為(A),然後通過與環境的互動獲得回饋(R 和 S')。接下來,我們計算 Q(S1, A1) 的「現實」值和「估計」值,現實值是根據回饋和未來預期收益來計算,而估計值則取自當前的 Q 表。最後,我們根據「現實」與「估計」的差距來更新 Q 值,並不斷優化決策過程。

def rl():
    q_table = build_q_table(N_STATES, ACTIONS)
    for episode in range(MAX_EPISODES):
        step_counter = 0
        S = 0
        is_terminated = False
        update_env(S, episode, step_counter) # 更新環境
        while not is_terminated:

            A = choose_action(S, q_table)
            S_, R = get_env_feedback(S, A)  # 代理人與環境互動
            q_predict = q_table.loc[S, A] # 估計
            if S_ != 'terminal':
                q_target = R + GAMMA * q_table.iloc[S_, :].max()   # 現實
            else:
                q_target = R     # 終點(回合结束)
                is_terminated = True

            q_table.loc[S, A] += ALPHA * (q_target - q_predict)  # 更新 Q 值: 差距=現實-估計
            S = S_  # 更新狀態
            update_env(S, episode, step_counter+1) # 更新環境
            step_counter += 1
    return q_table

Note

這段是核心的 Q-Learning 流程,代理人通過選擇行為、與環境互動並獲得回饋,逐步更新Q表。通過多次訓練,不斷調整 Q 值,最終找到到達寶藏的最佳路徑。

設定好所有的評估和更新準則後,我們就可以開始進行訓練了。將探索者放入環境中,讓它自己去探索並學習吧!

if __name__ == "__main__":
    q_table = rl()
    print('\r\nQ-table:\n')
    print(q_table)