查看原文
其他

飞桨框架v2.3 API最新升级!对科学计算、概率分布和稀疏Tensor等提供更全面支持!

飞桨PaddlePaddle 百度AI 2022-12-19


2022年5月飞桨框架2.3版本正式发布,相比飞桨框架2.2版本,API 体系更加丰富,新增了100多个 API,覆盖自动微分、概率分布、稀疏Tensor、拟牛顿优化器、线性代数、框架性能分析、硬件设备管理、视觉和语音领域等方面。整体上进一步丰富了飞桨框架动静统一、高低融合的 API 体系。



下面对每一类新增的 API 及其应用场景进行详细介绍,方便快速理解和上手。

1

新增自动微分 API

以更好支持科学计算

科学计算许多计算场景需要嵌入物理信息,Loss 往往通过高阶偏微分方程表示,如物理信息神经网络(Physics-Informed Neural Networks)。为了更好地支持科学计算场景,飞桨框架 v2.3 新增 Jacobian、Hessian、jvp、vjp4 个自动微分 API,其中 Jacobian、Hessian 支持按行延迟计算,能够在复杂偏微分方程组中避免计算无用导数项,提升计算性能。目前已经在赛桨PaddleScience中得到应用。

基于延迟计算求解雅可比矩阵与海森矩阵


Jacobian(func,xs)计算 func 在 xs 处的雅可比矩阵。为了降低计算整个矩阵的性能和显存开销,实现上采用了延迟计算方式,即按需计算部分行,并进行缓存。


import paddle

def func(x, y):
    return paddle.matmul(x, y)

x = paddle.to_tensor([[1., 2.], [3., 4.]])
J = paddle.incubate.autograd.Jacobian(func, [x, x])
print(J[:, :])
# Tensor(shape=[4, 8], dtype=float32, place=Place(gpu:0), stop_gradient=False,
#        [[1., 3., 0., 0., 1., 0., 2., 0.],
#         [2., 4., 0., 0., 0., 1., 0., 2.],
#         [0., 0., 1., 3., 3., 0., 4., 0.],
#         [0., 0., 2., 4., 0., 3., 0., 4.]])

print(J[0, :])
# Tensor(shape=[8], dtype=float32, place=Place(gpu:0), stop_gradient=False,
#        [1., 3., 0., 0., 1., 0., 2., 0.])
print(J[:, 0])
# Tensor(shape=[4], dtype=float32, place=Place(gpu:0), stop_gradient=False,
#        [1., 2., 0., 0.])

Jacobian 延迟计算示例

Hessian(func, xs)计算 func 在 xs 的海森矩阵,func 要求是 vector-scalar 函数,即输出是单个元素。海森矩阵是输出对输入的二阶导数,也可以理解为雅可比矩阵对输入的导数,因此实现上基于 Jacobian,同样支持延迟计算与缓存。

def _jac_func(*xs):
    jac = Jacobian(func, xs, is_batched=is_batched)
    if (is_batched and jac.shape[1] != 1) or (not is_batched and
                                              jac.shape[0] != 1):
        raise RuntimeError(
            "The function given to Hessian shoud return as single element Tensor or batched single element Tensor."
        )
    return jac[:, 0, :] if is_batched else jac[0, :]

self.symbolic = Jacobian(_jac_func, xs, is_batched=is_batched)

Hessian 实现核心代码

使用 jvp 和 vjp 计算前向微分与反向微分

jvp(func, xs, v)即雅可比矩阵-向量乘积,计算函数 func 在 xs 处的雅可比矩阵与向量v乘积。jvp 主要用途是计算前向微分,前向微分即依据链式法则,从前向后遍历计算图,计算当前变量对输入的微分。

如图,输入 x 为 n 元向量,经过 A、B、C 得到标量输出 y 利用 jvp 计算前向微分过程如下,其中J表示 jvp 计算。计算输出对所有输入的偏导需要 n 次前向计算过程。


