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