def train_model(self, train_data, eval_data=None):
        """
        Trains the model using 'train_data'

        Args:
            train_data: Pandas DataFrame containing the 2 columns - `input_text`, `target_text`.
                        - `input_text`: The input text sequence.
                        - `target_text`: The target text sequence
                        If `use_hf_datasets` is True, then this may also be the path to a TSV file with the same columns.
        Returns:
            training_details: training loss 
        """  # noqa: ignore flake8"

        logger.info("Training model...")
        os.makedirs(self.model_dir, exist_ok=True)
        source_texts, target_texts = create_dataset(train_data)

        self.src_2_ids = read_vocab(source_texts)
        self.trg_2_ids = read_vocab(target_texts)
        self.trg_pad_idx = self.trg_2_ids[PAD_TOKEN]
        save_word_dict(self.src_2_ids, self.src_vocab_path)
        save_word_dict(self.trg_2_ids, self.trg_vocab_path)
        train_src, train_trg = one_hot(source_texts, target_texts, self.src_2_ids, self.trg_2_ids, sort_by_len=True)

        id_2_srcs = {v: k for k, v in self.src_2_ids.items()}
        id_2_trgs = {v: k for k, v in self.trg_2_ids.items()}
        logger.debug(f'train src: {[id_2_srcs[i] for i in train_src[0]]}')
        logger.debug(f'train trg: {[id_2_trgs[i] for i in train_trg[0]]}')

        self.model = ConvSeq2Seq(
            encoder_vocab_size=len(self.src_2_ids),
            decoder_vocab_size=len(self.trg_2_ids),
            embed_size=self.embed_size,
            enc_hidden_size=self.hidden_size,
            dec_hidden_size=self.hidden_size,
            dropout=self.dropout,
            trg_pad_idx=self.trg_pad_idx,
            device=device,
            max_length=self.max_length
        )
        self.model.to(device)
        logger.debug(self.model)
        optimizer = torch.optim.Adam(self.model.parameters())

        train_data = gen_examples(train_src, train_trg, self.batch_size, self.max_length)
        train_losses = []
        best_loss = 1e3
        for epoch in range(self.num_epochs):
            self.model.train()
            total_loss = 0.
            total_iter = 0.
            for it, (mb_x, mb_x_len, mb_y, mb_y_len) in enumerate(train_data):
                src = torch.from_numpy(mb_x).to(device).long()
                trg = torch.from_numpy(mb_y).to(device).long()
                output, attn = self.model(src, trg[:, :-1])

                output_dim = output.shape[-1]
                output = output.contiguous().view(-1, output_dim)
                trg = trg[:, 1:].contiguous().view(-1)
                # output = [batch size * trg len - 1, output dim]
                # trg = [batch size * trg len - 1]
                loss = self.loss_fn(output, trg)
                total_loss += loss.item()
                total_iter += 1

                # update optimizer
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

                if it % 100 == 0:
                    logger.debug("Epoch :{}/{}, iteration :{}/{} loss:{:.4f}".format(epoch, self.num_epochs,
                                                                                     it, len(train_data),
                                                                                     loss.item()))
            cur_loss = total_loss / total_iter
            train_losses.append(cur_loss)
            logger.debug("Epoch :{}/{}, Training loss:{:.4f}".format(epoch, self.num_epochs, cur_loss))
            if epoch % 1 == 0:
                # find best model
                is_best = cur_loss < best_loss
                best_loss = min(cur_loss, best_loss)
                if is_best:
                    self.save_model()
                    logger.info('Epoch:{}, save new bert model:{}'.format(epoch, self.model_path))
                if eval_data:
                    self.eval_model(eval_data)

        return train_losses

        Trains the model using 'train_data'

        Args:
            train_data: Pandas DataFrame containing the 2 columns - `input_text`, `target_text`.
                        - `input_text`: The input text sequence.
                        - `target_text`: The target text sequence
                        If `use_hf_datasets` is True, then this may also be the path to a TSV file with the same columns.
        Returns:
            training_details: training loss 
        
pycorrector/seq2seq/conv_seq2seq_model.py