皆さんおはようございます
以下は、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
_
convnet
の出力間で_torch.nn.PairwiseDistance
_を使用しようとしました。ある種の改善を行いました。ネットワークは最初の数エポックで収束し始め、その後常に同じ高原に到達します。_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
_
問題が解決しました。毎回同じ画像を与えると、ネットワークは毎回同じ出力を予測することが判明????データのパーティショニング中の私の部分の小さなインデックス付けミス。皆の助けと援助をありがとう。これが現在の収束の例です。
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
あなたのアプローチは正しく、あなたはうまくやっていると思います。少し奇妙に見えるのは、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%の、少しドロップアウトを使用することをお勧めします。