Paddle 0-d Tensor 使用指南

Paddle 0-d Tensor 使用指南

1. 0-d Tensor 的定义

在深度学习框架中,Tensor 是存储和操作数据的基本数据结构。一个 Tensor 可以有 0 到任意多的维度,每个维度对应一个 shape 值。而 0-d Tensor,顾名思义,就是一个无任何维度的 Tensor,也被称为标量(scalar) Tensor。

从数学的角度来看,0维 Tensor 可以看作是一个单个的数值,没有向量或矩阵等更高维度的结构。例如:

import paddle# 创建0维Tensor
scalar = paddle.to_tensor(3.14)
print(scalar, scalar.shape)
# Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
#        3.14000010) []

可以看到,这个 scalar 是一个单个的浮点数 3.14,它的 shape 是一个空列表 [],表示没有任何维度。0-d Tensor 其对应的是Numpy的 0-D array,可以表示为 np.array(10.),其shape为 [],维度为 0,size为 1。

对比之下,一维的 Tensor 则表示一个向量,其对应的是 Numpy的 1-D array,如果只有 1 个元素,可以表示为 np.array([10.]),其 shape 为 [1],维度为 1,size为 1。下面我们来看一个一维 Tensor 的例子:

vector = paddle.to_tensor([1, 2, 3])
print(vector, vector.shape)
# Tensor(shape=[3], dtype=int64, place=Place(cpu), stop_gradient=True,
#        [1, 2, 3]) [3]

这里vector是一个一维张量,有3个元素,对应shape为[3]。

以上从数学角度上区分了 0-d Tensor 和 1-d Tensor,在物理学中,标量和矢量是两个基本的物理量概念。标量只有一个数值,没有方向; 而矢量除了数值外,还附带一个确定的方向。

0-d Tensor对应着物理学中的标量概念。一个 0-d Tensor,如 3.14、2.78 等,仅仅表示一个单一的数值,没有任何其他维度的信息。它可以表示一些简单的物理量,如温度、质量、电荷等。

而 1-d Tensor 则对应着矢量的概念。即使只有1个元素,如 [5.0],它也不是一个纯标量,而是一个有确定方向的向量。这个方向在物理意义上可能表示力、速度、电场强度等有方向性的物理量。

尽管在代码实现上,0-d 和 1-d Tensor 可能没有太大的区别,但它们对应的数学和物理概念是不同的。作为开发者,明确这种区别将有助于写出更加符合数学规范、更加符合物理意义的代码,从而减少逻辑错误和调试成本。

2. 0-d Tensor 滥用为 1-d Tensor 的危害

滥用 0d Tensor 来代替1维单元素Tensor(shape为[1]) 给使用体验带来一些负面影响,主要体现在以下几个方面:

2.1 潜在的纬度错误

标量张量与仅含有一个元素的向量张量容易造成混淆,它们的元素个数相同,但在数学定义上完全不同。若将其形状表示为 shape=[1],则无法区分标量和向量,这与数学语义和行业通用的计算规则相悖,可能导致模型出现意料之外的错误,并增加开发调试成本。

由于 0-d 和 1-d Tensor 在数学上有着本质区别,很多 API 在处理这两种情况时的行为也不尽相同。如果不加区分地混用,就可能导致 API 的行为出现异常。

import torchx = torch.tensor(3.14) 
out = torch.stack([x, x])
print(out.shape)  # 输出 torch.Size([2])  0D升为1D,符合预期

如果 Paddle 不支持 0-d Tensor,就需要额外判断 x 是否为 1D,然后补squeeze来矫正结果,这造成了代码的冗余与可读性降低。写法如下:

import paddlex = paddle.to_tensor(3.14)# Paddle 写法需4行:需要额外判断x是否为1D,是就需要补squeeze来矫正结果以对齐 pytorch
if len(x.shape) == 1:# 因为用shape=[1]的1维替代0维,导致x[0]还是1维,stack结果为2维,出现异常升维,需要补squeeze来校正维度out = paddle.stack([x[0], x[0]]).squeeze()
else:out = paddle.stack([x[0], x[0]])

如果 Paddle 支持 0-d Tensor,就无需增加这些额外判断的代码,代码可与其他深度学习框架(例如Pytorch)完全一致。写法如下:

import paddlex = paddle.to_tensor(3.14)out = paddle.stack([x, x])
print(out.shape)

由上可看出,支持0-d Tensor后的Paddle代码,在写法上简洁清晰很多,提升了用户体验与API易用性。

2.2 代码可读性降低

正如上面的例子,为了区分0维和1维的情况,需要增加很多额外的判断和操作代码,使得代码的可读性和可维护性大幅降低。而遵循标准的数学语义,区分对待0维和1维,则可以写出更加简洁优雅的代码。

2.3 与第三方库集成困难

很多第三方库在实现时,都会遵循标准的数学规范,区分对待0维和1维Tensor。如果我们的代码中滥用0维作1维,就可能导致无法与这些库正常交互、集成它们的算子和模型。

比如在 Paddle 2.5 支持 0-d Tensor 之前,EinOps(一个用户量较大的爱因斯坦求和库)计划支持 Paddle 后端,为与其他框架(MxNet、TF、Pytorch等)保持统一结构,需要使用 0-d Tensor,然而发现 Paddle 有些 API 不支持 0维Tensor,当前就只能暂停对 Paddle 的适配。

3. 应支持 0-d Tensor 的情况

3.1 逐元素计算类

对于所有的 elementwise 一元运算(如 tanh、relu 等)和二元运算 (如 add、sub、multiply 等),理应支持 0-d Tensor 作为输入或通过广播机制与高维Tensor进行计算。同时,复合运算如 Linear(相当于matmul+add)也应支持 0维输入。

Paddle 已经支持了全部逐元素计算类的运算:

import paddle# 一元运算
x = paddle.to_tensor(3.14)
y = paddle.tanh(x)
print(y) # 0.9953155994415283# 二元运算
x = paddle.to_tensor(2.0)
y = paddle.to_tensor([1.0, 2.0, 3.0])
z = x + y  # 0维可广播
print(z) # [3. 4. 5.]

在这个例子中,y 是 x 的 tanh 运算,是一个标量,因此适合用 0-d Tensor 来表示。z 是 x 和 y 的加法,x 是一个标量,y 是一个向量,通过广播机制,可以得到一个向量,因此适合用 0-d Tensor 来表示。

3.2 升维和降维操作

诸如 unsqueeze、squeeze、reshape 等显式改变 Tensor 形状的 API,都应当支持0维输入或输出。

Paddle 在这一块做得较好,下面是一些例子:

# 升维
x = paddle.to_tensor(3.14) 
y = paddle.unsqueeze(x, 0) 
print(y.shape) # [1]# 降维
z = paddle.squeeze(y)
print(z.shape) # []  # 0维输出
w = paddle.reshape(x, [])
print(w.shape) # []

当 x 是一个 0-d Tensor 时,unsqueeze 可以将其升维为 1-d Tensor,squeeze 可以将其降维为 0-d Tensor,reshape 可以将其形状改变为 []。

3.3 Tensor 创建相关

能够直接创建 0维Tensor 的 API 是很有必要的,它们包括:

  • 不指定 shape 时,如 to_tensor 将标量转为 0维
  • 显式指定 shape=[]
  • 拷贝已有 Tensor 时,维度信息应保持不变

Paddle 在这一部分的支持也是比较全面的:

# Python标量 -> 0维Tensor
scalar = paddle.to_tensor(3.14)
print(scalar)
# Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,
#        3.14000010)# 指定shape = []
zeros = paddle.zeros([])
ones = paddle.ones([], dtype="int32") 
print(zeros)
# Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True, 0.)
print(ones)
# Tensor(shape=[], dtype=int32, place=Place(cpu), stop_gradient=True, 1)# 保持原shape
t = paddle.to_tensor([1.0, 2.0])
scalar = t[0] # 0维输出
copy = paddle.assign(scalar) # 0维拷贝
print(scalar)
# Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True, 1.)

3.4 轴向归约运算

当对 Tensor 进行诸如 sum、mean、max 等的归约操作时,如果指定了 axis=None,就应当有0维输出的可能。Paddle 已经支持了这种情况:

# axis=None归约所有维度可0维输出  
x = paddle.rand([2, 3])
y = paddle.sum(x, axis=None) # 0维输出
print(y.shape) # []

对 x 进行 sum 操作,axis=None 表示对所有维度进行归约,输出是一个标量,因此适合用 0-d Tensor 来表示。

3.5 索引切片操作

在使用索引切片的时候,应当支持输入和输出是 0-d Tensor 的情况。

  • 索引输入0D时:使用标量作为索引的时候,输入0-D时,应该与int标量索引的效果一致,具有降维效果,以下是一个例子:
import paddlex = paddle.rand([2, 2, 2])
y = x[paddle.to_tensor(0)] 
print(y)
# Tensor(shape=[2, 2], dtype=float32, place=Place(cpu), stop_gradient=True,
#        [[0.97571695, 0.84757918],
#         [0.35047710, 0.37460467]])
  • 索引输出0D时:当索引的输出应当支持 0-d Tensor 时,例如3-D Tensor取 [0,0,0],降3维应输出 0D,以下是一个例子:
x = paddle.rand([2, 2, 2])
y = x[0, 0, 0]print(y)
Tensor(shape=[], dtype=float32, place=Place(cpu), stop_gradient=True,0.07096851)

同理,gather、scatter等类似功能API应具有相同效果,下面的例子展示了gather的 0维 输入和输出:

x = paddle.to_tensor([0, 1, 2, 3])
index = paddle.to_tensor(0)
# index 是 0-d Tensor
y = paddle.gather(x, index)
# 输出是 0-d Tensor
print(y)
# Tensor(shape=[], dtype=int64, place=Place(cpu), stop_gradient=True,
#        0)

3.6 标量属性输入

有些Op的属性语义上应该是标量值,如 shape、start/end、step 等,这种情况下应当支持 0-d Tensor 作为输入。

# paddle.linspace的start/end/step都支持0维输入
start = paddle.to_tensor(1.0)
end = paddle.to_tensor(5.0) 
values = paddle.linspace(start, end, 5)
print(values) # [1. 2. 3. 4. 5.]

在这个例子中,start 和 end 都是标量,适合用 0-d Tensor 来表示。linspace 的输出是一个向量,但是 start 和 end 是标量,因此适合用 0-d Tensor 来表示。

3.7 标量输出语义

有些计算的输出在语义上应该是个标量值,如向量点积、秩、范数、元素个数等,这种情况下应返回 0维Tensor。下面是一些例子:

# 点积输出0维Tensor  
x = paddle.rand([5])
y = paddle.rand([5])
z = paddle.dot(x, y)
print(z.shape) # []# 范数输出0维
norm = paddle.norm(x, p=2) 
print(norm.shape) # []

上面的例子中,z 是 x 和 y 的点积,是一个标量,因此适合用 0-d Tensor 来表示。同理,norm 是 x 的二范数,也是一个标量,适合用 0-d Tensor 来表示。

3.8 自动求导

在深度学习中,自动微分是一个非常核心的特性,支持标量对标量(0维对0维)的求导是很有必要的。

import paddle# 标量对标量导数
x = paddle.to_tensor(3.0, stop_gradient=False)
y = x**2 
y.backward()
print(x.grad)  # 6.0

上面的例子中,x 是一个 0-d Tensor,y 是 x 的平方,y.backward() 可以计算出 y 对 x 的导数,结果是 6.0。这种标量对标量的求导是深度学习中很常见的操作,因此支持 0-d Tensor 的自动求导是很有必要的。

3.9 损失函数输出

深度学习模型的损失函数输出通常是一个标量值,用以指示整个小批次的损失大小,这适合用 0维Tensor 来表示。

import paddle.nn.functional as Flogits = paddle.rand([4, 10])  # 假设是分类模型的输出логит
labels = paddle.randint(0, 10, [4])  # 对应的类别标签loss = F.cross_entropy(logits, labels)
print(loss.shape)  # []

在这个例子中,loss 是一个标量,用以表示整个小批次的交叉熵损失,因此适合用 0-d Tensor 来表示。

4. 总结

在深度学习框架中,0维Tensor虽然形式简单,但具有重要的概念意义和实际应用价值。它不仅对应数学和物理上的标量概念,也是各种标量计算和控制流程的基础表示形式。

