#设置默认数据类型为其他类型
torch.set_default_tensor_type(torch.DoubleTensor)
ic(torch.tensor([1.2,3.4]).dtype)
import torch
from icecream import ic
if __name__ == '__main__':
tensor = torch.Tensor([[1,2],[3,4],[5,6]]) #默认FloatTensor类型
ic(tensor)
#size()和shape可以查看tenser的尺寸
ic(tensor.size())
ic(tensor.shape)
#dtype查看元素数据类型
ic(tensor.dtype)
#numel查看元素个数
ic(tensor.numel())
#创建时为Tensor不赋值,只指定形状
tensor = torch.Tensor(5,4)
ic(tensor)
#根据另外一个Tensor的形状创建新的Tensor
tensor_tmp = torch.Tensor(tensor.shape)
ic(tensor_tmp)
#创建空Tensor
tensor = torch.empty((4,2))
ic(tensor)
#创建元素全是0的Tensor
tensor = torch.zeros(3,3)
ic(tensor)
#创建与输入参数Tensor形状相同的元素全部为0的Tensor
tensor = torch.zeros_like(tensor_tmp)
ic(tensor)
#创建形状为size且元素全为1的Tensor
tensor = torch.ones((6,1))
ic(tensor)
#创建与输入参数Tensor形状相同的元素全部为1的Tensor
tensor = torch.ones_like(tensor_tmp)
ic(tensor)
#返回形状为size,元素值在[0,1)内满足均匀分布的随机数
ic(torch.rand(4,3))
# 返回形状为size,均值为0,方差为1的标准正态分布的随机数
ic(torch.randn(4,1))
#创建一个float32类型并且需要计算梯度的张量(仅浮点数可以)
B = torch.tensor([[2,3],[2,1]],dtype=torch.float32,requires_grad=True)
ic(B)
#针对张量B计算Sum(B^2)上的梯度
y = B.pow(2).sum()
ic(y)
ic(y.backward())
ic(B.grad)
torch.manual_seed(123) #生成随机数种子
ic(torch.normal(mean=0.0,std=torch.tensor(1.0))) #生成服从正太(0,1)分布的随机数
ic(torch.randn((3,4))) #生成(3,4)的矩阵,服从[0,1]均匀分布
ic(torch.randn((3,3))) #生成(3,4)的矩阵,服从标准正态分布
Pytorch自动微分
x = torch.tensor([[1.0,2.0],
[3.0,4.0]],requires_grad=True)
y = torch.sum(x**2 + x*2 + 1)
ic(y)
y.backward()
ic(x.grad)
import torch
from icecream import ic
if __name__ == '__main__':
tensor_a = torch.FloatTensor([[1,2],[3,4]])
tensor_b = torch.FloatTensor([[14,21],[-1,12]])
#加法
ic(torch.add(tensor_a,tensor_b))
#减法
ic(torch.sub(tensor_a,tensor_b))
#乘法
ic(torch.mul(tensor_a,tensor_b))
#除法
ic(torch.div(tensor_a,tensor_b))
#求幂
ic(torch.pow(tensor_b,2))
#求绝对值
ic(torch.abs(tensor_b))
ic| torch.add(tensor_a,tensor_b): tensor([[15., 23.],
[ 2., 16.]])
ic| torch.sub(tensor_a,tensor_b): tensor([[-13., -19.],
[ 4., -8.]])
ic| torch.mul(tensor_a,tensor_b): tensor([[14., 42.],
[-3., 48.]])
ic| torch.div(tensor_a,tensor_b): tensor([[ 0.0714, 0.0952],
[-3.0000, 0.3333]])
ic| torch.pow(tensor_b,2): tensor([[196., 441.],
[ 1., 144.]])
ic| torch.abs(tensor_b): tensor([[14., 21.],
[ 1., 12.]])
import torch
from icecream import ic
if __name__ == '__main__':
tensor_a = torch.FloatTensor([[1,2],[3,4]])
tensor_b = torch.FloatTensor([[14,2],[-1,4]])
#判断两个tensor是否shape相同,元素相同
ic(torch.equal(tensor_a,tensor_b))
#逐个判断两个tensor元素的大小,大于则返回True,否则返回False
ic(torch.gt(tensor_a,tensor_b))
# 逐个判断两个tensor元素的大小,大于等于则返回True,否则返回False
ic(torch.ge(tensor_a, tensor_b))
# 逐个判断两个tensor元素的大小,相等则返回True,否则返回False
ic(torch.eq(tensor_a,tensor_b))
# 逐个判断两个tensor元素的大小,小于则返回True,否则返回False
ic(torch.lt(tensor_a,tensor_b))
ic| torch.equal(tensor_a,tensor_b): False
ic| torch.gt(tensor_a,tensor_b): tensor([[False, False],
[ True, False]])
ic| torch.ge(tensor_a, tensor_b): tensor([[False, True],
[ True, True]])
ic| torch.eq(tensor_a,tensor_b): tensor([[False, True],
[False, True]])
ic| torch.lt(tensor_a,tensor_b): tensor([[ True, False],
[False, False]])
tensor_a = torch.FloatTensor([[1,2],[3,4]])
tensor_b = torch.FloatTensor([[14,2],[-1,4]])
#返回最大值
ic(torch.max(tensor_a))
#返回平均值
ic(torch.mean(tensor_b))
#返回和
ic(torch.sum(tensor_b))
#返回所有元素乘积
ic(torch.prod(tensor_a))
ic| torch.max(tensor_a): tensor(4.)
ic| torch.mean(tensor_b): tensor(4.7500)
ic| torch.sum(tensor_b): tensor(19.)
ic| torch.prod(tensor_a): tensor(24.)
#tensor转numpy
nparray = tensor_a.numpy()
ic(tensor_a)
ic(nparray)
#tensor和numpy公用同一块内存
tensor_a[0][1] = -1
ic(tensor_a)
ic(nparray)
#numpy转tensor torch.from_numpy() torch.as_tensor()
ones_arr = np.ones(4)
tensor_a = torch.from_numpy(ones_arr)
ic(tensor_a)
ic(ones_arr)
ones_arr[0] = -1
ic(tensor_a)
ic(ones_arr)
ic| tensor_a: tensor([[1., 2.],
[3., 4.]])
ic| nparray: array([[1., 2.],
[3., 4.]], dtype=float32)
ic| tensor_a: tensor([[ 1., -1.],
[ 3., 4.]])
ic| nparray: array([[ 1., -1.],
[ 3., 4.]], dtype=float32)
ic| tensor_a: tensor([1., 1., 1., 1.], dtype=torch.float64)
ic| ones_arr: array([1., 1., 1., 1.])
ic| tensor_a: tensor([-1., 1., 1., 1.], dtype=torch.float64)
ic| ones_arr: array([-1., 1., 1., 1.])
tensor_a = torch.zeros((3,1,3,1,1,2))
ic(tensor_a)
#降维操作,两者共享同一片内存
tmp = tensor_a.squeeze()
ic(tmp)
#相对于的为增维 unsqueeze
tmp = tmp.unsqueeze(2)
ic(tmp)
torch.clamp()用于对tensor的元素进行范围过滤,将不符合条件的元素变换到范围内部边界上。常用于梯度裁剪,即梯度发生消失或梯度发生爆炸时对梯度进行处理。
tensor_a = torch.arange(0,9).view(3,3)
ic(tensor_a)
ic(torch.clamp(tensor_a,3))
ic(torch.clamp(tensor_a,3,5))
tensor_a = torch.arange(0,9).view(3,3)
ic(tensor_a)
#注意,索引结果与原内存共享
ic(tensor_a[0]) #第一行
ic(tensor_a[:,0]) #第一列
ic(tensor_a[:1]) #第一行
ic(tensor_a[:1,:1]) # 即tensor[0][0]
ic(tensor_a[:2,:2]) #前两行和前两列
ic| tensor_a: tensor([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
ic| tensor_a[0]: tensor([0, 1, 2])
ic| tensor_a[:,0]: tensor([0, 3, 6])
ic| tensor_a[:1]: tensor([[0, 1, 2]])
ic| tensor_a[:1,:1]: tensor([[0]])
ic| tensor_a[:2,:2]: tensor([[0, 1],
[3, 4]])
高级索引
tensor_a = torch.arange(0,27).view(3,3,3)
ic(tensor_a)
#高级索引不共享内存
a = tensor_a[[1,2],[1,2],[2,0]] # tensor [1][1][2] 和 [2][2][0]
ic(a)
ic(tensor_a[[2,1,0],[0],[1]]) #tensor[2][0][1],[1][0][1],[0][0][1]
ic(tensor_a[[0,2],...]) #a[0]和a[2]
ic| a: tensor([14, 24])
ic| tensor_a[[2,1,0],[0],[1]]: tensor([19, 10, 1])
ic| tensor_a[[0,2],...]: tensor([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
ta = torch.randn(10000,10000)
tb = torch.randn(10000,10000)
starttime = time.time()
ta.matmul(tb)
runtime = time.time() - starttime
ic(runtime)
ta = ta.cuda()
tb = tb.cuda()
starttime = time.time()
ta.matmul(tb)
runtime = time.time() - starttime
ic(runtime)
ic| runtime: 5.68085503578186
ic| runtime: 0.5600407123565674
Pytorch常用损失函数:均方误差、交叉熵、L1范数、KL散度、二进制交叉熵损失函数等
有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。
# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
shared, nn.ReLU(),
shared, nn.ReLU(),
nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])
tensor([True, True, True, True, True, True, True, True])
tensor([True, True, True, True, True, True, True, True])
当参数绑定时,梯度会发生什么情况? 答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。
共享参数通常可以节省内存,并在以下方面具有特定的好处:
- 对于图像识别中的CNN,共享参数使网络能够在图像中的任何地方而不是仅在某个区域中查找给定的功能。
对于单个张量,我们可以直接调用load
和save
函数分别读写它们。 这两个函数都要求我们提供一个名称,save
要求将要保存的变量作为输入。
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
我们现在可以将存储在文件中的数据读回内存。
x2 = torch.load('x-file')
x2
我们可以存储一个张量列表,然后把它们读回内存。
y = torch.zeros(4)
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')
(x2, y2)
我们甚至可以写入或读取从字符串映射到张量的字典。 当我们要读取或写入模型中的所有权重时,这很方便。
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
mydict2
深度学习框架提供了内置函数来保存和加载整个网络。 需要注意的一个重要细节是,这将保存模型的参数而不是保存整个模型。
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
接下来,我们将模型的参数存储在一个叫做“mlp.params”的文件中。
torch.save(net.state_dict(), 'mlp.params')
为了恢复模型,我们实例化了原始多层感知机模型的一个备份。 这里我们不需要随机初始化模型参数,而是直接读取文件中存储的参数。
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
由于两个实例具有相同的模型参数,在输入相同的X
时, 两个实例的计算结果应该相同。 让我们来验证一下。
Y_clone = clone(X)
Y_clone == Y
'''
tensor([[True, True, True, True, True, True, True, True, True, True],
[True, True, True, True, True, True, True, True, True, True]])
'''
使用nvidia-smi
命令来查看显卡信息。
默认情况下,张量是在内存中创建的,然后使用CPU计算它。
在PyTorch中,CPU和GPU可以用torch.device('cpu')
和torch.device('cuda')
表示。
应该注意的是,cpu
设备意味着所有物理CPU和内存, 这意味着PyTorch的计算将尝试使用所有CPU核心。 然而,gpu
设备只代表一个卡和相应的显存。 如果有多个GPU,我们使用torch.device(f'cuda:{i}')
来表示第i块GPU(i从0开始)。 另外,cuda:0
和cuda
是等价的。
import torch
from torch import nn
torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
查询可用gpu的数量。
torch.cuda.device_count()
定义了两个方便的函数, 这两个函数允许我们在不存在所需所有GPU的情况下运行代码。
def try_gpu(i=0): #@save
"""如果存在,则返回gpu(i),否则返回cpu()"""
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus(): #@save
"""返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"""
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
try_gpu(), try_gpu(10), try_all_gpus()
可以查询张量所在的设备。 默认情况下,张量是在CPU上创建的。
x = torch.tensor([1, 2, 3])
x.device
需要注意的是,无论何时我们要对多个项进行操作, 它们都必须在同一个设备上。 例如,如果我们对两个张量求和, 我们需要确保两个张量都位于同一个设备上, 否则框架将不知道在哪里存储结果,甚至不知道在哪里执行计算。
假设你至少有两个GPU,下面的代码将在第二个GPU上创建一个随机张量。
Y = torch.rand(2, 3, device=try_gpu(1))
Y
tensor([[0.5473, 0.1942, 0.2213],
[0.5998, 0.5565, 0.0372]], device='cuda:1')
如果我们要计算X + Y
,我们需要决定在哪里执行这个操作。 例如,如下图所示, 我们可以将X
传输到第二个GPU并在那里执行操作。 不要简单地X
加上Y
,因为这会导致异常, 运行时引擎不知道该怎么做:它在同一设备上找不到数据会导致失败。 由于Y
位于第二个GPU上,所以我们需要将X
移到那里, 然后才能执行相加运算。
Z = X.cuda(1)
print(X)
print(Z)
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
[1., 1., 1.]], device='cuda:1')
现在数据在同一个GPU上(Z
和Y
都在),我们可以将它们相加。
Y + Z
类似地,神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上。
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
卷积层对输入和卷积核权重进行互相关运算,并在添加标量偏置之后产生输出。 所以,卷积层中的两个被训练的参数是卷积核权重和标量偏置。 就像我们之前随机初始化全连接层一样,在训练基于卷积层的模型时,我们也随机初始化卷积核权重。
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size))
self.bias = nn.Parameter(torch.zeros(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
高度和宽度分别为h和w的卷积核可以被称为h×w卷积。 我们也将带有h×w卷积核的卷积层称为h×w卷积层。
在卷积神经网络中,对于某一层的任意元素xx,其感受野(receptive field)是指在前向传播期间可能影响xx计算的所有元素(来自所有先前层)。
有时,在应用了连续的卷积之后,我们最终得到的输出远小于输入大小。这是由于卷积核的宽度和高度通常大于1所导致的。比如,一个240×240像素的图像,经过10层5×5的卷积后,将减少到200×200像素。如此一来,原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法。 有时,我们可能希望大幅降低图像的宽度和高度。例如,如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。
在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。 解决这个问题的简单方法即为填充(padding):在输入图像的边界填充元素(通常填充元素是0)。
[
通常,如果我们添加 p h p_h ph行填充(大约一半在顶部,一半在底部)和 p w p_w pw列填充(左侧大约一半,右侧一半),则输出形状将为
( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_h−k_h+p_h+1)×(n_w−k_w+p_w+1) (nh−kh+ph+1)×(nw−kw+pw+1)
在许多情况下,我们需要设置 p h = k h − 1 p_h=k_h−1 ph=kh−1和 p w = k w − 1 p_w=k_w−1 pw=kw−1,使输入和输出具有相同的高度和宽度。
在计算互相关时,卷积窗口从输入张量的左上角开始,向下、向右滑动。 在前面的例子中,我们默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。
我们将每次滑动元素的数量称为步幅(stride)。
当垂直步幅为sh、水平步幅为sw时,输出形状为
⌊ ( n h − k h + p h + s h ) / s h ⌋ × ⌊ ( n w − k w + p w + s w ) / s w ⌋ . ⌊(n_h−k_h+p_h+s_h)/s_h⌋×⌊(n_w−k_w+p_w+s_w)/s_w⌋. ⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋.
如果我们设置了 p h = k h − 1 p_h=k_h−1 ph=kh−1和 p w = k w − 1 p_w=k_w−1 pw=kw−1,则输出形状将简化为 ⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ ⌊(n_h+s_h−1)/s_h⌋×⌊(n_w+s_w−1)/s_w⌋ ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋。 更进一步,如果输入的高度和宽度可以被垂直和水平步幅整除,则输出形状将为 ( n h / s h ) × ( n w / s w ) (n_h/s_h)×(n_w/_sw) (nh/sh)×(nw/sw)。
默认情况下,填充为0,步幅为1。在实践中,我们很少使用不一致的步幅或填充,也就是说,我们通常有ph=pwph=pw和sh=swsh=sw。
汇聚(pooling)层具有双重目的:降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。
MaxPool2d(3)默认为MaxPool2d(kernel_size=3, stride=3, padding=0, dilation=1, ceil_mode=False),即不填充,步距和kernel_size相等
import torch
from torch import nn
if __name__ == '__main__' :
X = torch.arange(16,dtype=float).reshape((1,1,4,4));
print(X)
pool = nn.MaxPool2d(3,padding=(0,1))
print(pool)
print(pool(X))
tensor([[[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]]]], dtype=torch.float64)
MaxPool2d(kernel_size=3, stride=3, padding=(0, 1), dilation=1, ceil_mode=False)
tensor([[[[ 9., 11.]]]], dtype=torch.float64)
处理多通道输入数据时,汇聚层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着汇聚层的输出通道数与输入通道数相同。
批量规范化可以持续加速深层网络的收敛速度。
mat = torch.ones((2,256,12,12))
print(mat.shape)
conv1 = nn.Conv2d(256,512,kernel_size=3,stride=1,padding=0) # 是一个类的实例对象
print(conv1(mat).shape)
conv2 = nn.Conv2d(256,1024,kernel_size=3,stride=1,padding=1)
print(conv2(mat).shape)
conv3 = nn.Conv2d(256,1024,kernel_size=3,stride=1,padding=1)
print(conv3(mat).shape)
mat1 = conv2(mat)
mat2 = conv3(mat)
mat3 = mat2 + mat1 # 是数值的相加,通道数不变
print(mat3.shape)
mat4 = torch.cat([mat1,mat2],1) # 在第2个维度上堆叠,即堆叠通道数
print(mat4.shape)
mat5 = torch.cat([mat1,mat2],2) # 在第3个维度上堆叠
print(mat5.shape)
torch.Size([2, 256, 12, 12])
torch.Size([2, 512, 10, 10])
torch.Size([2, 1024, 12, 12])
torch.Size([2, 1024, 12, 12])
torch.Size([2, 1024, 12, 12])
torch.Size([2, 2048, 12, 12])
torch.Size([2, 1024, 24, 12])