pytorch

PyTorch是一个基于Python的科学计算包,它主要有两个用途:

1、类似Numpy但是能利用GPU加速
2、一个非常灵活和快速的用于深度学习的研究平台

tensor

Tensor,也叫做张量,类似与NumPy的ndarray,但是可以用GPU加速。

而对于tensor的基础操作,可以从两个方面来讲。

一、如果从接口的角度,对一个tensor的操作可以分为两类:
torch.function,如torch.save
tensor.function,如tensor.view

对于这两种接口方法,大多数时候都是等价的,如torch.sum(a,b)a.sum(b)

二、如果从存储的角度讲,对tensor的操作也可以分为两类:
a.add(b),不会修改a自身的数据,加法的结果会返回一个新的tensor
a.add_(b),会修改a自身的数据,也就是说加法的结果存在a中

函数名以_结尾的都是修改调用者自身的数据。

创建tensor

以下是一些创建的函数,其中如果*size为列表,则按照列表的形状生成张量,否则传入的参数看作是张量的形状:

1
2
3
4
5
6
7
8
9
10
Tensor(*size)	    # 基础构造函数,默认为dtype(FloatTensor)类型
tensor(data) # 根据传入的数据创建张量,会根据传入的值推测类型。
ones(*sizes) # 全1Tensor
zeros(*sizes) # 全0Tensor
eye(*sizes) # 对角矩阵(对角线为1,其他为0,不要求行列一致)
arrange(s,e,step) # 从s到e,步长为step
linspace(s,e,steps) # 从s到e,均匀分成steps份
rand/randn(*sizes) # 均匀/标准分布
normal(mean,std)/uniform(from,tor) # 正态分布/均匀分布
randperm(m) # 随机排列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = t.Tensor(2, 3)      # 指定形状构建2*3维的张量
# tensor([[0., 0., 0.],
# [0., 0., 0.]])

b = t.tensor([[1, 2, 3], [4, 5, 6]]) # 通过传入列表构建2*3维的张量,类型为自动推测
# tensor([[1, 2, 3],
# [4, 5, 6]])

b = t.Tensor([[1, 2, 3], [4, 5, 6]]) # 通过传入列表构建2*3维的张量,类型为FloatTensor
# tensor([[1., 2., 3.],
# [4., 5., 6.]])

b.size() # 返回b的大小,等价于b.shape
b.numel() # 计算b中的元素个数,等价于b.nelement()

t.Tensor(*size)创建tensor时,系统不会马上分配空间,只有使用到tensor时才会分配内存,而其他操作都是在创建tensor后马上进行空间分配。

常用tensor操作

调整tensor的形状

view()方法调整tensor的形状,但是必须得保证调整前后元素个数一致,但是view方法不会修改原tensor的形状和数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = t.arange(0, 6)
# # tensor([0, 1, 2, 3, 4, 5])

b = a.view(2, 3)
# tensor([[0, 1, 2],
# [3, 4, 5]])

c = a.view(-1, 2) # -1会自动计算大小。
# tensor([[0, 1],
# [2, 3],
# [4, 5]])

a[1] = 0 # view方法返回的tensor和原tensor共享内存,修改一个,另外一个也会修改
print(f'a: {a}\n\n c:{c}')
# a: tensor([0, 0, 2, 3, 4, 5])
# c:tensor([[0, 0],
# [2, 3],
# [4, 5]])

resize()是另一种用来调整size的方法,但是它相比较view,可以修改tensor的尺寸,如果尺寸超过了原尺寸,则会自动分配新的内存,反之,则会保留老数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = t.arange(0, 6)
# tensor([0, 1, 2, 3, 4, 5])

a.resize_(2, 3 )
# tensor([[0, 1, 2],
# [3, 4, 5]])

a.resize_(3, 3) # 超过了的自动分配新的内存
# tensor([[0, 1, 2],
# [3, 4, 5],
# [0, 0, 0]])

a.resize_(2, 2) # 直接截取原数据
# tensor([[0, 1],
# [2, 3]])

添加或压缩tensor维度

unsqueeze()可以增加tensor的维度;squeeze()可以压缩tensor的维度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
a = t.arange(0, 6)
# tensor([0, 1, 2, 3, 4, 5])
# torch.Size([6])

b = a.unsqueeze(1) # 在第1维上增加“1”维,从6x0变成6x1
# tensor([[0],
# [1],
# [2],
# [3],
# [4],
# [5]])
# torch.Size([6, 1])

a = t.arange(0, 6)
a.resize_(2, 3)
# tensor([[0, 1, 2],
# [3, 4, 5]])
# torch.Size([2, 3])

b = a.unsqueeze(0) # 在第0维上增加“1”维,从2x3变成1x2x3
# tensor([[[0, 1, 2],
# [3, 4, 5]]])
# torch.Size([1, 2, 3])

c = a.unsqueeze(-1) # 在倒数第1维上增加“1”,也就是2*3的形状变成2*3*1。
# tensor([[[0],
# [1],
# [2]],
# [[3],
# [4],
# [5]]])
# torch.Size([2, 3, 1])
1
2
3
4
5
6
7
8
9
10
11
a = t.arange(0, 6)
b = a.view(1, 2, 3)
# tensor([[[0, 1, 2],
# [3, 4, 5]]])

