web-dev-qa-db-ja.com

Pytorch Siameseネットワークが収束しない

皆さんおはようございます

以下は、pytorchシャムネットワークの私の実装です。 32バッチサイズ、MSE損失、0.9の勢いのSGDをオプティマイザとして使用しています。

_class SiameseCNN(nn.Module):
    def __init__(self):
        super(SiameseCNN, self).__init__()                                      # 1, 40, 50
        self.convnet = nn.Sequential(nn.Conv2d(1, 8, 7), nn.ReLU(),             # 8, 34, 44
                                    nn.Conv2d(8, 16, 5), nn.ReLU(),             # 16, 30, 40
                                    nn.MaxPool2d(2, 2),                         # 16, 15, 20
                                    nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), # 32, 15, 20
                                    nn.Conv2d(32, 64, 3, padding=1), nn.ReLU()) # 64, 15, 20
        self.linear1 = nn.Sequential(nn.Linear(64 * 15 * 20, 100), nn.ReLU())
        self.linear2 = nn.Sequential(nn.Linear(100, 2), nn.ReLU())
        
    def forward(self, data):
        res = []
        for j in range(2):
            x = self.convnet(data[:, j, :, :])
            x = x.view(-1, 64 * 15 * 20)
            res.append(self.linear1(x))
        fres = abs(res[1] - res[0])
        return self.linear2(fres)
_

各バッチには交互のペア、つまり_[pos, pos], [pos, neg], [pos, pos]_など...が含まれています。ただし、ネットワークは収束せず、ネットワーク内のfresは各ペアで同じであるように見えます(それがは正または負のペアです)、self.linear2(fres)の出力は常におおよそ_[0.0531, 0.0770]_と等しくなります。これは、私が期待しているものとは対照的です。つまり、ネットワークが学習するにつれて、_[0.0531, 0.0770]_の最初の値は正のペアで1に近くなり、2番目の値は負のペアで1に近くなります。これらの2つの値も合計して1になる必要があります。

2チャネルネットワークアーキテクチャでまったく同じ設定と同じ入力画像をテストしました。この場合、_[pos, pos]_を入力する代わりに、numpy.stack([pos, pos], -1)のように深さ方向にこれらの2つの画像をスタックします。 。この設定では、nn.Conv2d(1, 8, 7)の寸法もnn.Conv2d(2, 8, 7)に変わります。これは完璧に機能します。

また、従来のCNNアプローチとまったく同じセットアップと入力イメージをテストしました。ここでは、(2-CHアプローチの場合のように)積み重ねたり、渡すのではなく、単一の正と負のグレースケールイメージをネットワークに渡します。画像ペアとして(シャムのアプローチと同様)。これも完全に機能しますが、2チャネルアプローチの場合ほど結果は良くありません。

編集(私が試したソリューション):

_def forward(self, data):
    res = []
    for j in range(2):
        x = self.convnet(data[:, j, :, :])
        x = x.view(-1, 64 * 15 * 20)
        res.append(x)
    fres = self.linear2(self.linear1(abs(res[1] - res[0]))))
    return fres 
_
_def forward(self, data):
    res = []
    for j in range(2):
        x = self.convnet(data[:, j, :, :])
        res.append(x)
    pdist = nn.PairwiseDistance(p=2)
    diff = pdist(res[1], res[0])
    diff = diff.view(-1, 64 * 15 * 10)
    fres = self.linear2(self.linear1(diff))
    return fres
_

もう1つ注意すべき点は、私の研究のコンテキスト内では、オブジェクトごとにシャムネットワークがトレーニングされていることです。したがって、最初のクラスは問題のオブジェクトを含む画像に関連付けられ、2番目のクラスは他のオブジェクトを含む画像に関連付けられます。これが問題の原因であるかどうかはわかりません。ただし、従来のCNNおよび2チャネルCNNアプローチのコンテキストでは問題にはなりません。

リクエストに従って、ここに私のトレーニングコードがあります:

_model = SiameseCNN().cuda()
ls_fn = torch.nn.BCELoss()
optim = torch.optim.SGD(model.parameters(),  lr=1e-6, momentum=0.9)
epochs = np.arange(100)
eloss = []
for Epoch in epochs:
    model.train()
    train_loss = []
    for x_batch, y_batch in dp.train_set:
        x_var, y_var = Variable(x_batch.cuda()), Variable(y_batch.cuda())
        y_pred = model(x_var)
        loss = ls_fn(y_pred, y_var)
        train_loss.append(abs(loss.item()))
        optim.zero_grad()
        loss.backward()
        optim.step()
    eloss.append(np.mean(train_loss))
    print(Epoch, np.mean(train_loss))
