回帰モデルは、基本的には1つの連続値を予測しますが、複数の値を同時に予測したいようなケースもあると思います。
その際に、古典的な機械学習手法は、scikit-learn の MultiOutputRegressor を使用することでできるそうなのですが、DNN の場合にはどのように実装できるか、という点をメモしておこうと思います。
方法
2つやり方があると思います。
1つは、単純に出力層のノード数を2つにすること。2つは、出力のノード数は1つで、2つ予測値をだすことです。
前提として、2つの予測値を1つのモデル出だしたくて、教師データは1サンプルごとに[t1, t2]という形で持っているとします。
出力層のノード数を2つにする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class Net(nn.Module): def __init__(self, input_dim, hidden_dim): super().__init__() self.input_dim = input_dim self.hidden_dim = hidden_dim self.model = nn.Sequential( nn.BatchNorm1d(input_dim), nn.Linear(input_dim, hidden_dim), nn.ReLU(True), nn.Linear(hidden_dim, 2) ) def forward(self, x): return self.model(x) criterion = nn.MSELoss() def train(dataloader): net.train() for batch in dataloader: x, t = batch x.to(device) t.to(device) optimizer.zero_grad() y = net(x) loss = criterion(t, y) loss.backward() optimizer.step() |
教師データも[t1, t2]といった形で1サンプルごとに2つ用意してあげます。
こちらは、1種類目の教師データt1 と 2種類目の教師データt2 をあわせて、損失の計算が行われます。スケールが整っていないと、スケールが大きい方の教師データにパラメータ更新が引っ張られるような気がします。
また、t1 についてはロスを大きく見積もって、t2については小さく見積もり、みたいに種類ごとにロスの重み付けをすることができません。それをしたい場合は、次のやり方になるのかなと思います。
出力のノード数は1つで、2つ予測値をだす
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class Net(nn.Module): def __init__(self, input_dim, hidden_dim): super().__init__() self.input_dim = input_dim self.hidden_dim = hidden_dim self.model1 = nn.Sequential( nn.BatchNorm1d(input_dim), nn.Linear(input_dim, hidden_dim), nn.ReLU(True), nn.Linear(hidden_dim, 1) ) self.model2 = nn.Sequential( nn.BatchNorm1d(input_dim), nn.Linear(input_dim, hidden_dim), nn.ReLU(True), nn.Linear(hidden_dim, 1) ) def forward(self, x): y1 = self.model1(x) y2 = self.model2(x) return torch.cat([y1, y2], dim=1) def my_criterion(t, y): loss1 = F.mse_loss(y[:, 0], t[:, 0]) loss2 = F.mse_loss(y[:, 1], t[:, 1]) return (loss1 + loss2) / len(t) def train(dataloader): net.train() for batch in dataloader: x, t = batch x.to(device) t.to(device) optimizer.zero_grad() y = net(x) loss = my_criterion(t, y) # 変更 loss.backward() optimizer.step() |
損失関数も、2つの予測値を受け取って、それぞれでロスを算出し、重み付け和を返すようにすると少し柔軟にできるように思います。
複数のモデルが1つのモデルの中に入っているようなイメージです。
いったんおわり。