[高光谱] 在开源项目Hyperspectral-Classification Pytorch中加入自己的网络

尝试在开源项目Hyperspectral-Classification Pytorch中加入自己的网络——记一次令人智熄的调试

如题,本人想在开源项目Hyperspectral-Classification Pytorch中加入自定义的网络。

然后产生了一系列的bug,差不多耗费了我一整天出头的时间。

这里把加入自定义网络的流程写出来。同时把调试bug的过程也记录下来。

加入自定义网络

模型读取模块

这部分是在确定模型后,初始化模型相关的超参数,并将模型实例化(即由类创建对象)。

需要在get_model(name, **kwargs):函数中加入下面的代码。

if name == 'nn':
	……
……
elif name == 'mou':
   ……
# -------------------------自加网络----------------------------
elif name =='OwnNet':
    patch_size = kwargs.setdefault('patch_size', 17)
    center_pixel = True
    model = OwnNet(n_bands, n_classes, n_planes=32, patch_size=patch_size)
    lr = kwargs.setdefault('learning_rate', 0.01)
    optimizer = optim.SGD(model.parameters(),
                          lr=lr, momentum=0.9, weight_decay=0.0005)
    epoch = kwargs.setdefault('epoch', 200)
    criterion = nn.CrossEntropyLoss(weight=kwargs['weights'])
    # kwargs.setdefault('scheduler', optim.lr_scheduler.MultiStepLR(optimizer, milestones=[epoch // 2, (5 * epoch) // 6], gamma=0.1))
# -------------------------自加网络----------------------------
else:
    raise KeyError("{} model is unknown.".format(name))

这里将自定义网络的名称定为OwnNet

需要注意,这些超参数和超参数的值都是以键值对的形式存储在dictionary kwargs中。

patch_size = kwargs.setdefault('patch_size', 17)patch_size的值定为17(当然也可以定为其他值),表示每次传入网络的高光谱数据立方W×H17×17setdefault()的作用就是在字典中加入键值对,如果已有则不变,没有在使用指定的默认值。

model = OwnNet(n_bands, n_classes, n_planes=32, patch_size=patch_size)class OwnNet实例化,同时传入n_bandsn_classesn_planespatch_size这四个超参数。

lr = kwargs.setdefault('learning_rate', 0.01)是设定学习率。

optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=0.0005),设定优化函数为SGD。(并没有采用常用的Adam)

epoch = kwargs.setdefault('epoch', 200)设定epoch为200。

criterion = nn.CrossEntropyLoss(weight=kwargs['weights']),设定损失函数交叉熵CrossEntropyLoss

网络定义模块

这个模块其实就是网络的class的定义。

# -------------------------自加网络---------------------------
class OwnNet(nn.Module):
    @staticmethod
    def weight_init(m):
        if isinstance(m, nn.Linear) or isinstance(m, nn.Conv3d):
            init.xavier_uniform_(m.weight.data)
            init.constant_(m.bias.data, 0)

    def __init__(self, input_channels, n_classes, n_planes=32, patch_size=17):
        super(OwnNet, self).__init__()
        self.input_channels = input_channels
        self.n_planes = n_planes
        self.patch_size = patch_size

        self.conv1 = nn.Conv3d(1, 32, (32, 4, 4), padding=(1, 1, 1))
        self.conv2 = nn.Conv3d(32, 2 * 32, (32, 5, 5), padding=(1, 1, 1))
        self.conv3 = nn.Conv3d(2 * 32, 4 * 32, (4, 3, 3), padding=(1, 0, 0))
        self.pool1 = nn.MaxPool3d((1, 2, 2), stride=(1, 2, 2))
        self.pool2 = nn.MaxPool3d((1, 2, 2), stride=(1, 2, 2))

        self.features_size = self._get_final_flattened_size()

        self.fc = nn.Linear(self.features_size, n_classes)

        self.apply(self.weight_init)

    def _get_final_flattened_size(self):
        with torch.no_grad():
            x = torch.zeros((1, 1, 103,
                             self.patch_size, self.patch_size))
            x = self.pool1(self.conv1(x))
            x = self.pool2(self.conv2(x))
            x = self.conv3(x)
            _, t, c, w, h = x.size()
        return t * c * w * h

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool1(x)
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        x = F.relu(self.conv3(x))
        print(x.size())
        print(self.features_size)
        x = x.view(-1, self.features_size)
        x = self.fc(x)
        return x
