【逆强化学习-1】学徒学习(Apprenticeship Learning)
文章目錄
- 0.引言
- 1.算法原理
- 2.仿真環境
- 3.運行
- 4.補充(學徒學習+深度Q網絡)
本文為逆強化學習系列第1篇,沒有看過逆強化學習介紹的那篇的朋友,可以看一下:
Inverse Reinforcement Learning-Introduction 傳送門
0.引言
\qquad可以說學徒學習(簡稱App)是最早的一種逆強化學習的方法,如果看過最原始那篇論文的讀者可能會覺得這個方法怎么這么晦澀難懂,但其實看完Code你會覺得這個Algorithm還挺粗糙的,這還是一件挺矛盾的事情。由于IRL-Introduction的論文介紹中沒有該論文的下載鏈接,因此將其提供如下:
本文完整代碼鏈接:CSDN下載
論文PDF鏈接:論文PDF
1.算法原理
\qquad在IRL-Introduction介紹過了,該方法是2004年出現的,那時候DL還不是很受歡迎,因此作者采用的是線性函數的方法計算Reward的。APP方法從Observation中提取特征,計算特征期望,再將reward作為特征期望的線性函數。
\qquad提取特征的函數可以是非線性的,對算法沒有任何影響,一般記為?(s)\phi(s)?(s),而軌跡π\piπ的特征期望μ(π)\mu(\pi)μ(π)在paper中的計算方法即為
μ(π)=∑t=0∞γt?(st)\mu(\pi)=\sum_{t=0}^{\infty}\gamma^t\phi(s_t)μ(π)=t=0∑∞?γt?(st?)
\qquad如果是環境在互動過程中具有隨機性,則求得的應該是特征期望的數學期望,用均值近似數學期望可以得到(其中n為軌跡數目,st(i)s_t^{(i)}st(i)?為第iii條軌跡的第ttt個狀態):
μ(π)=1n∑i=1n∑t=0∞γt?(st(i))\mu(\pi)=\frac{1}{n}\sum_{i=1}^{n}\sum_{t=0}^{\infty}\gamma^t\phi(s_t^{(i)})μ(π)=n1?i=1∑n?t=0∑∞?γt?(st(i)?)
在實際求解中,計算有限的時域ttt近似即可。
\qquad學徒學習的基本思想是尋找Reward使得所有的Agent產生的Reward都小于Expert的Reward的,再用這個reward去訓練Agent。但為了防止Margin走向0和無窮大兩個極端,在約束條件時還加入了一些限制。
該算法的步驟如下:
maxt,wts.t.{wTμ(πE)≥wTμ(πg)+t,g=0,1,...,i?1∥w∥2≤1\begin{aligned}& max_{t,w}\quad t\\ & s.t.\begin{cases}w^T\mu(\pi_E)\geq w^T\mu(\pi_g)+t,g=0,1,...,i-1 \\[2ex] \lVert w\rVert_2\leq1 \end{cases} \end{aligned} ?maxt,w?ts.t.????wTμ(πE?)≥wTμ(πg?)+t,g=0,1,...,i?1∥w∥2?≤1??
其他的步驟都沒什么,主要是步驟(5),步驟(5)雖然不是線性規劃,還帶有一個令人討厭的非線性約束(意味著只能采用拉格朗日乘子法),如果大家仔細分析這個問題就會發現,損失函數只和t有關,而www向量雖然有模長的限制,但是方向是可以隨意變化的,那么為了讓Margin最大,肯定要取和μ(πE)?μ(πg)\mu(\pi_E)-\mu(\pi_g)μ(πE?)?μ(πg?)平行的方向,然后取到最大模長1,由此步驟(5)就迎刃而解。對于g=0,1,2,...,ig=0,1,2,...,ig=0,1,2,...,i均成立的問題,只需要計算取Margin最小的w?w^*w?以滿足ttt即可:
wg?=μ(πE)?μ(πg)∥μ(πE)?μ(πg)∥2,(g=0,1,...,i)w?=arg?min?g=0,1,...,iwg?[μ(πE)?μ(πg)]t?=w?(μ(πE)?μ(πg))\begin{array}{l} w^*_g=\frac{\mu(\pi_E)-\mu(\pi_g)}{\lVert\mu(\pi_E)-\mu(\pi_g)\rVert_2},(g=0,1,...,i)\\[2ex] w^*=\argmin_{g=0,1,...,i} {w^*_g[\mu(\pi_E)-\mu(\pi_g)]}\\[2ex] t^*=w^*(\mu(\pi_E)-\mu(\pi_g))\\ \end{array}wg??=∥μ(πE?)?μ(πg?)∥2?μ(πE?)?μ(πg?)?,(g=0,1,...,i)w?=g=0,1,...,iargmin?wg??[μ(πE?)?μ(πg?)]t?=w?(μ(πE?)?μ(πg?))?
當然了,其實真正有用的只是w?w^*w?罷了(再確切一些,只是w?w^*w?的方向有用)。Python代碼中用的不是這個公式啊,但是它的解和公式解出來是一樣的。
2.仿真環境
\qquad仿真環境使用的是gym,在IRL-Introduction也提到過這是需要Linux環境的,另外python的版本不能超過3.7(超過之后雖然這個代碼可以運行,但是其他很多gym的環境都不可以)。
仿真環境說明-鏈接
\qquadMountainCar-v是連續的狀態空間,離散的動作空間的仿真環境,Action其實就是小車加速度(只能去-1,0,1),Observation是小車位置和速度,Boundary見上圖。真實的Reward是每隔一秒就會扣1分,知道小車到達終點,走左側坡道不扣分。當然我們只是用它來做evaluation,在train中是不起作用的。
\qquad學徒學習參考了github項目的代碼(稍微有點問題,已經在博客中修正并且另申請了一個branch)
鏈接:
Github代碼網址
\qquadpython環境依賴:numpy,gym,readchar
3.運行
\qquadGithub上的代碼是基于Q-Table的,沒有顯卡加速的,運行60000代大概需要20分鐘左右。由于貼出了GitHub上的下載鏈接,這里就不粘貼代碼了,如果發現不能下載的朋友也不用著急,我已經上傳到資源了(下載是免費的):
下載鏈接
\qquad直接運行mountaincar/app/test.py即可查看效果,trian.py是訓練(會把之前保存的Agent給覆蓋掉),結果保存在results文件夾下。
\qquadIRL的專家軌跡保存在expert_demo文件夾下,有一個make_expert.py是專門用來產生專家軌跡的(人工游戲產生),里面指定了三個按鍵用來采取左,停,右的動作(加速度)。事實上這個代碼有那么一點bug,我的電腦上是不能正常運行的,所以我重新寫了一個,運行的時候需要在程序根目錄下運行。但是它產生的軌跡包含的step數目并不是每次都相等的,只需要修改app.py中有關demonstration遍歷的代碼即可。
make_expert2.py \;\;\;操作:ASD按鍵
import gym import readchar import numpy as np import pickle as pkl # MACROS Push_Left = 0 No_Push = 1 Push_Right = 2# Key mapping arrow_keys = {'A': Push_Left,'S': No_Push,'D': Push_Right} end_key = 'Q' env = gym.make('MountainCar-v0')end_flag = False trajectories = [] for episode in range(20): # n_trajectories : 20trajectory = []env.reset()print("episode:{}".format(episode))score = 0while True: env.render()key = readchar.readkey().upper()if key not in arrow_keys.keys():print('invalid key:{}'.format(key))if key == end_key:end_flag = Truebreakaction = arrow_keys[key]state, reward, done, _ = env.step(action)score += rewardif state[0] >= env.env.goal_position: trajectory.append((state[0], state[1], action))env.reset()print('mission accomplished! env is reset.')breaktrajectory.append((state[0], state[1], action))if end_flag:print('end!')breaktrajectory_numpy = np.array(trajectory, float)print("trajectory_numpy.shape", trajectory_numpy.shape)print("score:{}".format(score))trajectories.append(trajectory) # don't need to seperate trajectories env.close() if not end_flag:with open('expert_demo.p',"wb")as f:pkl.dump(trajectories,f)app.py修改的部分【expert_feature_expectation】
def expert_feature_expectation(feature_num, gamma, demonstrations, env):feature_estimate = FeatureEstimate(feature_num, env)feature_expectations = np.zeros(feature_num)for demo_num,traj in enumerate(demonstrations):for demo_length in range(len(traj)):state = demonstrations[demo_num][demo_length]features = feature_estimate.get_features(state)feature_expectations += (gamma**(demo_length)) * np.array(features)feature_expectations = feature_expectations / len(demonstrations)學徒學習+Q-table的訓練結果
4.補充(學徒學習+深度Q網絡)
\qquad除此之外,我還嘗試了將Q-Table換成DQN加以訓練,由于DQN的輸入state不像Q-Table一樣是有限的,因此訓練的時候非常不穩定,在多次調參之后,獲得了一個差強人意的結果。
下面是gym仿真的gif截圖
\qquad需要增加和替換的代碼主要為dqn,train_dpn和test_dqn三個文件,另外只需對app.py添加一個計算dqn輸出的feature expectation即可運行。篇幅原因,這里僅給出dqn的代碼,train和test的過程讀者模仿原github項目中的train.py和test.py即可順利完成。(全套代碼鏈接,限時免費,本文點贊過一百將設為永久免費)
dqn.py
import torch as T import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import numpy as npdefault_dqn_paras=dict(gamma=0.99,epsilon=0.1,lr=5e-3,input_dims=2,\batch_size=128,n_actions=3,max_mem_size=int(4e3),\eps_end=0.01,eps_dec=1e-4,replace_target=50,weight_decay=5e-4) class DeepQNetwork(nn.Module):def __init__(self, lr, input_dims, fc1_dims, fc2_dims, n_actions,weight_decay):super(DeepQNetwork, self).__init__()self.input_dims = input_dimsself.fc1_dims = fc1_dimsself.fc2_dims = fc2_dimsself.n_actions = n_actionsself.fc1 = nn.Linear(self.input_dims, self.fc1_dims)self.fc2 = nn.Linear(self.fc1_dims, self.fc2_dims)self.fc3 = nn.Linear(self.fc2_dims, self.n_actions)self.optimizer = optim.Adam(self.parameters(), lr=lr,weight_decay=weight_decay)self.loss = nn.MSELoss()self.device = T.device('cuda:0' if T.cuda.is_available() else 'cpu')self.to(self.device)def forward(self, state):x = F.relu(self.fc1(state),inplace=True)x = F.relu(self.fc2(x),inplace=True)actions = self.fc3(x)return actionsclass Agent():def __init__(self, gamma, epsilon, lr, input_dims, batch_size, n_actions,max_mem_size=100000, eps_end=0.05, eps_dec=5e-4, replace_target=100, weight_decay=1e-4):self.gamma = gammaself.epsilon = epsilonself.eps_min = eps_endself.eps_dec = eps_decself.training = Trueself.lr = lrself.action_space = [i for i in range(n_actions)]self.mem_size = max_mem_sizeself.batch_size = batch_sizeself.mem_cntr = 0self.iter_cntr = 0self.replace_target = replace_targetself.Q_eval = DeepQNetwork(lr, n_actions=n_actions, input_dims=input_dims,fc1_dims=32, fc2_dims=32, weight_decay=weight_decay)self.Q_next = DeepQNetwork(lr, n_actions=n_actions, input_dims=input_dims,fc1_dims=32, fc2_dims=32, weight_decay=weight_decay)self.state_memory = np.zeros((self.mem_size, input_dims), dtype=np.float32)self.new_state_memory = np.zeros((self.mem_size, input_dims), dtype=np.float32)self.action_memory = np.zeros(self.mem_size, dtype=np.int32)self.reward_memory = np.zeros(self.mem_size, dtype=np.float32)self.terminal_memory = np.zeros(self.mem_size, dtype=np.bool)self.Q_eval.eval()self.Q_next.eval()def store_transition(self, state, action, reward, state_, terminal):index = self.mem_cntr % self.mem_sizeself.state_memory[index] = stateself.new_state_memory[index] = state_self.reward_memory[index] = rewardself.action_memory[index] = actionself.terminal_memory[index] = terminalself.mem_cntr += 1@T.no_grad()def choose_action(self, observation):"""Epsilon Greedy ExplorationArgs:observation ([iterable]): observation vectorReturns:action: element in env.action_space"""self.Q_eval.eval()if np.random.random() > self.epsilon or (not self.training):state = T.tensor(observation,dtype=T.float32).detach().to(self.Q_eval.device)actions = self.Q_eval.forward(state)action = T.argmax(actions).item()else:action = np.random.choice(self.action_space)return action@T.enable_grad()def learn(self):if self.mem_cntr < self.batch_size:returnself.Q_eval.train()self.Q_next.eval()self.Q_eval.optimizer.zero_grad()max_mem = min(self.mem_cntr, self.mem_size)batch = np.random.choice(max_mem, self.batch_size, replace=False)batch_index = np.arange(self.batch_size, dtype=np.int32)state_batch = T.tensor(self.state_memory[batch]).detach().requires_grad_(True).to(self.Q_eval.device)new_state_batch = T.tensor(self.new_state_memory[batch]).detach().requires_grad_(True).to(self.Q_eval.device)action_batch = self.action_memory[batch]reward_batch = T.tensor(self.reward_memory[batch]).detach().requires_grad_(True).to(self.Q_eval.device)terminal_batch = T.tensor(self.terminal_memory[batch]).to(self.Q_eval.device)q_eval = self.Q_eval.forward(state_batch)[batch_index, action_batch]q_next = self.Q_next.forward(new_state_batch).detach()q_next[terminal_batch] = 0.0 # when state is terminal state, value function is zeroq_target = reward_batch + self.gamma*T.max(q_next,dim=1)[0]loss = self.Q_eval.loss(q_target, q_eval).to(self.Q_eval.device)loss.backward()self.Q_eval.optimizer.step()self.iter_cntr += 1self.epsilon = max(self.epsilon - self.eps_dec, self.eps_min)if self.iter_cntr % self.replace_target == 0:self.Q_next.load_state_dict(self.Q_eval.state_dict())總結
以上是生活随笔為你收集整理的【逆强化学习-1】学徒学习(Apprenticeship Learning)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TextArea里Placeholder
- 下一篇: 基于双线性插值的图像旋转原理及MATLA