大家早上好
下面是我对pytorch暹罗网络的实现。我将32个批次大小,MSE损失和SGD与0.9的动量用作优化程序。
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,而第二个值对于负数对将接近1。这两个值也需要相加为1。
我已经为2通道网络体系结构测试了完全相同的设置和相同的输入图像,其中,不是以[pos, pos]
的形式馈入,而是以深度方式堆叠这两个图像,例如{{1 }}。在此设置中,numpy.stack([pos, pos], -1)
的尺寸也更改为nn.Conv2d(1, 8, 7)
。效果很好。
我还测试了与传统CNN方法完全相同的设置和输入图像,在该方法中,我只是将单个正负灰度图像传递到网络中,而不是将它们堆叠(如2-CH方法)或将它们作为图像对传递(与暹罗方法一样)。这也可以完美地工作,但是结果不如2通道方法那么好。
编辑(我尝试过的解决方案):
nn.Conv2d(2, 8, 7)
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
的输出之间使用torch.nn.PairwiseDistance
。有所改进;网络在前几个时期开始收敛,然后每次都达到相同的稳定水平:convnet
也许要注意的另一件事是,在我的研究范围内,针对每个对象训练了一个暹罗网络。因此,第一类与包含所讨论对象的图像相关联,第二类与包含其他对象的图像相关联。不知道这是否可能是问题的原因。但是,在传统CNN和2通道CNN方法的背景下这不是问题。
根据要求,这是我的培训代码:
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
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
注意是具有属性dp.train_set
的类,其中每个集合的创建方式如下:
train_set, valid_set, test_set
根据请求,以下是预测概率与真实标签的示例,您可以在其中看到模型似乎不是在学习:
DataLoader(TensorDataset(torch.Tensor(x), torch.Tensor(y)), batch_size=bs)
答案 0 :(得分:1)
我认为您的方法是正确的,并且您做的很好。对我来说有点奇怪的是最后一个具有RELU激活的层。通常,对于暹罗网络,当两个输入图像属于同一类别时,您希望输出高概率,否则要输出低概率。因此,您可以使用单个神经元输出和S形激活功能来实现此功能。
因此,我将重新实现您的网络,如下所示:
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 :(得分: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