第三章主要介绍Pytorch中最基本的数据结构——tensor的使用方法:创建、运算、存储、与Numpy的互操作性等。
Chapter 3 It starts with a tensor
- 简介
- Tensor的定义
- 索引
- 命名
- 类型
- 存储
- 元数据和连续性
- 设备
- 与Numpy的互操作性
- 序列化
- 练习
简介
- 神经网络通常是分阶段地学习数据从一种表达到另一种表达的转换,这意味着每个阶段的转换数据可以被看做是中间表达序列。例如在图像识别中,浅层的表达可以是边缘或者特定的纹理,而深层的表达可以捕获更加复杂的结构,如耳朵、鼻子和眼睛。
* 神经网络的中间表达其实是一系列浮点数的集合–>刻画输入,并捕获数据的结构
* 当前层的中间表达其实是输入和之前层神经元权重组合的结果。 - PyTorch如何处理和存储数据?引入了基本的数据结构 tensor
tensor: 任意维度向量和矩阵的统称,可以理解为 多维数组
与numpy的arrays相比,tensor的特点:
* 可以在GPU上执行快速计算;
* 可以将运算分配到多个设备或机器上;
* 跟踪图的计算过程。
Tensor的定义
PyTorch中的基本数据结构–>tensor,使用tensor可以表达表达许多类型的数据,例如:图像、时间序列、语句等。
import torch # 导入torch包"""1. 创建一维tensor"""
# 创建一维tensor, size = 3, value = 1
a = torch.ones(3)
print(a)
# 使用下标访问其中的元素
a[0]
# 更改tensor的值
a[1] = 2
a# 输出结果:
tensor([1., 1., 1.])
tensor([1., 2., 1.])- python中的list和tuple:列表和元组中的每一个元素以Python对象的形式被单独分配在内存空间中;
- PyTorch中的tensor和Numpy中的arrays:连续分配在内存块中,每个元素占32bit(4 byte)。
"""2. 创建二维tensor"""
# 传入python list
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
print(points)
# 获取tensor的shape
print(points.shape)
# 使用两个索引访问单独的元素-->返回的是tensor
print(points[1, 1])
# 访问第一个元素
print(points[0])# 输出结果:
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
torch.Size([3, 2])
tensor(3.)
tensor([4., 1.])
索引
points[1:] # 从第一个元素开始到末尾# 输出结果
tensor([[5., 3.],
[2., 1.]])points[1:, :] # 从第一行到所有的行,对应所有的列# 输出结果
tensor([[5., 3.],
[2., 1.]])points[1:, 0] # 从第一行到所有的行,每行取第0个元素# 输出结果
tensor([5., 2.])points[None] # 增加一个维度,与unsqueeze()功能相同tensor([[[4., 1.],
[5., 3.],
[2., 1.]]])命名
以将彩色图像转化为灰度图像为例
适用于PyTorch1.3以上的版本(作为experimental feature)
# 1.定义图片和权重
img_t = torch.randn(3, 5, 5) # shape [channels, rows, column]
batch_t = torch.randn(2, 3, 5, 5) # shape [batch, channels, rows, column]
weights = torch.tensor([0.2126, 0.7152, 0.0722])# 2.在channels维度(总是位于倒数第三个位置)上计算平均值
image_gray_naive = img_t.mean(-3)
batch_gray_navie = batch_t.mean(-3)# 3.对RGB三个通道加权
unsqueezed_weights = weights.unsqueeze(-1).unsqueeze_(-1) # 调整weights的shape为3×1×1
image_weights = img_t * unsqueezed_weights # 使用了广播机制
image_gray_weights = image_weights.sum(-3) # 在channels维度上加和
batch_weights = batch_t * unsqueezed_weights
batch_gray_weights = batch_weights.sum(-3)开始给tensor命名
# 4.在定义tensor时传入names参数,给tensor命名
weights_named = torch.tensor([0.2126, 0.7152, 0.0722], names=['channels'])
weights_named.names# 输出结果
('channels',)# 5.使用.refine_names()为已经定义的tensor添加name
img_named = img_t.refine_names(..., 'channel', 'row', 'column')
batch_named = batch_t.refine_names('batch', 'channel', 'row', 'column')
weights_named = weights.refine_names('channel')
img_named.names, batch_named.names, weights_named.names# 输出结果
(('channel', 'row', 'column'),
('batch', 'channel', 'row', 'column'),
('channel',))# 6.使用align_as()显示地对齐两个tensor,并将维度按照正确的顺序排列
weights_aligned = weights_named.align_as(img_named)
print(weights_aligned.names)
# 接收维度参数的函数也可以接收对应的name
gray_named = (img_named * weights_aligned).sum('channel')
gray_named.names# 输出结果
('channel', 'row', 'column')
('row', 'column')# 7.删除tensor的names
gray_plain = gray_named.rename(None)
gray_plain.names# 输出结果
(None, None)类型
- 定义tensor时可以在tensor的构造器中通过’dtype’参数指定tensor的数据类型,常用的tensor构造器有:tensor()、zeros()、ones()等;
- 默认为32-bit浮点型,即torch.float32 或者 torch.float;
- 当一个tensor作为其他tensor的索引时,必须使用整型类型。定义整型tensor时,默认为64-bit整型,即torch.int64 或者 torch.long;
- 在神经网络执行的计算中,通常使用32位浮点型,更高的精度(64位浮点型)并不会明显地提升模型的准确率,反而会增大内存需求和计算时间;更低的精度(16位浮点型)在标准CPU中没有定义,只有在GPU中存在。
# 1.定义tensor时指定其类型
double_points = torch.zeros(10, 2, dtype=torch.double)
short_points = torch.tensor([[1, 2], [3, 4]], dtype=torch.short)
# 通过.dtype查看tensor的类型
double_points.dtype, short_points.dtype# 输出结果:
(torch.float64, torch.int16)# 2.使用转换函数
float_points = torch.zeros(10, 2).float()
int_points = torch.tensor([[1, 2], [3, 4]]).int()
float_points.dtype, int_points.dtype# 输出结果:
(torch.float32, torch.int32)# 3.使用to()
half_points = torch.zeros(10, 2).to(torch.half) # float16
long_points = torch.tensor([[1, 2], [3, 4]]).to(dtype=torch.long)
half_points.dtype, long_points.dtype# 输出结果:
(torch.float16, torch.int64)- 使用.to()时,底层会检查所执行的类型转换是否必要,如果必要则执行;
- 在进行运算时,会执行自动类型转换:自动转化成较大的数据类型。
存储
- tensor中的数值被分配在连续的内存块中,由torch.Storage类的对象管理;
- storage: 一维数组,是一个包含给定类型数据的连续内存块;
- 二者的关系:无论tensor的维度是多少,storage总是以一维数组的形式存储所有的元素;tensor只是建立在storage上的视图,多个tensors可以在同一个storage上建立不同的索引;如果storage的值发生了改变,在其上建立的所有tensor的值都会发生改变;
- 修改存储的数据
- 不带下划线的方法:不修改原tensor,而是返回一个新的tensor;
- 带下划线的方法:直接改变原tensor–> in-place operation。
# 1.查看tensor对应的storage
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
print(points.shape)
print(points.storage()) # 尽管points是3行2列的张量,但是实际存储的storage是一维的# 输出结果:
torch.Size([3, 2])
4.0
1.0
5.0
3.0
2.0
1.0
[torch.FloatStorage of size 6]# 2.索引storage
points_storage = points.storage()
points_storage[0]# 输出结果:
4.0# 3.改变storage的值
points_storage[3] = 2.0
points# 输出结果:
tensor([[4., 1.],
[5., 2.],
[2., 1.]])# -place运算
a = torch.ones(3, 2)
a.zero_()
a# 输出结果:
tensor([[0., 0.],
[0., 0.],
[0., 0.]])
元数据和连续性
- size: 元组,表示Tensor的每个维度上有多少元素
- offset: tensor中的第一个元素在storage中的下标
- stride: 元组,表示在每个维度上,tensor中的index增加1时,storage中需要跳过的元素个数
- 访问二维tensor中第i行第j列的数据:
;
- 当转置一个tensor或者获取子tensor时,不会重新分配内存,而是分配一个新的tensor,具有与原来tensor不同的元数据;
- 当进行矩阵或者更高维数组时转置时,只需要交换相应维度的stride;
- 关于tensor的连续性
* Storage是一维数据存储tensor中所有元素,当按行优先遍历tensor得到的元素顺序与storage中的元素顺序一致时–>连续,否则为不连续;
* 例如转置后的tensor就是不连续的;
* 某些运算必须在连续的tensor上执行,例如.view()。因此可以使用.contiguous()将不连续的tensor转化为连续的tensor,此时对应的storage和stride会发生改变。
# 1.查看元数据
points.size(), points.storage_offset(), points.stride()# 输出结果:
(torch.Size([3, 2]), 0, (2, 1))# 2.提取一个子tensor
second_point = points[1]
second_point.size(), second_point.storage_offset(), second_point.stride()# 输出结果:
(torch.Size([2]), 2, (1,))# 3.子tensor与原tensor具有相同的storage
second_point[0] = 10.0
points# 输出结果:
tensor([[ 4., 1.],
[10., 2.],
[ 2., 1.]])# 4.若只想改变子tensor,使用.clone()把子tensor克隆一个新的tensor
second_point = points[1].clone()
second_point[1] = 100
second_point, points# 输出结果:
(tensor([ 10., 100.]), tensor([[ 4., 1.],
[10., 2.],
[ 2., 1.]]))# 5.转置tensor
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
points_t = points.t() # 对points进行转置
print(points_t)
# 查看points_t和points的内存地址
print(id(points.storage()) == id(points_t.storage())) # id()用于获取对象的内存地址
# 查看二者对应的stride
print(points.stride(), points_t.stride())# 输出结果:
tensor([[4., 5., 2.],
[1., 3., 1.]])
True
(2, 1) (1, 2)# 6.在更高维上进行转置
some_t = torch.randn(3, 4, 5)
transpose_t = some_t.transpose(0, 2) # 在指定的两个维度上进行转置
some_t.size(), transpose_t.size(), some_t.stride(), transpose_t.stride()# 输出结果:
(torch.Size([3, 4, 5]), torch.Size([5, 4, 3]), (20, 5, 1), (1, 5, 20))# 7.使用.is_contiguous()判断tensor的连续性
points.is_contiguous(), points_t.is_contiguous()# 输出结果:
(True, False)# 使用.contiguous()为不连续tensor创建连续的tensor
points_t_cont = points_t.contiguous()
points_t_cont, points_t_cont.storage()# 输出结果:
(tensor([[4., 5., 2.],
[1., 3., 1.]]), 4.0
5.0
2.0
1.0
3.0
1.0
[torch.FloatStorage of size 6])
设备
- PyTorch中的tensor既可以存储在CPU上,也可以存储在GPU上;每个tensor都可以转移到GPU上进行大规模高速地并行计算
- Tensor的‘device’属性用于管理tensor的存储设备
- cpu–>gpu
- 定义tensor时使用‘device=cuda’直接在GPU上创建tensor
- 已经存在于cpu上的tensor, 使用.to(device=‘cuda’)或者.cuda()将tensor复制到GPU上
- 存在多个GPU时,使用从0开始的整数进行标识,例如,cuda:0 - gpu–>cpu
- 使用.to(device=‘cpu’)或者.cpu() - 使用.to()可以同时改变tensor的‘dtype’和‘device’
# 1.在GPU上创建tensor
points_gpu = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]], device='cuda')
points_gpu# 输出结果:
tensor([[4., 1.],
[5., 3.],
[2., 1.]], device='cuda:0')# 2.使用.to()将CPU上已经创建的tensor复制到GPU上
points = torch.tensor([[4.0, 1.0], [5.0, 3.0], [2.0, 1.0]])
points_to_gpu = points.to(device='cuda:0') # 返回一个新的tensor,存储在GPU的RAM上;含有多个GPU时,使用从0开始的整数表示具体使用哪个GPU
print(points)
print(points_to_gpu)
# 使用.cuda()
points_to_gpu_2 = points.cuda() # .cuda()还可以传入参数,表示使用哪个GPU,eg:0
print(points_to_gpu_2)# 输出结果:
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
tensor([[4., 1.],
[5., 3.],
[2., 1.]], device='cuda:0')
tensor([[4., 1.],
[5., 3.],
[2., 1.]], device='cuda:0')# 3.如果tensor定义或者被复制到GPU上,基于该tensor的运算也在GPU上进行
d_points = 2 * points
d_points_gpu = 2 * points.to(device='cuda')
a_points_gpu = d_points_gpu + 4
d_points, d_points_gpu, a_points_gpu# 输出结果:
(tensor([[ 8., 2.],
[10., 6.],
[ 4., 2.]]), tensor([[ 8., 2.],
[10., 6.],
[ 4., 2.]], device='cuda:0'), tensor([[12., 6.],
[14., 10.],
[ 8., 6.]], device='cuda:0'))# 4.将tensor从GPU转移到CPU
points_cpu = points_gpu.to(device='cpu')
points_cpu2 = points_gpu.cpu()
points_cpu, points_cpu2# 输出结果:
(tensor([[4., 1.],
[5., 3.],
[2., 1.]]), tensor([[4., 1.],
[5., 3.],
[2., 1.]]))
与Numpy的互操作性
- tensor–>array:使用.numpy()
- array–>tensor:使用torch.from_numpy(array)
- 注意,这两种形式转换的tensor和array共享底层的缓冲区–>修改其中一个会导致另一个的改变
# tensor与array的转换
# tensor-->array
a = torch.ones(3, 4)
a_np = a.numpy()
print(a.dtype, a_np.dtype)
# array-->tensor
b = torch.from_numpy(a_np)
b# 输出结果:
torch.float32 float32
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
序列化
- 使用torch.save()和torch.load()
- torch.save(): 将tensor保存到文件中
- torch.load(): 从文件中加载tensor
这种方法与Python的其他库不兼容,只能在PyTorch中使用 - 基于HDF5文件格式,使用Python的h5py库
- HDF5是一种表达序列化多维数组的常用格式,以嵌套字典的形式组织数据;
- h5py库:接收和返回的数据形式为numpy的array。
# 1.基于torch.save()和torch.load()
with open('temp.t', 'wb')as f:
torch.save(points, f)
with open('temp.t', 'rb')as f:
points = torch.load(f)
points# 输出结果:
tensor([[4., 1.],
[5., 3.],
[2., 1.]])# 2.基于h5py
import h5py
f = h5py.File('temp.t', 'w')
dset = f.create_dataset('coords', data=points.numpy()) # 两个参数分别为key value
f.close()f = h5py.File('temp.t', 'r')
dset = f['coords']
all_points = torch.from_numpy(dset[:])
print(all_points)
f.close()# 输出结果:
tensor([[4., 1.],
[5., 3.],
[2., 1.]])
练习
# 1.从list(range(9))创建tensor, 查看其元数据
a = torch.tensor(list(range(9)))
a.size(), a.storage_offset(), a.stride()# 输出结果:
(torch.Size([9]), 0, (1,))# 2.使用.view()创建新的tensor
b = a.view(3, 3)
b, id(a.storage())==id(b.storage()) # 二者共享底层存储单元# 输出结果:
(tensor([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]), True)c = b[1:, 1:]
c, c.size(), c.storage_offset(), c.stride()# 输出结果:
(tensor([[4, 5],
[7, 8]]), torch.Size([2, 2]), 4, (3, 1))# 3.对a执行元素级数学运算
a_square = torch.square(a)
a_square# 输出结果:
tensor([ 0, 1, 4, 9, 16, 25, 36, 49, 64])a_sqrt = torch.sqrt(a)# 输出结果:
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-24-096f487a51f0> in <module>()
----> 1 a_sqrt = torch.sqrt(a)
RuntimeError: sqrt_vml_cpu not implemented for 'Long'
报错:a的数据类型为Long,但是torch.sqrt()方法不支持对长整型的运算# 转换数据类型
a_sqrt = torch.sqrt(a.float())
a_sqrt# 输出结果:
tensor([0.0000, 1.0000, 1.4142, 1.7321, 2.0000, 2.2361, 2.4495, 2.6458, 2.8284])
















