代码复现自论文《3-D Deep Learning Approach for Remote Sensing Image Classification》

先对部分基础知识做一些整理:

一、局部连接与参数共享(都减少了参数计算量)

  局部连接:基于图像局部相关的原理,保留了图像局部结构,同时减少了网络的权值个数,加快了学习速率,同时也在一定程度上减少了过拟合的可能。

  参数共享:下面用图例解释参数共享与不共享的区别。  

  如图是一个3*3大小的卷积核在进行特征提取,channel=1, 在每个位置进行特征提取的时候都是共享一个卷积核,假设有k个channel,则参数

总量为3*3*k,注意不同channel的参数是不能共享的。

  假设现在不使用参数共享,则卷积核作用于矩阵上的每一个位置时其参数都是不一样的,则卷积核的参数数量就与像素矩阵的大小保持一致了,假
设有k个channel,则参数数量为weight*height*k,这对于尺寸较大的图片来说明显是不可取的。

二、多通道输入输出:

  首先理解fliter与kernel区别:kernel是fliter的组成成分,一个fliter就会对应生成一个特征图。

  下图为多通道输入,单通道输出。有一个fliter,一个fliter包括三个kernel,最后生成一个特征图(一个fliter就对应一个特征图)

  下图为直观展示,输入为8*8*3的RGB三通道图片,设置了4个fliter对应四个输出通道,也就是输出4个特征图。每个fliter有3个kernel。

三、2D卷积代码示例:

 1 import torch as t
2 import torch.nn as nn
3
4 class A(nn.Module):
5 def __init__(self):
6 super(A, self).__init__()
7 #三个2D卷积层结构,每个卷积层的输入和输出通道数均为2,卷积核和大小是(3, 3)
8 #torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1,
9 #padding=0, dilation=1, groups=1, bias=True,
10 #padding_mode='zeros', device=None, dtype=None)
11 self.conv1 = nn.Conv2d(2, 2, kernel_size=(3, 3))
12 self.conv2 = nn.Conv2d(2, 2, kernel_size=(3, 3))
13 self.conv3 = nn.Conv2d(2, 2, kernel_size=(3, 3))
14
15 a = A()
16 print(list(a.parameters()))

运行结果:

 1 [Parameter containing:
2 tensor([[[[ 0.0213, -0.2054, 0.0985],
3 [ 0.1254, 0.1202, -0.1115],
4 [-0.0280, 0.0256, 0.1975]],
5
6 [[-0.2118, -0.1683, 0.0056],
7 [-0.0554, 0.2255, -0.1548],
8 [ 0.1747, 0.0449, 0.1606]]],
9
10
11 [[[-0.1328, -0.1700, -0.1048],
12 [ 0.0911, -0.2230, 0.0685],
13 [ 0.0886, 0.1765, -0.1879]],
14
15 [[-0.0581, 0.1266, -0.1030],
16 [-0.0170, 0.0387, -0.0641],
17 [-0.0127, -0.2099, -0.2213]]]], requires_grad=True), Parameter containing:
18 tensor([ 0.0877, -0.1969], requires_grad=True), Parameter containing:
19 tensor([[[[-0.0266, 0.1511, -0.0034],
20 [-0.1070, -0.1734, -0.1017],
21 [ 0.0053, 0.1358, -0.0542]],
22
23 [[-0.0436, 0.1587, -0.0375],
24 [ 0.0125, -0.0431, -0.0877],
25 [-0.0766, 0.0405, -0.1306]]],
26
27
28 [[[-0.1449, -0.0315, -0.0236],
29 [ 0.0118, 0.2230, -0.2137],
30 [-0.1108, -0.1178, 0.0027]],
31
32 [[ 0.2184, 0.1964, -0.0959],
33 [-0.0385, -0.0523, 0.2135],
34 [-0.0387, 0.1951, -0.1546]]]], requires_grad=True), Parameter containing:
35 tensor([-0.0942, -0.2029], requires_grad=True), Parameter containing:
36 tensor([[[[ 8.9598e-02, -3.3190e-02, 1.0606e-01],
37 [-2.2397e-02, 2.0944e-01, -6.8180e-02],
38 [-1.7312e-01, 2.2318e-01, 1.9368e-01]],
39
40 [[ 7.0529e-02, -2.0741e-01, -1.2648e-01],
41 [-1.7503e-01, 1.7972e-01, -1.0417e-01],
42 [-1.9124e-01, -4.2022e-02, 1.4635e-01]]],
43
44
45 [[[-8.0857e-02, 8.5098e-03, 7.0629e-02],
46 [ 1.6926e-01, -1.7654e-02, -9.3033e-02],
47 [-2.9836e-02, -2.2935e-01, 1.1450e-01]],
48
49 [[ 1.3848e-01, -5.7713e-02, 1.5293e-04],
50 [-6.3998e-03, -1.0745e-01, 2.8835e-03],
51 [-1.7894e-01, 2.2133e-01, 5.2435e-02]]]], requires_grad=True), Parameter containing:
52 tensor([-0.1026, 0.0207], requires_grad=True)]

  上面代码是定义的一个2D卷积网络架构,有3个卷积层,每个卷积层的输入通道和输出通道(对应fliter为2)都为2,每个fliter中有两个3*3的kernel(对应两个输入通道)。代码中的两个1*2的向量为bias。

