推荐文章《PyTorch 学习笔记汇总(完结撒花)》
为了能够计算权重梯度和数据梯度,神经网络需记录运算的过程,并构建出计算图。
下面介绍主要模块。具体都可以参考官方文档。
pytorch提供专门的torch.Tensor类,根据张量的数据格式和存储设备(CPU/GPU)来存储张量。
Tensors 类似于 NumPy 的 ndarrays ,同时 Tensors 可以使用 GPU 进行计算。
详细的张量操作参考:torch.Tensor、张量创建和运算: torch
torch.tensor([[1., -1.], [1., -1.]])#python列表转为张量,子列表长度必须一致
torch.tensor(np.array([[1, 2, 3], [4, 5, 6]]))#ndarray数组转为张量
x_np = torch.from_numpy(np_array)
张量转为numpy数组,大小为1的张量可以转为python标量:
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
A = X.numpy()
B = torch.tensor(A)
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
- 利用函数创建张量
shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
函数 | 功能 |
---|---|
ensor(sizes) | 基础构造函数 |
tensor(data) | 类似于np.array |
ones(sizes) | 全1 |
zeros(sizes) | 全0 |
eye(sizes) | 对角为1,其余为0 |
arange(s,e,step) | 从s到e,步长为step |
linspace(s,e,steps) | 从s到e,均匀分成step份 |
randn(sizes) | 标准正态分布 |
rand(size) | [0,1)j均匀分布 |
normal(mean,std) | 正态分布 |
uniform(from,to) | 均匀分布 |
randint(a,b,(sizes)) | 从a到b形状为size的整数张量 |
randperm(m) | 随机排列 |
t=torch.randn(3,3)
torch.zeros_like(t)#zeros还可以换成其它构造函数ones、randdeng
#如果t是整型,构造函数生成浮点型会报错
a=torch.tensor([[1., -1.], [1., -1.]])
print(a.dtype,a.type(),a.shape)
torch.float32 torch.FloatTensor torch.Size([2, 2])
#浮点型转整型
torch.randn(3,3).to(torch.int)
torch.randn(3,3).int()
t=torch.randn(3,4).to(torch.int)
t.nelement()#获取元素总数
t.ndimension()#获取张量维度
t.shape#张量形状
t.view(4,3)
t.view(-1,3)
t.view(12)#tensor([0, 0, 0, 0, -1, 1, 0, 2, 0, 2, 0, 0], dtype=torch.int32)
另外还有reshape和contiguous方法。reshape和view区别在于被操作的那个tensor是否是连续的:
两个张量只有在同一设备上才可以运算(CPU或者同一个GPU)
nvidia-smi#可以查看GPU的信息
!nvidia-smi#colab上命令是这个
torch.randn(3,3,device='cuda:0').device#在0号cuda上创建张量,查看张量存储设备
device(type='cuda', index=0)
torch.randn(3,3,device='cuda:0').cpu().device#cuda 0上的张量复制到CPU上
device(type='cpu')
torch.randn(3,3,device='cuda:0').cuda(1)
torch.randn(3,3,device='cuda:0').to('cuda:1')
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 495.44 Driver Version: 460.32.03 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Tesla P100-PCIE... Off | 00000000:00:04.0 Off | 0 |
| N/A 47C P0 28W / 250W | 0MiB / 16280MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
等同numpy的操作。如:
t=torch.randn(3,4,5)
t[:,1:-1,1:3])
t>0#得到一个掩码矩阵
t[t>0]
筛选出t中大于0的元素,最终得到一个一维向量
如果不想改变原张量的数值,可以先用clone得到张量的副本,再进行索引和切片的赋值操作。
所有运算符、操作符见文档:《Creation Ops》
t.mean()#对所有维度求均值
t.mean(0)#对第0维元素求均值
t.mean([0,1])#对0,1两维元素求均值
t=torch.randint(1,100,(3,4))
tensor([[20, 95, 9, 94],
[97, 61, 80, 67],
[76, 66, 64, 65]])
t.max(0),t.argmax(0),t.sort(-1,descending=True)
torch.return_types.max(values=tensor([97, 95, 80, 94]),indices=tensor([1, 0, 1, 0]))
tensor([1, 0, 1, 0])
torch.return_types.sort(values=tensor([[95, 94, 20, 9],
[97, 80, 67, 61],
[76, 66, 65, 64]]),
indices=tensor([[1, 3, 0, 2],
[0, 2, 3, 1],
[0, 1, 3, 2]]))
s=t.sum(0,keepdims=True)#结果还是一个二维矩阵
a=t/s
print(s,a)
(tensor([[193., 222., 153., 226.]])
tensor([[0.1036, 0.4279, 0.0588, 0.4159],
[0.5026, 0.2748, 0.5229, 0.2965],
[0.3938, 0.2973, 0.4183, 0.2876]]))
a=torch.arange(20).reshape(4,5)
a,a.cumsum(0)#沿每一列做累加和
tensor([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
tensor([[ 0, 1, 2, 3, 4],
[ 5, 7, 9, 11, 13],
[15, 18, 21, 24, 27],
[30, 34, 38, 42, 46]])
函数后面加下划线是原地操作,改变被调用的张量的值
在线性代数中,向量范数是将向量映射到标量的函数f。⾮正式地说,⼀个向量的范数告诉我们⼀个向量有多⼤。这⾥考虑的⼤⼩(size)概念不涉及维度,⽽是分量的⼤⼩。
代码演示:
u = torch.tensor([3.0, -4.0])
torch.abs(u).sum()#L1范数
torch.norm(u)#L2范数
tensor(5.)
tensor(7.)
torch.norm(torch.ones((4, 9)))#矩阵L2范数
tensor(6.)
向量点积其实就是类似加权求和,结果是一个标量。
x = torch.arange(4, dtype=torch.float32)
y = torch.ones(4, dtype = torch.float32)
x, y, torch.dot(x, y)
(tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
类似矩阵每一行进行加权求和,结果是一个向量(长度等于矩阵行数)。
对于矩阵 A ∈ R m × n A\in \mathbb{R}^{m\times n} A∈Rm×n和向量 x ∈ R n x\in \mathbb{R}^{n} x∈Rn,使用行向量表示矩阵 A A A:
A = [ a 1 T a 2 T . . . a m T ] A=\begin{bmatrix} a_{1}^{T}\\ a_{2}^{T}\\ ...\\ a_{m}^{T}\end{bmatrix} A=⎣⎢⎢⎡a1Ta2T...amT⎦⎥⎥⎤
每个 a i T a_{i}^{T} aiT都是⾏向量,表示矩阵的第i⾏。矩阵向量积 A x Ax Ax是⼀个⻓度为m的列向量,其第i个元素是点积 a i T x a_{i}^{T}x aiTx:
A x = [ a 1 T a 2 T . . . a m T ] x = [ a 1 T x a 2 T x . . . a m T x ] Ax=\begin{bmatrix} a_{1}^{T}\\ a_{2}^{T}\\ ...\\ a_{m}^{T}\end{bmatrix}x=\begin{bmatrix} a_{1}^{T}x\\ a_{2}^{T}x\\ ...\\ a_{m}^{T}x\end{bmatrix} Ax=⎣⎢⎢⎡a1Ta2T...amT⎦⎥⎥⎤x=⎣⎢⎢⎡a1Txa2Tx...amTx⎦⎥⎥⎤
调⽤torch.mv(A, x)时,会执⾏矩阵-向量积。A的列维数必须与x的维数相同。
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
x = torch.arange(4, dtype=torch.float32)
print(A)
print(x)
torch.mv(A, x)
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]])
tensor([0., 1., 2., 3.])
tensor([ 14., 38., 62., 86., 110.])
矩阵-矩阵乘法是一个矩阵的行和另一个矩阵的列的点乘,结果是一个矩阵。
假设我们有两个矩阵 A ∈ R n × k A\in \mathbb{R}^{n\times k} A∈Rn×k 和 B ∈ R k × m B\in \mathbb{R}^{k\times m} B∈Rk×m,矩阵乘积是A的⾏向量和B的列向量做点积,结果 C ∈ R n × m C\in \mathbb{R}^{n\times m} C∈Rn×m:
矩阵-矩阵乘法AB看作是简单地执⾏m次矩阵-向量积,并将结果拼接在⼀起,形成⼀个n × m矩阵,在pytorch中,矩阵乘法可以用a.mm(b)或者torch.mm(a,b)或者a@b或者torch.matmul(t,q)三种形式,a@b最好用。
t=torch.randint(1,100,(3,4))
q=torch.randint(1,100,(4,3))
print(t)
print(q)
#三种写法都是得到3×3的矩阵
t.mm(q)#或者torch.mm(t,q)或者t@q
tensor([[90, 86, 93, 73],
[90, 84, 5, 64],
[25, 34, 17, 20]])
tensor([[50, 6, 74],
[46, 33, 93],
[76, 45, 61],
[58, 86, 99]])
tensor([[19758, 13841, 27558],
[12456, 9041, 21113],
[ 5266, 3757, 8029]])
a = torch.randn(2,3,4) # 随机产生张量
c = torch.randn(2,4,3)
a.bmm(c) # 批次矩阵乘法的结果
torch.bmm(a,c)
a@b
如果是3维以上张量的乘积,称为缩并。需要用到爱因斯坦求和约定。对应函数为torch.einsum。
普通乘积:对应元素相乘,结果还是向量
x ∗ y = ( x 1 y 1 , x 2 y 2 , x 3 y 3 ) x*y=(x_{1}y_{1},x_{2}y_{2},x_{3}y_{3}) x∗y=(x1y1,x2y2,x3y3)
A=torch.randint(1,10,(3,4))
B=torch.randint(1,10,(3,4))
print(A)
print(B)
A*B
tensor([[8, 3, 2, 3],
[7, 5, 8, 6],
[9, 7, 7, 6]])
tensor([[4, 4, 5, 1],
[6, 2, 5, 7],
[6, 2, 6, 6]])
tensor([[32, 12, 10, 3],
[42, 10, 40, 42],
[54, 14, 42, 36]])
两个向量的叉乘,又叫向量积、外积、叉积(Cross product),叉乘的运算结果是一个矩阵而不是一个标量。其方向与这两个向量组成的坐标平面垂直。其定义为:
叉乘的几何定义:
方向为垂直两个向量组成的平面的方向(法向量的方向)。大小为:
→ a × → b = ∣ → a ∣ ∣ → b ∣ s i n θ \underset{a}{\rightarrow}\times \underset{b}{\rightarrow}=\left | \underset{a}{\rightarrow} \right |\left |\underset{ b}{\rightarrow} \right |sin\theta a→×b→=∣∣∣a→∣∣∣∣∣∣∣b→∣∣∣∣sinθ
类型 | 维度 | pythorch代码 | 说明 |
---|---|---|---|
向量点积 x ⋅ y x\cdot y x⋅y | x , y ∈ R d x,y\in \mathbb{R}^{d} x,y∈Rd | torch.dot(x, y),x.dot(y) ,x.inner(y),x.matmul(x) | 类似向量加权求和,结果是标量 |
矩阵向量点积 y = A ⋅ x y=A\cdot x y=A⋅x | A ∈ R m × n A\in \mathbb{R}^{m\times n} A∈Rm×n, x ∈ R n x\in \mathbb{R}^{n} x∈Rn, y ∈ R m y\in \mathbb{R}^{m} y∈Rm | torch.mv(A, x),A.mv(x),A.inner(x),A.matmul(x) | 矩阵每一行对x的加权求和,结果是一个向量。 |
矩阵-矩阵乘法 C = A B C=AB C=AB | A ∈ R n × k A\in \mathbb{R}^{n\times k} A∈Rn×k , B ∈ R k × m B\in \mathbb{R}^{k\times m} B∈Rk×m, C ∈ R n × m C\in \mathbb{R}^{n\times m} C∈Rn×m | A@B,A.mm(B),torch.mm(A,B),torch.matmul(A,B) | 矩阵的行乘以另一个矩阵的列 |
向量普通乘积z=x*y | x , y , z ∈ R d x,y,z\in \mathbb{R}^{d} x,y,z∈Rd | x*y或者x.multiply(y) | 向量按位相乘,结果是一个形状不变的向量 |
矩阵向量普通乘积 C = A ∗ x C=A*x C=A∗x | A , C ∈ R m × n , x ∈ R n A,C\in \mathbb{R}^{m\times n},x\in \mathbb{R}^{n} A,C∈Rm×n,x∈Rn | A*x或者A.multiply(x) | 向量乘以矩阵的每一行,矩阵形状不变 |
矩阵乘法或矩阵Hadamard积 C = A ⊙ B C=A\odot B C=A⊙B | A , B , C ∈ R m × n A,B,C\in \mathbb{R}^{m\times n} A,B,C∈Rm×n | A*B或torch.multiply(A,A) | 两个矩阵对应位置相乘,结果还是一个矩阵,形状不变 |
向量的外积 x × y x\times y x×y | x , y ∈ R d x,y\in \mathbb{R}^{d} x,y∈Rd | x.outer(y) | 列向量乘以行向量,结果是一个矩阵 |
代码 | 说明 |
---|---|
torch.dot(x,y) | 计算两个一维张量的点积 |
torch.mv(A,x) | 只能计算一个向量和一个矩阵的点积 |
torch.matmul(A,B) | 1.2.多维张量的矩阵乘积,具体看文档 |
torch.inner(A,B) | 计算1.2.多维张量内积 |
torch.outer(x,y) | 只能计算两个向量的外积 |
torch.multiply或者* | 1.2.多维张量间的普通乘积,按位相乘,形状不变 |
torch.stack:传入张量列表和维度,将张量沿此维度进行堆叠(新建一个维度来堆叠)
torch.cat:传入张量列表和维度,将张量沿此维度进行堆叠
两个都是拼接张量,torch.stack会新建一个维度来拼接,后者维度预先存在,沿着此维度堆叠就行。
t1 = torch.randn(3,4) # 随机产生三个张量
t2 = torch.randn(3,4)
t3 = torch.randn(3,4)
torch.stack([t1,t2,t3], -1).shape# 沿着最后一个维度做堆叠,返回大小为3×4×3的张量
torch.Size([3, 4, 3])
-----------------------------------------------------------------------------
torch.cat([t1,t2,t3], -1).shape # 沿着最后一个维度做拼接,返回大小为3×14的张量
torch.Size([3, 12])
torch.split(tensor, split_size_or_sections, dim=0)
torch.split函数,有三个参数。将张量沿着指定维度进行分割。
第二个参数可以是整数n或者列表list。前者表示这个维度等分成n份(最后一份可以是剩余的)。或者表示分成列表元素值来分割。
torch.chunk函数和slpit函数类似
t = torch.randint(1, 10,(3,6)) # 随机产生一个3×6的张量
tensor([[8, 9, 5, 3, 6, 7],
[1, 4, 2, 2, 7, 1],
[5, 2, 5, 7, 2, 7]])
------------------------------------------------------------------------------
t.split([1,2,3], -1) # 把张量沿着最后一个维度分割为三个张量
(tensor([[8],
[1],
[5]]),
tensor([[9, 5],
[4, 2],
[2, 5]]),
tensor([[3, 6, 7],
[2, 7, 1],
[7, 2, 7]]))
------------------------------------------------------------------------------
t.split(3, -1) # 把张量沿着最后一个维度分割,分割大小为3,输出的张量大小均为3×3
(tensor([[8, 9, 5],
[1, 4, 2],
[5, 2, 5]]),
tensor([[3, 6, 7],
[2, 7, 1],
[7, 2, 7]]))
t.chunk(3, -1) # 把张量沿着最后一个维度分割为三个张量,大小均为3×2
(tensor([[8, 9],
[1, 4],
[5, 2]]),
tensor([[5, 3],
[2, 2],
[5, 7]]),
tensor([[6, 7],
[7, 1],
[2, 7]]))
t = torch.rand(3, 4) # 随机生成一个张量
t.unsqueeze(-1).shape # 扩增最后一个维度
torch.Size([3, 4, 1])
t.unsqueeze(-1).unsqueeze(1).shape # 继续扩增一个维度
torch.Size([3, 1, 4, 1])
t = torch.rand(1,3,4,1) # 随机生成一个张量,有两个维度大小为1
t.squeeze().shape # 压缩所有大小为1的维度
torch.Size([3, 4])
t1 = torch.randn(3,4,5)
t2 = torch.randn(3,5)
t2 = t2.unsqueeze(1) # 张量2的形状变为3×1×5
print(t2)
tensor([[[ 0.7188, -1.1053, -0.1161, -2.2889, -0.8046]],
[[ 0.1434, -2.8369, -1.5712, 1.1490, 0.7161]],
[[-0.8259, 1.8744, -0.7918, -0.4208, 1.6935]]])
t3 = t1 + t2 #将t2沿着第二个维度复制4次,最后形状为(3,4,5)
print(t3)
tensor([[[ 1.6212, -1.0232, 1.9735, -2.3579, -2.8416],
[ 1.3389, -0.7377, -0.8453, -2.2385, -1.4370],
[ 1.4433, -1.8982, -0.0669, -2.8503, -1.0240],
[-0.0498, -2.2708, 0.4583, -0.3370, -2.7074]],
[[ 1.7768, -2.4552, 0.3409, -0.7948, 1.9718],
[ 0.1147, -3.2569, -1.4112, 1.3465, 0.2129],
[ 0.8951, -3.5355, -0.3349, 1.4523, 0.2659],
[ 0.6704, -2.3110, -1.1827, 0.8700, 2.9844]],
[[-0.3561, 0.7850, -0.9848, -0.8666, 0.0758],
[-0.1744, 1.3592, -1.7955, -0.0697, 3.8696],
[-2.5559, 2.6479, -0.1718, -0.2446, 1.7351],
[ 0.5748, 1.2866, -1.3801, 0.0290, 1.0740]]])
在大多数情况下,我们将沿着数组中长度为1的轴进行广播,如下例子:
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
print(a)
print(b)
a+b
(tensor([[0],
[1],
[2]]),
tensor([[0, 1]]))
tensor([[0, 1],
[1, 2],
[2, 3]])
所以有时候我们没想做广播,只是把张量相加,结果成了一个矩阵。这时候应该考虑是不是做了广播。
运行一些操作可能会导致为新结果分配内存。 例如,如果我们用Y = X + Y,将会为Y分配新的内存。
before = id(Y)
Y = Y + X
id(Y) == before
False
这可能是不可取的,原因有两个:
a=torch.arange(12)
b=a.reshape(3,4)
b[:]=1
a
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
Tensor 和 Function 互相连接并构建一个非循环图,它保存整个完整的计算过程的历史信息。每个张量都有一个 .grad_fn 属性保存着创建了张量的 Function 的引用,(如果用户自己创建张量,则g rad_fn 是 None )。
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
print(z, out)
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>)
tensor(27., grad_fn=<MeanBackward0>)
out.backward()
print(x.grad)
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
在 torch.nn 中,不计算梯度的参数通常称为冻结参数。 如果事先知道您不需要这些参数的梯度,则“冻结”模型的一部分很有用(通过减少自动梯度计算,这会带来一些表现优势)。
例如加载一个预训练的 resnet18 模型,并冻结所有参数,仅修改分类器层以对新标签进行预测。
from torch import nn, optim
model = torchvision.models.resnet18(pretrained=True)
# 冻结网络的所有参数
for param in model.parameters():
param.requires_grad = False
假设我们要在具有 10 个标签的新数据集中微调模型。 在 resnet 中,分类器是最后一个线性层model.fc。 我们可以简单地将其替换为充当我们的分类器的新线性层(默认情况下未冻结)。
model.fc = nn.Linear(512, 10)
# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
现在,除了model.fc的参数外,模型中的所有参数都将冻结。 计算梯度的唯一参数是model.fc的权重和偏差。(torch.no_grad()中的上下文管理器可以使用相同的排除功能。)
Autograd 在由函数对象组成的有向无环图(DAG)中记录张量、所有已执行的操作(以及由此产生的新张量)。 在此 DAG 中,叶子是输入张量,根是输出张量。 通过从根到叶跟踪此图,可以使用链式规则自动计算梯度。
在正向传播中,Autograd 同时执行两项操作:
当在 DAG 根目录上调用.backward()时,开始回传梯度,然后:
下面是我们示例中 DAG 的直观表示。 在图中,箭头指向前进的方向。 节点代表正向传播中每个操作的反向函数。 蓝色的叶节点代表我们的叶张量a和b: