|
| 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