Skip to content

Commit 7c00363

Browse files
author
Salah Rifai
committed
Added Contractive Autoencoder tutorial code
1 parent e3c3475 commit 7c00363

1 file changed

Lines changed: 324 additions & 0 deletions

File tree

code/cA.py

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
"""
2+
<<<<<<< HEAD
3+
This tutorial introduces Contractive auto-encoders (cA) using Theano.
4+
5+
=======
6+
This tutorial introduces denoising auto-encoders (dA) using Theano.
7+
8+
Denoising autoencoders are the building blocks for SdA.
9+
>>>>>>> 528aad7... added CAE code for tutorial
10+
They are based on auto-encoders as the ones used in Bengio et al. 2007.
11+
An autoencoder takes an input x and first maps it to a hidden representation
12+
y = f_{\theta}(x) = s(Wx+b), parameterized by \theta={W,b}. The resulting
13+
latent representation y is then mapped back to a "reconstructed" vector
14+
z \in [0,1]^d in input space z = g_{\theta'}(y) = s(W'y + b'). The weight
15+
matrix W' can optionally be constrained such that W' = W^T, in which case
16+
the autoencoder is said to have tied weights. The network is trained such
17+
that to minimize the reconstruction error (the error between x and z).
18+
19+
<<<<<<< HEAD
20+
Adding the squarred Frobenius norm of the Jacobian of the hidden mapping h
21+
with respect to the visible units yields the contractive auto-encoder:
22+
23+
- \sum_{k=1}^d[ x_k \log z_k + (1-x_k) \log( 1-z_k)] + \| \frac{\partial h(x)}{\partial x} \|^2
24+
=======
25+
For the denosing autoencoder, during training, first x is corrupted into
26+
\tilde{x}, where \tilde{x} is a partially destroyed version of x by means
27+
of a stochastic mapping. Afterwards y is computed as before (using
28+
\tilde{x}), y = s(W\tilde{x} + b) and z as s(W'y + b'). The reconstruction
29+
error is now measured between z and the uncorrupted input x, which is
30+
computed as the cross-entropy :
31+
- \sum_{k=1}^d[ x_k \log z_k + (1-x_k) \log( 1-z_k)]
32+
33+
>>>>>>> 528aad7... added CAE code for tutorial
34+
35+
References :
36+
- S. Rifai, P. Vincent, X. Muller, X. Glorot, Y. Bengio: Contractive
37+
Auto-Encoders: Explicit Invariance During Feature Extraction, ICML-11
38+
39+
<<<<<<< HEAD
40+
- S. Rifai, X. Muller, X. Glorot, G. Mesnil, Y. Bengio, and Pascal
41+
Vincent. Learning invariant features through local space
42+
contraction. Technical Report 1360, Universite de Montreal
43+
44+
=======
45+
>>>>>>> 528aad7... added CAE code for tutorial
46+
- Y. Bengio, P. Lamblin, D. Popovici, H. Larochelle: Greedy Layer-Wise
47+
Training of Deep Networks, Advances in Neural Information Processing
48+
Systems 19, 2007
49+
50+
"""
51+
52+
import numpy, time, cPickle, gzip, sys, os
53+
54+
import theano
55+
import theano.tensor as T
56+
57+
58+
from logistic_sgd import load_data
59+
from utils import tile_raster_images
60+
61+
import PIL.Image
62+
63+
64+
class cA(object):
65+
""" Contractive Auto-Encoder class (cA)
66+
67+
The contractive autoencoder tries to reconstruct the input with an
68+
additional constraint on the latent space. With the objective of
69+
obtaining a robust representation of the input space, we
70+
regularize the L2 norm(Froebenius) of the jacobian of the hidden
71+
representation with respect to the input. Please refer to Rifai et
72+
al.,2011 for more details.
73+
74+
If x is the input then equation (1) computes the projection of the
75+
input into the latent space h. Equation (2) computes the jacobian
76+
of h with respect to x. Equation (3) computes the reconstruction
77+
of the input, while equation (4) computes the reconstruction
78+
error and the added regularization term from Eq.(2).
79+
80+
.. math::
81+
82+
<<<<<<< HEAD
83+
h_i = s(W_i x + b_i) (1)
84+
85+
J_i = h_i (1 - h_i) * W_i (2)
86+
=======
87+
h = s(W x + b) (1)
88+
89+
J = h (1 - h) * W (2)
90+
>>>>>>> 528aad7... added CAE code for tutorial
91+
92+
x' = s(W' h + b') (3)
93+
94+
L = -sum_{k=1}^d [x_k \log x'_k + (1-x_k) \log( 1-x'_k)]
95+
+ lambda * sum_{i=1}^d sum_{j=1}^n J_{ij}^2 (4)
96+
97+
"""
98+
99+
def __init__(self, numpy_rng, input = None, n_visible= 784, n_hidden= 100,
100+
n_batchsize = 1, W = None, bhid = None, bvis = None):
101+
"""
102+
Initialize the cA class by specifying the number of visible units (the
103+
dimension d of the input ), the number of hidden units ( the dimension
104+
d' of the latent or hidden space ) and the contraction level. The
105+
constructor also receives symbolic variables for the input, weights and
106+
bias.
107+
108+
:type numpy_rng: numpy.random.RandomState
109+
:param numpy_rng: number random generator used to generate weights
110+
111+
:type theano_rng: theano.tensor.shared_randomstreams.RandomStreams
112+
:param theano_rng: Theano random generator; if None is given one is generated
113+
based on a seed drawn from `rng`
114+
115+
:type input: theano.tensor.TensorType
116+
:param input: a symbolic description of the input or None for standalone
117+
cA
118+
119+
:type n_visible: int
120+
:param n_visible: number of visible units
121+
122+
:type n_hidden: int
123+
:param n_hidden: number of hidden units
124+
125+
:type n_batchsize int
126+
:param n_batchsize: number of examples per batch
127+
128+
:type W: theano.tensor.TensorType
129+
:param W: Theano variable pointing to a set of weights that should be
130+
shared belong the dA and another architecture; if dA should
131+
be standalone set this to None
132+
133+
:type bhid: theano.tensor.TensorType
134+
:param bhid: Theano variable pointing to a set of biases values (for
135+
hidden units) that should be shared belong dA and another
136+
architecture; if dA should be standalone set this to None
137+
138+
:type bvis: theano.tensor.TensorType
139+
:param bvis: Theano variable pointing to a set of biases values (for
140+
visible units) that should be shared belong dA and another
141+
architecture; if dA should be standalone set this to None
142+
143+
144+
"""
145+
self.n_visible = n_visible
146+
self.n_hidden = n_hidden
147+
self.n_batchsize = n_batchsize
148+
# note : W' was written as `W_prime` and b' as `b_prime`
149+
if not W:
150+
# W is initialized with `initial_W` which is uniformely sampled
151+
# from -4*sqrt(6./(n_visible+n_hidden)) and
152+
# 4*sqrt(6./(n_hidden+n_visible))the output of uniform if
153+
# converted using asarray to dtype
154+
# theano.config.floatX so that the code is runable on GPU
155+
initial_W = numpy.asarray( numpy_rng.uniform(
156+
low = -4*numpy.sqrt(6./(n_hidden+n_visible)),
157+
high = 4*numpy.sqrt(6./(n_hidden+n_visible)),
158+
size = (n_visible, n_hidden)), dtype = theano.config.floatX)
159+
W = theano.shared(value = initial_W, name ='W')
160+
161+
if not bvis:
162+
bvis = theano.shared(value = numpy.zeros(n_visible,
163+
dtype = theano.config.floatX))
164+
165+
if not bhid:
166+
bhid = theano.shared(value = numpy.zeros(n_hidden,
167+
dtype = theano.config.floatX), name ='b')
168+
169+
170+
self.W = W
171+
# b corresponds to the bias of the hidden
172+
self.b = bhid
173+
# b_prime corresponds to the bias of the visible
174+
self.b_prime = bvis
175+
# tied weights, therefore W_prime is W transpose
176+
self.W_prime = self.W.T
177+
178+
# if no input is given, generate a variable representing the input
179+
if input == None :
180+
# we use a matrix because we expect a minibatch of several examples,
181+
# each example being a row
182+
self.x = T.dmatrix(name = 'input')
183+
else:
184+
self.x = input
185+
186+
self.params = [self.W, self.b, self.b_prime]
187+
188+
189+
def get_hidden_values(self, input):
190+
""" Computes the values of the hidden layer """
191+
return T.nnet.sigmoid(T.dot(input, self.W) + self.b)
192+
193+
def get_jacobian(self, hidden, W):
194+
""" Computes the jacobian of the hidden layer with respect to the input,
195+
reshapes are necessary for broadcasting the element-wise product on the
196+
right axis """
197+
return T.reshape(hidden*(1-hidden),(self.n_batchsize,1,self.n_hidden)) * T.reshape(W, (1,self.n_visible, self.n_hidden))
198+
199+
def get_reconstructed_input(self, hidden ):
200+
""" Computes the reconstructed input given the values of the hidden layer """
201+
return T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime)
202+
203+
def get_cost_updates(self, contraction_level, learning_rate):
204+
""" This function computes the cost and the updates for one trainng
205+
step of the cA """
206+
207+
y = self.get_hidden_values(self.x)
208+
z = self.get_reconstructed_input(y)
209+
J = self.get_jacobian(y,self.W)
210+
# note : we sum over the size of a datapoint; if we are using minibatches,
211+
# L will be a vector, with one entry per example in minibatch
212+
self.L_rec = - T.sum( self.x*T.log(z) + (1-self.x)*T.log(1-z), axis=1 )
213+
214+
# Compute the jacobian and average over the number of samples/minibatch
215+
self.L_jacob = T.sum(J**2) / self.n_batchsize
216+
217+
# note : L is now a vector, where each element is the cross-entropy cost
218+
# of the reconstruction of the corresponding example of the
219+
# minibatch. We need to compute the average of all these to get
220+
# the cost of the minibatch
221+
cost = T.mean(self.L_rec) + contraction_level*T.mean(self.L_jacob)
222+
223+
# compute the gradients of the cost of the `cA` with respect
224+
# to its parameters
225+
gparams = T.grad(cost, self.params)
226+
# generate the list of updates
227+
updates = {}
228+
for param, gparam in zip(self.params, gparams):
229+
updates[param] = param - learning_rate*gparam
230+
231+
return (cost, updates)
232+
233+
234+
235+
236+
def test_cA( learning_rate = 0.01, training_epochs = 20, dataset ='../data/mnist.pkl.gz',
237+
<<<<<<< HEAD
238+
batch_size = 10, output_folder = 'cA_plots',contraction_level = .1 ):
239+
=======
240+
batch_size = 1, output_folder = 'cA_plots' ):
241+
>>>>>>> 528aad7... added CAE code for tutorial
242+
243+
"""
244+
This demo is tested on MNIST
245+
246+
:type learning_rate: float
247+
:param learning_rate: learning rate used for training the contracting AutoEncoder
248+
249+
:type training_epochs: int
250+
:param training_epochs: number of epochs used for training
251+
252+
:type dataset: string
253+
:param dataset: path to the picked dataset
254+
255+
"""
256+
datasets = load_data(dataset)
257+
train_set_x, train_set_y = datasets[0]
258+
259+
# compute number of minibatches for training, validation and testing
260+
n_train_batches = train_set_x.get_value(borrow=True).shape[0] / batch_size
261+
262+
# allocate symbolic variables for the data
263+
index = T.lscalar() # index to a [mini]batch
264+
x = T.matrix('x') # the data is presented as rasterized images
265+
266+
267+
if not os.path.isdir(output_folder):
268+
os.makedirs(output_folder)
269+
os.chdir(output_folder)
270+
####################################
271+
# BUILDING THE MODEL #
272+
####################################
273+
274+
rng = numpy.random.RandomState(123)
275+
276+
ca = cA(numpy_rng = rng, input = x,
277+
n_visible = 28*28, n_hidden = 500, n_batchsize=batch_size )
278+
279+
<<<<<<< HEAD
280+
cost, updates = ca.get_cost_updates(contraction_level = contraction_level,
281+
=======
282+
cost, updates = ca.get_cost_updates(contraction_level = .1,
283+
>>>>>>> 528aad7... added CAE code for tutorial
284+
learning_rate = learning_rate)
285+
286+
287+
train_ca = theano.function([index], [T.mean(ca.L_rec),ca.L_jacob], updates = updates,
288+
givens = {x:train_set_x[index*batch_size:(index+1)*batch_size]})
289+
290+
start_time = time.clock()
291+
292+
############
293+
# TRAINING #
294+
############
295+
296+
# go through training epochs
297+
for epoch in xrange(training_epochs):
298+
# go through trainng set
299+
c = []
300+
for batch_index in xrange(n_train_batches):
301+
c.append(train_ca(batch_index))
302+
303+
c_array = numpy.vstack(c)
304+
print 'Training epoch %d, reconstruction cost '%epoch, numpy.mean(c_array[0]),' jacobian norm ',numpy.mean(numpy.sqrt(c_array[1]))
305+
306+
end_time = time.clock()
307+
308+
training_time = (end_time - start_time)
309+
310+
print >> sys.stderr, ('The code for file '+os.path.split(__file__)[1]+' ran for %.2fm' % ((training_time)/60.))
311+
image = PIL.Image.fromarray(tile_raster_images(X = ca.W.get_value(borrow=True).T,
312+
<<<<<<< HEAD
313+
img_shape = (28,28),tile_shape = (10,10),
314+
=======
315+
img_shape = (28,28),tile_shape = (50,10),
316+
>>>>>>> 528aad7... added CAE code for tutorial
317+
tile_spacing=(1,1)))
318+
image.save('cae_filters.png')
319+
320+
os.chdir('../')
321+
322+
323+
if __name__ == '__main__':
324+
test_cA()

0 commit comments

Comments
 (0)