2023 11.18~11.24 周报

一、上周工作

        重写dataset调用OpenFWI数据集,采用模仿FCNVMB的代码进行重写dataset。

二、本周计划

        运行OpenFWI中的InversionNet网络部分,并进行训练。抄代码,提取InversionNet相关代码重新写。

三、完成情况

        3.1 dataset.py

         ——数据加载:数据类型转换、归一化、数据维度······

        OpenFWI数据集的FlatVel-A类共有60个npy文件,每个npy文件包含500条数据。PyTorch提供了两种datasets:map-style和iterable-style。如果使用map-style datasets会存在问题:由于Dataset支持随机读取,则实现__getitem__()时需要反复进行np.load()操作,这会导致读盘时间过长,拖慢训练速度。因此,使用IterableDataset类。

        构造一个dataset数据类,需要继承Dataset,并重写Dataset中的方法:__init__、__getitem____len__。

        为了消除指标之间的量纲影响,需要进行数据标准化处理,以解决数据指标之间的可比性。Min-Max Normalization离差标准化,是对原始数据的线性变换,使结果值映射到[0-1]之间。转换函数如下:

x* = ( x − min ) / ( max − min )

其中max为样本数据的最大值,min为样本数据的最小值。该方法缺陷:当有新数据加入时,可能导致max和min的变化,需要重新定义。

# 定义自己的数据集
class FWIDataset(Dataset):
    # anno:注释文件的路径
    # preload:是否将整个数据集加载到内存中
    # sample_ratio:地震数据的下采样率
    # file_size:每个npy文件中的样本数
    def __init__(self, anno, preload=True, sample_ratio=1, file_size=500,
                 transform_data=None, transform_label=None):
        if not os.path.exists(anno):
            print(f'Annotation file {anno} does not exists')
        self.preload = preload
        self.sample_ratio = sample_ratio
        self.file_size = file_size
        self.transform_data = transform_data
        self.transform_label = transform_label
        # 读文件,r:以只读方式打开文件。文件的指针将会放在文件的开头。
        with open(anno, 'r') as f:
            # readlines():返回列表,包含所有的行。即可以一次读取所有内容并按行返回list
            self.batches = f.readlines()
        if preload:
            self.data_list, self.label_list = [], []
            for batch in self.batches:
                data, label = self.load_every(batch)
                self.data_list.append(data)
                if label is not None:
                    self.label_list.append(label)

    # Load from one line
    def load_every(self, batch):
        # split():拆分字符串。通过指定分隔符对字符串进行切片,并返回分割后的字符串列表list
        batch = batch.split('\t')
        # 切片操作
        # [:-1]:除了最后一个取全部
        data_path = batch[0] if len(batch) > 1 else batch[0][:-1]
        # 取数组所有元素中每隔1步长的所有元素
        data = np.load(data_path)[:, :, ::self.sample_ratio, :]
        # 通过astype()方法强制转换数据的类型
        data = data.astype('float32')
        # 取label
        if len(batch) > 1:
            label_path = batch[1][:-1]
            label = np.load(label_path)
            label = label.astype('float32')
        else:
            label = None
        return data, label

    def __getitem__(self, idx):
        # "//"(取整运算)是向下取整,即不会进行四舍五入
        batch_idx, sample_idx = idx // self.file_size, idx % self.file_size
        if self.preload:
            data = self.data_list[batch_idx][sample_idx]
            label = self.label_list[batch_idx][sample_idx] if len(self.label_list) != 0 else None
        else:
            data, label = self.load_every(self.batches[batch_idx])
            data = data[sample_idx]
            label = label[sample_idx] if label is not None else None
        if self.transform_data:
            data = self.transform_data(data)
        if self.transform_label and label is not None:
            label = self.transform_label(label)
        return data, label if label is not None else np.array([])

    def __len__(self):
        return len(self.batches) * self.file_size

        3.2 network.py

可参考培训视频InversionNet部分。网络结构中展示的每个卷积操作实质上都是由卷积层/反卷积层、批归一化(BN)和LeaklyReLU共同构成的。

2023 11.18~11.24 周报_第1张图片

图1 

2023 11.18~11.24 周报_第2张图片