四、3D卷积的示意图和示例代码:

 1 class B(nn.Module):
2 def __init__(self):
3 super(B, self).__init__()
4 self.conv1 = nn.Conv3d(
5 1, # 输入图像的channel数,C_in
6 3, # 卷积产生的channel数,C_out
7 kernel_size=2, # 卷积核的尺寸,这里实际是(2,2,2),第一维表示卷积核处理的帧数
8 stride=(1,1,1), # 卷积步长,(D,H,W)
9 padding=(0,0,0), # 输入的每一条边补充0的层数,(D,H,W)
10 bias=False)
11 self.conv2 = nn.Conv3d(1, 3, kernel_size=(2, 2, 2), stride=(1, 1, 1), padding=(0, 0, 0), bias=False)
12
13 b = B()
14 print(list(b.parameters()))

运行结果:

 1 [Parameter containing:
2 tensor([[[[[-0.0895, -0.1651],
3 [-0.1319, 0.2510]],
4
5 [[-0.1616, -0.2614],
6 [ 0.0383, 0.2656]]]],
7
8
9
10 [[[[-0.0809, -0.1821],
11 [-0.0624, -0.1401]],
12
13 [[-0.3170, 0.1499],
14 [-0.3449, 0.2639]]]],
15
16
17
18 [[[[ 0.2779, -0.0731],
19 [ 0.0439, -0.0353]],
20
21 [[ 0.2853, -0.3177],
22 [ 0.0559, -0.3290]]]]], requires_grad=True), Parameter containing:
23 tensor([[[[[-0.2529, -0.0537],
24 [ 0.0955, -0.1513]],
25
26 [[ 0.0310, 0.2558],
27 [-0.1903, -0.1561]]]],
28
29
30
31 [[[[-0.2919, -0.3142],
32 [ 0.0190, -0.1089]],
33
34 [[-0.0586, 0.1541],
35 [-0.2402, -0.3339]]]],
36
37
38
39 [[[[-0.3260, -0.0500],
40 [ 0.1392, 0.1486]],
41
42 [[ 0.1664, 0.1514],
43 [ 0.0345, -0.1979]]]]], requires_grad=True)]

  三维数据输入通道为1,输出通道为3,那么fliter数为3,每个fliter中有1个kernel(2,2,2),因为两个3Dconv参数一样,所以每个3Dconv有3个2*2*2的tensor。这里如果对应到高光谱数据集,输入通道也为1。因为整个高光谱数据集就是一个3维的volume,整个数据集图像由长*宽*光谱维组成。

论文复现:

原论文网络结构:

复现网络:

输入维度:Image has dimensions 610x340 and 103 channels

网络结构及参数:

 复现代码:

1     elif name == "hamida":
2 patch_size = kwargs.setdefault("patch_size", 5)
3 center_pixel = True
4 model = HamidaEtAl(n_bands, n_classes, patch_size=patch_size)
5 lr = kwargs.setdefault("learning_rate", 0.01)
6 optimizer = optim.SGD(model.parameters(), lr=lr, weight_decay=0.0005)
7 kwargs.setdefault("batch_size", 100)
8 criterion = nn.CrossEntropyLoss(weight=kwargs["weights"])
 1 class HamidaEtAl(nn.Module):
