獲取更多資訊,趕快關注上面的公衆號吧!
【強化學習系列】
- 第一章 強化學習及OpenAI Gym介紹-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第二章 馬爾科夫決策過程和貝爾曼等式-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第三章 動態規劃-基於模型的RL-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第四章 蒙特卡洛方法-強化學習理論學習與代碼實現(強化學習導論第二版)
- 第五章 基於時序差分和Q學習的無模型預測與控制-強化學習理論學習與代碼實現(強化學習導論第二版)
- (本文)第六章 函數逼近-強化學習理論學習與代碼實現(強化學習導論第二版)
文章目錄
第六章 函數逼近
在前面的章節中我們介紹的都是表格型強化學習,但是當問題的狀態空間很大,表格型強化學習需要爲每一個狀態存儲其每一個可選動作的估計值,勢必需要很大的內存佔用,例如西洋雙陸棋的狀態空間爲,計算機圍棋的狀態空間爲,甚至像直升機控制這樣的問題其狀態空間爲無限大。那麼該如何才能將之前講的無模型預測和控制擴展到這種情況呢?這就是本部分要講的函數近似或逼近。
6.1 學習目標
- 理解相較於查表法函數逼近的動機;
- 理解如何將函數逼近集成到現有的算法中;
- 代碼實現線性函數逼近的Q學習。
6.2 值函數近似
之前的內容是通過查表法來表達值函數,一種方式就是記錄每個狀態的值V(s),另一種就是狀態-動作值函數Q(s,a),但是面對大規模MDPs時,可能會有太多的狀態或動作而無法內存存儲,哪怕能存儲,對於這麼大的一張表格進行學習,速度也是很慢的。因此一種簡單的方式就是建立參數近似函數來逼近真正的價值。
其中表示權重,或者說是逼近器的參數,通過對特徵基進行加權求和。
通過這樣的方式,只要給定狀態或狀態-動作對,就能給出相應的近似值,一方面減少了內存佔用,更重要的是可以預測未知狀態下的價值,從而大大增強泛化性。而可以藉助MC或TD學習進行更新。
值函數近似就像是一個黑盒子,只要給定輸入就能得到輸出,根據輸入和輸出的不同,可以分爲三種類型,如圖1所示。
- 輸入狀態,輸出值函數;
- 輸入狀態和動作,輸出狀態-動作值函數;
- 輸入狀態,輸出每個動作的狀態-動作值函數。
應該選擇什麼樣的函數逼近器呢?目前有很多方式可以實現,如下所示,但一般考慮選擇可微分的函數逼近器,例如下面的線性特徵組合和神經網絡。除此之外,還需要一種適用於非平穩、非獨立同分布數據的訓練方法。
- 線性特徵組合
- 神經網絡
- 決策樹
- 最近鄰域
- 傅里葉/小波基
- ……
6.2 增量式方法
6.2.1 梯度下降
梯度下降
設爲參數向量的可微函數,的梯度定義爲對在的各個維度上分別進行偏微分,即
爲了找到的局部最小值,只需要沿着負梯度方向更新即可:
其中爲步長參數。
隨機梯度下降的值函數近似
一個好的逼近器就是要儘量減少近似值和真實值之間的誤差,因此函數逼近的目標就是找到一個參數向量,使得近似值和之間的均方誤差最小:
通過使用梯度下降,可以按照如下進行參數更新:
而在實際更新時並不是使用全梯度下降,而是隨機採樣一個狀態,去評判該狀態下的真實價值與近似值之間的誤差:
6.2.2 線性函數逼近
特徵向量
可以使用一組特徵向量來表達狀態,特徵向量中每個元素都是狀態的一個具體表現:
例如:
- 機器人與地標的距離
- 股票市場的走勢
- 象棋中車和卒的局面
線性值函數近似
使用特徵向量的最簡單的方法就是對這些特徵進行線性組合來表達近似值函數:
同樣目標函數是真實值與近似值之間的均方誤差:
同樣採用隨機梯度下降,可以得到如下很簡單的參數更新規則:
因此,在線性值函數近似情況下,更新規則可以概括爲:
Update step-size prediction error feature value
查表特徵
查表實際上是線性值函數近似的一種特殊情況,查表特徵可以表示爲:
對於狀態1,如果處在狀態1,則對應位置的值爲1否則爲0,其他狀態以此類推(類似於獨熱編碼)。
只要給定參數向量,就可以通過查表特徵與權重的點積計算近似值函數。
6.2.3 增量預測算法
增量預測算法
前面講的兩種方法(梯度下降和線性逼近)其實有一個前提,就是真實價值函數是已知的,這樣就變成了監督學習,但是很遺憾的是,真實值函數往往不能提前已知,並且強化學習中也只能給出獎勵值,最基本的方法就是利用之前的蒙特卡洛方法和時序差分方法來設立真實值函數的目標。
- 對於MC,目標就是回報值,則對應更新爲:
- 對於TD(0),目標爲TD目標:
- 對於TD(),目標爲回報:
基於值函數近似的蒙特卡洛
其實上面講的設定目標的過程和監督學習很相近,當使用蒙特卡洛方法時,主要使用的是return,這個過程就是完善建立訓練數據,但這個過程是逐漸累積完成的,首先看到了狀態,在該狀態下有一個軌跡,然後得到一個回報,接下來狀態得到回報,以此類推直到結束狀態。
現在需要做的基本上和監督學習一下,把上述過程生成的數據看成真實數據,只需要調整值函數近似模型去不斷逼近這些數值即可。
例如使用線性蒙特卡洛策略評估的更新方式如下:
在蒙特卡洛中是無偏的,通過隨機梯度下降,蒙特卡洛評估總能收斂到最優,但是學習過程會比較漫長。
基於值函數近似的TD學習
TD學習同樣採用了相同的思想,只不過現在的TD目標是有偏的,但仍然可以將監督學習應用以下訓練數據:
例如對於線性TD(0),更新如下:
線性TD(0)能收斂或接近全局最優。
基於值函數近似的TD()
也是真實值函數的有偏估計,可以將監督學習應用到以下訓練數據:
例如對於前向視角線性TD(),更新如下:
對於後向視角線性TD(),更新如下:
但是對於前向和後向線性TD()是等效的。
6.2.4 增量控制算法
基於值函數近似的控制
控制部分採用廣義迭代策略的概念,先進行策略評估,這裏運用的是近似策略評估,然後進行-greedy策略改進。
動作值函數近似
這裏同樣使用參數向量來近似表達動作值函數:
目標函數就是最小化近似動作值函數與真實動作值函數之間的均方誤差:
使用隨機梯度下降進行參數更新,找到局部最優:
線性動作值函數近似
使用特徵向量表達狀態和動作:
通過特徵的加權線性組合可以表達動作值函數:
隨機梯度下降更新如下:
增量控制算法
- 對於MC,目標是,更新如下:
- 對於TD(0),目標是TD目標,更新如下:
- 對於前向,目標爲動作值-回報,更新如下:
- 對於後向,等效的更新如下:
6.3 代碼實現
代碼實現部分我們選用Mountain Car作爲環境,環境使用位置和速度表達狀態,動作有3個:向左,不動和向右。下面給出了線性逼近的Q學習算法代碼。
import gym
import itertools
import matplotlib
import numpy as np
import sys
import sklearn.pipeline
import sklearn.preprocessing
if "../" not in sys.path:
sys.path.append("../")
from Lib import plotting
from sklearn.linear_model import SGDRegressor
from sklearn.kernel_approximation import RBFSampler
matplotlib.style.use('ggplot')
env = gym.envs.make("MountainCar-v0")
# 特徵預處理:歸一化爲均值爲0,方差爲1
# 從觀察空間中採樣部分樣本
observation_examples = np.array([env.observation_space.sample() for x in range(10000)])
scaler = sklearn.preprocessing.StandardScaler()
scaler.fit(observation_examples)
# 用於將狀態轉換成特徵表達
# 使用不同方差的RBF核來覆蓋空間的不同部分
featurizer = sklearn.pipeline.FeatureUnion([
("rbf1", RBFSampler(gamma=5.0, n_components=100)),
("rbf2", RBFSampler(gamma=2.0, n_components=100)),
("rbf3", RBFSampler(gamma=1.0, n_components=100)),
("rbf4", RBFSampler(gamma=0.5, n_components=100))
])
featurizer.fit(scaler.transform(observation_examples))
class Estimator():
"""
值函數逼近器.
"""
def __init__(self):
# 爲環境的動作空間中的每個動作創建一個單獨的模型。或者,我們可以以某種方式將動作編碼到特性中,但是這樣更容易編碼。
self.models = []
for _ in range(env.action_space.n):
model = SGDRegressor(learning_rate="constant")
# 需要調用一次partial_fit來初始化模型
# 或者在預測時獲取NotFittedError
model.partial_fit([self.featurize_state(env.reset())], [0])
self.models.append(model)
def featurize_state(self, state):
"""
返回狀態的特徵化表達.
"""
scaled = scaler.transform([state])
featurized = featurizer.transform(scaled)
return featurized[0]
def predict(self, s, a=None):
"""
進行值函數預測.
參數:
s: 需要預測的狀態
a: (可選) 需要預測的動作
返回值:
如果給定了動作a,則返回一個數值作爲預測結果
如果沒有給定a,則返回一個向量來預測環境中的所有動作,其中pred[i]爲對動作i的預測
"""
features = self.featurize_state(s)
if not a:
return np.array([m.predict([features])[0] for m in self.models])
else:
return self.models[a].predict([features])[0]
def update(self, s, a, y):
"""
給定狀態和動作,更新逼近器參數以靠近目標y
"""
features = self.featurize_state(s)
self.models[a].partial_fit([features], [y])
def make_epsilon_greedy_policy(estimator, epsilon, nA):
"""
根據給定的Q函數逼近器和epsilon,創建epsilon貪婪策略.
參數:
逼近器: 返回給定狀態下的q值
epsilon: 隨機選擇動作的概率between 0 and 1
nA: 環境中動作數量
返回值:
返回一個函數,以觀察爲參數,以長度爲nA的numpy數組的形式返回每個動作的概率
"""
def policy_fn(observation):
A = np.ones(nA, dtype=float) * epsilon / nA
q_values = estimator.predict(observation)
best_action = np.argmax(q_values)
A[best_action] += (1.0 - epsilon)
return A
return policy_fn
def q_learning(env, estimator, num_episodes, discount_factor=1.0, epsilon=0.1, epsilon_decay=1.0):
"""
使用函數逼近進行離策略TD控制的Q學習.
遵循epsilon貪婪策略以尋找最優貪婪策略.
參數:
env: OpenAI環境.
estimator: 動作值函數逼近器
num_episodes: 迭代次數.
discount_factor: Gamma折扣因子.
epsilon: 隨機選擇動作的概率betwen 0 and 1.
epsilon_decay: 每個片段中,epsilon都以該因子進行衰減
返回值:
一個片段狀態對象,包括兩個numpy數組,用於分別存放片段長度和片段獎勵.
"""
# 進行必要的統計
stats = plotting.EpisodeStats(
episode_lengths=np.zeros(num_episodes),
episode_rewards=np.zeros(num_episodes))
for i_episode in range(num_episodes):
# 正在遵循的策略
policy = make_epsilon_greedy_policy(
estimator, epsilon * epsilon_decay ** i_episode, env.action_space.n)
last_reward = stats.episode_rewards[i_episode - 1]
sys.stdout.flush()
# 重置環境
state = env.reset()
# 只針對SARSA有用
next_action = None
# 環境中迭代執行每一步
for t in itertools.count():
# 選擇動作
# 如果使用的時SARSA,next_action在前一步已經確定了
if next_action is None:
action_probs = policy(state)
action = np.random.choice(np.arange(len(action_probs)), p=action_probs)
else:
action = next_action
# 單步執行
next_state, reward, done, _ = env.step(action)
# 更細統計
stats.episode_rewards[i_episode] += reward
stats.episode_lengths[i_episode] = t
# TD更新
q_values_next = estimator.predict(next_state)
# 學習Q-Learning的TD目標
td_target = reward + discount_factor * np.max(q_values_next)
# 使用下面的代碼進行SARSA在策略控制
# next_action_probs = policy(next_state)
# next_action = np.random.choice(np.arange(len(next_action_probs)), p=next_action_probs)
# td_target = reward + discount_factor * q_values_next[next_action]
# 使用目標更新函數逼近器
estimator.update(state, action, td_target)
print("\rStep {} @ Episode {}/{} ({})".format(t, i_episode + 1, num_episodes, last_reward), end="")
if done:
break
state = next_state
return stats
estimator = Estimator()
# 注意: 對於Mountain Car遊戲,不必保證epsilon>0.0
# 因爲對所有狀態的初始估計太過樂觀,從而導致對所有狀態進行探索.
stats = q_learning(env, estimator, 100, epsilon=0.0)
plotting.plot_cost_to_go_mountain_car(env, estimator)
plotting.plot_episode_stats(stats, smoothing_window=25)
最終學習到的位置和速度與值函數的關係如下。
整體上隨着迭代次數的增多,小車爬上山坡需要的部屬逐漸下降,如下所示。
隨着迭代次數的增加,片段獎勵呈現上升趨勢,如下所示。