vjp(func, xs, v)即向量-雅可比矩阵乘积,计算向量 v 与函数 func 在 xs 处的雅可比矩阵乘积。vjp 主要用途是计算反向微分,反向微分即依据链式法则,从后向前遍历计算图,计算输出对当前变量的微分。

反向微分计算过程如下,其中 J^T 表示 vjp 计算过程。


2

体系化完善概率分布类 API

以支持更多应用场景

概率分布在科学计算、强化学习、变分推断等场景中有着广泛应用。为了增强飞桨在概率编程方面能力,飞桨框架 v2.3 对概率分布基础设施及高层 API 进行统一规划与设计,提供低耦合、高内聚的基础设施以及更为灵活、完备的 API 功能。该类 API 总体数量扩充到25个,新增6个概率分布类、13个分布变换类及2个 KL 散度计算类 API,并可持续扩展,丰富了随机采样算法,提供完备向量化语义支持,同时支持随机变量之间的变换,对于强化学习中策略梯度求解等场景,通过重参数化机制支持反向传播。


更为灵活完备的 API 体系

飞桨框架v2.3对概率分布 API 进行了整合与增强,新增指数族、狄利克雷等统计分布,提供13个概率分布变换以及 KL 散度计算功能,除此之外,基于基础分布和分布变换,提供构建高阶分布功能。

(1)概率统计与随机采样

新增 Dirichelt、Beta、Multinomial 三类分布,支持上述分布随机采样与概率统计,同时抽象 ExponentialFamily 分布基类,提供统一的熵计算、自然参数计算以及对数正则化等指数分布族相关基础功能。

import paddle 

beta = paddle.distribution.Beta(0.5, 0.5)
samples = beta.sample((10000,))

Beta 分布采样示例


(2)分布变换

分布变换类 API 用于将原分布的随机采样数据,经过线性/非线形变换生成目标分布。飞桨框架 v2.3 新增13个随机变量变换类 API,提供随机变量之间可逆、可组合变换,如仿射变换 AffineTransform、幂变换 PowerTransform、指数变换 ExpTransform 等基础变换,以及变换之间的链式组合 ChianTransform。

如下示例,将标准正态分布经过仿射变换,生成满足特定需求的目标分布。

import paddle
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns


normal = paddle.distribution.Normal(0., 1.)
samples = normal.sample([10000])

sns.distplot(samples)
plt.title('samples from a Norma(0., 1.)')
plt.show()

affine = paddle.distribution.AffineTransform(paddle.to_tensor(2.), paddle.to_tensor(2.))
transformed_samples = affine.forward(samples)
sns.distplot(transformed_samples)
plt.title('samples from a AffineTransform(2., 2.) Normal(0., 1.)')
plt.show()



(3)统一 KL 散度计算

提供统一的 KL 散度计算 API,kl_divergence 以及 register_kl. kl_divergence 用于计算相同或不同分布之间的 KL 散度,register_kl 用于注册用户自定义的 KL 散度计算逻辑。

import paddle

p = paddle.distribution.Beta(alpha=0.5, beta=0.5)
q = paddle.distribution.Beta(alpha=0.3, beta=0.7)

print(paddle.distribution.kl_divergence(p, q))
# Tensor(shape=[1], dtype=float32, place=CUDAPlace(0), stop_gradient=True,
#        [0.21193528]


(4)构建高阶分布

除上述基础功能外,新增了 TransformedDistribution(base: Distribution, transforms: Sequence[Transform])),用户可使用该 API,基于一个基础分布和一系列分布变换,灵活构建更高阶分布。

import paddle 

# 基于标准正态分布和仿射变换,构建新的高阶分布
d = paddle.distribution.TransformedDistribution(
    paddle.distribution.Normal(0., 1.), 
    [paddle.distribution.AffineTransform(paddle.to_tensor(1.), paddle.to_tensor(2.))]
)