c = b.squeeze(0) # 压缩第0维的“1”,某一维度为“1”才能压缩,如果第0维的维度是“2”如(2,1,3)则无法亚索第0维
# tensor([[0, 1, 2],
# [3, 4, 5]])
# torch.Size([2, 3])

b.squeeze() # 把所有维度为“1”的压缩。

索引操作

tensor的索引操作和ndarray的索引操作类似,并且索引出来的结果与原tensor共享内存

高级索引

PyTorch 支持绝大多数 NumPy 的高级索引,高级索引可以看成是基本索引的扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = torch.arange(9).view([3, 3])
# tensor([[0, 1, 2],
# [3, 4, 5],
# [6, 7, 8]])

print(a[[0, 1], ...]) # 等价a[0]和a[1],相当于索引张量的第一行和第二行元素;
# tensor([[0, 1, 2],
# [3, 4, 5]])

print(a[[0, 1], [1, 2]]) # 等价a[0, 1]和a[1, 2],相当于索引张量的第一行的第二列和第二行的第三列元素;
# tensor([1, 5])

print(a[[1, 0, 2], [0]]) # 等价a[1, 0]和a[0, 0]和a[2, 0]
# tensor([3, 0, 6])
1
2
3
4
5
index_select(input,dim: _int, index: Tensor)	# 在指定维度dim上选取,例如选取某些行、某些列。输出结果与输入张量的维度相同。
masked_select(input, mask: Tensor) # a.masked_select(a>1)等价于a[a>1]
non_zero(input) # 获取非0元素的下标
gather(input,dim,index) # 根据index,在dim维度上选取数据,输出的size与index一样
take(index: Tensor) # 在原来Tensor的shape基础上打平,然后在打平后的Tensor上进行索引。

index_select

1
2
3
4
5
6
7
8
a = t.arange(9).view([3, 3])
# tensor([[0, 1, 2],
# [3, 4, 5],
# [6, 7, 8]])

b = a.index_select(0, t.tensor([0, 2])) # 选择第0维的0,2元素数据
# tensor([[0, 1, 2],
# [6, 7, 8]])

masked_select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = t.arange(9).view(3, 3)
# tensor([[0, 1, 2],
# [3, 4, 5],
# [6, 7, 8]])

mask = a.gt(5) # 生成a这个Tensor中大于5的元素的掩码
# tensor([[False, False, False],
# [False, False, False],
# [ True, True, True]])

b = a.masked_select(mask) # 取出a这个Tensor中大于5的元素
# tensor([6, 7, 8])
# torch.Size([3])

c = a[a>5]
# tensor([6, 7, 8])

take

1
2
3
4
5
6
7
a = t.arange(9).view(3, 3)
# tensor([[0, 1, 2],
# [3, 4, 5],
# [6, 7, 8]])

b = a.take(t.tensor([0, 2, 5]))
# tensor([0, 2, 5])

PyTorch的nn模块

PyTorch有一个专门用于神经网络的完整子模块:torch.nn。该子模块包含创建各种神经网络体系结构所需的构建块。这些构建块在PyTorch术语中称为module(模块),在其他框架中称为layer(层)。

PyTorch模块都是从基类nn.Module继承而来的Python类。模块可以具有一个或多个参数(Parameter)实例作为属性,这些参数就是在训练过程中需要优化的张量(在之前的线性模型中即w和b)。模块还可以具有一个或多个子模块(nn.Module的子类)属性,并且也可以追踪其参数。

你可以毫不奇怪地可以找到一个名为nn.Linearnn.Module子类,它对其输入进行仿射变换(通过参数属性weight和bias);它就相当于之前在温度计实验中实现的方法。现在,从上次中断的地方开始,将之前的代码转换为使用nn的形式。

所有PyTorch提供的nn.Module子类都定义了其调用方法,使你可以实例化nn.Linear并将其像一个函数一样进行调用,如下面的代码所示:

1
2
3
4
import torch.nn as nn

linear_model = nn.Linear(1, 1) # 参数: input size, output size, bias(默认True)
linear_model(t_un_val)

使用一组参数调用nn.Module实例最终会调用带有相同参数的名为forward的方法,forward方法会执行前向传播计算;不过在调用之前和之后还会执行其他相当重要的操作。因此,虽然从技术上讲直接调用forward是可行的,并且它产生的结果与调用nn.Module实例相同,但用户不应该这样做。

nn.Liner表示线性模型(全连接层),nn.Linear的构造函数接受三个参数:输入特征的数量,输出特征的数量以及线性模型是否包含偏差(此处默认为True)。

这里特征的数量是指输入和输出张量的尺寸,因此本例是1和1。例如,如果在输入中同时使用了温度和气压,则在其中输入具有两个特征输入和而输出只有一个特征。如你所见,对于具有多个中间模块的更复杂的模型,模型的容量与特征的数量有关。

文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2023/12/15/机器学习/pytorch/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog