Total Complexity | 45 |
Total Lines | 283 |
Duplicated Lines | 14.84 % |
Changes | 4 | ||
Bugs | 0 | Features | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like LSTM often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | import os |
||
210 | class LSTM: |
||
211 | """Single Layer LSTM Implementation |
||
212 | |||
213 | In this new implementation, state_is_tuple is disabled to suppress the "deprecated" warning and |
||
214 | performance improvement. The static unrolling of the RNN is replaced with dynamic unrolling. |
||
215 | As a result, no batch injector is needed for prediction. |
||
216 | |||
217 | Args: |
||
218 | num_features (:obj:`int`): Number of input features. |
||
219 | num_classes (:obj:`int`): Number of target classes. |
||
220 | num_hidden (:obj:`int`): Number of units in the input hidden layer. |
||
221 | num_units (:obj:`int`): Number of units in the RNN layer. |
||
222 | |||
223 | Attributes: |
||
224 | num_features (:obj:`int`): Number of input features. |
||
225 | num_classes (:obj:`int`): Number of target classes. |
||
226 | num_hidden (:obj:`int`): Number of units in the input hidden layer. |
||
227 | num_units (:obj:`int`): Number of units in the RNN layer. |
||
228 | summaries (:obj:`list`): List of tensorflow summaries to be displayed on tensorboard. |
||
229 | x (:obj:`tf.Tensor`): Input tensor of size [num_batches, length, num_features] |
||
230 | length (:obj:`tf.Tensor`): 1D length array (int) of size [num_batches, 1] for the length of each batch data. |
||
231 | init_state (:obj:`tf.Tensor`): Initial states. 2D tensor (float) of size [num_batches, 2*num_units]. |
||
232 | y_ (:obj:`tf.Tensor`): Ground Truth of size [num_batches, length, num_classes]. |
||
233 | """ |
||
234 | def __init__(self, num_features, num_classes, num_hidden, num_units, num_skip=0, graph=None, optimizer=None): |
||
235 | self.num_features = num_features |
||
236 | self.num_classes = num_classes |
||
237 | self.num_units = num_units |
||
238 | self.num_skip = num_skip |
||
239 | self.summaries = [] |
||
240 | if graph is None: |
||
241 | self.graph = tf.Graph() |
||
242 | with self.graph.as_default(): |
||
243 | # Inputs |
||
244 | with tf.name_scope('input'): |
||
245 | # Input tensor X, shape: [batch, length, features] |
||
246 | self.x = tf.placeholder(tf.float32, shape=[None, None, num_features], name='input_x') |
||
247 | # Length, shape: [batch, length] |
||
248 | self.length = tf.placeholder(tf.float32, shape=[None, ], name='input_x_length') |
||
249 | # Initial states (as tupples), shape: [batch, units] |
||
250 | self.initial_state_c = tf.placeholder(tf.float32, shape=[None, num_units], name='initial_state_c') |
||
251 | self.initial_state_h = tf.placeholder(tf.float32, shape=[None, num_units], name='initial_state_h') |
||
252 | # Targets, shape: [batch, length, classes] |
||
253 | self.y_ = tf.placeholder(tf.float32, shape=[None, None, num_classes], name='targets') |
||
254 | # Input hidden layer with num_hidden units |
||
255 | with tf.name_scope('input_layer'): |
||
256 | self.input_W = tf.Variable( |
||
257 | tf.truncated_normal( |
||
258 | shape=[num_features, num_hidden], stddev=1.0 / math.sqrt(float(num_hidden))), |
||
259 | name='weights') |
||
260 | self.input_b = tf.Variable(tf.zeros(shape=[num_hidden]), name='bias') |
||
261 | |||
262 | def hidden_fn(slice): |
||
263 | return tf.nn.sigmoid(tf.matmul(slice, self.input_W) + self.input_b) |
||
264 | # Activation of hidden layer, shape: [batch, length, num_hidden] |
||
265 | self.hidden_y = tf.map_fn(hidden_fn, self.x) |
||
266 | # Recursive Layer (RNN) |
||
267 | with tf.name_scope('rnn'): |
||
268 | # Apply RNN |
||
269 | self.cell = tfcontrib.rnn.BasicLSTMCell(num_units=num_units, state_is_tuple=True) |
||
270 | # rnn outputs, shape: [batch, length, num_units] |
||
271 | rnn_outputs, rnn_states = tf.nn.dynamic_rnn( |
||
272 | self.cell, self.hidden_y, sequence_length=self.length, |
||
273 | initial_state=tfcontrib.rnn.LSTMStateTuple(self.initial_state_c, self.initial_state_h)) |
||
274 | # Apply Softmax Layer to all outputs in all batches |
||
275 | with tf.name_scope('output_layer'): |
||
276 | self.output_W = tf.Variable( |
||
277 | tf.truncated_normal(shape=[num_units, num_classes], stddev=1.0/math.sqrt(float(num_units))), |
||
278 | name='weights' |
||
279 | ) |
||
280 | self.output_b = tf.Variable(tf.zeros(shape=[num_classes]), name='biases') |
||
281 | |||
282 | def out_mult_fn(slice): |
||
283 | return tf.matmul(slice, self.output_W) + self.output_b |
||
284 | |||
285 | def out_softmax_fn(slice): |
||
286 | return tf.nn.softmax(slice) |
||
287 | |||
288 | def out_class_fn(slice): |
||
289 | return tf.argmax(slice, axis=1) |
||
290 | |||
291 | def out_softmax_entropy(params): |
||
292 | logits, labels = params |
||
293 | return tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels) |
||
294 | |||
295 | # self.logit_outputs is a tensor of shape [batch, length, num_classes] |
||
296 | self.logit_outputs = tf.map_fn(out_mult_fn, rnn_outputs) |
||
297 | # self.softmax_outputs applies softmax to logit_outputs as a tensor of shape |
||
298 | # [batch, length, num_classes] |
||
299 | self.softmax_outputs = tf.map_fn(out_softmax_fn, self.logit_outputs) |
||
300 | # Probability output y, shape: [batch, length-num_skip, num_classes] |
||
301 | self.y = self.softmax_outputs[:, num_skip:, :] |
||
302 | self.y_class = tf.map_fn(out_class_fn, self.y, dtype=tf.int64) |
||
303 | |||
304 | # Acciracy |
||
305 | def accuracy_fn(params): |
||
306 | prediction, truth = params |
||
307 | return tf.reduce_mean(tf.cast(tf.equal(prediction, tf.argmax(truth, 1)), tf.float32)) |
||
308 | |||
309 | self.accuracy_outputs = tf.map_fn(accuracy_fn, (self.y_class, self.y_[:, num_skip:, :]), dtype=tf.float32) |
||
310 | self.accuracy = tf.reduce_mean(self.accuracy_outputs) |
||
311 | # self.class_outputs gets the class label for each item in sequence as a tensor of shape |
||
312 | # [batch_size, max_time, 1] |
||
313 | self.entropy_outputs = tf.map_fn(out_softmax_entropy, |
||
314 | (self.logit_outputs[:, num_skip:, :], self.y_[:, num_skip:, :]), |
||
315 | dtype=tf.float32) |
||
316 | # Softmax Cross-Entropy Loss |
||
317 | self.loss = tf.reduce_mean(self.entropy_outputs) |
||
318 | # Setup Optimizer |
||
319 | if optimizer is None: |
||
320 | self.optimizer = tf.train.AdamOptimizer() |
||
321 | else: |
||
322 | self.optimizer = optimizer |
||
323 | # Fit Step |
||
324 | with tf.name_scope('train'): |
||
325 | self.fit_step = self.optimizer.minimize(self.loss) |
||
326 | # Setup Summaries |
||
327 | self.summaries.append(variable_summary(self.input_W, tag='input_layer/weights')) |
||
328 | self.summaries.append(variable_summary(self.input_b, tag='input_layer/biases')) |
||
329 | self.summaries.append(variable_summary(self.output_W, tag='output_layer/weights')) |
||
330 | self.summaries.append(variable_summary(self.output_b, tag='output_layer/biases')) |
||
331 | self.summaries.append(tf.summary.scalar('cross_entropy', self.loss)) |
||
332 | self.summaries.append(tf.summary.scalar('accuracy', self.accuracy)) |
||
333 | self.merged = tf.summary.merge(self.summaries) |
||
334 | self.init_op = tf.global_variables_initializer() |
||
335 | self.sess = None |
||
336 | |||
337 | def fit(self, x, y, length, batch_size=100, iter_num=100, summaries_dir=None, summary_interval=100, |
||
338 | test_x=None, test_y=None, session=None, criterion='const_iteration', reintialize=True): |
||
339 | """Fit the model to the dataset |
||
340 | |||
341 | Args: |
||
342 | x (:obj:`numpy.ndarray`): Input features x, shape: [num_samples, num_features]. |
||
343 | y (:obj:`numpy.ndarray`): Corresponding Labels of shape (num_samples) for binary classification, |
||
344 | or (num_samples, num_classes) for multi-class classification. |
||
345 | length (:obj:`int`): Length of each batch (needs to be greater than self.num_skip. |
||
346 | batch_size (:obj:`int`): Batch size used in gradient descent. |
||
347 | iter_num (:obj:`int`): Number of training iterations for const iterations, step depth for monitor based |
||
348 | stopping criterion. |
||
349 | summaries_dir (:obj:`str`): Path of the directory to store summaries and saved values. |
||
350 | summary_interval (:obj:`int`): The step interval to export variable summaries. |
||
351 | test_x (:obj:`numpy.ndarray`): Test feature array used for monitoring training progress. |
||
352 | test_y (:obj:`numpy.ndarray): Test label array used for monitoring training progress. |
||
353 | session (:obj:`tensorflow.Session`): Session to run training functions. |
||
354 | criterion (:obj:`str`): Stopping criteria. 'const_iterations' or 'monitor_based' |
||
355 | """ |
||
356 | if session is None: |
||
357 | if self.sess is None: |
||
358 | session = tf.Session() |
||
359 | self.sess = session |
||
360 | else: |
||
361 | session = self.sess |
||
362 | if summaries_dir is not None: |
||
363 | train_writer = tf.summary.FileWriter(summaries_dir + '/train') |
||
364 | test_writer = tf.summary.FileWriter(summaries_dir + '/test') |
||
365 | valid_writer = tf.summary.FileWriter(summaries_dir + '/valid') |
||
366 | else: |
||
367 | train_writer = None |
||
368 | test_writer = None |
||
369 | valid_writer = None |
||
370 | if reintialize: |
||
371 | session.run(self.init_op) |
||
372 | with self.graph.as_default(): |
||
373 | saver = tf.train.Saver() |
||
374 | num_samples = x.shape[0] |
||
375 | # Get Stopping Criterion |
||
376 | if criterion == 'const_iteration': |
||
377 | _criterion = ConstIterations(num_iters=iter_num) |
||
378 | elif criterion == 'monitor_based': |
||
379 | valid_set_start = int(4/5 * (num_samples - self.num_skip)) |
||
380 | valid_x = x[valid_set_start:num_samples, :] |
||
381 | valid_y = y[valid_set_start:num_samples, :] |
||
382 | x = x[0:valid_set_start + self.num_skip, :] |
||
383 | y = y[0:valid_set_start + self.num_skip, :] |
||
384 | _criterion = MonitorBased(n_steps=iter_num, |
||
385 | monitor_fn=self.predict_accuracy, |
||
386 | monitor_fn_args=(valid_x, valid_y), |
||
387 | save_fn=saver.save, |
||
388 | save_fn_args=(session, summaries_dir + '/best.ckpt')) |
||
389 | else: |
||
390 | logger.error('Wrong criterion %s specified.' % criterion) |
||
391 | return |
||
392 | # Setup batch injector |
||
393 | injector = BatchSequenceInjector(data_x=x, data_y=y, batch_size=batch_size, length=self.num_skip + length, |
||
394 | with_seq=True) |
||
395 | # Iteration Starts |
||
396 | i = 0 |
||
397 | while _criterion.continue_learning(): |
||
398 | # Learning |
||
399 | batch_x, batch_y, batch_length = injector.next_batch(skip=50) |
||
400 | loss, accuracy, _ = session.run( |
||
401 | [self.loss, self.accuracy, self.fit_step], |
||
402 | feed_dict={self.x: batch_x, self.y_: batch_y, self.length: batch_length, |
||
403 | self.initial_state_c: np.zeros((batch_x.shape[0], self.num_units)), |
||
404 | self.initial_state_h: np.zeros((batch_x.shape[0], self.num_units))}) |
||
405 | # Take summaries |
||
406 | if summaries_dir is not None and (i % summary_interval == 0): |
||
407 | accuracy, loss = self.predict_accuracy(x, y, writer=train_writer, writer_id=i, with_loss=True) |
||
408 | logger.info('Step %d, train_set accuracy %g, loss %g' % (i, accuracy, loss)) |
||
409 | accuracy, loss = self.predict_accuracy(test_x, test_y, writer=test_writer, writer_id=i, with_loss=True) |
||
410 | logger.info('Step %d, test_set accuracy %g, loss %g' % (i, accuracy, loss)) |
||
411 | if criterion == 'monitor_based': |
||
412 | accuracy, loss = self.predict_accuracy(valid_x, valid_y, writer=valid_writer, writer_id=i, with_loss=True) |
||
413 | logger.info('Step %d, valid_set accuracy %g, loss %g' % (i, accuracy, loss)) |
||
414 | # Get Summary |
||
415 | i += 1 |
||
416 | # Finish Iteration |
||
417 | if criterion == 'monitor_based': |
||
418 | saver.restore(session, os.path.join(summaries_dir, 'best.ckpt')) |
||
419 | logger.debug('Total Epoch: %d, current batch %d', injector.num_epochs, injector.cur_batch) |
||
420 | |||
421 | View Code Duplication | def predict_proba(self, x, session=None, writer=None, writer_id=None): |
|
422 | """Predict probability (Softmax) |
||
423 | """ |
||
424 | if session is None: |
||
425 | if self.sess is None: |
||
426 | session = tf.Session() |
||
427 | self.sess = session |
||
428 | else: |
||
429 | session = self.sess |
||
430 | targets = [self.y] |
||
431 | if writer is not None: |
||
432 | targets += [self.merged] |
||
433 | results = session.run(targets, |
||
434 | feed_dict={self.x: x.reshape(tuple([1]) + x.shape), |
||
435 | self.length: np.array([x.shape[0]], dtype=np.int), |
||
436 | self.initial_state_c: np.zeros((1, self.num_units)), |
||
437 | self.initial_state_h: np.zeros((1, self.num_units))}) |
||
438 | if writer is not None: |
||
439 | writer.add_summary(results[1], writer_id) |
||
440 | batch_y = results[0] |
||
441 | # Get result |
||
442 | return batch_y[0, :, :] |
||
443 | |||
444 | View Code Duplication | def predict(self, x, session=None, writer=None, writer_id=None): |
|
445 | if session is None: |
||
446 | if self.sess is None: |
||
447 | session = tf.Session() |
||
448 | self.sess = session |
||
449 | else: |
||
450 | session = self.sess |
||
451 | targets = [self.y_class] |
||
452 | if writer is not None: |
||
453 | targets += [self.merged] |
||
454 | results = session.run(targets, |
||
455 | feed_dict={self.x: x.reshape(tuple([1]) + x.shape), |
||
456 | self.length: np.array([x.shape[0]], dtype=np.int), |
||
457 | self.initial_state_c: np.zeros((1, self.num_units)), |
||
458 | self.initial_state_h: np.zeros((1, self.num_units))}) |
||
459 | if writer is not None: |
||
460 | writer.add_summary(results[1], writer_id) |
||
461 | batch_y = results[0] |
||
462 | # Get result |
||
463 | return batch_y[0, :] |
||
464 | |||
465 | def predict_accuracy(self, x, y, session=None, writer=None, writer_id=None, with_loss=False): |
||
466 | """Get Accuracy given feature array and corresponding labels |
||
467 | """ |
||
468 | if session is None: |
||
469 | if self.sess is None: |
||
470 | session = tf.Session() |
||
471 | self.sess = session |
||
472 | else: |
||
473 | session = self.sess |
||
474 | targets = [self.accuracy] |
||
475 | if with_loss: |
||
476 | targets += [self.loss] |
||
477 | if writer is not None: |
||
478 | targets += [self.merged] |
||
479 | results = session.run(targets, |
||
480 | feed_dict={self.x: x.reshape(tuple([1]) + x.shape), |
||
481 | self.y_: y.reshape(tuple([1]) + y.shape), |
||
482 | self.length: np.array([x.shape[0]], dtype=np.int), |
||
483 | self.initial_state_c: np.zeros((1, self.num_units)), |
||
484 | self.initial_state_h: np.zeros((1, self.num_units))}) |
||
485 | if with_loss: |
||
486 | return_values = results[0], results[1] |
||
487 | else: |
||
488 | return_values = results[0] |
||
489 | if writer is not None: |
||
490 | writer.add_summary(results[-1], writer_id) |
||
491 | # Get result |
||
492 | return return_values |
||
493 | |||
684 | self.init_state: np.zeros(2*self.num_units)}) |