周报(8.12-8.18)
本周工作
DD-Net学习与代码复现
DD-Net网络结构如上图所示。DD-Net也有一个为处理OpenFWI数据的版本:DD-Net70:
与传统DL-FWI不同的是,DD-Net同时拥有两个解码器,第一个解码器的目标是传统的速度模型,它侧重于速度值的精确拟合,这也是用于生成速度图像的主要解码器;第二个解码器的目标是速度模型边缘信息,将Canny轮廓提取后的黑白边缘速度模型进一步用作第二解码器的拟合目标。此解码器将用作训练轮廓信息的辅助解码器。
网络结构复现
与盐数据不同的是,OpenFWI在时间域上的跨度非常大,时间域和空间域的比例值较大,直接进行特征提取工作会导致空间上的时间丢失。因此,在开始特征提取工作之前,我们需要先对时间域进行压缩。第一步压缩使用插值完成,使用了以下函数:
torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False)
该函数可以使用不同的插值算法碓输入进行插值,输入参数解释如下:
- input: 输入
- size: 输出数据的形状
- scale_factor: 表示在输入的不同维度上进行缩放的比例
- mode: 使用的差值算法,可以使用’nearest’最邻近、'linear’线性、'bilinear’双线性、‘bicubic’、‘trilinear’、‘area’和’nearest-exact’。
- align_corners:将像素视为正方形而不是点。对于将像素视为点的情况,计算某像素值时使用临近点的像素值进行;对于将像素视为正方形的情况,计算某像素值时会将该点与周围三点合并为像素块,并使用临近像素块的值进行计算。
- recompute_scale_factor: 如果设置为True,函数将强制使用scale_factor作为输出大小的依据。
- antialias: 抗锯齿。
其他操作是经典的卷积操作,不再解释。结合起来,可以获得下采样部分的代码:
class DownSampling(nn.Module):
def __init__(self, shot_num):
super().__init__()
self.conv1_1 = ConvBlock(shot_num, 8, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.conv1_2 = ConvBlock(8, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
self.conv2_1 = ConvBlock(8, 16, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.conv2_2 = ConvBlock(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
self.conv3_1 = ConvBlock(16, 32, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.conv3_2 = ConvBlock(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
def forward(self, inputs):
width = inputs.shape[3]
new_size = [width * 8, width]
outputs = F.interpolate(inputs, size=new_size, mode='bilinear', align_corners=False)
outputs = self.conv1_1(outputs)
outputs = self.conv1_2(outputs)
outputs = self.conv2_1(outputs)
outputs = self.conv2_2(outputs)
outputs = self.conv3_1(outputs)
outputs = self.conv3_2(outputs)
return outputs
随后DD-Net70使用了与FCNVMB相似的网络结构:
class unetConv2(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 3, 1, 1), nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True))
self.conv2 = nn.Sequential(
nn.Conv2d(out_channels, out_channels, 3, 1, 1), nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True))
def forward(self, inputs):
outputs = self.conv1(inputs)
outputs = self.conv2(outputs)
return outputs
class unetDown(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.conv = unetConv2(in_channels, out_channels)
self.down = nn.MaxPool2d(2, 2, ceil_mode=True)
def forward(self, inputs):
outputs = self.conv(inputs)
outputs = self.down(outputs)
return outputs
class unetJumpUp(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.up = nn.ConvTranspose2d(
in_channels, out_channels, kernel_size=2, stride=2)
self.conv = unetConv2(out_channels*2, out_channels)
def forward(self, input1, input2):
# inputs1是上一层输入数据,inputs2是跳跃连接数据
outputs = self.up(input1)
outputs = F.interpolate(
outputs, size=(input2.shape[2],input2.shape[3]), mode='bilinear')
outputs = self.conv(torch.cat([outputs, input2], 1))
return outputs
class unetUp(nn.Module):
def __init__(self, in_channels, out_channels, output_shape):
super().__init__()
self.up = nn.ConvTranspose2d(
in_channels, out_channels, kernel_size=2, stride=2)
self.conv = unetConv2(out_channels, out_channels)
self.output_shape = output_shape
def forward(self, input1):
outputs = self.up(input1)
outputs = F.interpolate(outputs, size=self.output_shape, mode='bilinear')
outputs = self.conv(outputs)
return outputs
与FCNVMB不同的是,DD-Net中使用了interpolate函数进行函数裁剪,而不是使用pad函数。
网络代码复现
对于网络的代码实现,我抱有部分疑虑,以下是我参考论文复现出的代码:
class DDNet70(nn.Module):
def __init__(self):
super().__init__()
self.downSampling = DownSampling()
# Intrinsic UNet section
self.down1 = unetDown(32, 64)
self.down2 = unetDown(64, 128)
self.down3 = unetDown(128, 256)
self.down4 = unetDown(256, 512)
# Decode1
self.up1_1 = unetJumpUp(512, 256)
self.up1_2 = unetJumpUp(256, 128)
self.up1_3 = unetUp(128, 64, (35, 35))
self.up1_4 = unetUp(64, 32, (70, 70))
self.conv1 = ConvBlock_Tanh(32, 1)
# Decode2
self.up2_1 = unetJumpUp(512, 256)
self.up2_2 = unetJumpUp(256, 128)
self.up2_3 = unetUp(128, 64, (35, 35))
self.up2_4 = unetUp(64, 32, (70, 70))
self.conv2 = ConvBlock_Tanh(32, 2)
def forward(self, inputs,):
outputs = self.downSampling(inputs)
outputs = self.down1(outputs)
outputs_jump1 = self.down2(outputs)
outputs_jump2 = self.down3(outputs_jump1)
center = self.down4(outputs_jump2)
# Decoder 1
outputs = self.up1_1(center, outputs_jump2)
outputs = self.up1_2(outputs, outputs_jump1)
outputs = self.up1_3(outputs)
print(outputs.shape)
outputs = self.up1_4(outputs)
outputs1 = self.conv1(outputs)
# Decoder 2
outputs = self.up2_1(center, outputs_jump2)
outputs = self.up2_2(outputs, outputs_jump1)
# delete no use
del center
del outputs_jump2
del outputs_jump1
outputs = self.up2_3(outputs)
outputs = self.up2_4(outputs)
outputs2 = self.conv2(outputs)
return [outputs1, outputs2]
但论文代码中没有完全区分两个解码器的卷积函数,即进行速度图像解码的卷积与进行边缘图像的卷积是同一个卷积对象, 理论上具有相同的参数。虽然有较大的相似性,但这确实是两个不同的任务,虽然有较大的相似性,使用同一组卷积对象是否有所不妥?
损失函数
DD-Net的另一个创新点在于,DD-Net使用了复合损失函数,因为边界图像是黑白图像,直接使用MSE并不完全合理。但如果把边缘图像视作“该像素是否是边缘”的标签,就可以使用交叉熵损失进行损失计算。DD-Net的输出同时含有速度图像与边缘图像,因此就必须构建一个复合的损失函数,简单来说,DD-Net构建了一个这样的损失函数。
L
o
s
s
=
α
1
⋅
M
S
E
+
α
2
⋅
C
r
o
s
s
E
n
t
r
o
p
y
Loss = \alpha_1\cdot MSE+\alpha_2\cdot CrossEntropy
Loss=α1⋅MSE+α2⋅CrossEntropy
根据论文介绍,对于归一化后的OpenFWI数据,
α
2
α
1
\frac{\alpha_2}{\alpha_1}
α1α2最好在
[
10
,
1
0
2
]
[10, 10^2]
[10,102]内。复现代码如下:
class LossDDNet:
def __init__(self, weights=[1, 1], crossEntropy_weight=[1, 1]):
self.criterion1 = nn.MSELoss()
crossEntropyWeight = torch.from_numpy(np.array(crossEntropy_weight, dtype=np.float32)).cuda()
self.criterion2 = nn.CrossEntropyLoss(weight=crossEntropyWeight)
self.weights = weights
def __call__(self, outputs1, outputs2, targets1, targets2):
mse = self.criterion1(outputs1, targets1)
cross = self.criterion2(outputs2, torch.squeeze(targets2).long())
criterion = (self.weights[0] * mse + self.weights[1] * cross)
return criterion
其中,torch.squeeze
函数会移除对象中所有长度为1的维度。
分阶段学习
为了使训练效果更好,DD-Net在训练过程中对数据集难度进行分级,总共分为3级,从简单的数据开始训练,完成阶段后训练更难得数据集。
学习地震正演
根据相关书籍,继续学习地震正演理论,持续记录学习笔记。
下周工作
- 训练DD-Net70网络,观察网络效果
- 继续学习地震正演
- 开展对地震速度图像拼接得研究