手把手教你看懂一个开源深度学习项目的代码,其实深度学习的代码结构就这些!

前言

学习深度学习,必备技能就是读懂开源深度学习项目代码,然而对于深度学习来说,代码结构是没有一个统一标准的。一方面,代码结构取决于开发者自身的编程观念和水平,另一方面,不同规模的项目,本身需要的结构也是很不一样的。深度学习项目代码,小到几百行的测试demo,大到成千万行的开源项目,读起来方法肯定各有不同。因此,想要读懂一个深度学习项目的代码,并非一件易事。

所以今天我分享一下方法,教你如何快速看懂一个深度学习项目代码(至少能够大致了解项目是如何搭建和运行的,这对于我们弄懂项目有很大帮助。

在详细讲解之前,我要为大家分享这份100G人工智能学习资料

包含数学与Python编程基础、深度学习+机器学习入门到实战,计算机视觉+自然语言处理+大模型资料合集,不仅有配套教程讲义还有对应源码数据集。更有零基础入门学习路线,不论你处于什么阶段,这份资料都能帮助你更好地入门到进阶。

需要的兄弟可以按照这个图的方式免费获取


一:了解项目结构

(1)关键部分

我们在阅读一些典型论文或者项目的代码时,常常摸不到头脑,因为项目结构看起来非常的复杂,代码结构组织的方式各不相同,但是总的来说,一个深度学习项目代码所囊括的无非就是这几个方面:

1. 数据集读取、预处理与加载

2. 模型与网络层定义

3. 模型训练、测试与保存

这些是项目中最重要之处,除此之外,还会有配置、检查点、优化、日志之类的内容。

(2)实例分析

project_name/ :项目名
data/:数据集
__init__.py
datasets/:数据相关操作,如加载数据
__init__.py
data_loader.py:加载数据
layers/:网络模型层
__init__.py
layer1.py:第1层
layer2.py:第2层
models/:backbone模型
__init__.py
a_model.py:a模型
b_model.py:b模型
exp/:包含定义、训练、验证、测试和预测方法
__init__.py
exp_main.py:包含定义、训练、验证、测试和预测方法
configs/:配置文件
__init__.py
config.py:方便超参数搜索等
utils/:用到的功能函数,可以是日志、评价指标计算等等
__init__.py
utils.py:功能函数
metrics.py:评价指标计算
logs/:日志文件
__init__.py
scripts/:脚本文件
__init__.py
checkpoints/:保存的模型权重
__init__.py
checkpoints.pt:保存的模型权重文件
model_hub/:预训练模型权重
__init__.py
basic_model.pt:预训练好的模型权重文件
main.py:主程序
requirements.txt:需要的python依赖
environment.yml:环境信息
readme.md:项目说明

重点分析:

  1. __init__.py一个目录如果包含了__init__.py文件,那么它就变成了一个包。该文件可以为空,也可以定义包的属性和方法,但它必须存在。

  2. datasets:数据相关操作,比如用Dataset封装数据集,再用Dataloader实现数据并加载。

  3. layerslayers文件夹放的是模型所需要的层,不同model可能需要同样的网络层,为了保证模型代码的简洁性,单独列一个文件夹定义。

  4. models:一般而言,核心的代码都是以model命名,或者就是以它这个模型的名字命名。可以有多个模型,一个模型一个.py文件。

  5. exp:包含定义、训练、验证、测试、保存和预测方法,这些也可以放到main中,但是为了main的简洁性,单独设置一个文件夹进行定义。关于模型保存,Pytorch可以通过torch.save()函数将训练的模型保存为.pth/.pt/.pkl文件,Keras中训练的模型可以保存为.h5文件

  6. configs:包含了一些预定义的配置文件,用于训练和测试深度学习模型。这些配置文件包含了模型架构、损失函数、优化器、训练和测试的超参数等,通过修改这些配置文件,可以方便地调整模型的参数和超参数,如将需要配置的参数均放在这个文件中,比如batchsize,transform,epochs,lr等超参数,以满足不同的需求和任务。

  7. utils:通常是存放深度学习中常用的工具函数或类的文件夹,这些工具函数或类可以被多个深度模型共用,提高代码的复用性与可维护性,通常包括数据预处理函数、模型评估函数、损失函数等。

  8. scripts:脚本文件集合,脚本文件是函数命令的集合。针对不同的任务,可以运行不同的脚本文件。

  9. checkpoints:断点保存,保存模型权重,防止程序突然终止丢失权重文件。

  10. model_hub:预训练好的模型权重, 是一个通过大量数据上进行训练并被保存下来的网络。可以将其通俗的理解为前人为了解决类似问题所创造出来的一个模型,有了前人的模型,当我们遇到新的问题时,便不再需要从零开始训练新模型,而可以直接用这个模型入手,进行简单的学习便可解决该新问题。

  11. main.py:主程序,解析命令行传递超参数,执行exp中的训练、验证、测试、预测方法。

  12. requirements.txt:需要的python依赖,可以在终端中输入pip install -r requirements.txt命令快速在所使用环境中安装依赖。

  13. environment.yml:用于定义和创建 Conda 环境的配置文件。它通常包含了项目所需的所有依赖项及其版本信息。可以使用该文件快速配置conda环境,具体使用方法可见:如何使用environment.yml文件配置conda环境

  14. readme.md:一种说明文件,通常随着一个软件而发布,里面记载有软件的描述或注意事项。一般来说,开源项目的readme里作者都会写明如何使用代码和进行部署,这可以帮助我们实现这个项目。

不同项目之间的代码架构不同,所以这个结构只是一个样例供大家参考,还是要根据具体项目分析其架构。不过了解了一个深度学习项目代码所囊括的几个方面,就能以不变应万变。


二:找到入口文件

打开项目以后,从运行入口开始阅读,一般来说,项目入口都是在第一层目录中的,带有main或者run字眼的,找到入口,我们就开始分析入口文件具体的内容。(以基于Informer预测股票价格为例子)

(1)引入模块

在本项目中,入口文件是main_informer.py,打开该文件,首先看到的是各种import

#导入命令行选项、参数和子命令解析器模块,供接下来的参数定义
import argparse
#导入toch模块,深度学习项目必用模块
import torch
#导入informer模块,提供接下来的训练、测试、预测方法
from exp.exp_informer import Exp_Informer

(2)参数设置

在训练卷积神经网络时需要预定义很多参数,例如batch_size, backbone,dataset,dataset_root等等,这些参数多而且特别零散;如果我们最初不把这些参数定义,到时候修改是一件特别麻烦的事情,需要逐个修改;所以这个时候用到了python的add_argument()很好的规避了这些问题。接下来,对argparse使用步骤进行讲解:

1.创建 ArgumentParser() 对象:

使用argparse的第一步是创建一个ArgumentParser对象,大多数对ArgumentParser 构造方法的调用都会使用description= 关键字参数。这个参数简要描述这个程序做什么以及怎么做。

parser = argparse.ArgumentParser(description='test')

2.调用 add_argument() 方法添加参数:

给一个ArgumentParser添加程序参数信息是通过调用add_argument()方法完成的。通常,这些调用指定ArgumentParser如何获取命令行字符串并将其转换为对象。这些信息在parse_args()调用时被存储和使用。

parser.add_argument('--sparse', action='store_true', default=False, help='GAT with sparse version or not.')
parser.add_argument('--seed', type=int, default=72, help='Random seed.')
parser.add_argument('--epochs', type=int, default=10000, help='Number of epochs to train.')

该方法的参数定义如下:

ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
  1. name or flags选项字符串的名字或者列表,例如 foo 或者 -f, --foo。

  2. action:命令行遇到参数时的动作,默认值是 store。如果action=‘store_true’,只要运行时该变量有传参就将该变量设为True。

  3. store_const:表示赋值为const。

  4. append:将遇到的值存储成列表,也就是如果参数重复则会保存多个值。

  5. append_const:将参数规范中定义的一个值保存到一个列表。

  6. count:存储遇到的次数;此外,也可以继承 argparse.Action 自定义参数解析。

  7. nargs:应该读取的命令行参数个数,可以是具体的数字,或者是?号,当不指定值时对于 Positional argument 使用 default,对于 Optional argument 使用 const;或者是 * 号,表示 0 或多个参数;或者是 + 号表示 1 或多个参数。

  8. const:action 和 nargs 所需要的常量值。

  9. default:不指定参数时的默认值。

  10. type:命令行参数应该被转换成的类型。

  11. choices:输入值的范围,如add_argument(“–gb”, choices=[‘A’, ‘B’, ‘C’, 0])。

  12. required:默认False, 若为 True, 表示必须输入该参数。

  13. help:参数作用解释,如add_argument(“a”, help=“params means”)

  14. metavar:在 usage 说明中的参数名称,对于必选参数默认就是参数名称,对于可选参数默认是全大写的参数名称。

  15. dest:解析后的参数名称,默认情况下,对于可选参数选取最长的名称,中划线转换为下划线。

在学习这一块的时候,我有一个问题,就是为什么参数名的定义前要有一个--,不知道大家有没有这样的问题,那么关于add_argument参数名的定义,我们一起来看一下解释:

#add_argument 说明
不带'--'的参数
    是位置参数,调用脚本时必须输入值,且参数输入的顺序与程序中定义的顺序一致
'-'的参数
    是可选参数,如add_argument("-a"),只能是1个字符,区分大小写,通常用于表示单字符的命令行选项,也称为短选项。短选项通常用于表示简单的开关或标志。例如,“-v” 可以表示启用详细输出。
'--'参数
    参数别名,是可选参数,通常用于表示完整的命令行选项,也称为长选项。长选项通常用于提供更具描述性的选项名称和更丰富的配置选项。例如,“–verbose” 可以表示启用详细输出。

其实只要记住一点,带’- -‘的参数可以不提供输入(可选的),并且给值时候必须要声明参数名(相反地,不带’- -'的参数不用提供参数名,但必须但需输入值)。如果不懂的话,请看下面这两个例子:

例1(不带’- -'):

import argparse

parser = argparse.ArgumentParser(description='test')

parser.add_argument('seed', type=int, default=72, help='Random seed.')
parser.add_argument('epochs', type=int, default=10000, help='Number of epochs to train.')

args = parser.parse_args()

print(args.seed)
print(args.epochs)

    可见不带’- -'的参数必须要给出输入,是位置参数,必须按序输入,且不能指定变量名。

    例2(带’- -'):

    import argparse
    
    parser = argparse.ArgumentParser(description='test')
    
    parser.add_argument('--seed', type=int, default=72, help='Random seed.')
    parser.add_argument('--epochs', type=int, default=10000, help='Number of epochs to train.')
    
    args = parser.parse_args()
    
    print(args.seed)
    print(args.epochs)

    可见带’- -'的参数可以不给出输入(会输出default值),是关键词参数,不用按序输入,必须指定变量名进行赋值。

    3.使用 parse_args() 解析添加的参数:

    ArgumentParser通过parse_args()方法解析参数。它将检查命令行,把每个参数转换为适当的类型然后调用相应的操作。在脚本中,通常parse_args()会被不带参数调用,而ArgumentParser将自动从sys.argv中确定命令行参数。

    args = parser.parse_args()

    4.示例:

    接下来通过一个例子感受下argparse添加参数的整体流程:

    import argparse
    
    parser = argparse.ArgumentParser(description='test')
    
    parser.add_argument('--sparse', action='store_true', default=False, help='GAT with sparse version or not.')
    parser.add_argument('--seed', type=int, default=72, help='Random seed.')
    parser.add_argument('--epochs', type=int, default=10000, help='Number of epochs to train.')
    
    args = parser.parse_args()
    print(args.sparse)
    print(args.seed)
    print(args.epochs)

    (三)主程序运行

    Exp = Exp_Informer
    
    for ii in range(args.itr):
        # setting record of experiments
        setting = '{}_{}_ft{}_sl{}_ll{}_pl{}_dm{}_nh{}_el{}_dl{}_df{}_at{}_fc{}_eb{}_dt{}_mx{}_{}_{}'.format(args.model, args.data, args.features,
                    args.seq_len, args.label_len, args.pred_len,
                    args.d_model, args.n_heads, args.e_layers, args.d_layers, args.d_ff, args.attn, args.factor,
                    args.embed, args.distil, args.mix, args.des, ii)
    
        exp = Exp(args) # set experiments
        print('>>>>>>>start training : {}>>>>>>>>>>>>>>>>>>>>>>>>>>'.format(setting))
        exp.train(setting)
    
        print('>>>>>>>testing : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
        exp.test(setting)
    
        if args.do_predict:
            print('>>>>>>>predicting : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
            exp.predict(setting, True)
    
        torch.cuda.empty_cache()

    这段代码可以说是非常简洁了,先是使用format函数对参数进行格式化并赋值给setting,对Exp_Informer类进行实例化,然后调用train、test、predict方法进行训练、测试和预测。

    写程序的好习惯:最后调用torch.cuda.empty_cache()方法,是因为PyTorch是有缓存区的设置的,意思就是一个Tensor就算被释放了,进程也不会把空闲出来的显存还给GPU,而是等待下一个Tensor来填入这一片被释放的空间。所以我们用nvidia-smi/gpustat看到的显存占用不会减少。用这个方法可以清空缓冲区,在程序中加上会使速度变慢一些,但是有些情况下会有用,例如程序之前test的时候总是爆显存,然后在循环中加上了这句就不爆了。


    如果对大家有帮助的话可以点个关注

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值