-
Notifications
You must be signed in to change notification settings - Fork 170
Expand file tree
/
Copy pathTD-Control.qmd
More file actions
262 lines (205 loc) · 7.17 KB
/
Copy pathTD-Control.qmd
File metadata and controls
262 lines (205 loc) · 7.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
---
title: "Reinforcement Learning: TD Control with Sarsa, Q-Learning and Expected Sarsa"
author: "Michael Hahsler"
format:
html:
theme: default
toc: true
number-sections: true
code-line-numbers: true
embed-resources: true
---
This code is provided under [Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License.](https://creativecommons.org/licenses/by-sa/4.0/)

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(tidy = TRUE)
options(digits = 2)
```
# Introduction
[Reinforcement Learning: An Introduction (RL)](http://incompleteideas.net/book/the-book-2nd.html) by Sutton and Barto (2020) introduce several temporal-difference learning control algorithms in
Chapter 6: Temporal-Difference Learning.
Here we implement the on-policy TD control algorithm Sarsa.
We will implement the
key concepts using R for the AIMA 3x4 grid world example.
The used environment is an MDP, but instead of trying to solve the MDP directly
and estimating the value function estimates $U(s)$, we will try to learn
a policy from interactions with the environment.
The MDP's transition model will only be used to simulate the response of the environment
to actions by the agent.
The code in this notebook defines explicit functions
matching the textbook definitions and is for demonstration purposes only. Efficient implementations for larger problems use fast vector multiplications
instead.
{{< include _AIMA-4x3-gridworld.qmd >}}
# Implementing the Temporal-Difference Learning Algorithm
Here is the pseudo code for Sarsa from the RL book,
Chapter 6.4:

The algorithm uses a __temporal-difference (TD) learning__ since it updates
using the TD error given by $Q(S',A') - Q(S, A)$.
The algorithm performs __on-policy learning__ since it uses
only a single policy (e.g., $\epsilon$-greedy) as the behavior and
target policy.
## Behavior and Target Policies
Next, we implement the greedy and $\epsilon$-greedy policy action choice
given $Q$. The greedy policy is deterministic and always chooses the best
action with the highest
$Q$-value for the current state.
The $\epsilon$-greedy policy is a stochastic policy which chooses the best
action with probability $1 - \epsilon$ and a random action otherwise.
Setting $\epsilon = 0$ reduces the $\epsilon$-greedy policy to a
deterministic greedy policy.
```{r}
greedy_action <- function(s, Q, epsilon = 0) {
available_A <- actions(s)
if (epsilon == 0 ||
length(available_A) == 1L || runif(1) > epsilon) {
a <- available_A[which.max(Q[s, available_A])]
} else {
a <- sample(available_A, size = 1L)
}
a
}
greedy_prob <- function(s, Q, epsilon = 0) {
p <- structure(rep(0, length(A)), names = A)
available_A <- actions(s)
a <- available_A[which.max(Q[s, available_A])]
p[a] <- 1 - epsilon
p[available_A] <- p[available_A] + epsilon / length(available_A)
p
}
```
## The TD Learning Algorithm
The temporal-difference learning algorithm follows the Sarsa implementation,
but just changing how the TD error is calculated lets the algorithm
also perform Q-learning and expected Sarsa.
```{r}
TD_learning <- function(method = "sarsa",
alpha = 0.1,
epsilon = 0.1,
gamma = 1,
N = 100,
verbose = FALSE) {
method <- match.arg(method, c("sarsa", "q", "expected_sarsa"))
# Initialize Q
Q <-
matrix(
NA_real_,
nrow = length(S),
ncol = length(A),
dimnames = list(S, A)
)
for (s in S)
Q[s, actions(s)] <- 0
# loop episodes
for (e in seq(N)) {
s <- start
a <- greedy_action(s, Q, epsilon)
# loop steps in episode
i <- 1L
while (TRUE) {
s_prime <- sample_transition(s, a)
r <- R(s, a, s_prime)
a_prime <- greedy_action(s_prime, Q, epsilon)
if (verbose) {
if (step == 1L)
cat("\n*** Episode", e, "***\n")
cat("Step", i, "- s a r s' a':", s, a, r, s_prime, a_prime, "\n")
}
if (method == "sarsa")
# is called Sarsa because it uses the sequence s, a, r, s', a'
Q[s, a] <-
Q[s, a] + alpha * (r + gamma * Q[s_prime, a_prime] - Q[s, a])
else if (method == "q") {
# a' is greedy instead of using the behavior policy
a_max <- greedy_action(s_prime, Q, epsilon = 0)
Q[s, a] <-
Q[s, a] + alpha * (r + gamma * Q[s_prime, a_max] - Q[s, a])
} else if (method == "expected_sarsa") {
p <- greedy_prob(s_prime, Q, epsilon)
exp_Q_prime <-
sum(greedy_prob(s_prime, Q, epsilon) * Q[s_prime, ], na.rm = TRUE)
Q[s, a] <-
Q[s, a] + alpha * (r + gamma * exp_Q_prime - Q[s, a])
}
s <- s_prime
a <- a_prime
if (is_terminal(s))
break
i <- i + 1L
}
}
Q
}
```
## Sarsa
Sarsa is on-policy and calculates the TD-error for the update as:
$$
\gamma\,Q(S', A') - Q(S, A)
$$
where the $S'$ and $A'$ are determined by the same policy that is used for the agents
behavior. In this case it is $\epsilon$-greedy.
```{r}
Q <- TD_learning(method = "sarsa", N = 10000, verbose = FALSE)
Q
```
Calculate the value function $U$ from the learned Q-function as the largest
Q value of any action in a state.
```{r}
U <- apply(Q, MARGIN = 1, max, na.rm = TRUE)
show_layout(U)
```
Extract the greedy policy for the learned $Q$-value function.
```{r}
pi <- A[apply(Q, MARGIN = 1, which.max)]
show_layout(pi)
```
## Q-Learning
Q-Learning is off-policy and calculates the TD-error for the update as:
$$
\gamma\,\mathrm{max}_a Q(S', a) - Q(S, A)
$$
where the target policy is greedy reflected by the maximum that chooses the
action with the largest $Q$-value.
```{r}
Q <- TD_learning(method = "q", N = 10000, verbose = FALSE)
Q
```
Calculate the value function $U$ from the learned Q-function as the largest
Q value of any action in a state.
```{r}
U <- apply(Q, MARGIN = 1, max, na.rm = TRUE)
show_layout(U)
```
Extract the greedy policy for the learned $Q$-value function.
```{r}
pi <- A[apply(Q, MARGIN = 1, which.max)]
show_layout(pi)
```
## Expected Sarsa
Expected Sarsa calculates the TD-error for the update as:
$$
\gamma\, E_\pi[Q(S', Q')] - Q(S, A) = \gamma \sum_a\pi(a|S')Q(S', a) - Q(S, A)
$$
using the expected value under the current policy. It moves deterministically
in the same direction as Sarsa moved in expectation. Because it uses the expectation,
we can set $\alpha$ to large values and even 1.
```{r}
Q <- TD_learning(method = "expected_sarsa", N = 10000, alpha = 1, verbose = FALSE)
Q
```
Calculate the value function $U$ from the learned Q-function as the largest
Q value of any action in a state.
```{r}
U <- apply(Q, MARGIN = 1, max, na.rm = TRUE)
show_layout(U)
```
Extract the greedy policy for the learned $Q$-value function.
```{r}
pi <- A[apply(Q, MARGIN = 1, which.max)]
show_layout(pi)
```
Not implemented yet.
## Reducing $\epsilon$ and $\alpha$ Over Time
To improve convergence, $\epsilon$ and $\alpha$ are typically reduced
slowly over time. This is not implemented here.