2 """
3 3-D Deep Learning Approach for Remote Sensing Image Classification
4 Amina Ben Hamida, Alexandre Benoit, Patrick Lambert, Chokri Ben Amar
5 IEEE TGRS, 2018
6 https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8344565
7 """
8
9 @staticmethod
10 def weight_init(m):#权重初始化
11 if isinstance(m, nn.Linear) or isinstance(m, nn.Conv3d):
12 init.kaiming_normal_(m.weight) #kaiming_normal_均匀初始化方法
13 init.zeros_(m.bias)
14
15 def __init__(self, input_channels, n_classes, patch_size=5, dilation=1):
16 super(HamidaEtAl, self).__init__()
17 # The first layer is a (3,3,3) kernel sized Conv characterized
18 # by a stride equal to 1 and number of neurons equal to 20
19 self.patch_size = patch_size#每次截取图像patch大小(5,5)
20 self.input_channels = input_channels#输入通道数(波段数)
21 dilation = (dilation, 1, 1)
22
23 if patch_size == 3:
24 self.conv1 = nn.Conv3d(
25 1, 20, (3, 3, 3), stride=(1, 1, 1), dilation=dilation, padding=1
26 )
27 else:
28 self.conv1 = nn.Conv3d(
29 1, 20, (3, 3, 3), stride=(1, 1, 1), dilation=dilation, padding=0
30 )
31 # Next pooling is applied using a layer identical to the previous one
32 # with the difference of a 1D kernel size (1,1,3) and a larger stride
33 # equal to 2 in order to reduce the spectral dimension
34 #为什么要用卷积层要替代池化层:
35 # 使用2×2的最大池化,与使用卷积(stride为2)来做down sample性能并没有明显差别,
36 # 而且使用卷积(stride为2)相比卷积(步进为1)+池化,还可以减少卷积运算量和一个池化层。何乐而不为呢。
37 self.pool1 = nn.Conv3d(
38 20, 20, (3, 1, 1), dilation=dilation, stride=(2, 1, 1), padding=(1, 0, 0)
39 )
40 # Then, a duplicate of the first and second layers is created with
41 # 35 hidden neurons per layer.
42 self.conv2 = nn.Conv3d(
43 20, 35, (3, 3, 3), dilation=dilation, stride=(1, 1, 1), padding=(1, 0, 0)
44 )
45 self.pool2 = nn.Conv3d(
46 35, 35, (3, 1, 1), dilation=dilation, stride=(2, 1, 1), padding=(1, 0, 0)
47 )
48 # Finally, the 1D spatial dimension is progressively reduced
49 # thanks to the use of two Conv layers, 35 neurons each,
50 # with respective kernel sizes of (1,1,3) and (1,1,2) and strides
51 # respectively equal to (1,1,1) and (1,1,2)
52 self.conv3 = nn.Conv3d(
53 35, 35, (3, 1, 1), dilation=dilation, stride=(1, 1, 1), padding=(1, 0, 0)
54 )
55 self.conv4 = nn.Conv3d(
56 35, 35, (2, 1, 1), dilation=dilation, stride=(2, 1, 1), padding=(1, 0, 0)
57 )
58
59 # self.dropout = nn.Dropout(p=0.5)
60
61 self.features_size = self._get_final_flattened_size()
62 # The architecture ends with a fully connected layer where the number
63 # of neurons is equal to the number of input classes.
64 self.fc = nn.Linear(self.features_size, n_classes)
65
66 self.apply(self.weight_init)
67
68 def _get_final_flattened_size(self):#计算fc前的输出tensor大小,用随机的X去测试得到,这样就不用自己去推到了
69 with torch.no_grad():
70 x = torch.zeros(
71 (1, 1, self.input_channels, self.patch_size, self.patch_size)
72 )
73 x = self.pool1(self.conv1(x))
74 x = self.pool2(self.conv2(x))
75 x = self.conv3(x)
76 x = self.conv4(x)
77 _, t, c, w, h = x.size()
78 return t * c * w * h
79
80 def forward(self, x):
81 x = F.relu(self.conv1(x))
82 x = self.pool1(x)
83 x = F.relu(self.conv2(x))
84 x = self.pool2(x)
85 x = F.relu(self.conv3(x))
86 x = F.relu(self.conv4(x))
87 x = x.view(-1, self.features_size)#对tensor resize相当于做展平(flatten)操作
88 # x = self.dropout(x)
89 x = self.fc(x)
90 return x

关于参数dilation的解释:https://blog.csdn.net/qimo601/article/details/112624091

运行结果:

通过阅读代码发现和原论文中的结构并不完全相同,试着去修改了一层网络参数去接近原网络结构:

self.conv3 = nn.Conv3d(
35, 35, (3, 1, 1), dilation=dilation, stride=(1, 1, 1), padding=(1, 0, 0)
) ->> self.conv3 = nn.Conv3d(
35, 35, (3, 3, 3), dilation=dilation, stride=(1, 1, 1), padding=(1, 0, 0)
)

但发现最终效果不如上面结果,可能是对原论文提出的结构稍作改进了吧。

参考资料:

https://github.com/nshaud/DeepHyperX

https://blog.csdn.net/weixin_38481963/article/details/109906338

https://blog.csdn.net/abbcdc/article/details/123332063

