周报(7.15-7.21)

周报(7.15-7.21)

本周工作

  1. 修改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
            
    
  2. 修改数据集代码,使其拥有一定的随机性。

    原来的数据集实现存在如下问题:

在这里插入图片描述

如图所示,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))

  1. 对之前存在的部分问题进行解决。

存在问题

  1. 标准化错误导致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]的范围内时,在原来数据集上预训练的模型也无法完全拟合,这个问题该如何理解?

下周任务

  1. 完成对FCNVMB的对比和优化工作,观察师兄的实现,完成对自实现代码的优化。并训练网络。
  2. 比较InversionNet与FCNVMB的效果与模型特点。
  3. 预学习SeisInvNet。
  4. 继续学习地震反演原理,深度学习原理。
  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值