自定义小型 CNN 比预训练分类器具有更好的准确性

Custom small CNN has better accuracy than the pretrained classifiers

我有一个尺寸为 300*300 的激光焊接图像数据集,其中包含两个 class 不良焊缝和良好焊缝。我关注了 Pytorch fine-tuning tutorial 的 inception-v3 classifier.

另一方面,我还构建了一个具有 3 个 conv 层和 3 个 fc 的自定义 CNN。我观察到的是微调显示验证准确性有很多变化。基本上,我每次训练模型时都会看到不同的最大准确度。另外,我在微调方面的准确性远低于我的自定义 CNN!!例如,我使用 inception-v3 从 GAN 合成图像的准确率为 86%,而使用我的自定义 CNN 时准确率为 94%。两个网络的真实数据显示出几乎相似的行为和准确度,但自定义 CNN 的准确度高出约 2%。

我训练了 200、500 和 1000 个训练集图像的不同训练规模(每个 class 有一半像 200 张图像,我们有 100 张好图像和 100 张坏图像)。我还在我的 train_loader 中包含了 224 的调整大小转换;在微调教程中,对于 inception-v3,此调整大小自动完成为 299。对于每次试验,验证大小及其内容是恒定的。

您知道导致这种行为的原因吗?是因为我的数据集与预训练模型 classes 有很大不同吗?我不应该通过微调获得更好的结果吗?

我的自定义 CNN:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv3 = nn.Conv2d(16, 24, 5)
        self.fc1 = nn.Linear(13824, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        #x = x.view(-1, 16 * 5 * 5)
        x = x.view(x.size(0),-1)
        #print(x.shape)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        #x = F.softmax(x, dim=1)
        return x


model = Net()
criterion = nn.CrossEntropyLoss()
#optimizer = optim.Adam(model.parameters(), lr=0.001)
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=5e-4)
model.to(device) 

训练循环为:

epochs = 15
steps = 0
running_loss = 0
print_every = 10
train_losses, test_losses = [], []
train_acc, test_acc = [], []
for epoch in range(epochs):
    for inputs, labels in trainloader:
        steps += 1
        inputs, labels = inputs.to(device), labels.to(device)
        optimizer.zero_grad()
        logps = model.forward(inputs)
        loss = criterion(logps, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
        
        if steps % print_every == 0:
            test_loss = 0
            accuracy = 0
            model.eval()
            with torch.no_grad():
                for inputs, labels in testloader:
                    inputs, labels = inputs.to(device), labels.to(device)
                    logps = model.forward(inputs)
                    batch_loss = criterion(logps, labels)
                    test_loss += batch_loss.item()
                    
                    ps = torch.exp(logps)
                    top_p, top_class = ps.topk(1, dim=1)
                    equals = top_class == labels.view(*top_class.shape)
                    accuracy += torch.mean(equals.type(torch.FloatTensor)).item()

            train_losses.append(running_loss/len(trainloader))
            test_losses.append(test_loss/len(testloader))
            
            #train_acc.append(running_loss/len(trainloader))
            test_acc.append(accuracy/len(testloader))  
            print(f"Epoch {epoch+1}/{epochs}.. "
                  f"Train loss: {running_loss/print_every:.3f}.. "
                  f"Test loss: {test_loss/len(testloader):.3f}.. "
                  f"Test accuracy: {accuracy/len(testloader):.3f}")
            running_loss = 0
            model.train()

这是我的理论:

当您想利用现有数据帮助模型训练类似数据时,预训练很有用,而您的实例很少。至少这是 Unet 医学图像分割架构背后的原因。

现在,对我来说关键在于“相似”的概念。如果您的网络已经针对猫、狗进行了预训练,并且您想要外推到焊缝,那么您的预训练可能不会帮助甚至妨碍模型的正确训练。

为什么?

训练 CNN 时,您会获得随机初始化的权重,而使用预训练网络时,您会获得预训练的权重。如果您提取的特征在整个数据集中相似,那么您可以通过让网络已经适应这些特征来抢先一步。

例如,猫和狗在视觉上具有相似的空间特征(眼睛位置、鼻子、耳朵...)。因此,您有可能在训练期间更快地收敛到局部最小值,因为您已经从一个良好的基础开始,只需要适应数据的新细节。

结论:

如果相似性假设不成立,这意味着您的模型将不得不“忘记”他已经学到的东西以适应您数据集的新细节,我想这就是训练更加困难并且确实如此的原因没有像空白的 CNN 那样好的结果。 (尤其是当您没有那么多数据时)。

PS : 我很想知道如果你给它更多的训练周期,你的预训练模型最终是否能赶上你的 CNN。