print(d.sample([10]))
# Tensor(shape=[10], dtype=float32, place=Place(gpu:0), stop_gradient=True,
#        [-0.10697651,  3.33609009, -0.86234951,  5.07457638,  0.75925219,
#         -4.17087793,  2.22579336, -0.93845034,  0.66054249,  1.50957513])
print(d.log_prob(paddle.to_tensor(0.5)))
# Tensor(shape=[1], dtype=float32, place=Place(gpu:0), stop_gradient=True,
#        [-1.64333570])


更为完善的向量化语义

传统使用概率分布采样,通常传入标量参数,构造一个分布实例。针对多元分布,通过多个独立一元分布构建;针对不同参数,构造不同参数的多个实例;针对批量采样,执行采样方法多次;最终将上述结果进行组合,得到想要结果。无法有效利用现代张量运算及对应 GPU 加速等能力。

通过如下三个维度将采样过程进行向量化:

  • 采样形状(sample_shape):向量化批量采样过程;
  • 批形状(batch_shape):向量化不同参数的分布;
  • 事件形状(event_shape):向量化多元分布;

实现上,上述每个 shape 均为张量支持飞桨框架广播语义,最终样本数据形状为:

sample_data.shape = sample_shaple + batch_shape + event_shape

import paddle 

# 创建两个3元多项分布,分布1概率为[0.2, 0.3, 0.5],分布2概率为[0.1, 0.9, 0.8],因此事件形状为3,批形状为2
multinomial = paddle.distribution.Multinomial(total_count=10, probs=paddle.to_tensor([[0.2, 0.3, 0.5], [0.1, 0.9, 0.8]]))
print('event_shape: ', multinomial.event_shape)
print('batch_shape: ', multinomial.batch_shape)

# 采样形状为4,因此最终样本形状为[4, 2, 3]
samples = multinomial.sample((4,))
print('sample_shape: ', samples.shape)

# event_shape:  (3,)
# batch_shape:  (2,)
# sample_shape:  [4, 2, 3]


更为丰富的采样算法

随机采样是生成概率分布样本的关键过程。不同随机采样算法在应用场景、性能存在较大差距。飞桨框架 v2.3 在原有算法基础上,新增逆变换采样以及接受-拒绝采样算法,以支持不同概率分布采样需求。如多项分布使用逆变换采样比传统采样方法具备更高的接受率,狄利克雷分布由于其反函数解析式复杂,使用接受-拒绝采样能更好的模拟真实分布过程。除上述通用采样算法之外,在基础设施层,还保留了对特定领域优化采样算法扩展性,如对于正态分布,box-muller 算法具备更好性能,可按需扩展到正态分布上。

支持反向传播

概率分布参数化目的让此类 API 接口具备反向传播能力,应用在策略梯度、变分推理等场景。因为随机采样方法 sample 不支持反向传播,考虑业界使用习惯,我们在 Distribution 基类中增加了重参数化采样 rsample,将目标分布通过不含参数的标准分布的表示,采样过程可微。

如非标准正态分布使用标准正态分布表示,能够支持反向传播。

def rsample(self, sample_shape):    
    shape = self._extended_shape(sample_shape)    
    eps = _standard_normal(shape, dtype=self.loc.dtype, device=self.loc.device)    
    return self.loc + eps * self.scale


3

新增创建2种稀疏 Tensor 的 API

支持和常规 Tensor 进行转换

通常的神经网络是稠密网络,但是随着模型大小的指数型爆炸,越来越多的科研工作者和公司都无法提供足够的资源去训练这些强大却昂贵的模型,而网络稀疏化已经被认证可以用来加快网络的训练,同时降低对硬件资源的要求。

目前越来越多的场景有稀疏训练的需求,比如 NLP 中的稀疏 Attention,CV,3D 点云等。对于深度学习框架,其涵盖了 Sparse Tensor 基础数据结构、Sparse Tensor 的计算类 API/OP、Sparse Tensor 的稀疏训练网络层三个方面的能力。飞桨框架 v2.3 已支持创建和使用2种稀疏 Tensor。

COO 稀疏格式