# -------------------------自加网络---------------------------

class的主要的框架参考Hyperspectral-Classification Pytorch中已有的网络类。

主要修改的如下:

  • def __init__(self, input_channels, n_classes, n_planes=32, patch_size=17):

主要就是卷积层和池化层的超参数,全连接层保持不变。

self.conv1 = nn.Conv3d(1, 32, (32, 4, 4), padding=(1, 1, 1))
self.conv2 = nn.Conv3d(32, 2 * 32, (32, 5, 5), padding=(1, 1, 1))
self.conv3 = nn.Conv3d(2 * 32, 4 * 32, (4, 3, 3), padding=(1, 0, 0))
self.pool1 = nn.MaxPool3d((1, 2, 2), stride=(1, 2, 2))
self.pool2 = nn.MaxPool3d((1, 2, 2), stride=(1, 2, 2))
  • def _get_final_flattened_size(self):

    这一部分是计算全连接层的输入展成一维的长度

    整体的思路是在with torch.no_grad():下,用与网络输入数据同维度的全零tensor,按顺序走一遍网络的卷积层和池化层(直到全连接层之前)。然后提取数据维度,将channels、depth、width、height相乘即为所求。

    def _get_final_flattened_size(self):
            with torch.no_grad():
                x = torch.zeros((1, 1, 103,
                                 self.patch_size, self.patch_size))
                x = self.pool1(self.conv1(x))
                x = self.pool2(self.conv2(x))
                x = self.conv3(x)
                _, t, c, w, h = x.size()
            return t * c * w * h
    
  • def forward(self, x):

    这部分是前向传播过程。

    def forward(self, x):
            x = F.relu(self.conv1(x))
            x = self.pool1(x)
            x = F.relu(self.conv2(x))
            x = self.pool2(x)
            x = F.relu(self.conv3(x))
            print(x.size())
            print(self.features_size)
            x = x.view(-1, self.features_size)
            x = self.fc(x)
            return x
    

调试过程

确定卷积层和池化层超参数

这部分主要是设定合适的patch_size,并根据论文提供的架构(论文题目见LiEtAi的介绍)选择合适的卷积层和池化层参数。

[外链图片转存失败(img-pppAwpUK-1568986092193)(C:\Users\73416\AppData\Local\Temp\1568980471509.png)]

论文确实给出了网络的架构,但是有3个问题:

  • 邻域neighborhood的选择:

    对于 Pavia University 数据集,邻域neighborhood的选择为27×27。但是这么大的数据量在我的电脑中无法运行。

    RuntimeError: CUDA out of memory. Tried to allocate 253.13 MiB (GPU 0; 2.00 GiB total capacity; 1.15 GiB already allocated; 191.68 MiB free; 1.10 MiB cached)
    
  • 关于网络架构architecture的理解。

    我之前对4×4×32×32的理解是kernel_height × kernel_width × kernel_depth × kernel_number,但是这样理解的话,到第三个卷积层,kernel_depth为32,已经大于的数据的depth。运行是会报错的。出于这个原因我不得不大幅度修改了第三卷积层的kernel_depth

    后来我也可以理解为pooling为2×2的意思是,只在W,H两个维度上进行池化,而保持D维度不变。这样就得到了新的网络结构(上面代码中的网络结构)。

  • padding与否。

    在每一个卷积层和池化层,论文并没有给出是否在D,H,W三个维度上是否使用padding。

调试过程中的坑(个人纪录)

调试bug的建议
如何避免出现bug 整体替换

代码替换的时候,最好以大块代码替换为好。

因为如果你以为单位替换代码,可能会导致上下句之间的报错。在一个类中,以方法为单位替换代码,可能会导致不同方法之家你的报错。

比如我下面的第一个坑。

如何fix已出现的bug 溯源

