周报(7.15-7.21)
本周工作
-
修改FCNVMB网络代码,在FCNVMB网络层前加入纵轴采样层,使其能支持OpenFWI数据的训练工作。
由于我手上没有SEG的训练数据,所有修改模型,在保留FCNVMB特点的情况下引入InversionNet的下采样部分,把InversionNet的纯卷积下采样修改为FCNVMB风格的池化层下采样,使其能完成对OpenFWI数据的训练工作,修改模型如下
class FCNVMB_OpenFWI(nn.Module): def __init__(self): super().__init__() # From InversionNet self.convblock1 = UnetDown(5, 32, kernel_size=(7,1), stride=(2,1), padding=(2,0)) self.convblock2 = UnetDown(32,64,kernel_size=(3, 1), stride=(2,1), padding=(0,0)) self.convblock3 = UnetDown(64,out_channel=64,kernel_size=(3, 1), stride=(2,1), padding=(1,0)) self.convblock4 = UnetDown(64, 128, kernel_size=(3,1), stride=(2,1),padding=(1,0)) self.unetDown_3 = UnetDown(128, 256,padding=(1,0)) self.unetDown_4 = UnetDown(256, 512,padding=(1,0)) self.unetUP_1 = UnetUP(512, 1024, 512, output_padding=(1,0)) self.unetUP_2 = UnetUP(1024, 512, 256) self.unetConv_1 = UnetConv2(512, 64) self.conv = nn.Conv2d(64, 1, 1) def __pad(self, inputs, shape_likes): offset1 = (shape_likes.shape[2] - inputs.shape[2]) offset2 = (shape_likes.shape[3] - inputs.shape[3]) padding = [offset2 // 2, (offset2 + 1) // 2, offset1 // 2, (offset1 + 1) // 2] outputs = nn.functional.pad(inputs, padding) return outputs def forward(self, inputs): # 来自InversionNet的连续下采样 outputs = self.convblock1(inputs) # torch.Size([1, 32, 500, 70]) outputs = self.convblock2(outputs) # torch.Size([1, 64, 250, 70]) outputs = self.convblock3(outputs) # torch.Size([1, 64, 126, 70]) outputs = self.convblock4(outputs) # torch.Size([1, 128, 64, 70]) # 原来FCNVMB使用与下两层相似的两层下采样,但由于数据集的形状不同,修改为如上网络层 # 也由于修改了网络层,与速度模型大小较小的缘故,新的模型只能容纳两层跳跃连接 # 来自FCNVMB的下采样与跳跃连接 output3 = self.unetDown_3(outputs) # torch.Size([1, 256, 33, 35]) output4 = self.unetDown_4(output3) # torch.Size([1, 512, 17, 18]) # 上采样与跳跃连接 outputs = self.unetUP_1(output4) # torch.Size([1, 512, 35, 36]) output4 = self.__pad(output4, outputs) outputs = torch.cat([outputs, output4], 1) outputs = self.unetUP_2(outputs) # torch.Size([1, 256, 70, 72]) output3 = self.__pad(output3, outputs) outputs = torch.cat([outputs, output3], 1) # 输出裁剪 outputs = self.unetConv_1(outputs) # torch.Size([1, 64, 70, 72]) outputs = nn.functional.pad(outputs, [-1, -1, 0, 0]) # torch.Size([1, 64, 70, 70]) outputs = self.conv(outputs) # torch.Size([1, 1, 70, 70]) return outputs
-
修改数据集代码,使其拥有一定的随机性。
原来的数据集实现存在如下问题:
如图所示,Epoch4和Epoch5的Loss输出存在对应关系,比如说两轮的[100/30000]、[10100/30000]、[20100/30000]其实是相同数据,这导致Loss的波动存在对应关系,而且输出的Loss也无法代表整个模型的训练情况。顺序的数据还可能导致模型对某批数据过拟合,让其他批次的数据欠拟合,因此我重新实现了一版数据集类,实现了一定程度上的随机生成,缓减了这个问题。
简要来说,在读取数据集之前,我对数据集的读取顺序进行打乱,并在读取数据文件后在内部再次打乱顺序,使得程序拥有一定程度的随机性。
from torch import nn, Tensor
from torch.utils.data import DataLoader, Dataset
import torch
from queue import Queue
from threading import Thread
import os, re, random
import numpy as np
class OpenFWI_V3(Dataset):
def __init__(self,/, shuffle=False, shuffleBatch=3, CacheLength=5, Normalize=True, shuffleAtStart=True):
self.DataPath = "F:/FlatVel_A/"
pattern = re.compile(r"seismic(\d*)")
fileList = os.listdir(os.path.join(self.DataPath, "seismic"))
self.DataList = []
for i in fileList:
dataFileName = re.search(pattern,i)[1]
if dataFileName not in self.DataList:
self.DataList.append(dataFileName)
self.DataList.sort()
self.Offset = 500 # CurveFault_A 每个数据包含500条数据
self.Length = len(self.DataList)*self.Offset
self.DataMin = -26.9410
self.DataMax = 52.7698
self.LabelMin = 1500.0
self.LabelMax = 4500.0
# 3数据缓存
self.CacheLength = CacheLength
self.CacheQueue = Queue(self.CacheLength)
self.ThisCache = None
self.ThisIndex = -2
self.ThreadIndex = 0
self.Thread = Thread(target=DataReadThreader, args=(self,))
self.ThreadRunning = True
self.Shuffle = shuffle
self.ShuffleBatch = shuffleBatch
self.shuffleAtStart = shuffleAtStart
self.Normalize = Normalize
if shuffle:
self.ShuffleDataListOuter, self.ShuffleDataListInsider = self.doShuffle(self.DataList)
def doShuffle(self, data):
tempData = data.copy()
random.shuffle(tempData)
realDataOuter = []
realDataInsider = []
temp = []
for i in tempData:
if len(temp) < 3:
temp.append(i)
else:
insiderTemp = []
outerTemp = []
for j in range(len(temp)):
outerTemp.append(temp[j])
for k in range(self.Offset):
insiderTemp.append((j,k))
realDataOuter.append(outerTemp)
random.shuffle(insiderTemp)
realDataInsider.append(insiderTemp)
temp = []
if len(temp) != 0:
insiderTemp = []
outerTemp = []
for j in range(len(temp)):
outerTemp.append(temp[j])
for k in range(self.Offset):
insiderTemp.append((j,k))
realDataOuter.append(outerTemp)
random.shuffle(insiderTemp)
realDataInsider.append(insiderTemp)
return realDataOuter, realDataInsider
def __normalization(self, data, label):
data = (data - self.DataMin)/(self.DataMax-self.DataMin)
label = (label - self.LabelMin)/(self.LabelMax - self.LabelMin)
return data, label
def __len__(self)->int:
return self.Length
def pushData(self, data):
self.CacheQueue.put(data)
def __getitem__(self,index):
if not self.Shuffle:
realIndex = int(index / self.Offset)
realoffset = index % self.Offset
else:
realIndex = int(index/(self.Offset*self.ShuffleBatch))
realoffset = int(index%(self.Offset*self.ShuffleBatch))
if realIndex == self.ThisIndex:
# print("Directly visit, Now cache %d"%self.CacheQueue.qsize())
if self.Normalize:
return self.__normalization(self.ThisCache[0][realoffset,:,:,:],self.ThisCache[1][realoffset,:,:,:])
else:
return self.ThisCache[0][realoffset,:,:,:], self.ThisCache[1][realoffset,:,:,:]
elif realIndex == self.ThisIndex+1:
# print("Cache get, Now cache %d"%self.CacheQueue.qsize())
self.ThisIndex += 1
self.ThisCache = self.CacheQueue.get()
if self.Normalize:
return self.__normalization(self.ThisCache[0][realoffset,:,:,:],self.ThisCache[1][realoffset,:,:,:])
else:
return self.ThisCache[0][realoffset,:,:,:], self.ThisCache[1][realoffset,:,:,:]
else:
# print("Cache Missing, reset to sequence index %d"%realIndex)
self.ThreadIndex = realIndex
self.ThisIndex = realIndex
if self.Thread.is_alive():
self.ThreadRunning = False
while not self.CacheQueue.empty():
self.CacheQueue.get()
self.Thread.join()
self.ThreadRunning = True
self.CacheQueue = Queue(self.CacheLength)
self.Thread = Thread(target=DataReadThreader, args=(self,))
self.Thread.start()
self.ThisCache = self.CacheQueue.get()
if self.Normalize:
return self.__normalization(self.ThisCache[0][realoffset,:,:,:],self.ThisCache[1][realoffset,:,:,:])
else:
return self.ThisCache[0][realoffset,:,:,:], self.ThisCache[1][realoffset,:,:,:]
def DataReadThreader(dataset:OpenFWI_V3):
if not dataset.Shuffle:
for i in range(dataset.ThreadIndex, len(dataset.DataList)):
if not dataset.ThreadRunning:
return
data = np.load(os.path.join(dataset.DataPath, "seismic", "seismic%s.npy"%dataset.DataList[i]))
label = np.load(os.path.join(dataset.DataPath, "vmodel", "vmodel%s.npy"%dataset.DataList[i]))
data = Tensor(data)
label = Tensor(label)
dataset.CacheQueue.put((data, label))
else:
if dataset.shuffleAtStart:
dataListOuter, dataListInsider = dataset.doShuffle(dataset.DataList)
else:
dataListOuter, dataListInsider = dataset.ShuffleDataListOuter, dataset.ShuffleDataListInsider
for listIndex in range(dataset.ThreadIndex, len(dataListOuter)):
if not dataset.ThreadRunning:
return
outerDatas = []
outerLabels = []
outerList = dataListOuter[listIndex]
insiderList = dataListInsider[listIndex]
realDatas = []
realLabels = []
for outer in outerList:
outerDatas.append(np.load(os.path.join(dataset.DataPath, "seismic", "seismic%s.npy"%outer)))
outerLabels.append(np.load(os.path.join(dataset.DataPath, "vmodel", "vmodel%s.npy"%outer)))
for i, j in insiderList:
realDatas.append(outerDatas[i][j,:,:,:])
realLabels.append(outerLabels[i][j,:,:,:])
del outerDatas
del outerLabels
del outerList
del insiderList
realDatas = Tensor(np.array(realDatas))
realLabels = Tensor(np.array(realLabels))
dataset.CacheQueue.put((realDatas, realLabels))
- 对之前存在的部分问题进行解决。
存在问题
-
标准化错误导致loss巨大,loss不继续下降,无法得出最优结果。
在之前的网络训练中,由于标准化操作没有覆盖每一个数据导出分支,导致速度模型的数据在输入到模型时并没有完全标准化,部分数据仍然保留了4位数的数值。但是标准化后的数值范围应该是[-1, 1]。在过去的网络训练中MSE Loss函数输出了8位数的loss,在修改后loss值恢复正常,网络能输出与标签相似的预测速度模型。
但在本周训练FCNVMB网络过程中,我发现部分速度模型标签的数值范围约为[-1.5, 1.5],仍然超出了标准化应有的范围,同时之前训练的InversionNet最后一层使用了tanh激活函数,无法输出小于-1或大于1的值,导致梯度下降提前终止,虽然模型仍能对数据进行预测,但某些预测数据与映射超限的标签数据仍有差距。
在检查后,我发现了问题所在,标准化函数使用数据的标准差和平均值进行数据处理,但在计算这两个值时发生了小数精度丢失的问题,导致标准差和平均值并不标准和平均,进而导致标签数据并没有被完全缩放到[-1, 1]的范围内。而使用tanh激活函数的网络无法拟合[-1, 1]以外的值,最终导致了网络模型并没有达到最优。
在了解问题后,我是用归一化替代了标准化,归一化使用数据的最大值和最小值,将所有数据缩放到[0-1]的范围内,可以被网络映射,在计算最大值和最小值的过程中也不会出现精度丢失的问题。
但这也引出了另一个疑惑——在更改数据集后,数据集的最大值和最小值也会发生变化,如果此时的速度模型标签无法被缩放到[0, 1]的范围内时,在原来数据集上预训练的模型也无法完全拟合,这个问题该如何理解?
下周任务
- 完成对FCNVMB的对比和优化工作,观察师兄的实现,完成对自实现代码的优化。并训练网络。
- 比较InversionNet与FCNVMB的效果与模型特点。
- 预学习SeisInvNet。
- 继续学习地震反演原理,深度学习原理。