//  [python]
def run(net_path, training, validation, nem, seed, log_dir, _run):

    # clear debug dir
    if log_dir and net_path is None:
        utils.create_directory(log_dir)
        utils.delete_files(log_dir, recursive=True)

    # Set up data pipelines
    nr_iters = nem['nr_steps'] + 1
    train_inputs = InputPipeLine('training', shuffle=True, sequence_length=nr_iters, batch_size=training['batch_size'])
    valid_inputs = InputPipeLine('validation', shuffle=False, sequence_length=nr_iters, batch_size=validation['batch_size'])

    # Build Graph
    train_op, train_graph, valid_graph, debug_graph = build_graphs(train_inputs.output, valid_inputs.output)
    init = tf.global_variables_initializer()

    # print vars
    utils.print_vars(tf.trainable_variables())

    with tf.Session() as session:
        tf.set_random_seed(seed)

        # continue training from net_path if specified.
        saver = tf.train.Saver()
        if net_path is not None:
            saver.restore(session, net_path)
        else:
            session.run(init)

        # start training pipelines
        coord = tf.train.Coordinator()
        train_enqueue_thread = threading.Thread(target=train_inputs.enqueue, args=[session, coord])
        coord.register_thread(train_enqueue_thread)
        train_enqueue_thread.start()
        valid_enqueue_thread = threading.Thread(target=valid_inputs.enqueue, args=[session, coord])
        coord.register_thread(valid_enqueue_thread)
        valid_enqueue_thread.start()

        best_valid_loss = np.inf
        best_valid_epoch = 0
        for epoch in range(1, training['max_epoch'] + 1):

            t = time.time()
            train_loss, others, train_scores, train_ub_loss_last = run_epoch(
                session, train_inputs, train_graph, debug_graph, training['debug_samples'], "train_e{}".format(epoch),
                train_op=train_op)

            add_log('training.loss', train_loss)
            add_log('training.others', others)
            add_log('training.score', train_scores[0])
            add_log('training.score_last', train_scores[1])
            add_log('training.ub_loss_last', train_ub_loss_last)

            create_curve_plots('train_loss', get_logs('training.loss'), [0, 2000])

            print("Epoch: %d Train Loss: %.3f, ARI: %.3f (conf: %0.3f), Last ARI: %.3f (conf: %.3f) took %.3fs" %
                  (epoch, train_loss, train_scores[0], train_scores[2], train_scores[1], train_scores[3], time.time() - t))
            print("    Other Train Losses:      ({})".format(", ".join(["%.2f" % o for o in others.mean(0)])))
            print("    Train Loss UB last: %.2f" % train_ub_loss_last)

            t = time.time()
            valid_loss, others, valid_scores, valid_ub_loss_last = run_epoch(
                session, valid_inputs, valid_graph, debug_graph, validation['debug_samples'], "valid_e{}".format(epoch))

            # valid_scores = seq_ARI, last_ARI, seq_conf, last_conf
            add_log('validation.loss', valid_loss)
            add_log('validation.others', others)
            add_log('validation.score', valid_scores[0])
            add_log('validation.score_last', valid_scores[1])
            add_log('validation.ub_loss_last', valid_ub_loss_last)

            create_curve_plots('valid_loss', get_logs('validation.loss'), [0, 2000])
            create_curve_plots('valid_score', get_logs('validation.score'), [0, 1])
            create_curve_plots('valid_score_last', get_logs('validation.score_last'), [0, 1])

            print("    Validation Loss: %.3f, ARI: %.3f (conf: %0.3f), Last ARI: %.3f (conf: %.3f) took %.3fs" %
                  (valid_loss, valid_scores[0], valid_scores[2], valid_scores[1], valid_scores[3], time.time() - t))
            print("    Other Validation Losses: ({})".format(", ".join(["%.2f" % o for o in others.mean(0)])))
            print("    Valid Loss UB last: %.2f" % valid_ub_loss_last)

            if valid_loss < best_valid_loss:
                best_valid_loss = valid_loss
                best_valid_epoch = epoch
                _run.result = float(valid_scores[0]), float(valid_scores[1]), float(valid_loss)
                print("    Best validation loss improved to %.03f" % best_valid_loss)
                save_destination = saver.save(session, os.path.abspath(os.path.join(log_dir, 'best')))
                print("    Saved to:", save_destination)
            if epoch in training['save_epochs']:
                save_destination = saver.save(session, os.path.abspath(os.path.join(log_dir, 'epoch_{}'.format(epoch))))
                print("    Saved to:", save_destination)
            best_valid_loss = min(best_valid_loss, valid_loss)
            if best_valid_loss < np.min(get_logs('validation.loss')[-training['max_patience']:]):
                print('Early Stopping because validation loss did not improve for {} epochs'.format(training['max_patience']))
                break

            if np.isnan(valid_loss):
                print('Early Stopping because validation loss is nan')
                break

        # shutdown everything to avoid zombies
        coord.request_stop()
        session.run(train_inputs.queue.close(cancel_pending_enqueues=True))
        session.run(valid_inputs.queue.close(cancel_pending_enqueues=True))
        coord.join()

    return float(get_logs('validation.score')[best_valid_epoch - 1]), float(get_logs('validation.score_last')[best_valid_epoch - 1]), float(get_logs('validation.loss')[best_valid_epoch - 1])