为什么我的 CNN 回归器不起作用(Pytorch)

Why my CNN regressor doesn't work (Pytorch)

我正在尝试将我的 tensorflow 代码转换为 pytorch。

简单地说,它使用 CNN 从图像中估计 7 个值(数字)。(回归量)

backbone 网络是具有预训练权重的 vgg16,我想将最后一个 fcl(实际上由于 ImageNet 数据集,最后一个 fcl 输出是 1000 类)转换为(4096 x 4096), 并添加更多 fcls。

之前:

vgg 最后一整箱 (4096 x 1000)

之后:

vgg 最后一整箱(更改为 4096 x 4096)

----添加 fcl1 (4096 x 4096)

----添加 fcl2 (4096 x 2048)

└ 添加 fclx (2048 x 3)

└ 添加 fclq (2048 x 4)

: fcl2 连接到两个不同的张量,大小为 3 和 4

在这里,我尝试仅使用一张图像(仅用于调试)和具有 L2 Loss 的 GT 值(7 个值)来完成。 如果我使用 Tensorflow 这样做,损失会急剧减少,并且当我推断图像时,它给出的值几乎与 GT 相似。

但是,如果我尝试使用 Pytorch 进行训练,似乎训练效果不佳。

我想损失应该在训练时急剧减少(几乎每次迭代)

有什么问题?

from torchvision import models
import torch.nn as nn
import torch
from torch.autograd import Variable
import torch.optim as optim
import os
import os.path
import torch.utils.data as data
from torchvision import transforms as T
from PIL import Image

class DataSource(data.Dataset):
    def __init__(self, root, train=True, transforms=None, txtName='dataset_train'):
        self.root = os.path.expanduser(root)
        self.transforms = transforms
        self.train = train
        self.imageFormat = '.jpg'
        self.image_poses = []
        self.image_paths = []
        self.txtName = txtName
        self._get_data()

        if transforms is None:
            normalize = T.Normalize(mean=[0.485, 0.456, 0.406],
                                    std=[0.229, 0.224, 0.225])
            if not train:
                self.transforms = T.Compose(
                    [T.Resize(256),
                     T.CenterCrop(224),
                     T.ToTensor(),
                     normalize]
                )
            else:
                self.transforms = T.Compose(
                    [T.Resize(256),
                     T.CenterCrop(224),
                     # T.RandomCrop(224),
                     T.ToTensor(),
                     normalize]
                )

    def _get_data(self):
        txt_file = self.root + '/' + self.txtName + '.txt'
        count = 0
        with open(txt_file, 'r') as f:
            for line in f:
                if len(line.split()) != 8:
                    next(f)
                fname, p0, p1, p2, p3, p4, p5, p6 = line.split()
                p0 = float(p0); p1 = float(p1); p2 = float(p2);
                p3 = float(p3); p4 = float(p4); p5 = float(p5); p6 = float(p6)
                ImageFullName = self.root + '/' +  fname
                if count == 0:
                    if os.path.isfile(ImageFullName) == False:
                        self.imageFormat = '.png'

                if self.imageFormat != '.jpg':
                    ImageFullName = ImageFullName.replace('.jpg', self.imageFormat)

                self.image_poses.append([p0, p1, p2, p3, p4, p5, p6])
                self.image_paths.append(ImageFullName)
                count += 1
        print('Total : ', len(self.image_paths), ' images')

    def __getitem__(self, index):
        img_path = self.image_paths[index]
        img_pose = self.image_poses[index]
        data = Image.open(img_path)
        data = self.transforms(data)
        return data, torch.tensor(img_pose)

    def __len__(self):
        return len(self.image_paths)