对于已经出现的bug,仔细理解报错信息(报错信息非常重要),并且推测报错的原因。

之后定为到报错的语句(值可以追溯到的最原始的语句),并且在合适的位置print相关数据的信息。

之后根据打印出的信息,确定可能出错的位置。

重复上述的操作,直到确定出错的代码。

第一个坑
RuntimeError: shape '[-1, 564480]' is invalid for input of size 768

这一部分报错的语句是x = x.view(-1, self.features_size),即将数据展平时发生错误。

究其原因应该是网络的输入数据维度不对,虽然全连接层之前的运算没有报错(got luck),但是到了将特征展平的时候发生了错误。

对应的解决方式就是强制设置超参数:n_planes=32, patch_size=17

至此,第一个报错解决。

第二个坑
RuntimeError: Calculated padded input size per channel: (5 x 18 x 18). Kernel size: (32 x 5 x 5). Kernel size can't be greater than actual input size at c:\a\w\1\s\tmp_conda_3.7_183942\conda\conda-bld\pytorch_1549564939537\work\aten\src\thnn\generic/VolumetricConvolutionMM.c:78

这一部分是在卷积层报错。报错语句是:self.features_size = self._get_final_flattened_size()

而且注意到(5 x 18 x 18),这里的Depth不应该为5,而应该比5大得多。

这种错误不会发生在卷积层和池化层运算中,只可能出现在输入数据维度的初始化上。

后面检查发现将波段数从103写成了32,导致了波段数异常,而报错。

至此第二个报错解决。

第三个坑
RuntimeError: shape '[-1, 564480]' is invalid for input of size 768

首先我将view的参数强制置为564480,想先看一下网络的信息。

尝试失败,报错信息和上面一样。

之后将view的强制置为768,想先看一下网络的信息。

尝试继续失败,但报错信息改变了:

RuntimeError: size mismatch, m1: [1 x 768], m2: [564480 x 10] at c:\a\w\1\s\tmp_conda_3.7_183942\conda\conda-bld\pytorch_1549564939537\work\aten\src\thc\generic/
THCTensorMathBlas.cu:266

这里看到依旧是维度的问题,报错语句是x = x.view(-1, self.features_size)

现在问题更明显了:_get_final_flattened_size()函数计算出来的size,和summary计算的size不同,二者中至少有一个是错的。

现在将self.features_size恢复(不再用数字代替),并在报错的x = x.view(-1, self.features_size)前打印数据x的size

torch.Size([2, 128, 44, 1, 1]) 

而正常的维度应该是:

[-1, 128, 44, 1, 1]

可以看到batch的值从100变到了2,这是不正常的。正常来说batch的值不应该变化。

尝试去打印一下self.features_size的信息,看是不是128×44×1×1=5,632‬。单并不是,反而是564480这个数。

为什么?

回去看_get_final_flattened_size()这个函数。

好家伙,忘了更新网络的结构,导致现在还是之前网络的结果(只有卷积层没有池化层),算出来的维度肯定不对。改过来。

然后成功!

Network :
torch.Size([100, 1, 103, 17, 17])
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv3d-1       [-1, 32, 74, 16, 16]          16,416
         MaxPool3d-2         [-1, 32, 74, 8, 8]               0
            Conv3d-3         [-1, 64, 45, 6, 6]       1,638,464
         MaxPool3d-4         [-1, 64, 45, 3, 3]               0
            Conv3d-5        [-1, 128, 44, 1, 1]         295,040
            Linear-6                   [-1, 10]          56,330
================================================================
Total params: 2,006,250
Trainable params: 2,006,250
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.11
Forward/backward pass size (MB): 6.81
Params size (MB): 7.65
Estimated Total Size (MB): 14.58
----------------------------------------------------------------
请按任意键继续. . .

当然这里有一个小疑问,这里的全连接层用不用再多加一层,现在是直接从5,632‬降到10。

现在由于patch_size太大,导致在我的笔记本电脑上运行过于慢,导致现在网络的性能在暂时无法评判。

你可能感兴趣的:(开源项目使用,高光谱图像分类)