今日内训主要研习理解FCNVMB,一种基于FCN(fully convolutional networks)的VMB(velocity-model build)方法。
FCNVMB
FCN指的是全卷积网络,其具体定义可参考StackExchange的解释。与传统CNN有所不同的是,FCN的所有操作均为卷积计算,而前者的全连接层中并不包含卷积操作。该网络在图像语义分割中有着广泛应用。
FCN虽然这篇文章的作者强调该算法属于FCN引导的VMB,但在代码层面上该算法更类似于UNet结构——一种FCN改进后的变体。
UNet网络分析
如前文所提,FCNVMB可以理解为UNet网络架构下的FWI。与UNet相同,FCNVMB会分别经历图像下采样(编码器)和上采样(解码器)两个步骤。
下采样主要用于提取图像的深层特征;而上采样过程中,通过与对应的浅层特征图像拼接,更好地融合了图像的浅层特征和深层特征两个方面。其中,浅层特征图像更倾向于表达例如点、线、边缘轮廓等基本特征单元,蕴含的空间信息更多;而深层特征图更倾向于表达图像的语义信息,蕴含的空间信息更少,语义特征更多。
FCNVMB网络结构
编码器
下采样过程中,每一轮都会经历两轮红色箭头操作,不改变图像尺寸;并在最后经历一次紫色箭头操作,使得图像尺寸缩小为原来的一半。
红色箭头包括了三样操作:kernal size = 3 * 3的卷积计算、批归一化操作BN以及ReLU激活函数。
紫色箭头是一次size = 2 * 2的最大池化操作,用于缩小图像尺寸,挖掘图像深层语义信息。
解码器
红色箭头同上所述,不改变特征图像尺寸的同时,对特征进行重整合。
黄色箭头为kernel size = 2 * 2的反卷积操作,使得图像尺寸扩大为原本的两倍。
在每次黄色箭头操作后,都需要将图像对应的浅层特征图像拼接到深层特征图像后,融合特征信息,增加图像的通道数。
代码实现
函数封装
分析了FCNVMB的算法原理后,我们可将图像处理过程封装为3个函数:
-
卷积组合操作:
python复制代码1class unetConv2(nn.Module): 2 def __init__(self, in_size, out_size, is_batchnorm): # in/out_size分别为输入输出的通道数 3 ''' 4 Convolution with two basic operations 5 [Affiliated with FCNVMB] 6 7 :param in_size: Number of channels of input 8 :param out_size: Number of channels of output 9 :param is_batchnorm: Whether to use BN 10 ''' 11 super(unetConv2, self).__init__() 12 if is_batchnorm: 13 self.conv1 = nn.Sequential(nn.Conv2d(in_size, out_size, 3, 1, 1), 14 nn.BatchNorm2d(out_size), 15 nn.ReLU(inplace=True), ) 16 self.conv2 = nn.Sequential(nn.Conv2d(out_size, out_size, 3, 1, 1), 17 nn.BatchNorm2d(out_size), 18 nn.ReLU(inplace=True), ) 19 else: 20 self.conv1 = nn.Sequential(nn.Conv2d(in_size, out_size, 3, 1, 1), 21 nn.ReLU(inplace=True), ) 22 self.conv2 = nn.Sequential(nn.Conv2d(out_size, out_size, 3, 1, 1), 23 nn.ReLU(inplace=True), ) 24 25 def forward(self, inputs): 26 ''' 27 28 :param inputs: Input Image 29 :return: 30 ''' 31 outputs = self.conv1(inputs) 32 outputs = self.conv2(outputs) 33 return outputs
-
下采样操作:
python复制代码1class unetDown(nn.Module): 2 def __init__(self, in_size, out_size, is_batchnorm): 3 ''' 4 Downsampling Unit 5 [Affiliated with FCNVMB] 6 7 :param in_size: Number of channels of input 8 :param out_size: Number of channels of output 9 :param is_batchnorm: Whether to use BN 10 ''' 11 super(unetDown, self).__init__() 12 self.conv = unetConv2(in_size, out_size, is_batchnorm) 13 self.down = nn.MaxPool2d(2, 2, ceil_mode=True) 14 15 def forward(self, inputs): 16 ''' 17 18 :param inputs: Input Image 19 :return: 20 ''' 21 outputs = self.conv(inputs) 22 outputs = self.down(outputs) 23 return outputs
-
上采样操作:
python复制代码1class unetUp(nn.Module): 2 def __init__(self, in_size, out_size, is_deconv): 3 ''' 4 Upsampling Unit 5 [Affiliated with FCNVMB] 6 7 :param in_size: Number of channels of input 8 :param out_size: Number of channels of output 9 :param is_deconv: Whether to use deconvolution 10 ''' 11 super(unetUp, self).__init__() 12 self.conv = unetConv2(in_size, out_size, True) 13 # Transposed convolution 14 if is_deconv: 15 self.up = nn.ConvTranspose2d(in_size, out_size, kernel_size=2, stride=2) 16 else: 17 self.up = nn.UpsamplingBilinear2d(scale_factor=2) 18 19 def forward(self, inputs1, inputs2): 20 ''' 21 22 :param inputs1: Layer of the selected coding area via skip connection 23 :param inputs2: Current network layer based on network flows 24 :return: 25 ''' 26 outputs2 = self.up(inputs2) 27 offset1 = (outputs2.size()[2] - inputs1.size()[2]) 28 offset2 = (outputs2.size()[3] - inputs1.size()[3]) 29 padding = [offset2 // 2, (offset2 + 1) // 2, offset1 // 2, (offset1 + 1) // 2] 30 31 # Skip and concatenate 32 outputs1 = F.pad(inputs1, padding) 33 return self.conv(torch.cat([outputs1, outputs2], 1))
匹配尺寸
上述的封装函数unetUp
中,在拼接浅层特征和深层特征图像时两者的尺寸并不一致,而是相差一倍。为此,需要对inputs1
(浅层特征图像)进行pad
操作进行图像尺寸匹配。
网络类
经过函数封装后,FCNVMB
类可以简写如下:
python复制代码1class FCNVMB(nn.Module):
2 def __init__(self, n_classes, in_channels, is_deconv, is_batchnorm):
3 '''
4 Network architecture of FCNVMB
5
6 :param n_classes: Number of channels of output (any single decoder)
7 :param in_channels: Number of channels of network input
8 :param is_deconv: Whether to use deconvolution
9 :param is_batchnorm: Whether to use BN
10 '''
11 super(FCNVMB, self).__init__()
12 self.is_deconv = is_deconv
13 self.in_channels = in_channels
14 self.is_batchnorm = is_batchnorm
15 self.n_classes = n_classes
16
17 filters = [64, 128, 256, 512, 1024]
18
19 self.down1 = unetDown(self.in_channels, filters[0], self.is_batchnorm)
20
21 self.down2 = unetDown(filters[0], filters[1], self.is_batchnorm)
22 self.down3 = unetDown(filters[1], filters[2], self.is_batchnorm)
23 self.down4 = unetDown(filters[2], filters[3], self.is_batchnorm)
24 self.center = unetConv2(filters[3], filters[4], self.is_batchnorm)
25 self.up4 = unetUp(filters[4], filters[3], self.is_deconv)
26 self.up3 = unetUp(filters[3], filters[2], self.is_deconv)
27 self.up2 = unetUp(filters[2], filters[1], self.is_deconv)
28 self.up1 = unetUp(filters[1], filters[0], self.is_deconv)
29 self.final = nn.Conv2d(filters[0], self.n_classes, 1)
30
31 def forward(self, inputs, label_dsp_dim):
32 '''
33
34 :param inputs: Input Image
35 :param label_dsp_dim: Size of the network output image (velocity model size)
36 :return:
37 '''
38 down1 = self.down1(inputs)
39 down2 = self.down2(down1)
40 down3 = self.down3(down2)
41 down4 = self.down4(down3)
42 center = self.center(down4)
43 up4 = self.up4(down4, center)
44 up3 = self.up3(down3, up4)
45 up2 = self.up2(down2, up3)
46 up1 = self.up1(down1, up2)
47 up1 = up1[:, :, 1:1 + label_dsp_dim[0], 1:1 + label_dsp_dim[1]].contiguous()
48
49 return self.final(up1)
异同比较
主要对比Day 3介绍的InversionNet。
相同点
- 无论是FCNVMB还是InversionNet,它们都是单一的端到端深度网络并没有利用更多的物理含义。
- 都采用了编码器-解码器的架构。
- 都是利用叠前多炮数据的不同炮集直接投入训练,并未处理。
不同点
- InversionNet在编码的过程中最终将图像压缩为完全的一维向量,抛弃了空间关联性;而FCNVMB在压缩后仍保留了25 * 19的空间尺寸关联。
- FCNVMB面向SEG数据,InversionNet面向部分OpenFWI的数据。因为OpenFWI数据的特点,InversionNet有非常明显的高度降维部分。
- FCNVMB使用了迁移学习的训练手段,后者InversionNet是单一的训练思想。
- FCNVMB采用了包含skip connection的UNet的架构,而InversionNet是单一的CNN架构。