首页 > 编程语言 >MegEngine Python 层模块串讲(中)

MegEngine Python 层模块串讲(中)

时间:2023-07-28 11:23:49浏览次数:54  
标签:MegEngine 串讲 Python self Module Tensor 模块 def op

前面的文章中,我们简单介绍了在 MegEngine imperative 中的各模块以及它们的作用。对于新用户而言可能不太了解各个模块的使用方法,对于模块的结构和原理也是一头雾水。Python 作为现在深度学习领域的主流编程语言,其相关的模块自然也是深度学习框架的重中之重。

模块串讲将对 MegEngine 的 python 层相关模块分别进行更加深入的介绍,会涉及到一些原理的解释和代码解读。Python 层模块串讲共分为上、中、下三个部分,本文将介绍 Python 层的 functionalmodule 和 optimizer 模块。理解并掌握这几个模块对于高效搭建神经网络非常重要。

Python 层计算接口 —— functional 模块

我们在定义网络结构时经常需要包含一些计算操作,这些计算操作就定义在 functional 中。

functional 中实现了各类计算函数,包含对很多 op 的封装,供实现模型时调用。

functional 中有些 op 完全是由 Python 代码实现,有些则需要调用 C++ 接口完成计算(没错,这里的计算就需要 MegDNN kernel)。对于后者,需要有机制确保我们的实现能够转发到底层正确执行,所以你在 functional 的许多 op 实现中会看到 builtin 和 apply

  • builtin

    builtin 封装了所有的 op,我们在 functional 中通过 builtin.SomeOp(param) 的方式获得一个算子 SomeOpparam 表示获取 SomeOp 需要的参数。

  • apply

    通过 builtin 获取到 op 后,需要调用 apply 接口来调用 op 的底层实现进行计算。apply 是在 Python 层调用底层 op 的接口,apply 的第一个参数是 op(通过 builtin 获得),后面的参数是执行这个 op 需要的参数,都为 Tensor。在 imperative 中 op 计算通过 apply(op, inputs) 的方式向下传递并最终调用到 MegDNN 中的 kernel``。

Functional 中的许多 op 都需要通过 builtin 和 apply 调用底层 MegDNN 的 op 来进行计算操作。然而在实际的计算发生前,很多时候需要在 Python 层做一些预处理。

来看下面这个例子

def concat(inps: Iterable[Tensor], axis: int = 0, device=None) -> Tensor:
    ...
    if len(inps) == 1:
        return inps[0]
​
    if device is None:
        device = get_device(inps)
    device = as_device(device)
    (result,) = apply(builtin.Concat(axis=axis, comp_node=device.to_c()), *inps)
    return result

这里 concat 方法先对输入 tensor 数量、device 在 python 层做了一些预处理,然后才调用 builtin 和 apply 向下转发。

而对于 diag 这个 op,无需预处理直接向下传递即可:

def diag(inp, k=0) -> Tensor:
    ...
    op = builtin.Diag(k=k)
    (result,) = apply(op, inp)
    return result

对于实现了对应 kernel 的 op,其在 imperative 层的实现通常非常的短。

上面 concat 和 diag 的 apply 调用会进入 py_apply 函数,并通过解析 Python 中的参数,将它们转换成 C++ 中的对应类型,然后调用 imperative::apply,进入 dispatch 层。

部分 functional 的 op 不直接调用 py_apply 而是有对应的 cpp 实现,比如 squeeze

def squeeze(inp: Tensor, axis: Optional[Union[int, Sequence[int]]] = None) -> Tensor:
    return squeeze_cpp(inp, axis)

这样的实现往往是需要在调用 py_apply 之前做一些预处理,但使用 python 实现性能较差,所以我们将相关预处理以及 py_apply 的逻辑在 C++ 层面实现。

本文主要介绍 Python 层的方法,关于 C++ 部分的实现会在之后的文章进行更深入的介绍。

在这里我们只需要知道,functional 中包装了所有关于 Tensor 计算相关的接口,是所有计算的入口,实际的计算操作通常会被转发到更底层的 C++ 实现。

用户可以参考官方文档获取所有 functional 中的方法介绍。

模块结构的小型封装版本 —— module 模块

神经网络模型是由对输入数据执行操作的各种层(Layer),或者说模块(Module)组成。

Module 用来定义网络模型结构,用户实现算法时要用组合模块 Module (megengine/module) 的方式搭建模型,定义神经网络时有些结构经常在模型中反复使用,将这样的结构封装为一个 Module,既可以减少重复代码也降低了复杂模型编码的难度。

一个 module 类主要有两类函数:

  • __init__:构造函数,定义了模型各个层的大小。用户自定义的 Module 都源自基类 class Module,所以在构造函数中一定要先调用 super().__init__(),设置 Module 的一些基本属性。模型要使用的所有层 / 模块都需要在构造函数中声明。
class Module(metaclass=ABCMeta):
    r"""Base Module class.
​
    Args:
        name: module's name, can be initialized by the ``kwargs`` parameter
            of child class.
    """
​
    def __init__(self, name=None):
        self._modules = []
