平时工作,程序员们挥洒自如的(Ctrl)编(c)码(v),实现一个个功能模块,形成千篇千律的代码风格,个性十足。
然而,对于整个项目组,代码融合和review时候,就像把瓷砖、红砖、木板砖揉捏在一起,难以筑成高台。
最近看到一篇关于深度学习项目代码结构和代码风格的文章,将重点分享给大家。
“关注点分离”的原则,即每个功应该是一个不同的组件。
好处是可以很容易地修改扩展,而不会破坏代码的其他部分。此外,可以在许地方重用代码,而不需要编写重复代码。
遵循DRY(Don't Repeat Yourself)原则。
1. configs: 在这个模块中,定义所有可以配置的内容,方便在将来更改。例如:超参数、文件夹路径、标志等。可以是json、yaml等文件类型。
2、dataloader: 数据加载器。我们把数据加载、数据集定义和数据预处类和函数都放在这里。
3、evaluation: 评估部分,是评估模型的性能和准确性相关的代码。
4、notebook:如果使用Jupyter Notebook实验代码,可以放在这里。
5、apis:开发的接口服务,都可以放在这里。
6、data:存放数据的地方
7、models:存放训练好的模型的地方
8、modules:主要功能块存放的地方,比如Transformers、某个Network主体部分。
9、utils:在多个地方使用的一些函数方法。tools或者scripts等都可以放在这。
10、test:一些测试脚本
11、requirements.txt:项目依赖库
12、Dockerfile:这个文件主要用于给 Docker 构建镜像使用,建议在生
产环境部署时通过 Docker 进行部署。
13、README.md:主要用于放项目介绍、使用说明的 MarkDown 文档。
以上是平时构建项目使用的代码结构,可以根据自己风格和项目需要增减。
了解项目大致结构后,那么Python代码应该如何更像正规军?
有的代码是一气呵成,像面条从上到下、像涓涓长河奔流不息,让人从头读到尾累得半死。因不容易找到每函数的职责,不知道更改后是否影响到代码的其他部分,调试也变得困难很多。
如果我们灵活运用抽象和继承、静态方法和类方法,代码将会具有可维护性、可扩展性和简单性。
① 抽象和继承
在编程的时候,我一般会先考虑整体的执行逻辑,定义命名类和方法名,然后再填充具体的细节代码。
在没深入研究代码的每个部分前,定义的类可以作为一个基类、抽象类,里面的方法是抽象方法。然后定义一个新的类继承它。
举个栗子:
我们定义一个基本的抽象类,声明一些函数名后啥也不用添加。基类的函数没有主体,可以将所有可能需要的功能都声明为一个抽象方法或者类。可以先决定高一个level的实现,然后详细地处理每个部分。
在具体的模型网络中,继承我们的基类。可以多个类似于YourNet的网络继承基类,这是多态。在YourNet网络中,我们使用super()调用父类的__init__函数,然后写具体的网络结构定义就可以了。
② 静态方法和类方法
使用静态方法和类方法是因为它们可以简化代码,更不容易出错。
关于类方法、静态方法、类本身、类实例、实例对象、构造函数、类属性等概念,我们后面可以单独写一篇文章探讨。这里可以直接跳过,调到直接看下图。
类方法将实际的类作为参数,通常用作构造函数 ,用于创建类的新实例。类方法还有一个用途就是可以对类属性进行修改,在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变。
形式上,类方法的第一个参数是类本身,我们通常用cls表示,通过cls可以调用类方法、类属性和静态方法。类内的其他常规方法,第一个参数是self,在调用的时候,是具体的类的实例。
我们从json文件构造一个配置,创建一个Config类,定义一个类方法from_json 去加载我们的配置信息(下图代码第20行起)。可以研究一下如何使用类方法和参数cls的。
在上面BaseModel中,第32行代码Config.from_json()处调用加载。当然了,也有的项目使用yaml文件等,看个人喜好。
静态方法,既没有self也没有cls参数,在定义类的时候,有时候部分函数跟类没关系,但是我们又需要用到它。可以在类外面单独定义这个函数,但是这样子会使代码变得难理解,因此使用静态方法,把这个单独的函数搬到类中表示。
我们需要创建一个新的DataLoader的实例吗?其实不需要的,因为在这个功能中我们不需要改变什么,一切都是稳定的。
③、配置文件
主要用于程序的参数和初始设置的文件,可读性很强,易于修改。
这里只是一个简单示例,根据具体项目需要自行修改即可。
假如我们想添加learning rate或者修改batch size,那么只需要在这里修改一下即可。
④ 类型检查
类型检查是检查变量是字符串、整数还是对象等,这样的好处是我们可以尽早发现bug和错误。我们也可以使用 Pytype 、Pylint 等库。如果你有什么好用的工具,欢迎留言分享。
⑤、注释
注释真的太重要了,不为队友考虑,也要为自己日后审查调用的时候考虑,过了一段时间,懵逼地发现需要考虑一下某段代码是啥意思。
在一个模块开头写一个文档字符串说明文件的目的,在每个类声明下和每个函数中都说明一下,第一行指示代码的功能,函数中说明各个参数啥、返回值是啥等。
希望你不是划拉划拉屏幕到这里的,看完全文只用了5s内,哦不,是3s。
本次分享了深度学习的代码的项目结构,然后分享类、静态方法、类方法、抽象和继承。同时,对于项目配置文件、类型检查和注释做了样例说明。
当然,还有很多以后可以继续讨论,比如依赖管理、模块引用、异常管理等,后面会再分享。
期待大家的代码==漂亮!
2022-12-05
2022-12-05
留学生技术素人博主
◆◆◆◆◆◆◆◆◆◆◆
知识的传播者。利他主义者
<