目录
一、文章简介
- 文章名称:《InversionNet: An Efficient and Accurate Data-Driven Full Waveform Inversion》
- 文章出处:IEEE TRANSACTIONS ON COMPUTATIONAL IMAGING
- 文章作者:Yue Wu and Youzuo Lin
- 面向数据集:OpenFWI中的Vel类、Fault类、Style类(时间域与宽度的比值很高)——2019年提出的完全端到端算法(最简单的一个,采用的是最基础的CNN网络进行架构)
输入数据的详细参数如下图所示:
二、网络简单介绍
InversionNet(深度学习实现的反演)构建了一个具有编码器-解码器结构的卷积神经网络,用以模拟地震数据与地下速度结构之间的对应关系。
2.1 编码器
编码器(上侧部分)主要采用卷积层(非方形卷积)构建的,从输入的地震数据中提取出高级特征,并将高级特征压缩为单一的高维向量。在最后一个卷积层中并没有实施填充零的操作,一遍特征向量可以压缩为一个单一的向量。
这个高维向量是由1*1的图块构成的512通道张量,可以认为是一个512维的向量。
非方形卷积——空间压缩,因为输入的地震波图像的时间维度(图像的高)相比于宽度是很高的,所以需要使用一些比较窄的卷积将时间域压缩来与宽度差不多的程度,先经历宽度不变,高度被压缩的过程。
从上图可以直观的感受到,最初的输入有6个通道,经过第一次卷积压缩变为32个通道(图像的尺寸变小、通道数变多才能保证信息的平衡),1000的高度变成了500;第二次使用两个卷积先将500的时间维度变为250,再使用这两个卷积将250的高度变为125,相当于经历了两次压缩过程(一次压缩使用2个卷积,相比于第一次卷积的压缩更加温和),一共用了4个卷积。第三次操作通过一次压缩、四次卷积的过程将125变为32,此时非方形卷积的目的已经达到了。第四操作中时间域与空间域的操作同时进行,采用一般卷积(方形卷积)进行压缩。
注意:压缩其实是一个特征提取的过程,每一次的压缩不一定改变通道数,但是最终的目的会在有限次压缩后将通道数改变。特征提取的过程中需要保持通道数的增加,否则会导致空间信息严重丢失。
这篇论文读出了一个结论:上述压缩信息是合理的,因为没有必要保留地震数据中的时间和空间的相关性。(ps:这个结论我们不能完全赞同,因为后面有人写了论文评判了这个思想。这也是InversionNet的一个缺点:对空间进行了舍弃。)
2.2 解码器
解码器(底部部分)通过一组反卷积(转置卷积)将这些特征转换为速度模型。
下图架构中给出的h和w方便用户在未来适配更多的速度模型环境,对于70*70的速度模型,作者给出的h=w=5,在该模型下最终输出的尺寸为80*80(16w),最后通过裁剪得到70*70。
反卷积可以将图像的维度进行升维,同时最大程度保留图像原本的性质,可以通过在输入特征地图上填充零来实现。反卷积的效果比一般的反池化(信息丢失较多)操作拥有更好的效果。
三、代码介绍
注意:没有使用原论文中提到的1000*32的数据,而是使用的1000*70的OpenFWI的数据,这里采用的代码参照OpenFWI论文(2022年)中公开的InversionNet的修改版代码,该代码的卷积过程与2019年发布的InversionNet论文中所展示的架构存在一定程度的差异。
OpenFWI也是InversionNet作者所在的团队新提出来的,后续对网络的架构进行了修改。
- 网络结构中展示的每个卷积操作实质上都是由卷积层、批归一化(BN)和LeaklyReLU共同构成的。
- 在卷积层中,卷积承担输入信号的责任,同时担任滤波器(提取有意义的特征)的作用。
- 卷积:是应用于DL-FWI问题的理想方法,因为地震测量在空间上是连续的,而卷积层的局部连通性和权重共享使得特征提取有效和高校。
- 批量归一化(Batch Normalization):如果网络的输入具有零均值、单位方差和去相关,则深层网络的收敛速度会加快。有关研究进一步表明,使中间层的输出具有这些属性也是有利的。批量归一化用于在每次迭代时,对馈送到网络中的中间层的数据子集在输出时进行归一化。
- LeaklyReLU:用于解决ReLu的神经元死亡现象,LeaklyReLU通过将x的非常小的线性分量给予负输入αx来调整负值的零梯度问题,此外也可以扩大函数y的范围。
InversionNet的末端采用的标准损失函数是L1规范的损失函数。(但是平常在进行测试的时候,使用L2更多一些,效果好一些)
其中,yi是真实速度模型,zi是预测速度模型,n是速度模型中空间像素的数量。
- ConvBlock卷积的声明:
class ConvBlock(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, norm='bn', relu_shop=0.2, dropout=None):
"""
Standard convolution operation [Affiliated with InversionNet]
:param in_channels: Number of channels of input 输入的通道数
:param out_channels: Number of channel of output 输出的通道数
:param kernel_size: Size of the convolution
:param stride: Step of the convolution
:param padding: Zero-fill width
:param norm: The means of normalization
:param relu_shop: Parameter of relu
:param dropout: Weather of apply dropout
"""
super(ConvBlock, self).__init__()
# 构造卷积层
layers = [nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding)]
# 批归一化层
if norm in NORM_LAYERS:
layers.append(NORM_LAYERS[norm](out_channels))
# 激活函数层
layers.append(nn.LeakyReLU(relu_shop, inplace=True))
if dropout:
layers.append(nn.Dropout2d(0.8))
self.layers = nn.Sequential(*layers)
def forward(self, x):
'''
:param x: Input Image
:return:
'''
return self.layers(x)
class ConvBlock_Tanh(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, norm='bn'):
'''
Convolution operation of the output part
[Affiliated with InversionNet]
:param in_channels: Number of channels of input
:param out_channels: Number of channels of output
:param kernel_size: Size of the convolution kernel
:param stride: Step size of the convolution
:param padding: Zero-fill width
:param norm: The means of normalization
'''
super(ConvBlock_Tanh, self).__init__()
layers = [nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding)]
if norm in NORM_LAYERS:
layers.append(NORM_LAYERS[norm](out_channels))
layers.append(nn.Tanh())
self.layers = nn.Sequential(*layers)
def forward(self, x):
'''
:param x: Input Image
:return:
'''
return self.layers(x)
class DeconvBlock(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=2, stride=2, padding=0, output_padding=0, norm='bn'):
'''
Deconvolution operation
[Affiliated with InversionNet]
:param in_channels: Number of channels of input
:param out_channels: Number of channels of output
:param kernel_size: Size of the convolution kernel
:param stride: Step size of the convolution
:param padding: Zero-fill width of input
:param output_padding: Zero-fill width of output
:param norm: The means of normalization
'''
super(DeconvBlock, self).__init__()
layers = [nn.ConvTranspose2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, output_padding=output_padding)]
if norm in NORM_LAYERS:
layers.append(NORM_LAYERS[norm](out_channels))
layers.append(nn.LeakyReLU(0.2, inplace=True))
self.layers = nn.Sequential(*layers)
def forward(self, x):
'''
:param x: Input Image
:return:
'''
return self.layers(x)
- 网络搭建:
注意:2019年论文只针对时间域压缩,但是2022年论文不再只针对时间域的压缩,因为此时长宽比已经很接近与1。
class InversionNet(nn.Module):
def __init__(self, dim1=32, dim2=64, dim3=128, dim4=256, dim5=512, **kwargs):
'''
Network architecture of InversionNet
:param dim1: Number of channels in the 1st layer
:param dim2: Number of channels in the 2nd layer
:param dim3: Number of channels in the 3rd layer
:param dim4: Number of channels in the 4th layer
:param dim5: Number of channels in the 5th layer
:param sample_spatial: Scale parameters for sampling in space
'''
super(InversionNet, self).__init__()
# 时间域的第一次降维,直接通过一批(32个)卷积进行降维——T:1000->T:500
# ConvBlock中进行了封装,包括二维的卷积操作、批归一化BN以及Relu三个操作
self.convblock1 = ConvBlock(5, dim1, kernel_size=(7, 1), stride=(2, 1), padding=(3, 0))
# 时间域的第二次降维,通过两批(64个)卷积进行降维——T:500->T:250
self.convblock2_1 = ConvBlock(dim1, dim2, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.convblock2_2 = ConvBlock(dim2, dim2, kernel_size=(3, 1), padding=(1, 0))
# 时间域的第三次降维,通过两批(64个)卷积进行降维——T:250->T:125
self.convblock3_1 = ConvBlock(dim2, dim2, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.convblock3_2 = ConvBlock(dim2, dim2, kernel_size=(3, 1), padding=(1, 0))
# 时间域的第四次降维,通过两批(64个)卷积进行降维——T:125->T:63
self.convblock4_1 = ConvBlock(dim2, dim3, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.convblock4_2 = ConvBlock(dim3, dim3, kernel_size=(3, 1), padding=(1, 0))
# 单方面降维结束,进入两个方向同时的降维——R:70->35 T:63->32
self.convblock5_1 = ConvBlock(dim3, dim3, stride=2) # 默认构造方形卷积
self.convblock5_2 = ConvBlock(dim3, dim3)
# 两个方向同时的降维——R:35->18 T:32->16
self.convblock6_1 = ConvBlock(dim3, dim4, stride=2)
self.convblock6_2 = ConvBlock(dim4, dim4)
# 两个方向同时的降维——R:16->8 T:16->8
self.convblock7_1 = ConvBlock(dim4, dim4, stride=2)
self.convblock7_2 = ConvBlock(dim4, dim4)
self.convblock8 = ConvBlock(dim4, dim5, kernel_size=(8, math.ceil(70 * 1.0 / 8)), padding=0)
self.deconv1_1 = DeconvBlock(dim5, dim5, kernel_size=5)
self.deconv1_2 = ConvBlock(dim5, dim5)
self.deconv2_1 = DeconvBlock(dim5, dim4, kernel_size=4, stride=2, padding=1)
self.deconv2_2 = ConvBlock(dim4, dim4)
self.deconv3_1 = DeconvBlock(dim4, dim3, kernel_size=4, stride=2, padding=1)
self.deconv3_2 = ConvBlock(dim3, dim3)
self.deconv4_1 = DeconvBlock(dim3, dim2, kernel_size=4, stride=2, padding=1)
self.deconv4_2 = ConvBlock(dim2, dim2)
self.deconv5_1 = DeconvBlock(dim2, dim1, kernel_size=4, stride=2, padding=1)
self.deconv5_2 = ConvBlock(dim1, dim1)
# 裁剪输出层
self.deconv6 = ConvBlock_Tanh(dim1, 1)
测试:
if __name__ == '__main__':
# InversionNet
x = torch.zeros((10, 5, 1000, 70))
model = InversionNet(inchannel= 5, w = 70)
out = model(x)
print("out: ", out.size())
四、疑惑
- CNN网络架构?
- 完全端到端的理解?
- 步长、零填充的理解?
- 反池化、反卷积的理解?
- 滤波器?
- 零均值、单位方差和去相关?
- LeaklyReLU与ReLU的区别?