​
        if name is not None:
            assert (
                isinstance(name, str) and name.strip()
            ), "Module's name must be a non-empty string"
​
        self.name = name
​
        # runtime attributes
        self.training = True
        self.quantize_disabled = False
​
        # hooks
        self._forward_pre_hooks = OrderedDict()
        self._forward_hooks = OrderedDict()
​
        # used for profiler and automatic naming
        self._name = None
        self._short_name = None
​
    # 抽象方法,由继承的 Module 自己实现
    @abstractmethod
    def forward(self, inputs):
        pass
    
    # 其他方法
    ...
  • forward:定义模型结构,实现前向传播,也就是将数据输入模型到输出的过程。这里会调用 Functional (megengine/functional) 中的函数进行前向计算,forward 表示的是模型实现的逻辑。来看一个例子:
class Simple(Module):
    def __init__(self):
        super().__init__()
        self.a = Parameter([1.23], dtype=np.float32)
​
    def forward(self, x):
        x = x * self.a
        return x

__init__ 表明模型中有一个参数 a,它的初值是固定的,forward 中实现了具体的计算逻辑,也就是对传入的参数与 a 进行乘法运算。

对于一些更复杂的计算操作(如卷积、池化等)就需要借助 functional 中提供的方法来完成。

除了 __init__ 和 forward,基类 class Module 提供了很多属性和方法,常用的有:

  • def buffers(self, recursive: bool = True, **kwargs) -> Iterable[Tensor]:返回一个可迭代对象,遍历当前模块的所有 buffers
  • def parameters(self, recursive: bool = True, **kwargs) -> Iterable[Parameter]:返回一个可迭代对象,遍历当前模块所有的 parameters
  • def tensors(self, recursive: bool = True, **kwargs) -> Iterable[Parameter]:返回一个此 module 的 Tensor 的可迭代对象;
  • def children(self, **kwargs) -> "Iterable[Module]":返回一个可迭代对象,该对象包括属于当前模块的直接属性的子模块;
  • def named_buffers(self, prefix: Optional[str] = None, recursive: bool = True, **kwargs) -> Iterable[Tuple[str, Tensor]]:返回当前模块中 key 与 buffer 的键值对的可迭代对象,这里 key 是从该模块至 buffer 的点路径(dotted path);
  • def named_parameters(self, prefix: Optional[str] = None, recursive: bool = True, **kwargs) -> Iterable[Tuple[str, Parameter]]:返回当前模块中 key 与 parameter 的键值对的可迭代对象,这里 key 是从该模块至 buffer 的点路径(dotted path);
  • def named_tensors(self, prefix: Optional[str] = None, recursive: bool = True, **kwargs) -> Iterable[Tuple[str, Tensor]]:返回当前模块中 key 与 Tensorbuffer + parameter) 的键值对的可迭代对象,这里 key 是从该模块至 Tensor 的点路径(dotted path);
  • def named_modules(self, prefix: Optional[str] = None, **kwargs) -> "Iterable[Tuple[str, Module]]":返回一个可迭代对象,该对象包括当前模块自身在内的其内部所有模块组成的 key-module 键-模块对,这里 key 是从该模块至各子模块的点路径(dotted path);
  • def named_children(self, **kwargs) -> "Iterable[Tuple[str, Module]]":返回一个可迭代对象,该对象包括当前模块的所有子模块(submodule)与键(key)组成的 key-submodule 对,这里 key 是子模块对应的属性名;
  • def state_dict(self, rst=None, prefix="", keep_var=False):返回模块的状态字典,状态字典是一个保存当前模块所有可学习的 Tensor (buffer + parameter)的字典。出于兼容性考虑,字典中的 value 的数据结构类型为 numpy.ndarray (而不是 Tensor),并且不可修改,是只读的;
  • def load_state_dict(self, state_dict: Union[dict, Callable[[str, Tensor], Optional[np.ndarray]]], strict=True, ):加载一个模块的状态字典,这个方法常用于模型训练过程的保存与加载。

值得一提的是,Parameters 和 Buffer 都是与 Module 相关的 Tensor,它们的区别可以理解为:

  • Parameter 是模型的参数,在训练过程中会通过反向传播进行更新,因此值是可能改变的,常见的有 weightbias 等;
  • Buffer 是模型用到的统计量,不会在反向传播过程中更新,常见的有 meanvar 等。

在 MegEngine 的 module目录 下可以看到已经有很多常见的 module 实现,用户实现自己的模型可以根据需要复用其中的模块。

使用 optimizer 模块优化模型参数

MegEngine 中的 optimizer 模块实现了基于各种常见优化策略的优化器,为用户提供了包括 SGDADAM 在内的常见优化器实现。这些优化器能够基于参数的梯度信息,按照算法所定义的策略执行更新。

大部分情况下用户不会自己实现优化器,这里以 SGD 优化器为例,优化神经网络模型参数的基本流程如下:

