Paddle点灯人介绍
Paddle点灯人这个专栏,我希望更多是给予部分已有深度学习基础亦或者是想快速部署应用的进行学习,这样利用paddle做出更简单更好的方法,因为如果从0开始写Paddle的使用和介绍,我相信paddle的文档已经很详细了,如果想从0开始学,可以查看https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/index_cn.html
所以我希望我基于此,更多的是为了,让大家在学习torch的同时,对paddle也有一定了解,可以使用paddle更便捷的部署在自己的项目中,这样能加快学习的效率,也可以免费使用paddle的GPU资源,有更好的产出和应用部署。
Tensor介绍
Tensor,又名张量,读者可能对这个名词似曾相识,可以说他几乎在所有的深度学习框架中都出现过,也是Theano、TensorFlow、
Torch和MxNet中重要的数据结构。关于张量的本质不乏深度的剖析,但从工程角度来讲,可简单地认为它就是一个数组,且支持高效的科学计算。它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)和更高维的数组(高阶数据)。Tensor和Numpy的ndarrays类似,和pytorch一样的是,他们都可以运行在 GPU 及各种 AI 芯片上,以实现计算加速。
Pytorch和Paddle的相似之处
Paddle和Pytorch是两个流行的深度学习框架。Paddle是由百度开发的开源框架,而Pytorch是由英伟达开发的开源框架。它们都提供了大量的工具和功能,帮助开发人员构建和训练深度学习模型。
那我们为什么写Pytorch版的Paddle呢,这个名字取的有点奇怪哈哈,这就是我的一个小小的理解,或者说,在这一部分,几乎可以认为两者是差不多的,似乎这是一个Pytorch版的paddle,为什么这么说呢,如果你有学习过Pytorch,你会发现其实Paddle的张量很多操作都是类似与Pytorch的方法,这也可能会更好的从Pytorch迁移到Paddle中,两者也更容易进行转化,还是非常方便进行学习迁移和部署的。
不过,Paddle和Pytorch之间也有一些区别。Paddle专注于提供高性能的分布式训练和计算,而Pytorch提供了更多用于计算机视觉和自然语言处理的工具。此外,Paddle和Pytorch使用不同的技术和算法,因此它们可能在某些情况下表现不同。例如,Paddle使用了更多的预训练模型和数据集,而Pytorch则提供了更多的模型自定义选项和更为灵活的模型训练过程。
创建张量Tensor
与 Numpy 创建数组方式类似,通过给定 Python 序列(如列表 list、元组 tuple),可使用 paddle.to_tensor 创建任意维度的 Tensor。示例如下:
(1)创建类似向量(vector)的 1 维 Tensor:
import paddle # 后面的示例代码默认已导入 paddle 模块 ndim_1_Tensor = paddle.to_tensor([2.0, 3.0, 4.0]) print(ndim_1_Tensor)
Tensor(shape=[3], dtype=float32, place=Place(gpu:0), stop_gradient=True, [2., 3., 4.])
特殊地,如果仅输入单个标量(scalar)数据(例如 float/int/bool 类型的单个元素),则会创建形状为 [1] 的 Tensor,即 0 维 Tensor:
paddle.to_tensor(2) paddle.to_tensor([2])
# 上述两种创建方式完全一致,形状均为 [1],输出如下: Tensor(shape=[1], dtype=int64, place=Place(gpu:0), stop_gradient=True, [2])
(2)创建类似矩阵(matrix)的 2 维 Tensor:
ndim_2_Tensor = paddle.to_tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) print(ndim_2_Tensor)
Tensor(shape=[2, 3], dtype=float32, place=Place(gpu:0), stop_gradient=True, [[1., 2., 3.], [4., 5., 6.]])
(3)创建 3 维 Tensor:
ndim_3_Tensor = paddle.to_tensor([[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]], [[11, 12, 13, 14, 15], [16, 17, 18, 19, 20]]]) print(ndim_3_Tensor) Tensor(shape=[2, 2, 5], dtype=int64, place=Place(gpu:0), stop_gradient=True, [[[1 , 2 , 3 , 4 , 5 ], [6 , 7 , 8 , 9 , 10]], [[11, 12, 13, 14, 15], [16, 17, 18, 19, 20]]])
上述不同维度的 Tensor 可视化的表示如下图所示:
图 不同维度的 Tensor 可视化表示
图片/文本转Tensor
其实对于pytorch来说,我们有一个东西叫transform,里面可以直接进行ToTensor,也就是将读入的图片直接转化为Tensor张量,同理来说,Paddle也有相类似的方法,里面也有一个tramsforms函数,也有一个ToTensor方法,所以说两者的方法格式还是非常类似的。
对于图像场景,可使用 paddle.vision.transforms.ToTensor 直接将 PIL.Image 格式的数据转为 Tensor,使用 paddle.to_tensor 将图像的标签(Label,通常是 Python 或 Numpy 格式的数据)转为 Tensor。
- 对于文本场景,需将文本数据解码为数字后,再通过 paddle.to_tensor 转为 Tensor。不同文本任务标签形式不一样,有的任务标签也是文本,有的则是数字,均需最终通过 paddle.to_tensor 转为 Tensor。
import numpy as np from PIL import Image import paddle.vision.transforms as T import paddle.vision.transforms.functional as F fake_img = Image.fromarray((np.random.rand(224, 224, 3) * 255.).astype(np.uint8)) # 创建随机图片 transform = T.ToTensor() tensor = transform(fake_img) # 使用 ToTensor()将图片转换为 Tensor print(tensor)
Tensor(shape=[3, 224, 224], dtype=float32, place=Place(gpu:0), stop_gradient=True, [[[0.78039223, 0.72941178, 0.34117648, ..., 0.76470596, 0.57647061, 0.94901967], ..., [0.49803925, 0.72941178, 0.80392164, ..., 0.08627451, 0.97647065, 0.43137258]]])
有趣的是,我仔细看看打印的结果,在pytorch中的device,在paddle中是place,说明指定的GPU运行,以及pytorch的gradient的自动求导的机制,paddle中为stop_gradient,所以两者还是很类似的。
DataLoader不需要加ToTensor
paddle还有一些不同的点,例如 paddle.io.DataLoader 能够基于原始 Dataset,返回读取 Dataset 数据的迭代器,迭代器返回的数据中的每个元素都是一个 Tensor,不需要类似与Pytorch每次都需要写一个ToTensor
import paddle from paddle.vision.transforms import Compose, Normalize transform = Compose([Normalize(mean=[127.5], std=[127.5], data_format='CHW')]) test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform) print(test_dataset[0][1]) # 打印原始数据集的第一个数据的 label loader = paddle.io.DataLoader(test_dataset) for data in enumerate(loader): x, label = data[1] print(label) # 打印由 DataLoader 返回的迭代器中的第一个数据的 label break
[7] # 原始数据中 label 为 Python list Tensor(shape=[1, 1], dtype=int64, place=Place(gpu_pinned), stop_gradient=True, [[7]]) # 由 DataLoader 转换后,label 为 Tensor
Paddle中的 Tensor 的属性
在前文中,可以看到打印 Tensor 时有 shape、dtype、place 等信息,这些都是 Tensor 的重要属性,想要了解如何操作 Tensor 需要对其属性有一定了解,接下来分别展开介绍 Tensor 的属性相关概念。
形状 shape
首先shape大家其实已经很了解了,这里主要介绍一下,修改shape的一些方法,有
- paddle.reshape 可重置 Tensor 的形状
- paddle.squeeze,可实现 Tensor 的降维操作,即把 Tensor 中尺寸为 1 的维度删除。
- paddle.unsqueeze,可实现 Tensor 的升维操作,即向 Tensor 中某个位置插入尺寸为 1 的维度。
- paddle.flatten,将 Tensor 的数据在指定的连续维度上展平。
- paddle.transpose,对 Tensor 的数据进行重排。
数据类型(dtype)
Tensor 的数据类型 dtype 可以通过 Tensor.dtype 查看,支持类型包括:bool
、float16
、float32
、float64
、uint8
、int8
、int16
、int32
、int64
、complex64
、complex128
。
同一 Tensor 中所有元素的数据类型均相同,通常通过如下方式指定:
通过给定 Python 序列创建的 Tensor,可直接使用 dtype 参数指定。如果未指定:
- 对于 Python 整型数据,默认会创建
int64
型 Tensor;
- 对于 Python 浮点型数据,默认会创建
float32
型 Tensor,并且可以通过 paddle.set_default_dtype 来调整浮点型数据的默认类型。
Tensor 的设备位置(place)
初始化 Tensor 时可以通过 Tensor.place 来指定其分配的设备位置,可支持的设备位置有:CPU、GPU、固定内存、XPU(Baidu Kunlun)、NPU(Huawei)、MLU(寒武纪)、IPU(Graphcore)等。其中固定内存也称为不可分页内存或锁页内存,其与 GPU 之间具有更高的读写效率,并且支持异步传输,这对网络整体性能会有进一步提升,但其缺点是分配空间过多时可能会降低主机系统的性能,因为其减少了用于存储虚拟内存数据的可分页内存。
说明:
当未指定 place 时,Tensor 默认设备位置和安装的飞桨框架版本一致。如安装了 GPU 版本的飞桨,则设备位置默认为 GPU,即 Tensor 的place 默认为 paddle.CUDAPlace。
使用 paddle.device.set_device 可设置全局默认的设备位置。Tensor.place 的指定值优先级高于全局默认值。
以下示例分别创建了 CPU、GPU 和固定内存上的 Tensor,并通过 Tensor.place
查看 Tensor 所在的设备位置:
- 创建 CPU 上的 Tensor
cpu_Tensor = paddle.to_tensor(1, place=paddle.CPUPlace()) print(cpu_Tensor.place)
Place(cpu)
- 创建 GPU 上的 Tensor
gpu_Tensor = paddle.to_tensor(1, place=paddle.CUDAPlace(0)) print(gpu_Tensor.place) # 显示 Tensor 位于 GPU 设备的第 0 张显卡上
Place(gpu:0)
- 创建固定内存上的 Tensor
pin_memory_Tensor = paddle.to_tensor(1, place=paddle.CUDAPinnedPlace()) print(pin_memory_Tensor.place)
Place(gpu_pinned)
stop_gradient 属性
这个属性很像Pytorch,也就是说明是否求导,也就是是否计算梯度,stop_gradient 表示是否停止计算梯度,默认值为 True,表示停止计算梯度,梯度不再回传。在设计网络时,如不需要对某些参数进行训练更新,可以将参数的 stop_gradient 设置为 True。可参考以下代码直接设置 stop_gradient 的值。
eg = paddle.to_tensor(1) print("Tensor stop_gradient:", eg.stop_gradient) eg.stop_gradient = False print("Tensor stop_gradient:", eg.stop_gradient)
Tensor stop_gradient: True Tensor stop_gradient: False
Tensor 与 Numpy 数组相互转换
这一部分也几乎和Pytorch是一模一样的,所以如果你已熟悉 Numpy,通过以下要点,可以方便地理解和迁移到 Tensor 的使用上:
Tensor 的很多基础操作 API 和 Numpy 在功能、用法上基本保持一致。如前文中介绍的指定数据、形状、区间创建 Tensor,Tensor 的形状、数据类型属性,Tensor 的各种操作,以及 Tensor 的广播,可以很方便地在 Numpy 中找到相似操作。
但是,Tensor 也有一些独有的属性和操作,而 Numpy 中没有对应概念或功能,这是为了更好地支持深度学习任务。如前文中介绍的通过图像、文本等原始数据手动或自动创建 Tensor 的功能,能够更便捷地处理数据,Tensor 的设备位置属性,可以很方便地将 Tensor 迁移到 GPU 或各种 AI 加速硬件上,Tensor 的 stop_gradient 属性,也是 Tensor 独有的,以便更好地支持深度学习任务。
如果已有 Numpy 数组,可使用 paddle.to_tensor 创建任意维度的 Tensor,创建的 Tensor 与原 Numpy 数组具有相同的形状与数据类型。
tensor_temp = paddle.to_tensor(np.array([1.0, 2.0])) print(tensor_temp)
Tensor(shape=[2], dtype=float64, place=Place(gpu:0), stop_gradient=True, [1., 2.])
注意:
- 基于 Numpy 数组创建 Tensor 时,飞桨是通过拷贝方式创建,与原始数据不共享内存。
相对应地,飞桨也支持将 Tensor 转换为 Numpy 数组,可通过 Tensor.numpy 方法实现。
tensor_to_convert = paddle.to_tensor([1.,2.]) tensor_to_convert.numpy()
array([1., 2.], dtype=float32)
总结
在学习过程中,我发现对于这一部分来说,Paddle和Pytorch有高度相似性,如果掌握了其中一个,另一个的Tensor张量操作就已经拿捏了,因为两者是非常之像的,可以互通的。
此外,这一部分参考了一下Paddle的文档https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/beginner/tensor_cn.html,里面写的更详细更全面,如果你从0开始学起可以看看整篇文档。