支持 0-d Tensor的使用,可以让框架更加贴合数学规范,让代码更加简洁优雅。同时,它也是实现很多实用功能的基石。框架要避免在处理0维和1维Tensor时产生行为分歧,尽量与其他主流框架保持一致,方便模型和算子在不同框架间的移植。当下 Paddle 框架中已经全面支持 0-D Tensor,并实际上已成为后续新增算子的开发规范,让用户能够方便地使用 0-d Tensor。

参考文献

  1. https://github.com/PaddlePaddle/community/blob/master/pfcc/paddle-code-reading/ZeroDim/judge_zero_dim.md
  2. https://github.com/PaddlePaddle/community/blob/master/pfcc/paddle-code-reading/ZeroDim/zero_dim_concept.md
  3. https://github.com/PaddlePaddle/community/blob/master/pfcc/paddle-code-reading/ZeroDim/all_zero_dim_api.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/18509.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle 数据泵(Data Pump)的impdp解析

impdp 是 Oracle 数据泵(Data Pump)用于数据导入的命令行工具。 directory: 指定转储文件和日志文件所在的操作系统目录对象名。此目录对象必须事先使用 CREATE DIRECTORY 命令创建并在数据库中定义。 impdp system/password directorydir_name ...dumpf…

LuatOS学习

开发顺序 Lua是脚本语言中运行速度最快的语言 资源占用极低 脚本语言运行方式 脚本语言是从上往下一行一行运行的 变量 coun 123456 a,b,c 1,2,3交换 a,b b,a在测试环境中,用print(a,b)打印 nil类型 未声明的变量就是nil,nil用来表示此变量为空…

STM32高级控制定时器(STM32F103):检测输入PWM周期和占空比

目录 概述 1 PWM 输入模式 1.1 原理介绍 1.2 应用实例 1.3 示例时序图 2 使用STM32Cube配置工程 2.1 软件环境 2.2 配置参数 2.3 生成项目文件 3 功能实现 3.1 PWM占空比函数 3.2 输入捕捉回调函数 4 功能测试 4.1 测试软件框架结构 4.2 实验实现 4.2.1 测试实…

视觉语音识别挑战赛 CNVSRC 2024

CNVSRC 2024由NCMMSC 2024组委会发起,清华大学、北京邮电大学、海天瑞声、语音之家共同主办。竞赛的目标是通过口唇动作来推断发音内容,进一步推动视觉语音识别技术的发展。视觉语音识别(也称为读唇技术)是一种通过观察唇部动作推…

二叉树顺序结构实现【堆的实现】【详细图解】

P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。 P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。 目录 1、二叉树的顺序结构2、堆的概念3、堆的实现3.1 堆实现的前提3.1.1 向上调整3.1.2 向下调…

采用java语言+B/S架构+后端SpringBoot前端Vue开发的ADR药品不良反应智能监测系统源码

采用java语言+B/S架构+后端SpringBoot前端Vue开发的ADR药品不良反应智能监测系统源码 ADR监测引擎每日主动获取检验数据、病历内容(可拓展)、以及其他临床数据,根据知识库内容自动判定患者是否有不良反应迹象&#xf…

算法基础之Nim游戏

Nim游戏 核心思想&#xff1a;博弈论 结论&#xff1a;将所有堆的石子数全部异或起来 得到的结果若为1 则先手必胜 若为0 则先手必败 #include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N 100010;int a[N];int n…

k8s笔记——client-go与 Kubernetes APIServer 交互的客户端

文章目录 四种Kubernetes APIServer 交互的客户端RESTClientclientSetdynamicClientDiscoveryClient 创建、更新、查询、删除Deployment参考资料 四种Kubernetes APIServer 交互的客户端 Client-Go 共提供了 4 种与 Kubernetes APIServer 交互的客户端。分别是 RESTClient、Di…

LeetCode精华75题(持续更新)

LeetCode刷题笔记 数组/字符串 交替合并字符串 题目&#xff1a; 给你两个字符串 word1 和 word2 。请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符串的末尾。 返回 合并后的字符…

【iOS】UI学习(一)