from megengine.autodiff import GradManager
import megengine.optimizer as optim
​
model = MyModel()
gm = GradManager().attach(model.parameters())
optimizer = optim.SGD(model.parameters(), lr=0.01)  # lr may vary with different model
​
for data, label in dataset:
    with gm:
        pred = model(data)
        loss = loss_fn(pred, label)
        gm.backward()
        optimizer.step().clear_grad()
  • 这里我们构造了一个优化器 optimizer,传入参数是 model 需要被优化的 Parameter,和 learning rate

  • 优化器通过执行 step() 方法进行一次优化;

  • 优化器通过执行 clear_grad() 方法清空参数梯度。

    • 为何要手动清空梯度?

      梯度管理器执行 backward() 方法时, 会将当前计算所得到的梯度以累加的形式积累到原有梯度上,而不是直接做替换。 因此对于新一轮的梯度计算,通常需要将上一轮计算得到的梯度信息清空。 何时进行梯度清空是由人为控制的,这样可允许灵活进行梯度的累积。

用户也可以继承 class Optimizer,实现自己的优化器。

以上就是关于 functional,Module,optimizer 的模块的基本介绍,这几个模块是我们搭建模型训练的最核心的部分,熟悉这部分后,我们就可以高效搭建神经网络了。

更多 MegEngine 信息获取,您可以:查看文档GitHub 项目,或加入 MegEngine 用户交流 QQ 群:1029741705。欢迎参与 MegEngine 社区贡献,成为 Awesome MegEngineer,荣誉证书、定制礼品享不停。

标签:MegEngine,串讲,Python,self,Module,Tensor,模块,def,op
From: https://www.cnblogs.com/megengine/p/17587106.html

相关文章

  • python 读写文件内容包含中文
    encoding="utf-8"#文件内容:#11111,ssss,eee,哈哈哈电话,hhh#11111,ssss,eee,哈哈哈电话,hhh#11111,ssss,eee,哈哈哈电话,hhhh#打开输入文件encoding="utf-8"解决中文乱码withopen('D:\\PCCW_Test_Script\\22.txt','r',encoding="utf-8&quo......
  • 关于python中对np.array数据进行元素操作的讨论(形参与实参)
    最近发现了python中,如果将np.array(ndarray)类型的数据作为实参,传递给形参时,实参和形参会同时改变。例如下面的代码:importnumpyasnpnum=np.array([[1,2],[3,4]])deftest(a):a[0,1]=9print(a)test(num)print(num)输出结果:[[19][34]][[19][34]]会发......
  • 【Python】数字取反(相反数)的几种方法
     方法一:绝对值if__name__=="__main__":"""run"""print("负数取反-绝对值:{}".format(abs(-28)))print("正数数取反-绝对值:{}".format(abs(32)*-1))  结果: 方法二:numpy库#coding:utf-8importnumpyas......
  • python 单例模式
    python单例模式单例模式是一种设计模式,目的是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。有些类只需要一个全局唯一的实例,例如数据库连接池、线程池、日志记录器等。使用单例模式可以确保这些类只有一个实例存在,从而避免了资源的浪费和不一致的状态。单例模式......
  • 【Python】异常
     异常defexecpt_test(a,b):result=a/breturnresultdefdivide_numbers(a,b):try:execpt_test(a,b)exceptZeroDivisionError:print("除数不能为零!")print(divide_numbers(10,0)) ......
  • python教程 入门学习笔记 第2天 第一个python程序 代码规范 用默认的IDLE (Python GUI
    四、第一个python程序1、用默认的IDLE(PythonGUI)编辑器编写2、在新建文件中写代码,在初始窗口中编译运行3、写完后保存为以.py扩展名的文件4、按F5键执行,在初始窗口观看运行结果5、代码规范:1)先保存再执行2)一句代码单独占一行3)语法中的符号,必须使用英文4)代码前面不能有......
  • 掌握 Python RegEx:深入探讨模式匹配
    动动发财的小手,点个赞吧!什么是正则表达式?正则表达式通常缩写为regex,是处理文本的有效工具。本质上,它们由一系列建立搜索模式的字符组成。该模式可用于广泛的字符串操作,包括匹配模式、替换文本和分割字符串。历史数学家StephenColeKleene在20世纪50年代首次引入正则......
  • python截取文本
    defsclip():withopen("file.txt","r")asfile:lines=file.readlines()first_line=lines[0:2]last_10_lines=lines[5:]first_line.extend(last_10_lines)withopen("output.txt","w")......
  • Python生成30万条Excel 测试数据
    使用Python生成30万条Excel测试数据fromopenpyxlimportWorkbookfromconcurrent.futuresimportThreadPoolExecutor#定义生成数据的函数defgenerate_data(start,end,sheet):#生成数据foriinrange(start,end+1):sheet.append([i,f"姓名{i......
  • python虚拟环境拷贝到另一台电脑
    背景介绍:建立了虚拟环境后,在其中安装的包很多,也很费时间,你想将这个MyApp放到其他电脑上去开发,是不是直接复制MyApp文件夹就可以了呢?不行!因为在建立虚拟环境时,虚拟环境中的python.exe,pip.exe......等一些文件会“硬编码”,记录的是绝对路径,放到其他电脑后,因为路径不同会出错!......