https://wenku.baidu.com/view/fa899796f221dd36a32d7375a417866fb84ac087.html

DeepHyperX代码理解-HamidaEtAl的更多相关文章

  1. linux io的cfq代码理解

    内核版本: 3.10内核. CFQ,即Completely Fair Queueing绝对公平调度器,原理是基于时间片的角度去保证公平,其实如果一台设备既有单队列,又有多队列,既有快速的NVME,又有 ...

  2. 通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

    秦鼎涛  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验一 通过汇编一个简单的C程序,分析汇编代码 ...

  3. 『TensorFlow』通过代码理解gan网络_中

    『cs231n』通过代码理解gan网络&tensorflow共享变量机制_上 上篇是一个尝试生成minist手写体数据的简单GAN网络,之前有介绍过,图片维度是28*28*1,生成器的上采样使 ...

  4. 通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

    实验一:通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的 学号:20135114 姓名:王朝宪 注: 原创作品转载请注明出处   <Linux内核分析>MOOC课程http: ...

  5. linux内核分析作业:以一简单C程序为例,分析汇编代码理解计算机如何工作

    一.实验 使用gcc –S –o main.s main.c -m32 命令编译成汇编代码,如下代码中的数字请自行修改以防与他人雷同 int g(int x) { return x + 3; } in ...

  6. (原创)JS闭包看代码理解

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...

  7. junit4X系列源码--Junit4 Runner以及test case执行顺序和源代码理解

    原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3534846.html.感谢作者的无私分享. 前一篇文章我们总体介绍了Junit4的用法以及一些简单的测试 ...

  8. java代码理解

    public int maxProfit(int k, int[] prices) {            int pl = prices.length;            int nothin ...

  9. LearnOpenGL学习笔记(一)——现有代码理解

    首先,给出这次学习的代码原网址.------>原作者的源代码 (黑体是源码,注释是写的.) 引用的库(预编译): #include <glad/glad.h> //控制编译时函数的具 ...

随机推荐

  1. 基于串口校时的数字钟设计(verilog实现)

    任务: 电路图设计: 设计: 模块1:1.先设计一个计数时钟,为了仿真方便,这里把1000ns当作1s. 创建一个计数器second_lim,当计数到1000/20时清零,即1s. 秒显示器secon ...

  2. python3学习笔记之字符串

    字符串 1.一个个字符组成的有序的序列,是字符的集合: 2.使用单引号.双引号.三引号引住的字符序列 3.字符串是不可变对象 4.python3起,字符串就是Unicode类型: 字符串特殊举例: 不 ...

  3. SkyWalking分布式系统应用程序性能监控工具-中

    其他功能 性能剖析 在系统性能监控方法上,Skywalking 提出了代码级性能剖析这种在线诊断方法.这种方法基于一个高级语言编程模型共性,即使再复杂的系统,再复杂的业务逻辑,都是基于线程去进行执行的 ...

  4. 题解 P1999【覆盖墙壁】

    数学题 令 \(A_n\) 为 \(2\times n\) 的墙壁放满块的方案数,考虑递推. 显然 \(A_0=1\),我们令对于 \(k<0\),\(A_k=0\) . 放直线型的块非常好递推 ...

  5. SqlServer获取当前日期的详细写法

    SqlServer获取当前日期1. 获取当前日期 select GETDATE()格式化: select CONVERT(varchar,GETDATE(),120) --2018-04-23 14: ...

  6. 【原创】Asp.NET Core Web API与Vue 3.0搭建前后分离项目

    特地记录一下,网上的教程写的稀里糊涂的,整得我都心塞塞的,其实实现的过程蛮简单的 问题是这样的:我将Vue构建生成好的文件,放在后端wwwroot文件里面,并开启静态文件访问功能,结果总是无法显示相应 ...

  7. BootStrap详解

    1. bootstrap的安装和使用 官网: https://getbootstrap.com/ 中文网: https://www.bootcss.com/ 菜鸟驿站教程网: https://www. ...

  8. Redis 03 字符串

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 应用场景:计数 ...

  9. 【原创】JDK 9-17新功能30分钟详解-语法篇-var

    JDK 9-17新功能30分钟详解-语法篇-var 介绍 JDK 10 JDK 10新增了新的关键字--var,官方文档说作用是: Enhance the Java Language to exten ...

  10. 基于 Apache Hudi 和DBT 构建开放的Lakehouse

    本博客的重点展示如何利用增量数据处理和执行字段级更新来构建一个开放式 Lakehouse. 我们很高兴地宣布,用户现在可以使用 Apache Hudi + dbt 来构建开放Lakehouse. 在深 ...