Xin chào các bạn. Tiếp nối series về Reinforcement Learning (RL), hôm nay mình xin giới thiệu một ví dụ đơn giản có thể coi như là “Hello world” của RL.
1. Giới thiệu
Trong bài trước Đôi điều cơ bản về học tăng cường mình đã giới thiệu một số khái niệm của RL. Trong đó môi trường và các trạng thái, phần thưởng là những yếu tố quan trọng. Để cho có thể học được những chiến lược tối ưu hoặc tìm được chuỗi hành động tối ưu, tác nhân phải thử nhiều lần và học từ các lần thử đó, và rất khó để cho tác nhân có thể học được trong thế giới thực. Do đó việc mô phỏng lại môi trường là rất quan trọng. Rất may OpenAI đã có một opensource mang tên Gym để mô phỏng lại một số môi trường đơn giản. Trong bài này mình sẽ sử dụng Q-Learning để giải quyết game mountain-car trong thư viện gym.
Với mountain-car, mục tiêu của chúng ta là điều khiển xe ô tô đến lá cờ:
Ta xét các thuộc tính của game này:
- Tập trạng thái S=R2S = R^{2} bao gồm vị trí của xe trên trục ngang và vận tốc của xe, chiều dương từ trái sang phải. Cụ thể hơn:
S={(x,v)∣x,v∈R;−1.2≤x≤0.6;−0.07≤v≤0.07}S = left { (x ,v ) | x,v in R; -1.2 leq x leq 0.6; -0.07 leq v leq 0.07 right }
- Tập hành động của xe:
A={0;1;2}mathcal{A} = left {0; 1; 2 right }
Với a∈Aa in mathcal{A}:
- a = 0: tăng tốc sang trái
- a = 1: Không tăng tốc
- a = 2: Tăng tốc sang phải
- Phần thưởng: Với mỗi hành động, nếu chưa tới đích (x<0.5)(x < 0.5) nhận được phần thưởng -1, nếu không thì nhận được 0 và kết thúc game
- Trạng thái khởi tạo x0∈[−0.6,−0.4]x_0 in [-0.6, -0.4] và v0=0v_0=0
- Trạng thái kết thúc: Đến được cờ tại x=0.5x=0.5 hoặc tổng phần thưởng đạt đến -200
Code import thư viện và khởi tạo môi trường:
# importimport gym
import time
import numpy as np
import IPython.display
from tqdm.notebook import tqdm
# create environment
env = gym.make('MountainCar-v0')
Hãy cùng chạy thử 1 ví dụ:
s = env.reset()# reset to starting State
action_list =list(range(env.action_space.n))whileTrue:
a = np.random.choice(action_list)# random an action
s, r, done, _ = env.step(a)# perform action, get state, reward, is terminatedif done:break
env.render()
time.sleep(0.05)
2. Code thôi
2.1 Xây dựng Q-function
Hãy cùng xét công thức cuối cùng của bài viết trước:
Q(s,a)=(1−α)Q(s,a)+αEs∼P(s′∣s,a)(R(s,a)+γmaxa∈AQ∗(s,a))Q(s,a) = (1-alpha) Q(s,a) + alpha E_{s sim P(s’ | s , a)} left (R(s, a) + gammamax _ {a in mathcal{A}}Q^*(s,a) right )
Trong trường hợp chúng ta đang xét, do thư viện gym mô phỏng lại hiện tượng vật lý từ thực tế nên tại mỗi trạng thái ss, ta thực hiện hành động aa sẽ đưa ta đến trạng thái s′s’ xác định. Do đó hàm QQ trong trường hợp này được viết lại:
Q(s,a)=(1−α)Q(s,a)+α(R(s,a)+γmaxa∈AQ∗(s,a))(1)tag{1}
Q(s,a) = (1-alpha) Q(s,a) + alpha left (R(s, a) + gammamax _ {a in mathcal{A}}Q^*(s,a) right )
Trong game này,không gian trạng thái có 2 chiều (xx và vv), tập hành động chỉ có 3 hành động, do đó chúng ta có thể mô hình hàm QQ bằng một bảng Q−tableQ-table, với các cột tương ứng với các giá trị khác nhau của vận tốc vv, các cột tương ứng với các giá trị khác nhau của tọa độ xx. Tuy nhiên x,v∈Rx,v in R mà số cột và hàng phải hữu hạn nên ta cần lượng tử hóa trạng thái của xe thành NN trạng thái rời rạc khác nhau, nghĩa là chia chia các giá trị của tọa độ và vận tốc thành NN đoạn bằng nhau, trạng thái của xe sẽ là chỉ số của các đoạn tương ứng.
Code lượng tử hóa cho 1 chiều của trạng thái
classNBinDiscretizer:def__init__(self, min_val, max_val, nbins):
self.min_val = min_val
self.max_val = max_val
self.step =(max_val - min_val)/ nbins
self.nbins =int(nbins)def__call__(self, val):returnint(round((val - self.min_val)/ self.step))% self.nbins
Code lượng tử hóa cho không gian trạng thái:
classDicretezation:def__init__(self, discretezers):
self.discretezers = discretezers
def__getitem__(self, index):assertlen(index)==len(self.discretezers)returntuple([self.discretezers[i](index[i])for i inrange(len(index))])
Tạo các quantizer:
n_quantization =50
x_quantizer = NBinDiscretizer(env.observation_space.low[0], env.observation_space.high[0], n_quantization)
v_quantizer = NBinDiscretizer(env.observation_space.low[0], env.observation_space.high[0], n_quantization)
state_quantizer = Dicretezation([x_quantizer, v_quantizer])
2.2 Xây dựng quá trình học
Các bước của quá trình học như sau:
- Khởi tạo một giá xác xuất ϵepsilon là xác suất thực hiện một bước đi ngẫu nhiên, còn lại 1−ϵ1 – epsilon là xác suất thực hiện theo chiến lược π∗=arg maxa∈AQ∗(s,a)pi ^ * = argmax _{a in mathcal{A}} Q^{*} (s,a). ϵepsilon sẽ giảm dần trong quá trình học bởi ban đầu khi mô hình chưa học được nhiều thì ta cần lấy các bước ngẫu nhiên để tạo thêm dữ liệu cho mô hình học, còn khi học được rồi thì sẽ học từ chính mô hình.
- Tại mỗi epoch:
- Reset môi trường về trạng thái khởi tạo s=s0s=s_0
- Chọn một bước aa đi với xác suất ϵepsilon ngẫu nhiên, và 1-epsilion tuân theo chiến lược π∗pi^*.
- Thực hiện bước đi aa, nhận về trạng thái tiếp theo s′s’, lợi tức tức thời rr và trạng thái kết thúc.
- Cập nhật QQ theo (1)
Code:
# inititalize some variables
lr =0.1
gamma =0.9
epochs =10000
epsilon =0.9
epsilon_scale = epsilon /(epochs /4)# some metrics
max_reward =-1000
max_pos =-1000# logging# inititalize some variables
epochs =10000
epsilon =0.9
epsilon_scale = epsilon /(epochs /4)# some metrics
max_reward =-1000
max_pos =-1000# logging
log = display('', display_id=True)
reach_log = display('', display_id=True)for epoch in tqdm(range(epochs), desc="Epoch"):
ep_max_pos =-1000
ep_reward =0# reset environment
obs = env.reset()
done =Falsewhilenot done:# take an actionif np.random.random_sample()> epsilon:
a = np.argmax(Q[state_quantizer[obs]])else:
a = np.random.randint(0, env.action_space.n)# perform action
new_obs, r, done, info = env.step(a)
ep_reward += r
if new_obs[0]>= env.goal_position:
reach_log.update(f"Reach goal at epoch {epoch} with reward: {ep_reward}")# update Q
cur_q_value = Q[state_quantizer[obs]][a]
new_q_value =(1-lr)* cur_q_value + lr *(r + gamma *max(Q[state_quantizer[new_obs]]))
Q[state_quantizer[obs]][a]= new_q_value
obs = new_obs
ep_max_pos =max(obs[0], ep_max_pos)
max_reward =max(ep_reward, max_reward)
max_pos =max(ep_max_pos, max_pos)
epsilon =max(0, epsilon - epsilon_scale)
log.update("epoch {}: ep_reward: {:9.6f}, max_reward: {:9.6f}, ep_max_pos: {:.6f}, max_pos: {:.6f}, epsilon: {:.6f}".format(epoch, ep_reward, max_reward, ep_max_pos, max_pos, epsilon))
3. Tổng kết
Hy vọng với một ví dụ nhỏ trong bài viết này sẽ giúp các bạn hiểu hơn về Q-function và Q-learning. Code đầy đủ trong bài viết này mình sẽ để ở đây. Nếu có gì góp ý thì đừng ngần ngại cho mình biết để hoàn thiện hơn. Còn nếu thấy hay thì cho mình xin 1 upvote 😄.
Nguồn: viblo.asia