记录一下,如果文章在文件夹里改名字了,要再打开一次,要不然vscode里改的bug不影响这个文件。。。
前置知识
使用Tensor来处理数据
“tensor”这个单词一般可译作“张量”,张量可以看作是一个多维数组。标量可以看作是0维张量,向量可以看作1维张量,矩阵可以看作是二维张量。
Tensor概念解释
- 飞桨使用张量(Tensor) 来表示神经网络中传递的数据
- Tensor 可以理解为多维数组,类似于 Numpy 数组(ndarray) 的概念
- 与 Numpy 数组相比,Tensor 除了支持运行在 CPU 上,还支持运行在 GPU 及各种 AI 芯片上,以实现计算加速
- 飞桨基于 Tensor,实现了深度学习所必须的反向传播功能和多种多样的组网算子,从而可更快捷地实现深度学习组网与训练等功能
Tensor的创建
指定数据创建
与 Numpy 创建数组方式类似,通过给定 Python 序列(如列表 list、元组 tuple),可使用 paddle.to_tensor 创建任意维度的 Tensor
创建类似向量的1维Tensor
1
2
3
4
5import paddle
2.0, 3.0, 4.0]) ndim_1_Tensor = paddle.to_tensor([
print(ndim_1_Tensor)
Tensor(shape=[3], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[2., 3., 4.])ndim:维度
如果仅输入单个标量(scalar)数据(例如 float/int/bool 类型的单个元素),则会创建形状为 [1] 的 Tensor,即 0 维 Tensor:1
2
3
4
5
6
7
82) paddle.to_tensor(
Tensor(shape=[], dtype=int64, place=Place(gpu:0), stop_gradient=True,
2)
2]) paddle.to_tensor([
Tensor(shape=[1], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[2])这两个有区别,维度不同,一个0维,一个1维
创建类似矩阵的2维Tensor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
191,2,3],[2,3,4]]) ndim_2_Tensor = paddle.to_tensor([[
print(ndim_2_Tensor)
Tensor(shape=[2, 3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[1, 2, 3],
[2, 3, 4]])
1.0,2.0,3.0],[2,3,4]]) ndim_2_Tensor = paddle.to_tensor([[
print(ndim_2_Tensor)
Tensor(shape=[2, 3], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[[1., 2., 3.],
[2., 3., 4.]])
1.0,2.0,3.0],[2.0,3.0,4.0]]) ndim_2_Tensor = paddle.to_tensor([[
print(ndim_2_Tensor)
Tensor(shape=[2, 3], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[[1., 2., 3.],
[2., 3., 4.]])创建3维Tensor
1
2
3
4
5
6
7
81,2,3],[2,3,4]],[[3,4,5],[4,5,6]]]) ndim_3_Tensor = paddle.to_tensor([[[
print(ndim_3_Tensor)
Tensor(shape=[2, 2, 3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[[1, 2, 3],
[2, 3, 4]],
[[3, 4, 5],
[4, 5, 6]]])上述不同维度的 Tensor 可视化的表示如下图所示(数据可能不同,看个图示就行):
需要注意的是,Tensor 必须形如矩形,即在任何一个维度上,元素的数量必须相等,否则会抛出异常,示例如下:
1
2
3
4
5
6
7
8
9
101,2,3],[1,2]]) x = paddle.to_tensor([[
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\lxcqm\anaconda3\envs\Paddle_Py3.12\Lib\site-packages\paddle\tensor\creation.py", line 794, in to_tensor
return _to_tensor_non_static(data, dtype, place, stop_gradient)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\lxcqm\anaconda3\envs\Paddle_Py3.12\Lib\site-packages\paddle\tensor\creation.py", line 577, in _to_tensor_non_static
data = np.array(data)
^^^^^^^^^^^^^^
ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (2,) + inhomogeneous part.
说明:- 飞桨也支持将 Tensor 转换为 Python 序列数据,可通过 paddle.tolist 实现,飞桨实际的转换处理过程是 Python 序列 <-> Numpy 数组 <-> Tensor。
- 基于给定数据创建 Tensor 时,飞桨是通过拷贝方式创建,与原始数据不共享内存。
指定形状创建
如果要创建一个指定形状的 Tensor,可以使用 paddle.zeros、paddle.ones、paddle.full 实现:
1 | 2,3]) paddle.zeros([ |
1 | 2,3]) paddle.ones([ |
1 | 2,3]) paddle.twos([ |
1 | 2,4],4) paddle.full([ |
指定区间创建
1 | paddle.arange(start, end, step) # 创建以步长 step 均匀分隔区间[start, end)的 Tensor |
实例:
1 | 1, end=10, step = 2) paddle.arange(start= |
1 | 1,stop=10,num=3) paddle.linspace(start= |
说明:
除了以上指定数据、形状、区间创建 Tensor 的方法,飞桨还支持如下类似的创建方式,如:
- 创建一个空 Tensor,即根据 shape 和 dtype 创建尚未初始化元素值的 Tensor,可通过 paddle.empty 实现。
- 创建一个与其他 Tensor 具有相同 shape 与 dtype 的 Tensor,可通过 paddle.ones_like 、 paddle.zeros_like 、 paddle.full_like 、paddle.empty_like 实现。
- 拷贝并创建一个与其他 Tensor 完全相同的 Tensor,可通过 paddle.clone 实现。
- 创建一个满足特定分布的 Tensor,如 paddle.rand, paddle.randn , paddle.randint 等。
- 通过设置随机种子创建 Tensor,可每次生成相同元素值的随机数 Tensor,可通过 paddle.seed 和 paddle.rand 组合实现。
指定图像/文本数据创建
在常见深度学习任务中,数据样本可能是图片(image)、文本(text)、语音(audio)等多种类型,在送入神经网络训练或推理前,这些数据和对应的标签均需要创建为 Tensor。以下是图像场景和 NLP(自然语言处理) 场景中手动转换 Tensor 方法的介绍。
对于图像场景,可使用 paddle.vision.transforms.ToTensor 直接将 PIL.Image 格式的数据转为 Tensor,使用 paddle.to_tensor 将图像的标签(Label,通常是 Python 或 Numpy 格式的数据)转为 Tensor。
对于文本场景,需将文本数据解码为数字后,再通过 paddle.to_tensor 转为 Tensor。不同文本任务标签形式不一样,有的任务标签也是文本,有的则是数字,均需最终通过 paddle.to_tensor 转为 Tensor。
下面以图像场景为例介绍,以下示例代码中将随机生成的图片转换为 Tensor:
1 | import numpy as np |
输出:
1 | (Paddle_Py3.12) C:\Users\lxcqm>python D:\PyTorch\Pytorch1\main.py |
说明:
实际编码时,由于飞桨数据加载的 paddle.io.DataLoader API 能够将原始 paddle.io.Dataset 定义的数据自动转换为 Tensor,所以可以不做手动转换。具体如下节介绍。
自动创建Tensor的功能介绍
除了手动创建 Tensor 外,实际在飞桨框架中有一些 API 封装了 Tensor 创建的操作,从而无需用户手动创建 Tensor。例如 paddle.io.DataLoader 能够基于原始 Dataset,返回读取 Dataset 数据的迭代器,迭代器返回的数据中的每个元素都是一个 Tensor。另外在一些高层 API,如 paddle.Model.fit 、paddle.Model.predict ,如果传入的数据不是 Tensor,会自动转为 Tensor 再进行模型训练或推理。
说明:
paddle.Model.fit、paddle.Model.predict 等高层 API 支持传入 Dataset 或 DataLoader,如果传入的是 Dataset,那么会用 DataLoader 封装转为 Tensor 数据;如果传入的是 DataLoader,则直接从 DataLoader 迭代读取 Tensor 数据送入模型训练或推理。因此即使没有写将数据转为 Tensor 的代码,也能正常执行,提升了编程效率和容错性。
以下示例代码中,分别打印了原始数据集的数据,和送入 DataLoader 后返回的数据,可以看到数据结构由 Python list 转为了 Tensor:
Compose&Normalize
Compose:
Compose
是一个组合多个图像变换的工具。你可以将多个变换(transforms)放入一个列表中,然后传递给Compose
。当你应用这个组合变换到一个图像上时,这些变换会按照列表中的顺序一个接一个地应用到图像上。- 例如,如果你想先对图像进行归一化,然后再将其转换为 Tensor,你可以这样做:
1
2
3
4transforms = Compose([
Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
ToTensor()
])
Normalize:
Normalize
用于对图像进行标准化。在深度学习中,我们经常对输入数据进行标准化,使其具有零均值和单位方差,这有助于模型的训练。Normalize
需要两个参数:mean
和std
,分别代表每个通道的均值和标准差。对于 RGB 图像,这三个通道分别是红色、绿色和蓝色。- 在上面的例子中,我们使用了
[0.5, 0.5, 0.5]
作为均值和[0.5, 0.5, 0.5]
作为标准差,这实际上是将图像的像素值从 [0, 1] 范围转换到 [-1, 1] 范围。
总之,Compose
和 Normalize
是 PaddlePaddle 中非常有用的工具,用于图像预处理和增强。
1 | import paddle |
输出:
1 | (Paddle_Py3.12) C:\Users\lxcqm>python D:\PyTorch\Pytorch1\main.py |
Tensor的属性
在前文中,可以看到打印 Tensor 时有 shape、dtype、place 等信息,这些都是 Tensor 的重要属性,想要了解如何操作 Tensor 需要对其属性有一定了解,接下来分别展开介绍 Tensor 的属性相关概念:
1 | Tensor(shape=[3], dtype=float32, place=Place(gpu:0), stop_gradient=True, |
Tensor的形状
形状的介绍
形状是 Tensor 的一个重要的基础属性,可以通过 Tensor.shape 查看一个 Tensor 的形状,以下为相关概念:shape:描述了 Tensor 每个维度上元素的数量。
ndim: Tensor 的维度数量,例如向量的维度为 1,矩阵的维度为 2,Tensor 可以有任意数量的维度。
axis 或者 dimension:Tensor 的轴,即某个特定的维度。
size:Tensor 中全部元素的个数。
创建 1 个四维 Tensor ,并通过图形来直观表达以上几个概念之间的关系:
1
ndim_4_Tensor = paddle.ones([2, 3, 4, 5])
axis0:2的意思是0轴,数值为2
1
2
3
4
5
6
7
8
9
10import paddle
ndim_4_Tensor = paddle.ones([2, 3, 4, 5])
print("Data Type of every element:", ndim_4_Tensor.dtype)
print("Number of dimensions:", ndim_4_Tensor.ndim)
print("Shape of Tensor:", ndim_4_Tensor.shape)
print("Elements number along axis 0 of Tensor:", ndim_4_Tensor.shape[0])
print("Elements number along the last axis of Tensor:", ndim_4_Tensor.shape[-1])
输出:
1
2
3
4
5
6
7
8(Paddle_Py3.12) C:\Users\lxcqm>python D:\PyTorch\Pytorch1\main.py
W0314 18:05:09.299489 8612 gpu_resources.cc:119] Please NOTE: device: 0, GPU Compute Capability: 8.9, Driver API Version: 12.4, Runtime API Version: 12.0
W0314 18:05:09.303498 8612 gpu_resources.cc:164] device: 0, cuDNN Version: 8.9.
Data Type of every element: paddle.float32
Number of dimensions: 4
Shape of Tensor: [2, 3, 4, 5]
Elements number along axis 0 of Tensor: 2
Elements number along the last axis of Tensor: 5重置 Tensor 形状(Reshape) 的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import paddle
ndim_1_Tensor = paddle.to_tensor([1, 2, 3])
print("origina:")
print(ndim_1_Tensor)
print("the shape of ndim_1_Tensor:", ndim_1_Tensor.shape)
reshape_Tensor = paddle.reshape(ndim_1_Tensor, [3, 1])
print("[3, 1]:")
print(reshape_Tensor)
print("After reshape:", reshape_Tensor.shape)
reshape_Tensor = paddle.reshape(ndim_1_Tensor, [1, 3])
print("[1, 3]:")
print(reshape_Tensor)
print("After reshape:", reshape_Tensor.shape)输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14original:
Tensor(shape=[3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[1, 2, 3])
the shape of ndim_1_Tensor: [3]
[3, 1]:
Tensor(shape=[3, 1], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[1],
[2],
[3]])
After reshape: [3, 1]
[1, 3]:
Tensor(shape=[1, 3], dtype=int64, place=Place(gpu:0), stop_gradient=True,
[[1, 2, 3]])
After reshape: [1, 3]在指定新的
shape
时存在一些技巧:-1
表示这个维度的值是从Tensor
的元素总数和剩余维度自动推断出来的。因此,有且只有一个维度可以被设置为-1
。0
表示该维度的元素数量与原值相同,因此shape
中0
的索引值必须小于Tensor
的维度(索引值从0
开始计,如第1
维的索引值是0
,第二维的索引值是1
)。
通过几个例子来详细了解:
origin代表原始形状
1
2
3
4
5
6
7
8
9
10origin:[3, 2, 5] reshape:[3, 10] actual: [3, 10] # 直接指定目标 shape
origin:[3, 2, 5] reshape:[-1] actual: [30] # 转换为 1 维,维度根据元素总数推断出来是 3*2*5=30
origin:[3, 2, 5] reshape:[-1, 5] actual: [6, 5] # 转换为 2 维,固定一个维度 5,另一个维度根据元素总数推断出来是 30÷5=6
origin:[3, 2, 5] reshape:[0, -1] actual: [3, 10] # reshape:[0, -1]中 0 的索引值为 0,按照规则,转换后第 0 维的元素数量与原始 Tensor 第 0 维的元素数量相同,为 3;第 1 维的元素数量根据元素总值计算得出为 30÷3=10。
origin:[3, 2] reshape:[3, 1, 0] error: # reshape:[3, 1, 0]中 0 的索引值为 2,但原 Tensor 只有 2 维,无法找到与第 3 维对应的元素数量,因此出错。
从上面的例子可以看到,通过 reshape:[-1] ,可以很方便地将 Tensor 按其在计算机上的内存分布展平为一维。
1
2
3
4
5
6
7import paddle
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("Tensor flattened to Vector:", paddle.reshape(ndim_3_Tensor, [-1]).numpy())
输出:
1
Tensor flattened to Vector: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]
说明:
除了 paddle.reshape 可重置 Tensor 的形状,还可通过如下方法改变 shape:- paddle.squeeze,可实现 Tensor 的降维操作,即把 Tensor 中尺寸为 1 的维度删除。
- paddle.unsqueeze,可实现 Tensor 的升维操作,即向 Tensor 中某个位置插入尺寸为 1 的维度。
- paddle.flatten,将 Tensor 的数据在指定的连续维度上展平。
- paddle.transpose,对 Tensor 的数据进行重排。
原位(Inplace)操作和非原位操作的区别
课程笔记
创建一个tensor
IN
1 | import paddle |
OUT
1 | Tensor(shape=[12], dtype=int64, place=Place(gpu:0), stop_gradient=True, |
改变张量形状
IN
1 | import paddle |
OUT
1 | Tensor(shape=[3, 4], dtype=int64, place=Place(gpu:0), stop_gradient=True, |
tensor的计算
IN
1 | import paddle |
OUT
1 | Tensor(shape=[4], dtype=float32, place=Place(gpu:0), stop_gradient=True, |
tensor的连结
IN
1 | import paddle |
OUT
1 | Tensor(shape=[6, 4], dtype=float32, place=Place(gpu:0), stop_gradient=True, |
IN
1 | import paddle |
OUT
1 | Tensor(shape=[3, 8], dtype=float32, place=Place(gpu:0), stop_gradient=True, |
通过逻辑运算符构建二维tensor
IN
1 | import paddle |
OUT
1 | Tensor(shape=[3, 4], dtype=bool, place=Place(gpu:0), stop_gradient=True, |
求和产生一个元素的张量
IN
1 | import paddle |
OUT
1 | Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True, |
IN
1 | y = paddle.to_tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) |
OUT
1 | Tensor(shape=[4], dtype=float32, place=Place(gpu:0), stop_gradient=True, |
广播机制
IN
1 | import paddle |
OUT
1 | Tensor(shape=[3, 1], dtype=int64, place=Place(gpu:0), stop_gradient=True, |
访问元素
IN
1 | import paddle |
OUT
1 | Tensor(shape=[2, 1], dtype=int64, place=Place(gpu:0), stop_gradient=True, |
赋值矩阵中的元素
赋值单个元素
IN
1 | import paddle |
OUT
1 | Tensor(shape=[3, 1], dtype=int64, place=Place(gpu:0), stop_gradient=True, |
赋值多个元素
IN
1 | import paddle |
OUT
1 | Tensor(shape=[3, 4], dtype=int64, place=Place(gpu:0), stop_gradient=True, |