# MindSpore实现多输出模型的loss用LossBase类实现

**1. 系统环境**

硬件环境\(Ascend/GPU/CPU\): Ascend/GPU/CPU

软件环境: MindSpore 版本: 2.0.0

执行模式（静态图/动态图）： 不限

Python 版本: 3.7.6

操作系统平台: linux

**2. 报错信息**

2.1 问题描述

模型训练需要用到模型中间层特征来计算loss（特征间的距离），因此实现模型的时候会额外输出中间的特征。

2.2 脚本信息

```
class DCTLN(nn.Cell):
    def __init__(self, input_shape, num_classes, BatchNormal):
        super().__init__()
        self.feature_extractor = FeatureExtractor(input_shape, 20, 5, 1, 'same', 2, BatchNormal)
        self.hd_classifier = HealthConditionClassifier(100, num_classes)
        self.domain_classifier = DomainClassifier(100)
        self.grl = GradReverse()
        self.apply(self._init_weights)

    def construct(self, x):
        features = self.feature_extractor(x)
        health_condition = self.hd_classifier(features)
        domain = self.domain_classifier(self.grl(features))
        return features, health_condition, domain
```

使用pytorch写模型和训练脚本的时候，需要计算出loss然后backward就行。在mindspore的反向传播有些不同，目前写成了forward\_fn，然后用value\_rand\_grad函数来获得梯度并反向传播。

```
def forward_fn(X_source, y_source, X_target, y_target, penalty_param):
    X = ops.concat([X_source, X_target], axis=0)
    # predict and calculate the loss
    features, logits, domain = model(X)

    classification_loss = loss_fn(logits.narrow(0, 0, source_size), y_source)
    domain_label = ops.concat([ops.zeros(source_size), ops.ones(target_size)], axis=0)
    domain_loss = ops.binary_cross_entropy(domain, domain_label.reshape(-1, 1))

    mmd_value = mmd(features.narrow(0, 0, source_size), features.narrow(0, source_size, target_size))

    # 更新惩罚系数
    model.set_lambda(penalty_param)
    loss = classification_loss #+ domain_loss + penalty_param * mmd_value

    return loss, logits

grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)
(loss, logits), grads = grad_fn(X_source, y_source, X_target, y_target, penalty_param)
# backpropagation
optimizer(grads)
```

这种方式就不能用下面方式进行训练

```
Model(net, loss, opt, metrics={'acc', 'loss'})
```

如何把多输出模型的loss也用LossBase类实现，然后使用上面这种方式来训练？

**3. 根因分析**

多输出的模型是能够用Model的方式来训练的，需要自己实现loss\_fn和metric。

**4. 解决方案**

```
import mindspore as ms
from mindspore import nn
from mindspore.dataset import vision, transforms
from mindspore.dataset import MnistDataset

from mindspore import Accuracy

# download data
from download import download


url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/" \
      "notebook/datasets/MNIST_Data.zip"
path = download(url, "./data", kind="zip", replace=False)

# prepare data
train_dataset = MnistDataset('./data/MNIST_Data/train/', 'train', shuffle=True)
test_dataset = MnistDataset('./data/MNIST_Data/test/', 'test')

def datapipe(dataset, batch_size):
    image_transforms = [
        vision.Rescale(1.0 / 255.0, 0),
        vision.Normalize(mean=(0.1307,), std=(0.3081,)),
        vision.HWC2CHW()
    ]
    label_transform = transforms.TypeCast(ms.int32)
    dataset = dataset.map(image_transforms, 'image')
    dataset = dataset.map(label_transform, 'label')
    dataset = dataset.batch(batch_size)
    return dataset

train_dataset = datapipe(train_dataset, batch_size=64)
test_dataset = datapipe(test_dataset, batch_size=64)

# define model
class Net(nn.Cell):
    def __init__(self):
        super().__init__()
        self.faltten = nn.Flatten()
        self.dense_relu_sequential = nn.SequentialCell(
            nn.Dense(28*28, 512),
            nn.ReLU(),
            nn.Dense(512, 512),
            nn.ReLU(),
            nn.Dense(512, 10)
        )
        self.conv_relu_sequential = nn.SequentialCell(
            nn.Conv2d(1, 10, 4),
            nn.ReLU(),
            nn.Conv2d(10, 10, 3),
            nn.ReLU(),
            nn.Flatten(),
            nn.Dense(7840, 100),
            nn.ReLU(),
            nn.Dense(100, 10)
        )
    def construct(self, x):
        logits_conv = self.conv_relu_sequential(x)
        x = self.faltten(x)
        logits = self.dense_relu_sequential(x)
        return logits, logits_conv

model = Net()

# define loss_fn and metric
from mindspore.train import Metric
class double_loss(nn.LossBase):
    def __init__(self, reduction='mean'):
        super().__init__(reduction)
        self.loss_fn = nn.CrossEntropyLoss()

    def construct(self, logits, labels):
        loss = self.loss_fn(logits[0], labels) + self.loss_fn(logits[1], labels)
        return loss

class double_acc(Metric):
    def __init__(self):
        super().__init__()
        self.acc1 = Accuracy()
        self.acc2 = Accuracy()

    def clear(self):
        self.acc1.clear()
        self.acc2.clear()

    def update(self, logits, labels):
        self.acc1.update(logits[0], labels)
        self.acc2.update(logits[1], labels)

    def eval(self):
        return (self.acc1.eval(), self.acc2.eval())

# training with Model API
loss_fn = double_loss()
optimizer = nn.SGD(model.trainable_params(), 1e-2)

from mindspore.train import Model, LossMonitor
loss_monitor = LossMonitor(100)
trainer = Model(model, loss_fn, optimizer, metrics={'acc':double_acc()})

trainer.fit(3, train_dataset, test_dataset, callbacks=[loss_monitor])
```

