def model_fit(self, data_loader, epoch):
    discrm_distnc_mtrx = np.zeros((self.num_tsk, self.num_tsk))
    loss_mtrx_hypo_vlue = np.zeros((self.num_tsk, self.num_tsk))
    (weigh_loss_hypo_vlue, correct_hypo) = (np.zeros(self.num_tsk), np.zeros(self.num_tsk))
    Total_loss = 0
    n_batch = 0
    self.FE.train()
    for t in range(self.num_tsk):
        self.hypothesis[t].train()
        for j in range((t + 1), self.num_tsk):
            self.discrm['{}{}'.format(t, j)].train()
    for tasks_batch in zip(*data_loader):
        (Loss_1, Loss_2) = (0, 0)
        n_batch += 1
        inputs = torch.cat([batch[0] for batch in tasks_batch])
        btch_sz = len(tasks_batch[0][0])
        targets = torch.cat([batch[1] for batch in tasks_batch])
        inputs = inputs.to(self.device)
        targets = targets.to(self.device)
        features = self.FE(inputs)
        features = features.view(features.size(0), (- 1))
        for t in range(self.num_tsk):
            w = torch.tensor([np.tile(self.alpha[(t, i)], reps=len(data[0])) for (i, data) in enumerate(tasks_batch)], dtype=torch.float).view((- 1))
            w = w.to(self.device)
            label_prob = self.hypothesis[t](features)
            pred = label_prob[(t * btch_sz):((t + 1) * btch_sz)].argmax(dim=1, keepdim=True)
            correct_hypo[t] += (pred.eq(targets[(t * btch_sz):((t + 1) * btch_sz)].view_as(pred)).sum().item() / btch_sz)
            hypo_loss = torch.mean((w * F.cross_entropy(label_prob, targets, reduction='none')))
            Loss_1 += hypo_loss
            weigh_loss_hypo_vlue[t] += hypo_loss.item()
            loss_mtrx_hypo_vlue[t, :] += [F.cross_entropy(label_prob[(j * btch_sz):((j + 1) * btch_sz), :], targets[(j * btch_sz):((j + 1) * btch_sz)], reduction='mean').item() for j in range(self.num_tsk)]
            for k in range((t + 1), self.num_tsk):
                alpha_domain = torch.tensor((self.alpha[(t, k)] + self.alpha[(k, t)]), dtype=torch.float)
                alpha_domain = alpha_domain.to(self.device)
                if (self.criterion == 'h_divergence'):
                    domain_y = torch.cat([torch.ones(len(tasks_batch[t][0]), dtype=torch.float), torch.zeros(len(tasks_batch[k][0]), dtype=torch.float)])
                    domain_y = domain_y.to(self.device)
                    domain_features = torch.cat([features[(t * btch_sz):((t + 1) * btch_sz)], features[(k * btch_sz):((k + 1) * btch_sz)]])
                    domain_features = domain_features.view(domain_features.size(0), (- 1))
                    domain_pred = self.discrm['{}{}'.format(t, k)](domain_features).squeeze()
                    disc_loss = F.binary_cross_entropy(domain_pred, domain_y)
                    domain_lbl = (domain_pred >= 0.5)
                    domain_lbl = domain_lbl.type(torch.cuda.FloatTensor)
                    discrm_distnc_mtrx[(t, k)] += (domain_lbl.eq(domain_y).sum().item() / len(domain_y))
                    discrm_distnc_mtrx[(k, t)] = discrm_distnc_mtrx[(t, k)]
                    print(discrm_distnc_mtrx[t, :])
                elif (self.criterion == 'wasserstien'):
                    features_t = features[(t * btch_sz):((t + 1) * btch_sz)]
                    features_t = features_t.view(features_t.size(0), (- 1))
                    features_k = features[(k * btch_sz):((k + 1) * btch_sz)]
                    features_k = features_k.view(features_k.size(0), (- 1))
                    pred_k = self.discrm['{}{}'.format(t, k)](features_k).squeeze()
                    pred_t = self.discrm['{}{}'.format(t, k)](features_t).squeeze()
                    gradient_pntly = self.gradient_penalty(inputs[(t * btch_sz):((t + 1) * btch_sz)], inputs[(k * btch_sz):((k + 1) * btch_sz)], t, k)
                    disc_loss = ((pred_t.mean() - pred_k.mean()) + (self.grad_weight * gradient_pntly))
                    discrm_distnc_mtrx[(t, k)] += (- (pred_t.mean() - pred_k.mean()).item())
                    discrm_distnc_mtrx[(k, t)] = discrm_distnc_mtrx[(t, k)]
                disc_loss = (alpha_domain * disc_loss)
                Loss_2 += disc_loss
        if ((n_batch % 500) == 0):
            grid_img = torchvision.utils.make_grid(inputs, nrow=5, padding=30)
            self.writer.add_image('result Image', grid_img)
        Loss = (torch.mean(Loss_1) + (Loss_2 * (1 / self.num_tsk)))
        Total_loss += Loss.item()
        self.optimizer.zero_grad()
        Loss.backward()
        self.optimizer.step()
    discrm_distnc_mtrx /= n_batch
    weigh_loss_hypo_vlue /= n_batch
    loss_mtrx_hypo_vlue /= n_batch
    correct_hypo /= n_batch
    Total_loss /= n_batch
    print('================== epoch {:d} ========'.format(epoch))
    print('Final Total Loss {:.3f}'.format(Total_loss))
    print(((('discriminator distance based on ' + self.criterion) + '\n') + str(discrm_distnc_mtrx)))
    print((' hypothesis loss \n' + str(loss_mtrx_hypo_vlue)))
    print((' hypothesis accuracy \n' + str((correct_hypo * 100))))
    print('coefficient:', self.alpha)
    self.writer.add_scalars('MTL_total_loss', {'MTL_total_loss': Total_loss}, epoch)
    for t in range(self.num_tsk):
        for j in range(self.num_tsk):
            if (j != t):
                self.writer.add_scalars((('task_' + str(t)) + '/Discrm_distance'), {('loss_D' + '_'.join([self.tsklist[t], self.tsklist[j]])): discrm_distnc_mtrx[(t, j)]}, epoch)
            self.writer.add_scalars((('task_' + str(t)) + '/alpha'), {('alpha' + '_'.join([self.tsklist[t], self.tsklist[j]])): self.alpha[(t, j)]}, epoch)
    if ((epoch % 1) == 0):
        (c_2, c_3) = ((1 * np.ones(self.num_tsk)), (self.c3_value * np.ones(self.num_tsk)))
        self.alpha = alpha_opt.min_alphacvx(self.alpha.T, c_2, c_3, loss_mtrx_hypo_vlue.T, discrm_distnc_mtrx.T)
        self.alpha = self.alpha.T
    return Total_loss