图2 

 :没有使用原论文中提到的1000*32的数据(图1),而是使用的1000*70的OpenFWI的数据(图2),这里采用的代码参照OpenFWI论文(2022年)中公开的InversionNet的修改版代码。

2023 11.18~11.24 周报_第3张图片

补充LeakyReLU:当输入为负值时,它会提供一个小的负斜率,从而允许一些负值通过,避免了梯度消失的问题。因此,LeakyReLU在处理一些具有稀疏数据的任务时表现更好。 

# 对于一次卷积操作进行封装:conv2d -> nn.BatchNorm2d -> nn.LeakyReLU
NORM_LAYERS = { 'bn': nn.BatchNorm2d, 'in': nn.InstanceNorm2d, 'ln': nn.LayerNorm }
class ConvBlock(nn.Module):
    def __init__(self, in_fea, out_fea, kernel_size=3, stride=1, padding=1, norm='bn', relu_slop=0.2, dropout=None):
        super(ConvBlock,self).__init__()
        # 卷积层,卷积:有输入信号、滤波器即提取有意义特征的作用。
        layers = [nn.Conv2d(in_channels=in_fea, out_channels=out_fea, kernel_size=kernel_size, stride=stride, padding=padding)]
        # 如果网络输入具有零均值、单位方差和去相关性,则深层网络的收敛速度会加快。
        # 所以 使中间层的输出具有这些属性也是有利的。
        # 批量归一化层 用于在每次迭代时,对送到网络中的中间层的数据子集在输出时进行归一化。
        if norm in NORM_LAYERS:
            layers.append(NORM_LAYERS[norm](out_fea))
        # 激活函数层,LeaklyReLU通过将x的非常小的线性分量 给予 负输入αx来调整负值的零梯度问题,此外也可以扩大函数y的范围。
        layers.append(nn.LeakyReLU(relu_slop, inplace=True))
        if dropout:
            # 赋值对象是彩色的图像数据(N,C,H,W)的一个通道里的每一个数据,
            # 即输入为 Input: (N,C,H,W) 时,对每一个通道维度C按概率赋值为0。输出和输入的形状一致
            # 0.8:元素置零的概率
            layers.append(nn.Dropout2d(0.8))
        # 输入也可以是list,然后输入的时候用*来引用,否则会报错 TypeError: list is not a Module subclass
        # *作用在形参上,代表这个位置接收任意多个非关键字参数,转化成元组方式;
        # *作用在实参上,代表的是将输入迭代器拆成一个个元素。
        self.layers = nn.Sequential(*layers)

    def forward(self, x):
        # x:输入图像
        return self.layers(x)
