今日内训主要研习理解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个函数:

  1. 卷积组合操作(红箭头):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class unetConv2(nn.Module):
    def __init__(self, in_size, out_size, is_batchnorm): # in/out_size分别为输入输出的通道数
    '''
    Convolution with two basic operations
    [Affiliated with FCNVMB]

    :param in_size: Number of channels of input
    :param out_size: Number of channels of output
    :param is_batchnorm: Whether to use BN
    '''
    super(unetConv2, self).__init__()
    if is_batchnorm:
    self.conv1 = nn.Sequential(nn.Conv2d(in_size, out_size, 3, 1, 1),
    nn.BatchNorm2d(out_size),
    nn.ReLU(inplace=True), )
    self.conv2 = nn.Sequential(nn.Conv2d(out_size, out_size, 3, 1, 1),
    nn.BatchNorm2d(out_size),
    nn.ReLU(inplace=True), )
    else:
    self.conv1 = nn.Sequential(nn.Conv2d(in_size, out_size, 3, 1, 1),
    nn.ReLU(inplace=True), )
    self.conv2 = nn.Sequential(nn.Conv2d(out_size, out_size, 3, 1, 1),
    nn.ReLU(inplace=True), )

    def forward(self, inputs):
    '''

    :param inputs: Input Image
    :return:
    '''
    outputs = self.conv1(inputs)
    outputs = self.conv2(outputs)
    return outputs
  2. 下采样操作(红箭头+紫箭头):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class unetDown(nn.Module):
    def __init__(self, in_size, out_size, is_batchnorm):
    '''
    Downsampling Unit
    [Affiliated with FCNVMB]

    :param in_size: Number of channels of input
    :param out_size: Number of channels of output
    :param is_batchnorm: Whether to use BN
    '''
    super(unetDown, self).__init__()
    self.conv = unetConv2(in_size, out_size, is_batchnorm)
    self.down = nn.MaxPool2d(2, 2, ceil_mode=True)

    def forward(self, inputs):
    '''

    :param inputs: Input Image
    :return:
    '''
    outputs = self.conv(inputs)
    outputs = self.down(outputs)
    return outputs
  3. 上采样操作(黄箭头+红箭头):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class unetUp(nn.Module):
    def __init__(self, in_size, out_size, is_deconv):
    '''
    Upsampling Unit
    [Affiliated with FCNVMB]

    :param in_size: Number of channels of input
    :param out_size: Number of channels of output
    :param is_deconv: Whether to use deconvolution
    '''
    super(unetUp, self).__init__()
    self.conv = unetConv2(in_size, out_size, True)
    # Transposed convolution
    if is_deconv:
    self.up = nn.ConvTranspose2d(in_size, out_size, kernel_size=2, stride=2)
    else:
    self.up = nn.UpsamplingBilinear2d(scale_factor=2)

    def forward(self, inputs1, inputs2):
    '''

    :param inputs1: Layer of the selected coding area via skip connection
    :param inputs2: Current network layer based on network flows
    :return:
    '''
    outputs2 = self.up(inputs2)
    offset1 = (outputs2.size()[2] - inputs1.size()[2])
    offset2 = (outputs2.size()[3] - inputs1.size()[3])
    padding = [offset2 // 2, (offset2 + 1) // 2, offset1 // 2, (offset1 + 1) // 2]

    # Skip and concatenate
    outputs1 = F.pad(inputs1, padding)
    return self.conv(torch.cat([outputs1, outputs2], 1))

匹配尺寸

上述的封装函数unetUp中,在拼接浅层特征和深层特征图像时两者的尺寸并不一致,而是相差一倍。为此,需要对inputs1(浅层特征图像)进行pad操作进行图像尺寸匹配。

网络类

经过函数封装后,FCNVMB类可以简写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class FCNVMB(nn.Module):
def __init__(self, n_classes, in_channels, is_deconv, is_batchnorm):
'''
Network architecture of FCNVMB

:param n_classes: Number of channels of output (any single decoder)
:param in_channels: Number of channels of network input
:param is_deconv: Whether to use deconvolution
:param is_batchnorm: Whether to use BN
'''
super(FCNVMB, self).__init__()
self.is_deconv = is_deconv
self.in_channels = in_channels
self.is_batchnorm = is_batchnorm
self.n_classes = n_classes

filters = [64, 128, 256, 512, 1024]

self.down1 = unetDown(self.in_channels, filters[0], self.is_batchnorm)

self.down2 = unetDown(filters[0], filters[1], self.is_batchnorm)
self.down3 = unetDown(filters[1], filters[2], self.is_batchnorm)
self.down4 = unetDown(filters[2], filters[3], self.is_batchnorm)
self.center = unetConv2(filters[3], filters[4], self.is_batchnorm)
self.up4 = unetUp(filters[4], filters[3], self.is_deconv)
self.up3 = unetUp(filters[3], filters[2], self.is_deconv)
self.up2 = unetUp(filters[2], filters[1], self.is_deconv)
self.up1 = unetUp(filters[1], filters[0], self.is_deconv)
self.final = nn.Conv2d(filters[0], self.n_classes, 1)

def forward(self, inputs, label_dsp_dim):
'''

:param inputs: Input Image
:param label_dsp_dim: Size of the network output image (velocity model size)
:return:
'''
down1 = self.down1(inputs)
down2 = self.down2(down1)
down3 = self.down3(down2)
down4 = self.down4(down3)
center = self.center(down4)
up4 = self.up4(down4, center)
up3 = self.up3(down3, up4)
up2 = self.up2(down2, up3)
up1 = self.up1(down1, up2)
up1 = up1[:, :, 1:1 + label_dsp_dim[0], 1:1 + label_dsp_dim[1]].contiguous()

return self.final(up1)

异同比较

主要对比Day 3介绍的InversionNet。

相同点

  1. 无论是FCNVMB还是InversionNet,它们都是单一的端到端深度网络并没有利用更多的物理含义。
  2. 都采用了编码器-解码器的架构。
  3. 都是利用叠前多炮数据的不同炮集直接投入训练,并未处理。

不同点

  1. InversionNet在编码的过程中最终将图像压缩为完全的一维向量,抛弃了空间关联性;而FCNVMB在压缩后仍保留了25 * 19的空间尺寸关联。
  2. FCNVMB面向SEG数据,InversionNet面向部分OpenFWI的数据。因为OpenFWI数据的特点,InversionNet有非常明显的高度降维部分。
  3. FCNVMB使用了迁移学习的训练手段,后者InversionNet是单一的训练思想。
  4. FCNVMB采用了包含skip connection的UNet的架构,而InversionNet是单一的CNN架构。