def model_fit(self, data_loader, epoch):
    discrm_distnc_mtrx = np.zeros((self.num_tsk, self.num_tsk))
    loss_mtrx_hypo_vlue = np.zeros((self.num_tsk, self.num_tsk))
    (weigh_loss_hypo_vlue, correct_hypo) = (np.zeros(self.num_tsk), np.zeros(self.num_tsk))
    Total_loss = 0
    n_batch = 0
    self.FE.train()
    for t in range(self.num_tsk):
        self.hypothesis[t].train()
        for j in range((t + 1), self.num_tsk):
            self.discrm['{}{}'.format(t, j)].train()
    for tasks_batch in zip(*data_loader):
        (Loss_1, Loss_2) = (0, 0)
        n_batch += 1
        inputs = torch.cat([batch[0] for batch in tasks_batch])
        btch_sz = len(tasks_batch[0][0])
        targets = torch.cat([batch[1] for batch in tasks_batch])
        inputs = inputs.to(self.device)
        targets = targets.to(self.device)
        features = self.FE(inputs)
        features = features.view(features.size(0), (- 1))
        for t in range(self.num_tsk):
            w = torch.tensor([np.tile(self.alpha[(t, i)], reps=len(data[0])) for (i, data) in enumerate(tasks_batch)], dtype=torch.float).view((- 1))
            w = w.to(self.device)
            label_prob = self.hypothesis[t](features)
            pred = label_prob[(t * btch_sz):((t + 1) * btch_sz)].argmax(dim=1, keepdim=True)
            correct_hypo[t] += (pred.eq(targets[(t * btch_sz):((t + 1) * btch_sz)].view_as(pred)).sum().item() / btch_sz)
            hypo_loss = torch.mean((w * F.cross_entropy(label_prob, targets, reduction='none')))
            Loss_1 += hypo_loss
            weigh_loss_hypo_vlue[t] += hypo_loss.item()
            loss_mtrx_hypo_vlue[t, :] += [F.cross_entropy(label_prob[(j * btch_sz):((j + 1) * btch_sz), :], targets[(j * btch_sz):((j + 1) * btch_sz)], reduction='mean').item() for j in range(self.num_tsk)]
            for k in range((t + 1), self.num_tsk):
                alpha_domain = torch.tensor((self.alpha[(t, k)] + self.alpha[(k, t)]), dtype=torch.float)
                alpha_domain = alpha_domain.to(self.device)
                if (self.criterion == 'h_divergence'):
                    domain_y = torch.cat([torch.ones(len(tasks_batch[t][0]), dtype=torch.float), torch.zeros(len(tasks_batch[k][0]), dtype=torch.float)])
                    domain_y = domain_y.to(self.device)
                    domain_features = torch.cat([features[(t * btch_sz):((t + 1) * btch_sz)], features[(k * btch_sz):((k + 1) * btch_sz)]])
                    domain_features = domain_features.view(domain_features.size(0), (- 1))
                    domain_pred = self.discrm['{}{}'.format(t, k)](domain_features).squeeze()
                    disc_loss = F.binary_cross_entropy(domain_pred, domain_y)
                    domain_lbl = (domain_pred >= 0.5)
                    domain_lbl = domain_lbl.type(torch.cuda.FloatTensor)
                    discrm_distnc_mtrx[(t, k)] += (domain_lbl.eq(domain_y).sum().item() / len(domain_y))
                    discrm_distnc_mtrx[(k, t)] = discrm_distnc_mtrx[(t, k)]
                    print(discrm_distnc_mtrx[t, :])
                elif (self.criterion == 'wasserstien'):
                    features_t = features[(t * btch_sz):((t + 1) * btch_sz)]
                    features_t = features_t.view(features_t.size(0), (- 1))
                    features_k = features[(k * btch_sz):((k + 1) * btch_sz)]
                    features_k = features_k.view(features_k.size(0), (- 1))
                    pred_k = self.discrm['{}{}'.format(t, k)](features_k).squeeze()
                    pred_t = self.discrm['{}{}'.format(t, k)](features_t).squeeze()
                    gradient_pntly = self.gradient_penalty(inputs[(t * btch_sz):((t + 1) * btch_sz)], inputs[(k * btch_sz):((k + 1) * btch_sz)], t, k)
                    disc_loss = ((pred_t.mean() - pred_k.mean()) + (self.grad_weight * gradient_pntly))
                    discrm_distnc_mtrx[(t, k)] += (- (pred_t.mean() - pred_k.mean()).item())
                    discrm_distnc_mtrx[(k, t)] = discrm_distnc_mtrx[(t, k)]
                disc_loss = (alpha_domain * disc_loss)
                Loss_2 += disc_loss
        if ((n_batch % 500) == 0):
            grid_img = torchvision.utils.make_grid(inputs, nrow=5, padding=30)
            self.writer.add_image('result Image', grid_img)
        Loss = (torch.mean(Loss_1) + (Loss_2 * (1 / self.num_tsk)))
        Total_loss += Loss.item()
        self.optimizer.zero_grad()
        Loss.backward()
        self.optimizer.step()
    discrm_distnc_mtrx /= n_batch
    weigh_loss_hypo_vlue /= n_batch
    loss_mtrx_hypo_vlue /= n_batch
    correct_hypo /= n_batch
    Total_loss /= n_batch
    print('================== epoch {:d} ========'.format(epoch))
    print('Final Total Loss {:.3f}'.format(Total_loss))
    print(((('discriminator distance based on ' + self.criterion) + '\n') + str(discrm_distnc_mtrx)))
    print((' hypothesis loss \n' + str(loss_mtrx_hypo_vlue)))
    print((' hypothesis accuracy \n' + str((correct_hypo * 100))))
    print('coefficient:', self.alpha)
    self.writer.add_scalars('MTL_total_loss', {'MTL_total_loss': Total_loss}, epoch)
    for t in range(self.num_tsk):
        for j in range(self.num_tsk):
            if (j != t):
                self.writer.add_scalars((('task_' + str(t)) + '/Discrm_distance'), {('loss_D' + '_'.join([self.tsklist[t], self.tsklist[j]])): discrm_distnc_mtrx[(t, j)]}, epoch)
            self.writer.add_scalars((('task_' + str(t)) + '/alpha'), {('alpha' + '_'.join([self.tsklist[t], self.tsklist[j]])): self.alpha[(t, j)]}, epoch)
    if ((epoch % 1) == 0):
        (c_2, c_3) = ((1 * np.ones(self.num_tsk)), (self.c3_value * np.ones(self.num_tsk)))
        self.alpha = alpha_opt.min_alphacvx(self.alpha.T, c_2, c_3, loss_mtrx_hypo_vlue.T, discrm_distnc_mtrx.T)
        self.alpha = self.alpha.T
    return Total_loss