UI学习&#xff08;一&#xff09; UILabelUIButtonUIButton事件 UIViewUIView对象的隐藏UIView的层级关系 UIWindowUIViewController定时器与视图对象 UISwitch UILabel UILabel是一种可以显示在屏幕上&#xff0c;显示文字的一种UI。 下面使用代码来演示UILabel的功能&#…

AI学习指南数学工具篇-Python中的凸优化库

AI学习指南数学工具篇-Python中的凸优化库 在人工智能和机器学习领域&#xff0c;凸优化是一个非常重要的数学工具。它可以帮助我们解决各种问题&#xff0c;包括线性规划、二次规划、半定规划等。而在Python中&#xff0c;有一个非常优秀的凸优化库&#xff0c;即CVXPY。本文…

做好开源快速开发平台研发创新 助力行业高效发展!

随着信息化时代的到来&#xff0c;科技的力量无处不在。为了提高办公效率&#xff0c;很多大中型企业倾向于使用更为先进的软件平台来助力企业降本增效。在众多助力神器之中&#xff0c;开源快速开发平台低代码技术平台深得广大新老客户朋友的喜爱&#xff0c;它与生俱来的优势…

结合PyTest和Selenium进行网页自动化测试的例子

一个结合PyTest和Selenium进行网页自动化测试的示例。 这个测试用例模拟了一个简单的用户登录过程&#xff0c;并包含了对登录后页面状态的断言。我们将使用Selenium的WebDriver来控制浏览器&#xff0c;并使用PyTest来进行断言。 import pytest from selenium import webdri…

vue2+echarts地图下钻+地图遮盖物散点

一、下载工具 npm i echarts echarts-gl axios -S -S是生产依赖默认是-S不写也可以 -D是开发依赖 二、引入工具 import * as echarts from "echarts"; import "echarts-gl"; import axios from "axios"; 三、HTML部分代码 <div class&…

微信小程序路由跳转

1. wx.navigateTo 作用&#xff1a;保留当前页面&#xff0c;跳转到应用内的某个页面。特点&#xff1a;跳转后目标页面的生命周期函数 onLoad 和 onShow 会被触发。使用场景&#xff1a;一般用于跳转到应用内的其他页面&#xff0c;保留当前页面的状态&#xff0c;例如从文章…

Java数据类型

一、每种数据都定义了 明确的数据类型&#xff0c;在内存中分配了不同大小的 内存空间(字节)。 二、Java数据类型分为两种&#xff1a; 基本数据类型&#xff1a; 数值型&#xff1a; 整数类型&#xff0c;存放整数(byte[1] , short[2] , int[4] , long[8]) 浮点类型&#xff0…

UE5 读取本地图片并转换为base64字符串

调试网址&#xff1a;在线图像转Base64 - 码工具 (matools.com) 注意要加&#xff08;data:image/png;base64,&#xff09; FString UBasicFuncLib::LoadImageToBase64(const FString& ImagePath) {TArray<uint8> ImageData;// Step 1: 读取图片文件到字节数组if (!…

【蓝桥杯】第十四届蓝桥杯大赛软件赛国赛C/C++ 大学 B 组

答题结果页 - 蓝桥云课 (lanqiao.cn) 0子2023 - 蓝桥云课 (lanqiao.cn)&#xff08;暴力枚举 #include<bits/stdc.h> using lllong long; using ullunsigned long long; #define fir first #define sec second //#define int llconst int N1e510; const int mod1e97;int…

C++标准模板(STL)- C 内存管理库 - 分配内存 (std::malloc)

C 内存管理库 分配内存 std::malloc 定义于头文件 <cstdlib> void* malloc( std::size_t size ); 分配 size 字节的未初始化存储。 若分配成功&#xff0c;则返回指向分配的适合对任何标量类型对齐的内存块中&#xff0c;最低&#xff08;首&#xff09;字节的指针…

HT46R002 贴片 SOP8 经济型AD型OTP MCU单片机芯片

HT46R002在智能家居中的具体应用案例可以包括以下几个方面&#xff1a; 1. 智能照明控制&#xff1a;可以用于控制LED灯的亮度和色温&#xff0c;甚至可以通过手机APP远程控制开关和调节灯光效果。 2. 环境监测&#xff1a;用于监测室内温度、湿度、空气质量等&#xff0c;当检…