这里回顾dlsys第七讲,本讲内容是神经网络库抽象。

课程主页:

大纲

  • 编程抽象;
  • 高级模块化库组件;

编程抽象

  • 框架的编程抽象定义了实现、扩展和执行模型计算的常用方法;
  • 虽然设计的选择在看到实际实现之后可能看起来很明显,但了解思考过程很有用,因为:
    • 我们可以了解为什么要这样设计抽象;
    • 以及学习如何设计新的抽象;

案例分析

主要从对3个框架进行分析,分别是Caffe 1.0, TensorFlow 1.0以及PyTorch:

Forward和backward层接口(Caffe 1.0)

Caffe 1.0:

定义前向计算和后向(梯度)操作,用于cuda-convenet(AlexNet框架):

class Layer:
    def forward(bottom, top):
    	pass
    	
    def backward(top,
    			propagate_down,
    			bottom):
    	pass

计算图和声明式编程(Tensorflow 1.0)

首先声明计算图,然后通过输入值执行图:

import tensorflow as tf

v1 = tf.Variable()
v2 = tf.exp(v1)
v3 = v2 + 1
v4 = v2 * v3

sess = tf.Session()
value4 = sess.run(v4, feed_dict={v1: numpy.array([1]})

命令式自动微分(Pytorch, needle)

import needle as ndl

v1 = ndl.Tensor([1])
v2 = ndl.exp(v1)
v3 = v2 + 1
v4 = v2 * v3

在我们构造计算图时执行计算,允许轻松混合python控制流:

if v4.numpy() > 0.5:
	v5 = v4 * 2
else:
	v5 = v4
v5.backward()

高级模块化库组件

机器学习元素

机器学习的三要素为:

  1. 假设函数类;
  2. 损失函数;
  3. 优化方法;

问题:它们如何转化为代码中的模块化组件。

深度学习本质上是模块化的

nn.Module:将部件组合在一起

说明:

  • 输入输出都为tensor;

需要考虑的关键点:

  • 对于给定的输入,如何计算输出;
  • 获取(可训练的)参数列表;
  • 初始化参数的方法;

损失函数作为一种特殊的模块

说明:

  • 输入为标量,输出为tensor;

问题:

  • 如何将多个目标函数组合在一起?
  • 在训练后的推理时间内会发生什么?

优化器

优化器:

  • 从模型中获取权重列表,执行优化步骤;
  • 跟踪辅助状态(动量);

SGD:

正则化和优化器

合并正则化的两种方法:

  • 作为损失函数的一部分实现;
  • 直接合并为优化器更新的一部分;

例如带有$l_2$正则化的SGD更新方式为:

初始化

初始化策略取决于所涉及的模块和参数的类型。 大多数神经网络库都有一组通用的初始化方法:

  • 权重:均匀分布,数量级取决于输入/输出;
  • bias:零;
  • 方差和:1;

初始化可以合并到nn.module的构造阶段。

Data loader和预处理

  • 我们经常通过随机打乱和转换输入来预处理(扩充)数据集;
  • 数据增强是深度学习模型中预测准确性提升的重要部分;
  • 数据加载和扩充本质上也是可组合的;

深度学习本质上是模块化的

回顾编程抽象

在Caffe 1.0的例子中,耦合了梯度计算与模块:

class Layer:
    def forward(bottom, top):
    	pass
    	
    def backward(top,
    			propagate_down,
    			bottom):
    	pass

在Pytorch(needle)中至少有两层抽象:Tensors层面的计算图抽象,用来处理AD;高层次的抽象处理模块化组合:

import needle as ndl

v1 = ndl.Tensor([1])
v2 = ndl.exp(v1)
v3 = v2 + 1
v4 = v2 * v3