飞桨支持 COO 稀疏格式来存储 Tensor,COO 是最通用且简单的坐标存储方式,包含两个数据:非0元素坐标、非0元素值,如图示:


通过 paddle.sparse.sparse_coo_tensor,指定 非0元素坐标(indices)、非0元素值(values) 可创建一个 COO 格式的稀疏 Tensor,其支持 2D 及以上任意维度,同时还支持非0元素为 DenseTensor 的 Hybird COO 形式。其打印效果如下:

indices = [[0, 1, 1, 2, 3, 3], 
           [1, 0, 3, 2, 1, 3]]
values = [1, 2, 3, 4, 5, 6]
dense_shape = [4, 4]
coo = paddle.sparse.sparse_coo_tensor(indices, values, dense_shape)
print(coo)
# Tensor(shape=[4, 4], dtype=paddle.int64, place=Place(gpu:0), stop_gradient=True, 
#        indices=[[0, 1, 1, 2, 3, 3],
#                 [1, 0, 3, 2, 1, 3]], 
#        values=[1, 2, 3, 4, 5, 6])


CSR 稀疏格式

飞桨支持 CSR 稀疏格式存储 Tensor,CSR 是压缩行信息存储格式,其记录了每一行中第一个非0元素的索引,按行进行压缩。这种格式在行切片、矩阵乘、矩阵与向量乘的场景中运算更为高效。


通过 paddle.sparse.sparse_csr_tensor,指定每行首个非0元素索引(crows)0、非0元素列信息(cols)、非0元素值(values)可创建一个 CSR 格式的稀疏 Tensor,其支持 2D/3D 维度。其打印效果如下:

crows = [0, 1, 3, 4, 6]
cols = [1, 0, 3, 2, 1, 3]
values = [1, 2, 3, 4, 5, 6]
dense_shape = [4, 4]
csr = paddle.sparse.sparse_csr_tensor(crows, cols, values, dense_shape)
print(csr)
# Tensor(shape=[4, 4], dtype=paddle.int64, place=Place(gpu:0), stop_gradient=True, 
#        crows=[0, 1, 3, 4, 6], 
#        cols=[1, 0, 3, 2, 1, 3], 
#        values=[1, 2, 3, 4, 5, 6])


稀疏与稠密 Tensor 互转

直接通过非0元素的索引信息来创建稀疏 Tensor 较为繁琐,同时在训练网络中可能同时存在 DenseTensor 与 SparseTensor,飞桨支持通过 Tesor.to_dense() 、Tesor.to_sparse_coo()、Tesor.to_sparse_csr()三个Tensor 类成员 API,实现 Sparse 与 Dense 之间交互。

dense = paddle.to_tensor([[0, 1, 0, 2], 
                          [0, 0, 3, 4]], dtype='float32')
coo = dense.to_sparse_coo(sparse_dim=2)
# Tensor(shape=[2, 4], dtype=paddle.float32, place=Place(gpu:0), stop_gradient=True, 
#       indices=[[0, 0, 1, 1],
#                [1, 3, 2, 3]], 
#       values=[1., 2., 3., 4.])
csr = dense.to_sparse_csr()
# Tensor(shape=[2, 4], dtype=paddle.float32, place=Place(gpu:0), stop_gradient=True, 
#       crows=[0, 2, 4], 
#       cols=[1, 3, 2, 3], 
#       values=[1., 2., 3., 4.])

飞桨对稀疏 Tensor 的数据支持将为后续的 Sparse Tensor 的计算类 API/OP、Sparse Tensor 的稀疏训练网络层两方面的能力奠定基础,未来飞桨将围绕这两个方面展开更多工作,并通过 NLP 中的稀疏 Attention、3D 点云模型验证,整体提升飞桨的稀疏场景应用能力。

4

新增拟牛顿法二阶优化 API

