Mercurial > ift6266
comparison deep/crbm/crbm.py @ 337:8d116d4a7593
Added convolutional RBM (ala Lee09) code, imported from my working dir elsewhere. Seems to work for one layer. No subsampling yet.
author | fsavard |
---|---|
date | Fri, 16 Apr 2010 16:05:55 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
336:a79db7cee035 | 337:8d116d4a7593 |
---|---|
1 import sys | |
2 import os, os.path | |
3 | |
4 import numpy | |
5 | |
6 import theano | |
7 | |
8 USING_GPU = "gpu" in theano.config.device | |
9 | |
10 import theano.tensor as T | |
11 from theano.tensor.nnet import conv, sigmoid | |
12 | |
13 if not USING_GPU: | |
14 from theano.tensor.shared_randomstreams import RandomStreams | |
15 else: | |
16 from theano.sandbox.rng_mrg import MRG_RandomStreams | |
17 | |
18 _PRINT_GRAPHS = True | |
19 | |
20 def _init_conv_biases(num_filters, varname, rng=numpy.random): | |
21 b_shp = (num_filters,) | |
22 b = theano.shared( numpy.asarray( | |
23 rng.uniform(low=-.5, high=.5, size=b_shp), | |
24 dtype=theano.config.floatX), name=varname) | |
25 return b | |
26 | |
27 def _init_conv_weights(conv_params, varname, rng=numpy.random): | |
28 cp = conv_params | |
29 | |
30 # initialize shared variable for weights. | |
31 w_shp = conv_params.as_conv2d_shape_tuple() | |
32 w_bound = numpy.sqrt(cp.num_input_planes * \ | |
33 cp.height_filters * cp.width_filters) | |
34 W = theano.shared( numpy.asarray( | |
35 rng.uniform( | |
36 low=-1.0 / w_bound, | |
37 high=1.0 / w_bound, | |
38 size=w_shp), | |
39 dtype=theano.config.floatX), name=varname) | |
40 | |
41 return W | |
42 | |
43 # Shape of W for conv2d | |
44 class ConvolutionParams: | |
45 def __init__(self, num_filters, num_input_planes, height_filters, width_filters): | |
46 self.num_filters = num_filters | |
47 self.num_input_planes = num_input_planes | |
48 self.height_filters = height_filters | |
49 self.width_filters = width_filters | |
50 | |
51 def as_conv2d_shape_tuple(self): | |
52 cp = self | |
53 return (cp.num_filters, cp.num_input_planes, | |
54 cp.height_filters, cp.width_filters) | |
55 | |
56 class CRBM: | |
57 def __init__(self, minibatch_size, image_size, conv_params, | |
58 learning_rate, sparsity_lambda, sparsity_p): | |
59 ''' | |
60 Parameters | |
61 ---------- | |
62 image_size | |
63 height, width | |
64 ''' | |
65 self.minibatch_size = minibatch_size | |
66 self.image_size = image_size | |
67 self.conv_params = conv_params | |
68 | |
69 ''' | |
70 Dimensions: | |
71 0- minibatch | |
72 1- plane/color | |
73 2- y (rows) | |
74 3- x (cols) | |
75 ''' | |
76 self.x = T.tensor4('x') | |
77 self.h = T.tensor4('h') | |
78 | |
79 self.lr = theano.shared(numpy.asarray(learning_rate, | |
80 dtype=theano.config.floatX)) | |
81 self.sparsity_lambda = \ | |
82 theano.shared( \ | |
83 numpy.asarray( \ | |
84 sparsity_lambda, | |
85 dtype=theano.config.floatX)) | |
86 self.sparsity_p = \ | |
87 theano.shared( \ | |
88 numpy.asarray(sparsity_p, \ | |
89 dtype=theano.config.floatX)) | |
90 | |
91 self.numpy_rng = numpy.random.RandomState(1234) | |
92 | |
93 if not USING_GPU: | |
94 self.theano_rng = RandomStreams(self.numpy_rng.randint(2**30)) | |
95 else: | |
96 self.theano_rng = MRG_RandomStreams(234, use_cuda=True) | |
97 | |
98 self._init_params() | |
99 self._init_functions() | |
100 | |
101 def _get_visibles_shape(self): | |
102 imsz = self.image_size | |
103 return (self.minibatch_size, | |
104 self.conv_params.num_input_planes, | |
105 imsz[0], imsz[1]) | |
106 | |
107 def _get_hiddens_shape(self): | |
108 cp = self.conv_params | |
109 imsz = self.image_size | |
110 wf, hf = cp.height_filters, cp.width_filters | |
111 return (self.minibatch_size, cp.num_filters, | |
112 imsz[0] - hf + 1, imsz[1] - wf + 1) | |
113 | |
114 def _init_params(self): | |
115 cp = self.conv_params | |
116 | |
117 self.W = _init_conv_weights(cp, 'W') | |
118 self.b_h = _init_conv_biases(cp.num_filters, 'b_h') | |
119 ''' | |
120 Lee09 mentions "all visible units share a single bias c" | |
121 but for upper layers it's pretty clear we need one | |
122 per plane, by symmetry | |
123 ''' | |
124 self.b_x = _init_conv_biases(cp.num_input_planes, 'b_x') | |
125 | |
126 self.params = [self.W, self.b_h, self.b_x] | |
127 | |
128 # flip filters horizontally and vertically | |
129 W_flipped = self.W[:, :, ::-1, ::-1] | |
130 # also have to invert the filters/num_planes | |
131 self.W_tilde = W_flipped.dimshuffle(1,0,2,3) | |
132 | |
133 ''' | |
134 I_up and I_down come from the symbol used in the | |
135 Lee 2009 CRBM paper | |
136 ''' | |
137 def _I_up(self, visibles_mb): | |
138 ''' | |
139 output of conv is features maps of size | |
140 image_size - filter_size + 1 | |
141 The dimshuffle serves to broadcast b_h so that it | |
142 corresponds to output planes | |
143 ''' | |
144 fshp = self.conv_params.as_conv2d_shape_tuple() | |
145 return conv.conv2d(visibles_mb, self.W, | |
146 filter_shape=fshp) + \ | |
147 self.b_h.dimshuffle('x',0,'x','x') | |
148 | |
149 def _I_down(self, hiddens_mb): | |
150 ''' | |
151 notice border_mode='full'... we want to get | |
152 back the original size | |
153 so we get feature_map_size + filter_size - 1 | |
154 The dimshuffle serves to broadcast b_x so that | |
155 it corresponds to output planes | |
156 ''' | |
157 fshp = list(self.conv_params.as_conv2d_shape_tuple()) | |
158 # num_filters and num_planes swapped | |
159 fshp[0], fshp[1] = fshp[1], fshp[0] | |
160 return conv.conv2d(hiddens_mb, self.W_tilde, | |
161 border_mode='full',filter_shape=tuple(fshp)) + \ | |
162 self.b_x.dimshuffle('x',0,'x','x') | |
163 | |
164 def _mean_free_energy(self, visibles_mb): | |
165 ''' | |
166 visibles_mb is mb_size x num_planes x h x w | |
167 | |
168 we want to match the summed input planes | |
169 (second dimension, first is mb index) | |
170 to respective bias terms for the visibles | |
171 The dimshuffle isn't really necessary, | |
172 but I put it there for clarity. | |
173 ''' | |
174 vbias_term = \ | |
175 self.b_x.dimshuffle('x',0) * \ | |
176 T.sum(visibles_mb,axis=[2,3]) | |
177 # now sum over term per planes, get one free energy | |
178 # contribution per element of minibatch | |
179 vbias_term = - T.sum(vbias_term, axis=1) | |
180 | |
181 ''' | |
182 Here it's a bit more complex, a few points: | |
183 - The usual free energy, in the fully connected case, | |
184 is a sum over all hiddens. | |
185 We do the same thing here, but each unit has limited | |
186 connectivity and there's weight reuse. | |
187 Therefore we only need to first do the convolutions | |
188 (with I_up) which gives us | |
189 what would normally be the Wx+b_h for each hidden. | |
190 Once we have this, | |
191 we take the log(1+exp(sum for this hidden)) elemwise | |
192 for each hidden, | |
193 then we sum for all hiddens in one example of the minibatch. | |
194 | |
195 - Notice that we reuse the same b_h everywhere instead of | |
196 using one b per hidden, | |
197 so the broadcasting for b_h done in I_up is all right. | |
198 | |
199 That sum is over all hiddens, so all filters | |
200 (planes of hiddens), x, and y. | |
201 In the end we get one free energy contribution per | |
202 example of the minibatch. | |
203 ''' | |
204 softplused = T.log(1.0+T.exp(self._I_up(visibles_mb))) | |
205 # h_sz = self._get_hiddens_shape() | |
206 # this simplifies the sum | |
207 # num_hiddens = h_sz[1] * h_sz[2] * h_sz[3] | |
208 # reshaped = T.reshape(softplused, | |
209 # (self.minibatch_size, num_hiddens)) | |
210 | |
211 # this is because the 0,1,1,1 sum pattern is not | |
212 # implemented on gpu, but the 1,0,1,1 pattern is | |
213 dimshuffled = softplused.dimshuffle(1,0,2,3) | |
214 xh_and_hbias_term = - T.sum(dimshuffled, axis=[0,2,3]) | |
215 | |
216 ''' | |
217 both bias_term and vbias_term end up with one | |
218 contributor to free energy per minibatch | |
219 so we mean over minibatches | |
220 ''' | |
221 return T.mean(vbias_term + xh_and_hbias_term) | |
222 | |
223 def _init_functions(self): | |
224 # propup | |
225 # b_h is broadcasted keeping in mind we want it to | |
226 # correspond to each new plane (corresponding to filters) | |
227 I_up = self._I_up(self.x) | |
228 # expected values for the distributions for each hidden | |
229 E_h_given_x = sigmoid(I_up) | |
230 # might be needed if we ever want a version where we | |
231 # take expectations instead of samples for CD learning | |
232 self.E_h_given_x_func = theano.function([self.x], E_h_given_x) | |
233 | |
234 if _PRINT_GRAPHS: | |
235 print "----------------------\nE_h_given_x_func" | |
236 theano.printing.debugprint(self.E_h_given_x_func) | |
237 | |
238 h_sample_given_x = \ | |
239 self.theano_rng.binomial( \ | |
240 size = self._get_hiddens_shape(), | |
241 n = 1, | |
242 p = E_h_given_x, | |
243 dtype = theano.config.floatX) | |
244 | |
245 self.h_sample_given_x_func = \ | |
246 theano.function([self.x], | |
247 h_sample_given_x) | |
248 | |
249 if _PRINT_GRAPHS: | |
250 print "----------------------\nh_sample_given_x_func" | |
251 theano.printing.debugprint(self.h_sample_given_x_func) | |
252 | |
253 # propdown | |
254 I_down = self._I_down(self.h) | |
255 E_x_given_h = sigmoid(I_down) | |
256 self.E_x_given_h_func = theano.function([self.h], E_x_given_h) | |
257 | |
258 if _PRINT_GRAPHS: | |
259 print "----------------------\nE_x_given_h_func" | |
260 theano.printing.debugprint(self.E_x_given_h_func) | |
261 | |
262 x_sample_given_h = \ | |
263 self.theano_rng.binomial( \ | |
264 size = self._get_visibles_shape(), | |
265 n = 1, | |
266 p = E_x_given_h, | |
267 dtype = theano.config.floatX) | |
268 | |
269 self.x_sample_given_h_func = \ | |
270 theano.function([self.h], | |
271 x_sample_given_h) | |
272 | |
273 if _PRINT_GRAPHS: | |
274 print "----------------------\nx_sample_given_h_func" | |
275 theano.printing.debugprint(self.x_sample_given_h_func) | |
276 | |
277 ############################################## | |
278 # cd update done by grad of free energy | |
279 | |
280 x_tilde = T.tensor4('x_tilde') | |
281 cd_update_cost = self._mean_free_energy(self.x) - \ | |
282 self._mean_free_energy(x_tilde) | |
283 | |
284 cd_grad = T.grad(cd_update_cost, self.params) | |
285 # This is NLL minimization so we use a - | |
286 cd_updates = {self.W: self.W - self.lr * cd_grad[0], | |
287 self.b_h: self.b_h - self.lr * cd_grad[1], | |
288 self.b_x: self.b_x - self.lr * cd_grad[2]} | |
289 | |
290 cd_returned = [cd_update_cost, | |
291 cd_grad[0], cd_grad[1], cd_grad[2], | |
292 self.lr * cd_grad[0], | |
293 self.lr * cd_grad[1], | |
294 self.lr * cd_grad[2]] | |
295 self.cd_return_desc = \ | |
296 ['cd_update_cost', | |
297 'cd_grad_W', 'cd_grad_b_h', 'cd_grad_b_x', | |
298 'lr_times_cd_grad_W', | |
299 'lr_times_cd_grad_b_h', | |
300 'lr_times_cd_grad_b_x'] | |
301 | |
302 self.cd_update_function = \ | |
303 theano.function([self.x, x_tilde], | |
304 cd_returned, updates=cd_updates) | |
305 | |
306 if _PRINT_GRAPHS: | |
307 print "----------------------\ncd_update_function" | |
308 theano.printing.debugprint(self.cd_update_function) | |
309 | |
310 ############## | |
311 # sparsity update, based on grad for b_h only | |
312 | |
313 ''' | |
314 This mean returns an array of shape | |
315 (num_hiddens_planes, feature_map_height, feature_map_width) | |
316 (so it's a mean over each unit's activation) | |
317 ''' | |
318 mean_expected_activation = T.mean(E_h_given_x, axis=0) | |
319 # sparsity_p is broadcasted everywhere | |
320 sparsity_update_cost = \ | |
321 T.sqr(self.sparsity_p - mean_expected_activation) | |
322 sparsity_update_cost = \ | |
323 T.sum(T.sum(T.sum( \ | |
324 sparsity_update_cost, axis=2), axis=1), axis=0) | |
325 sparsity_grad = T.grad(sparsity_update_cost, [self.W, self.b_h]) | |
326 | |
327 sparsity_returned = \ | |
328 [sparsity_update_cost, | |
329 sparsity_grad[0], sparsity_grad[1], | |
330 self.sparsity_lambda * self.lr * sparsity_grad[0], | |
331 self.sparsity_lambda * self.lr * sparsity_grad[1]] | |
332 self.sparsity_return_desc = \ | |
333 ['sparsity_update_cost', | |
334 'sparsity_grad_W', | |
335 'sparsity_grad_b_h', | |
336 'lambda_lr_times_sparsity_grad_W', | |
337 'lambda_lr_times_sparsity_grad_b_h'] | |
338 | |
339 # gradient _descent_ so we use a - | |
340 sparsity_update = \ | |
341 {self.b_h: self.b_h - \ | |
342 self.sparsity_lambda * self.lr * sparsity_grad[1], | |
343 self.W: self.W - \ | |
344 self.sparsity_lambda * self.lr * sparsity_grad[0]} | |
345 self.sparsity_update_function = \ | |
346 theano.function([self.x], | |
347 sparsity_returned, updates=sparsity_update) | |
348 | |
349 if _PRINT_GRAPHS: | |
350 print "----------------------\nsparsity_update_function" | |
351 theano.printing.debugprint(self.sparsity_update_function) | |
352 | |
353 def CD_step(self, x): | |
354 h1 = self.h_sample_given_x_func(x) | |
355 x2 = self.x_sample_given_h_func(h1) | |
356 return self.cd_update_function(x, x2) | |
357 | |
358 def sparsity_step(self, x): | |
359 return self.sparsity_update_function(x) | |
360 | |
361 # these two also operate on minibatches | |
362 | |
363 def random_gibbs_samples(self, num_updown_steps): | |
364 start_x = self.numpy_rng.rand(*self._get_visibles_shape()) | |
365 return self.gibbs_samples_from(start_x, num_updown_steps) | |
366 | |
367 def gibbs_samples_from(self, start_x, num_updown_steps): | |
368 x_sample = start_x | |
369 for i in xrange(num_updown_steps): | |
370 h_sample = self.h_sample_given_x_func(x_sample) | |
371 x_sample = self.x_sample_given_h_func(h_sample) | |
372 return x_sample | |
373 | |
374 |