_

注_dp.train_set_のdpは、属性_train_set, valid_set, test_set_を持つクラスであり、各セットは次のように作成されます。

_DataLoader(TensorDataset(torch.Tensor(x), torch.Tensor(y)), batch_size=bs)
_

リクエストに従って、予測確率と真のラベルの例を次に示します。モデルが学習していないように見えます。

_Predicted:  0.5030623078346252 Label:  1.0
Predicted:  0.5030624270439148 Label:  0.0
Predicted:  0.5030624270439148 Label:  1.0
Predicted:  0.5030625462532043 Label:  0.0
Predicted:  0.5030625462532043 Label:  1.0
Predicted:  0.5030626654624939 Label:  0.0
Predicted:  0.5030626058578491 Label:  1.0
Predicted:  0.5030627250671387 Label:  0.0
Predicted:  0.5030626654624939 Label:  1.0
Predicted:  0.5030627846717834 Label:  0.0
Predicted:  0.5030627250671387 Label:  1.0
Predicted:  0.5030627846717834 Label:  0.0
Predicted:  0.5030627250671387 Label:  1.0
Predicted:  0.5030628442764282 Label:  0.0
Predicted:  0.5030627846717834 Label:  1.0
Predicted:  0.5030628442764282 Label:  0.0
_
4
Emile Beukes

問題が解決しました。毎回同じ画像を与えると、ネットワークは毎回同じ出力を予測することが判明????データのパーティショニング中の私の部分の小さなインデックス付けミス。皆の助けと援助をありがとう。これが現在の収束の例です。

0 0.20198837077617646
1 0.17636818194389342
2 0.15786472541093827
3 0.1412761415243149
4 0.126698794901371
5 0.11397973036766053
6 0.10332610329985618
7 0.09474560652673245
8 0.08779258838295936
9 0.08199785630404949
10 0.07704121413826942
11 0.07276330365240574
12 0.06907484836131335
13 0.06584368328005076
14 0.06295975042134523
15 0.06039590438082814
16 0.058096024941653016
0
Emile Beukes

あなたのアプローチは正しく、あなたはうまくやっていると思います。少し奇妙に見えるのは、RELUがアクティブ化されている最後のレイヤーです。通常、シャムネットワークでは、2つの入力画像が同じクラスに属している場合は高い確率を出力し、そうでない場合は低い確率を出力する必要があります。したがって、これを単一のニューロン出力とシグモイド活動化関数で実装できます。

したがって、次のようにネットワークを再実装します。

class SiameseCNN(nn.Module):
    def __init__(self):
        super(SiameseCNN, self).__init__()                                      # 1, 40, 50
        self.convnet = nn.Sequential(nn.Conv2d(1, 8, 7), nn.ReLU(),             # 8, 34, 44
                                    nn.Conv2d(8, 16, 5), nn.ReLU(),             # 16, 30, 40
                                    nn.MaxPool2d(2, 2),                         # 16, 15, 20
                                    nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), # 32, 15, 20
                                    nn.Conv2d(32, 64, 3, padding=1), nn.ReLU()) # 64, 15, 20
        self.linear1 = nn.Sequential(nn.Linear(64 * 15 * 20, 100), nn.ReLU())
        self.linear2 = nn.Sequential(nn.Linear(100, 1), nn.Sigmoid())
        
    def forward(self, data):
        for j in range(2):
            x = self.convnet(data[:, j, :, :])
            x = x.view(-1, 64 * 15 * 20)
            res.append(self.linear1(x))
        fres = res[0].sub(res[1]).pow(2)
        return self.linear2(fres)

次に、トレーニングで一貫性を保つには、バイナリ相互エントロピーを使用する必要があります。

criterion_fn = torch.nn.BCELoss()

また、両方の入力画像が同じクラスに属している場合は、ラベルを必ず1に設定してください。

また、linear1レイヤーの後に、ニューロンをドロップする確率が約30%の、少しドロップアウトを使用することをお勧めします。

1
Guillem