class InversionNet(nn.Module):
    # dim:通道数,5是起始通道数
    # input:(,5,1000,70)
    def __init__(self, dim1=32, dim2=64, dim3=128, dim4=256, dim5=512, sample_spatial=1.0, **kwargs):
        super(InversionNet, self).__init__()
        # 一共8次
        # 时间域的第一次降维,通过一批(32个)卷积来进行降维,(,32,500,70),H:1000->H:500
        self.convblock1 = ConvBlock(5, dim1, kernel_size=(7, 1), stride=(2, 1), padding=(3, 0))
        # 时间域的第二次降维,通过两批(64个)卷积来进行降维,(,64,250,70),H:500->H: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个)卷积来进行降维,(,64,125,70),H:250->H: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个)卷积来进行降维,(,128,63,70),H:125->H: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))
        # 单方向降维结束,进入两个方向同时的降维。(,128,32,35),W:70->35  W:63->32
        self.convblock5_1 = ConvBlock(dim3, dim3, stride=2)
        self.convblock5_2 = ConvBlock(dim3, dim3)
        # 两个方向同时的降维。(,256,16,18),W:35->18  W:32->16
        self.convblock6_1 = ConvBlock(dim3, dim4, stride=2)
        self.convblock6_2 = ConvBlock(dim4, dim4)
        # 两个方向同时的降维。(,256,8,9),W:16->8  W:16->8
        self.convblock7_1 = ConvBlock(dim4, dim4, stride=2)
        self.convblock7_2 = ConvBlock(dim4, dim4)
        # 512通道数,(512,1,1),失去空间信息
        self.convblock8 = ConvBlock(dim4, dim5, kernel_size=(8, ceil(70 * sample_spatial / 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)

    def forward(self, x):
        # Encoder Part
        x = self.convblock1(x)  # (None, 32, 500, 70)
        x = self.convblock2_1(x)  # (None, 64, 250, 70)
        x = self.convblock2_2(x)  # (None, 64, 250, 70)
        x = self.convblock3_1(x)  # (None, 64, 125, 70)
        x = self.convblock3_2(x)  # (None, 64, 125, 70)
        x = self.convblock4_1(x)  # (None, 128, 63, 70)
        x = self.convblock4_2(x)  # (None, 128, 63, 70)
        x = self.convblock5_1(x)  # (None, 128, 32, 35)
        x = self.convblock5_2(x)  # (None, 128, 32, 35)
        x = self.convblock6_1(x)  # (None, 256, 16, 18)
        x = self.convblock6_2(x)  # (None, 256, 16, 18)
        x = self.convblock7_1(x)  # (None, 256, 8, 9)
        x = self.convblock7_2(x)  # (None, 256, 8, 9)
        x = self.convblock8(x)  # (None, 512, 1, 1)

        # Decoder Part
        x = self.deconv1_1(x)  # (None, 512, 5, 5)
        x = self.deconv1_2(x)  # (None, 512, 5, 5)
        x = self.deconv2_1(x)  # (None, 256, 10, 10)
        x = self.deconv2_2(x)  # (None, 256, 10, 10)
        x = self.deconv3_1(x)  # (None, 128, 20, 20)
        x = self.deconv3_2(x)  # (None, 128, 20, 20)
        x = self.deconv4_1(x)  # (None, 64, 40, 40)
        x = self.deconv4_2(x)  # (None, 64, 40, 40)
        x = self.deconv5_1(x)  # (None, 32, 80, 80)
        x = self.deconv5_2(x)  # (None, 32, 80, 80)
        x = F.pad(x, [-5, -5, -5, -5], mode="constant", value=0)  # (None, 32, 70, 70) 125, 100
        x = self.deconv6(x)  # (None, 1, 70, 70)
        return x

        3.3 train.py

与训练和测试相关的文件:

  • dataset_config.json:包含每个数据集的生成参数,包括最小/最大速度、大小、频率等。
  • dataset.py:定义FWIDataset,它是 torch.utils.data 的子类,用于将每个数据样本从文本文件加载到数据加载器。
  • network.py:InversionNet模型架构。
  • train.py:执行 InversionNet的训练。
  • transforms.py:提供归一化、度量记录、损失函数等所需的外设功能。
  • utils.py:定义loss、评价指标等
  • vis.py支持速度图像和地震数据的可视化。

定义损失函数,L1和MSE损失函数,每一次训练都希望损失函数下降,通过反向传播,模型被更新,不断优化。

TensorBoard:可视化工具,用于训练时需要保存中间结果,如模型参数(方便中断后继续训练)、损失函数(观察训练过程)等等。还可看网络结构

参数对象:采用argparse模块,导入对象argparse,定义所有我们要用到的参数。路径、模型、训练(学习率、轮次、batchsize、多线程等等)、损失函数、分布式训练、tensorboard······

argparse模块是命令行选项、参数和子命令解析器。适用于代码需要频繁地修改参数的情况。用法如下:

  1. 首先导入argparse模块import argparse
  2. 创建一个 ArgumentParser 对象,该对象包含将命令行输入内容解析成 Python 数据的过程所需的全部功能。parser = argparse.ArgumentParser(description='······')
  3. 添加需要输入的命令行参数:parser.add_argument('参数1', type=int, help='······')。()中依次为参数名;参数类型,默认数据类型为str;描述信息。
  4. args = parser.parse_args() 。ArgumentParser 通过 parse_args() 方法解析参数,获取到命令行中输入的参数。
  5. 将获取到的参数内容args.参数1, args.参数2 传到方法中得出结果

命令行参数分为必选参数和可选参数:

  • 必选参数:又名位置参数,即在参数值不需要跟在参数名后面,而是通过在命令行里的相对位置来决定其属于哪个参数,且该参数值不可缺少。
  • 可选参数:在命令行里可有可无,取决于实际情况。参数值需要跟在以-或者–开头的参数名之后,由空格分隔开来。

各参数解释如下:

1. 参数nargs:
nargs='*' :表示参数可设置零个或多个
nargs='+':表示参数可设置一个或多个
nargs='?':表示参数可设置零个或一个

2. 位置参数:

在命令行中传入参数时候,传入的参数的先后顺序不同,运行结果往往会不同,这是因为采用了位置参数。

3. 默认值:

add_argument中有一个default参数。有时需要对某个参数设置默认值,即如果命令行中没有传入该参数的值,程序使用默认值。如果命令行传入该参数,则程序使用传入的值。

4. '-' 和 '--':

不带-的是位置参数,带-的是可选参数,位置参数在调动时必须传值。
'--'参数:add_argument("-shortname","--name", help="params means")。

缩写命名时,缩写的名字(一个短线)和全名代表同一个名字,如-d 和 --device 是同一个名字。
但是,打印的时候要写全名。可以一个名字对应多个缩写,但不要一个缩写对应多个名字。如-d 对应 --device 和 --depth,这样会出错。
argparse --- 命令行选项、参数和子命令解析器 — Python 3.12.0 文档

        3.4 相关资料补充:

1. Tensorboard的使用 ---- SummaryWriter类(pytorch版)

【精选】Tensorboard的使用 ---- SummaryWriter类(pytorch版)_chuanauc的博客-CSDN博客

2. torch.backends.cudnn.benchmark=True

将会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速。用场景是网络结构固定(不是动态变化的),网络的输入形状(包括 batch size,图片大小,输入的通道)是不变的,其实也就是一般情况下都比较适用。反之,如果卷积层的设置一直变化,将会导致程序不停地做优化,反而会耗费更多的时间。

torch.backends.cudnn.benchmark ?! - 知乎 (zhihu.com)

3. python 使用 with open() as 读写文件

Python中 with open(file_abs,'r') as f: 的用法以及意义-CSDN博客

4. 何为文件句柄?

何为文件句柄??_文件句柄什么意思-CSDN博客

5. PyTorch之torchvision.transforms详解

PyTorch之torchvision.transforms详解[原理+代码实现]-CSDN博客

6. python的print字符串前面加f表示格式化字符串,加f后可以在字符串里面使用用花括号括起来的变量和表达式,如果字符串里面没有表达式,那么前面加不加f输出应该都一样.

7. readlines() 方法用于读取所有行(直到结束符 EOF)并返回列表,该列表可以由 Python 的 for... in ... 结构进行处理。如果碰到结束符 EOF 则返回空字符串。

8. nn.Dropout2d

看pytorch文档学深度学习——Dropout Layer - 知乎 (zhihu.com)

9. nn.Sequential(*layers)

Pytorch 容器-CSDN博客

PyTorch 中的 ModuleList 和 Sequential: 区别和使用场景 - 知乎 (zhihu.com)

10. 激活函数区别

激活函数sigmoid 、tanh、Relu、Leaky Relu 优缺点对比(最全)_leakyrelu激活函数优缺点-CSDN博客

11. 反卷积

反卷积通俗详细解析与nn.ConvTranspose2d重要参数解释_iioSnail的博客-CSDN博客

ConvTranspose2d原理,深度网络如何进行上采样?_dwconvtranspose2d-CSDN博客

四、存在的主要问题

问题1:(已解决) 

——解决办法:在txt文件最后一行进行换行处理。debug时发现问题所在,原本的txt文件到最后一行内容就截止了,而每行最后是进行了换行处理,截图如下:

2023 11.18~11.24 周报_第4张图片

问题2:

2023 11.18~11.24 周报_第5张图片

——解决办法1: 在train.py中的DataLoader中的参数num_workers设置为0,不采用num_workers=args.workers。

——解决办法2(该代码是写在main函数中,仍报错):注意windows用户如果要使用多核多线程必须把训练放在if __name__ == '__main__':下才不会报错

问题3:loss仍然没有规律,并未一直下降。(已解决,正常现象,不断迭代更新的过程)

问题4:train.py中main函数里面,标签也归一化[-1,1]?

五、下一步工作

        继续OpenFWI中InversionNet的代码学习。

你可能感兴趣的:(周报,深度学习,pytorch)