拟牛顿法二阶优化器因为在参数优化中引入了更多信息,相比传统的一阶优化器(SGD、Adam 等)在收敛速度和精度上有着明显优势。经典的二阶优化算法有BFGS(Broyden–Fletcher–Goldfarb–Shanno 算法)、L-BFGS(Limited-memory BFGS,限制存储的BFGS)和CG(Conjugate Gradient,共轭梯度)。

飞桨框架 v2.3 新增2个二阶优化器 API:minimize_bfgs 和 minimize_lbfgs,二者均采用 Strong-Wolfe 线搜索方法寻找每次迭代的最佳步长。

这两个 API 采用函数式接口,在使用时,只需传入待优化的函数和起始坐标,即可求解出函数的最小值。例如下面的示例,使用BFGS找到了函数 func的最小值 (0, 0)。此外,二阶优化 API 还支持设置迭代次数、容差、初始逆 Hessian 矩阵、线搜索的初始步长等参数,具有极高的灵活度,方便有相关经验的用户进行算法调优。

import paddle

def func(x):
    return paddle.dot(x, x)

x0 = paddle.to_tensor([1.3, 2.7])
results = paddle.incubate.optimizer.functional.minimize_bfgs(func, x0)
print("is_converge: ", results[0])
print("the minimum of func is: ", results[2])
# is_converge:  is_converge:  Tensor(shape=[1], dtype=bool, place=Place(gpu:0), stop_gradient=True,
#        [True])
# the minimum of func is:  Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True,
#        [0., 0.])

目前,二阶优化 API 已经应用在赛桨 PaddleScience 圆柱绕流模型训练中,并取得了不错的效果。从下方的 loss-step 曲线可以看出,在同样的迭代次数,使用二阶优化器比一阶优化器能使得 Loss 下降更快。


5

其他新增 API

扩展线性代数 API

为了更好地支持科学计算相关需求,飞桨框架 v2.3 扩展了线性代数的 API,包括:计算线性方程组的最小二乘解 paddle.linalg.lstsq,计算向量间的协方差  paddle.linalg.cov,通过 Cholesky 分解来计算具有唯一解的线性方程组 paddle.linalg.cholesky_solve,计算矩阵 lu 分解 paddle.linalg.lu 以及对 lu 分解的结果进行解压缩 paddle.linalg.lu_unpack。

(1)框架性能分析 API

为了方便查找和使用,性能分析的9个 API 均整合到 paddle.profiler.*,提供对训推过程中性能数据的收集,导出和统计的功能。支持自定义打点,支持将性能数据保存为 google chrome tracing 格式的文件,可在 chrome 中使用 tracing 插件查看。

(2)硬件设备管理 API

paddle.device.cuda 下新增:

  • max_memory_allocated
  • max_memory_reserved
  • memory_allocated 和 memory_reserved
能够实时查看和分析模型显存占用。

新增 get_device_name 和 get_device_capability:能够获取 GPU 设备名称信息和计算能力的主要和次要修订号。

(3)视觉和语音领域高层API

基于飞桨的基础 API,大量扩充了 paddle.vision.models.* 常用视觉模型,用户可直接使用 AlexNet、DenseNet、GoogLeNet、InceptionV3、MobileNetV3、ResNeXt、ShuffleNetV2、SqueezeNet、WideResNet 等模型。


新增 Viterbi 解码 API:

  • paddle.text.ViterbiDecoder 和 paddle.text.viterbi_decode


已用于序列标注模型的预测。


除了上面的介绍外,飞桨框架 v2.3 还扩充了大量 Tensor 计算相关的 API,更好地支持业界论文中模型的实现,加速创新。

详细列表可参考 v2.3 release note,请点击阅读全文获取。

6

结语

飞桨框架2.3版本提供了更加丰富的 API 体系,不仅更好地支撑深度学习图像、语言、语音领域的快速迭代和创新,而且在不断扩展对科学计算、强化学习、3D 点云等场景应用的支持,同时也不断优化飞桨 API 的用户使用体验,使得 API 更易记、易查和易用,让深度学习技术的创新与应用更简单!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存