class PoseLoss(nn.Module):
    def __init__(self, beta, device = 'cuda'):
        super(PoseLoss, self).__init__()
        self.beta = beta
        self.device = device
        self.t_loss_fn = nn.MSELoss()

    def forward(self, x, q, poseGT):
        GT_x = poseGT[:, 0:3]
        GT_q = poseGT[:, 3:]

        xx = Variable(x, requires_grad=True).to(self.device)
        qq = Variable(q, requires_grad=True).to(self.device)
        print('GT', GT_x, GT_q)
        print('Estim', xx, qq)

        loss = torch.sqrt(self.t_loss_fn(GT_x[:, :3].cpu(), xx[:, :3].cpu())) + self.beta*torch.sqrt(self.t_loss_fn(GT_q[:, 3:].cpu(), qq[:, 3:].cpu()))
        return loss

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.backbone =  models.vgg16(pretrained=True)
        self.backbone._modules['classifier'][6] = nn.ReLU(nn.Linear(4096, 4096))
        self.fcl = nn.Sequential(nn.Linear(4096, 4096), nn.ReLU(), nn.Linear(4096, 2048), nn.ReLU())
        self.xyz = nn.Linear(2048, 3)
        self.q = nn.Linear(2048, 4)

    def forward(self, x):
        x1 = self.backbone(x)
        x2 = self.fcl(x1)
        xyz = self.xyz(x2)
        q = self.q(x2)
        return xyz, q

batch_size = 1
learning_rate = 10e-5
training_epochs = 100


if __name__ == "__main__":
    device = 'cuda' if torch.cuda.is_available() else 'cpu'

    data = DataSource(DatasetDirectory + DatasetFolder, train=True, transforms=None, txtName=TrainDatasetList)
    data_loader = torch.utils.data.DataLoader(dataset=data, batch_size=batch_size, shuffle=False, num_workers=4)

    model = Net().to(device)

    model.train()

    criterion = PoseLoss(beta = 100, device = device)

    optimizer = optim.Adam(model.parameters(), lr=learning_rate, betas = (0.9, 0.999), eps =0.00000001)

    iteration = 0
    minloss = 10e8
    minlossindex = -1
    for epoch in range(1, training_epochs):
        dataiter = iter(data_loader)
        for Images, Poses in dataiter:
            optimizer.zero_grad()
            Images = Images.to(device).float()
            x, q = model(Images)
            loss = criterion(x, q, Poses)
            loss.backward()
            loss = loss.item()/ batch_size
            optimizer.step()
            print(epoch, ' : ', iteration , ' -> ' , loss, ' minloss ', minloss, ' at ', minlossindex)
            if loss < minloss:
                minloss = loss
                minlossindex = iteration
                if epoch < (int)(training_epochs*0.8):
                    torch.save(model.state_dict(), 'Min.pth')
            iteration = iteration + 1
    torch.save(model.state_dict(), 'Fin.pth')

所有 7 个值的估计结果往往为零,我想不出为什么会给出这样的值。

此外,正如我上面提到的,训练时损失值不会急剧下降(我预计每次迭代都应该急剧下降直到收敛,因为我只使用了一张图像进行训练)

经我测试.cpu()不影响血压


我注意到你在最后的损失中添加了一个 .cpu(),PyTorch 无法将梯度从 CPU 传递到 GPU(我想创建了一个新的计算图)。只需删除 PoseLoss 中的 .cpu() 并保留 GPU 上的所有张量。另外,变量 API 已经不需要了,因为 PyTorch 支持自动标记计算图的叶节点。

如果将数据从 gpu 移动到 cpu,您将丢失计算图中的历史数据,因此导数不会传播到前面的层。

我是这样操作的,一般都是用dataloader采样后数据传到设备上

...
for Images, Poses in dataiter:
    Images = Images.to(device)
    Poses = Poses.to(device)
...

从这里您将获得 gpu 中的所有数据。此外,没有必要在 x 和 q 中应用变量。自动地,当在pytorch中定义一个层时,它已经表明张量是一个变量并且它必须具有梯度的累积。

另一方面,您也不需要损失中的 sqrt。认为 sqrt 函数是单调递增的,所以最小化 mse 和最小化 rmse 是一样的。放置 sqrt 可能会使训练更加不稳定,并且只有在您想要以与数据相同的数量级进行惩罚时才有用。