复现论文的必不可少的一步就是阅读作者开源的代码,但是大多数人往往瞥一眼代码就退怯了!退怯的原因归结起来往往有以下两点:
(1)高度封装,一眼看上去看不懂
在这里插入图片描述

(2)使用了一些 “奇怪的东西”(看到装饰器@就撤)
在这里插入图片描述

今天如何阅读深度学习项目源码谈一些自己的理解


Table of Contents

  1. 熟练Python语法,尤其是函数参数、迭代器与生成器、函数式编程、面向对象编程
  2. 学会查看封装函数的具体实现

1. Python语法

开源项目中不可能只用到简单的Python基础语法,这是需要你去专研一些Python的高阶知识点。

1.1 函数参数

先看个例子

** 是个啥?
在这里插入图片描述
我们来看函数的参数
在Python中有位置参数、默认参数、可变参数和关键字参数四种形式,这使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

1.1.1 位置参数

位置参数就是平时用到最多的一种情况:实参与形参数量相等、位置对应
举个例子:计算一个实数 x 的 n 次幂

def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

在这里插入图片描述
那么如果少一个参数或者位置不对应就达不到预期效果,甚至会报错。但我们平时用到的最多的就是 2 次幂,所以我们想即使少一个参数 n 也能达到预期效果,该怎么办呢?

1.1.2 默认参数
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

在这里插入图片描述
使用默认参数要注意一下几点:
(1)必选参数在前,默认参数在后,否则Python的解释器会报错(因为Python解释器会按照位从左至右的顺序去寻找);
(2)当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
你可能会说:这虽然解决了参数数量问题,还是得考虑位置对应问题呀!是的,看个例子:

def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)

在这里插入图片描述

所以,默认参数是按顺序提供的,即实参会与形参从左至右匹配,最后剩余没有匹配完的参数采用默认值。当然,也可以不按顺序提供部分默认参数,当不按顺序提供部分默认参数时,需要把参数名写上。
注: 定义默认参数要牢记一点:默认参数必须指向不变对象! 因为可变参数会导致默认参数改变

1.1.3 可变参数

你是不是在想:在参数数量不相等的情况下靠默认参数对应,能不能实现不依赖参数数量呢?即参数数量随时可变!

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

在这里插入图片描述

看!加一个 * 就实现可变参数的方式,其实在函数内部,参数numbers接收到的是一个tuple

注:Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。(1.1.4节关键字参数也一样)
在这里插入图片描述

1.1.4 关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
在这里插入图片描述
注:当没有关键字参数时默认是一个空字典{}。
关键字参数有什么用?它可以扩展函数的功能。就像上面所说的,他真正实现了“不依赖于参数数量与参数位置”,可以任意添加参数。

1.2 生成器

掌握生成器你必须理解两个问题:

  1. 什么是生成器?
  2. 为什么需要生成器?
1.2.1 什么是生成器?

在Python中,一边循环一边计算的机制就是生成器(generator)。那么如何创建 generator 呢?
方法一:把一个列表生成式的 [ ] 改成 () 。
在这里插入图片描述

注:generator 也是可迭代对象,因此可以通过 for 循环来遍历迭代器中每一个元素。
方法二:计算规则过于复杂,用列表生成式写不出来时,可以用函数实现。
在这里插入图片描述
注:如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

1.2.2 为什么需要生成器?

回顾一下,列表元素可以按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这有什么用呢?这样就不必创建完整的list,从而节省大量的空间。
因此,生成器可以创建很大容量的数据对象,但是不需要很大的存储空间。
在这里插入图片描述
上面例子可以充分体现生成器一个应用:用于生成训练数据,每调用一次生成一个batch的数据。

1.3 函数式编程

函数是编程是Python中极为重要的,主要包括高阶函数、匿名函数、返回函数、装饰器与偏函数。这里详细介绍一下装饰器(decorator),也是项目中的一个常用方法。
装饰器是什么? 在不希望修改函数的定义,在代码运行期间动态增加函数功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。
怎么去使用装饰器呢? decorator接受一个函数作为参数,并返回一个函数。 借助Python的@语法,把decorator置于函数的定义处,实现功能扩展的目的。
在这里插入图片描述
可以看到,now() 函数功能是打印今天的日期,log() 是定义的一个装饰器,借助@将装饰器置于函数now() 的定义处,是的now() 具有打印日志的功能。
但是这个原理是怎么样的呢? 千万不要以为是按顺序依次地调用了两个函数!
把 @log 放到 now() 函数的定义处,相当于执行了语句:now = log(now)

由于 log() 是一个decorator,返回一个函数,所以,原来的 now() 函数仍然存在,只是现在同名的 now 变量指向了新的函数,于是调用 now() 将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
所以原始函数 now() 的功能是在装饰器中实现的,通过传入的参数(是一个函数)来实现的。
现在你应该知道下面这些语句想要实现什么了
在这里插入图片描述

1.4 面向对象编程

面向对象的三个基本特征是:封装、继承、多态。面向对象的优势只有在实战中才能感受,就不多说空洞的话了!
注意:@property,多重继承、定制类、枚举类、元类等都是面向对象编程的重要知识点。

2. 封装函数

在深度学习框架中,我们可以看见基本都是封装的函数,在查看官方文档弄清楚函数功能之后,我们要读封装函数的具体实现。
比如 keras 中 fit_generator() 函数
在这里插入图片描述
如何进入函数内部呢?Ctrl+鼠标左键!
进入到函数定义处如下:
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