//  [python]
def train(self, data):
        """Training function.

        Parameters
        ----------
        data_tr : tuple
            Training data.

        data_va : tuple
            Validation data.

        x_va : ndarray
            Validation data.

        y_va : ndarray
            Validation labels.

        """

        # ----------------------------------------
        # Resume data if it already exists
        latest_checkpoint = tf.train.latest_checkpoint(
            self.res_dir_tr)
        b_resume = latest_checkpoint is not None
        if b_resume:
            # Restore network
            print("Restoring from {}...".format(
                self.res_dir_tr))
            self.saver_cur.restore(
                self.sess,
                latest_checkpoint
            )
            # restore number of steps so far
            step = self.sess.run(self.global_step)
            # restore best validation result
            if os.path.exists(self.va_res_file):
                with open(self.va_res_file, "r") as ifp:
                    dump_res = ifp.read()
                dump_res = parse(
                    "{best_va_res:e}\n", dump_res)
                best_va_res = dump_res["best_va_res"]
        else:
            print("Starting from scratch...")
            step = 0
            best_va_res = -1

        # ----------------------------------------
        # Unpack some data for simple coding
        xs_tr = data["train"]["xs"]
        ys_tr = data["train"]["ys"]
        Rs_tr = data["train"]["Rs"]
        ts_tr = data["train"]["ts"]

        # ----------------------------------------
        # The training loop
        print("Initializing...")
        self.sess.run(tf.global_variables_initializer())
        batch_size = self.config.train_batch_size
        max_iter = self.config.train_iter
        for step in trange(step, max_iter, ncols=self.config.tqdm_width):

            # ----------------------------------------
            # Batch construction

            # Get a random training batch
            ind_cur = np.random.choice(
                len(xs_tr), batch_size, replace=False)
            # Use minimum kp in batch to construct the batch
            numkps = np.array([xs_tr[_i].shape[1] for _i in ind_cur])
            cur_num_kp = numkps.min()
            # Actual construction of the batch
            xs_b = np.array(
                [xs_tr[_i][:, :cur_num_kp, :] for _i in ind_cur]
            ).reshape(batch_size, 1, cur_num_kp, 4)
            ys_b = np.array(
                [ys_tr[_i][:cur_num_kp, :] for _i in ind_cur]
            ).reshape(batch_size, cur_num_kp, 2)
            Rs_b = np.array(
                [Rs_tr[_i] for _i in ind_cur]
            ).reshape(batch_size, 9)
            ts_b = np.array(
                [ts_tr[_i] for _i in ind_cur]
            ).reshape(batch_size, 3)

            # ----------------------------------------
            # Train

            # Feed Dict
            feed_dict = {
                self.x_in: xs_b,
                self.y_in: ys_b,
                self.R_in: Rs_b,
                self.t_in: ts_b,
                self.is_training: True,
            }
            # Fetch
            fetch = {
                "optim": self.optim,
            }
            # Check if we want to write summary and check validation
            b_write_summary = ((step + 1) % self.config.report_intv) == 0
            b_validate = ((step + 1) % self.config.val_intv) == 0
            if b_write_summary or b_validate:
                fetch["summary"] = self.summary_op
                fetch["global_step"] = self.global_step
            # Run optimization
            try:
                res = self.sess.run(fetch, feed_dict=feed_dict)
            except (ValueError, tf.errors.InvalidArgumentError):
                print("Backward pass had numerical errors. "
                      "This training batch is skipped!")
                continue
            # Write summary and save current model
            if b_write_summary:
                self.summary_tr.add_summary(
                    res["summary"], global_step=res["global_step"])
                self.saver_cur.save(
                    self.sess, self.save_file_cur,
                    global_step=self.global_step,
                    write_meta_graph=False)

            # ----------------------------------------
            # Validation
            if b_validate:
                va_res = 0
                cur_global_step = res["global_step"]
                va_res = test_process(
                    "valid", self.sess, cur_global_step,
                    self.summary_op, self.summary_va,
                    self.x_in, self.y_in, self.R_in, self.t_in,
                    self.is_training,
                    None, None, None,
                    self.logits, self.e_hat, self.loss,
                    data["valid"],
                    self.res_dir_va, self.config, True)
                # Higher the better
                if va_res > best_va_res:
                    print(
                        "Saving best model with va_res = {}".format(
                            va_res))
                    best_va_res = va_res
                    # Save best validation result
                    with open(self.va_res_file, "w") as ofp:
                        ofp.write("{:e}\n".format(best_va_res))
                    # Save best model
                    self.saver_best.save(
                        self.sess, self.save_file_best,
                        write_meta_graph=False,
                    )