Skip to content

Commit 359480c

Browse files
committed
initial commit
1 parent d4ffbc8 commit 359480c

4 files changed

Lines changed: 170 additions & 0 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
# Theano_Tile_Coding
22
A tile coder in theano for Reinforcement Learning tasks
3+
4+
## An example of 2D function approximation
5+
![](https://github.com/mohammadpz/Theano_Tile_Coding/blob/master/example.gif)
6+
7+
8+
## Disclaimer
9+
THE CODE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14+
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15+
PERFORMANCE OF THIS CODE.

example.gif

871 KB
Loading

layers.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import numpy as np
2+
import theano
3+
import theano.tensor as T
4+
5+
6+
class TileCodingLayer:
7+
def __init__(self, min_val, max_val, num_tiles,
8+
num_tilings, num_features):
9+
self.min_val = min_val
10+
self.max_val = max_val
11+
self.num_tiles = num_tiles
12+
self.num_tilings = num_tilings
13+
self.num_features = num_features
14+
15+
# The range of each feature is divided into num_tile tiles.
16+
# But we keep one more tile. The reason is that since we shift
17+
# tilings by an offset, we want to make sure that every point
18+
# after shifting maps to one tile.
19+
# Shape: (num_tilings, (num_tiles+1) * num_features)
20+
self.weight = theano.shared(np.zeros(
21+
(num_tilings,) + (num_tiles + 1,) * num_features))
22+
23+
def quantize(self, x):
24+
len_tile = float(self.max_val - self.min_val) / self.num_tiles
25+
offsets = (len_tile * T.arange(self.num_tilings).astype('float32') /
26+
self.num_tilings)
27+
# shapes: (features, num_tilings) = (features, 1) + (1, num_tilings)
28+
mapped_x = x.dimshuffle(0, 'x') + offsets.dimshuffle('x', 0)
29+
# Since we have an extra tile:
30+
new_max_val = self.max_val + len_tile
31+
# Mapping into range [0, num_tiles + 1] to be able to do quantization
32+
# by casting to int.
33+
mapped_x = ((self.num_tiles + 1) * mapped_x /
34+
(new_max_val - self.min_val))
35+
# shape: (num_tilings, num_features)
36+
q_x = mapped_x.astype('int32').T
37+
return q_x
38+
39+
def approximate(self, q_x):
40+
indices = [T.arange(self.num_tilings)]
41+
for f in range(self.num_features):
42+
indices += [q_x[:, f]]
43+
self.indices = indices
44+
# shape: (num_tilings,)
45+
selected = self.weight.__getitem__(tuple(indices))
46+
y_hat = T.sum(selected)
47+
return y_hat
48+
49+
def update_rule(self, y, y_hat, learning_rate):
50+
# grad is only used to locate the weights which
51+
# are used for computing y_hat. Since
52+
# "y_hat = T.sum(selected)", T.grad will returns
53+
# 1 for corresponding weights and 0 for others.
54+
grad_ = T.grad(y_hat, self.weight)
55+
learning_rate = learning_rate / self.num_tilings
56+
upd = grad_ * learning_rate * (y - y_hat)
57+
updates = [(self.weight, self.weight + upd)]
58+
return updates
59+
60+
61+
def get_tile_coder(min_val, max_val, num_tiles, num_tilings,
62+
num_features, learning_rate):
63+
# x.shape: (num_features), y.shape: ()
64+
x = T.fvector('x')
65+
y = T.fscalar('y')
66+
67+
tile_coding_layer = TileCodingLayer(
68+
min_val=min_val, max_val=max_val,
69+
num_tiles=num_tiles, num_tilings=num_tilings,
70+
num_features=num_features)
71+
72+
# quantized_x
73+
q_x = tile_coding_layer.quantize(x)
74+
y_hat = tile_coding_layer.approximate(q_x)
75+
updates = tile_coding_layer.update_rule(y, y_hat, 0.1)
76+
77+
train = theano.function([x, y], y_hat, updates=updates, allow_input_downcast=True)
78+
eval_ = theano.function([x], y_hat, allow_input_downcast=True)
79+
80+
return train, eval_

test_tile_coding_layer.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
from matplotlib import cm
4+
from mpl_toolkits.mplot3d import Axes3D
5+
from layers import get_tile_coder
6+
7+
8+
# x.shape: (num_features)
9+
def target_function(x):
10+
return np.sin(x[0]) + np.cos(x[1]) + 0.01 * np.random.randn()
11+
12+
13+
def get_dataset(num_samples, min_val, max_val):
14+
# num_features is assumed to be 2 for this example.
15+
# However it can take any value in general.
16+
dataset = []
17+
for i in range(num_samples):
18+
x = np.array([np.random.random() * 7, np.random.random() * 7])
19+
y = target_function(x)
20+
dataset.append((x, y))
21+
return dataset
22+
23+
24+
def plot_function(ax, function, text, hold=False):
25+
ax.cla()
26+
x_0 = np.linspace(0, 7, 100)
27+
x_1 = np.linspace(0, 7, 100)
28+
z = np.zeros((100, 100))
29+
for i in range(100):
30+
for j in range(100):
31+
z[j, i] = function(np.array([x_0[i], x_1[j]]))
32+
X_0, X_1 = np.meshgrid(x_0, x_1)
33+
ax.plot_surface(X_0, X_1, z, rstride=8, cstride=8, alpha=0.3)
34+
ax.contourf(X_0, X_1, z, zdir='z', offset=-3, cmap=cm.coolwarm)
35+
ax.contourf(X_0, X_1, z, zdir='x', offset=-1, cmap=cm.coolwarm)
36+
ax.contourf(X_0, X_1, z, zdir='y', offset=-1, cmap=cm.coolwarm)
37+
ax.set_xlim(-1, 8)
38+
ax.set_ylim(-1, 8)
39+
ax.set_zlim(-3, 3)
40+
ax.view_init(45, 45)
41+
ax.set_title(text)
42+
if hold:
43+
plt.show()
44+
else:
45+
plt.draw()
46+
plt.savefig(text + '.png')
47+
plt.pause(.0001)
48+
49+
train, eval_ = get_tile_coder(
50+
min_val=0, max_val=7.0, num_tiles=10,
51+
num_tilings=10, num_features=2,
52+
learning_rate=0.1)
53+
54+
dataset = get_dataset(10000, 0, 7)
55+
fig = plt.figure(figsize=np.array([12, 5]))
56+
ax_0 = fig.add_subplot(1, 2, 1, projection='3d')
57+
ax_1 = fig.add_subplot(1, 2, 2, projection='3d')
58+
plot_function(ax_0, target_function, 'Target function')
59+
# import ipdb; ipdb.set_trace()
60+
61+
print 'Training'
62+
for i, datapoint in enumerate(dataset):
63+
x, y = datapoint
64+
train(x.astype('float32'), y.astype('float32'))
65+
if i <= 20:
66+
step = 5
67+
elif i <= 100:
68+
step = 20
69+
elif i <= 1500:
70+
step = 100
71+
else:
72+
step = 1000
73+
if i % step == 0:
74+
plot_function(ax_1, eval_, 'Seen points: ' + str(i))
75+
76+
if i == len(dataset) - 1:
77+
plot_function(ax_1, eval_, 'Learned Function', True)

0 commit comments

Comments
 (0)