引用自github.
Tensor
创建一个随机的二维数组(矩阵)1
2
3exam1 = torch.randn(2, 3)
exam2 = torch.zeros(2, 3)
exam3 = torch.ones(2, 3)
当然,我们也可以直接从python的数组直接构造
exam4 = torch.Tensor([[1, 2, 4], [2, 3, 6] ])
numpy 通常是通过 .shape 来获取数组的形状,但是对于torch.Tensor,使用的是 .size()
需要对数组形状进行改变,我们可以采用 .view() 的方式
exam5 = exam4.view(3, 2)
-1表示的是系统自动补齐
exam6 = exam4.view(1, -1)
torch.Tensor 支持大量的数学操作符 + , - , * , / 都是可以用的。
使用add函数会生成一个新的Tensor变量, add 函数会直接再当前Tensor变量上进行操作
所以,对于函数名末尾带有”“ 的函数都是会对Tensor变量本身进行操作的
对于常用的矩阵运算Tensor也有很好的支持1
2
3
4
5
6exam7 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
exam8 = torch.randn(2, 3)
# 矩阵乘法, 其中 t() 表示取转置
torch.mm(exam7, exam8.t())
# 矩阵对应元素相乘
exam7 * exam8
函数名后面带下划线 的函数会修改Tensor本身。例如,x.add(y)和x.t_()会改变 x,但x.add(y)和x.t()返回一个新的Tensor, 而x不变。
跟numpy一样,Tensor中也存在Broadcasting1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21a = torch.arange(0, 3).view(3, 1)
b = torch.arange(0, 2).view(1, 2)
print("a:", a)
print("b:", b)
print("a+b:", a + b)
a:
0
1
2
[torch.FloatTensor of size 3x1]
b:
0 1
[torch.FloatTensor of size 1x2]
a+b:
0 1
1 2
2 3
[torch.FloatTensor of size 3x2]
Tensor和Numpy的相互转换1
2
3
4
5
6
7
8
9
10
11import torch
import numpy as np
x = np.ones((2, 3))
print(x)
y = torch.from_numpy(x) # 从numpy -> torch.Tensor
print(y)
z = y.numpy() # 从torch.Tensor -> numpy
print(z)
Tensor和numpy对象共享内存,所以他们之间的转换很快,而且几乎不会消耗什么资源。但这也意味着,如果其中一个变了,另外一个也会随之改变。
Tensor可通过.cuda 方法转为GPU的Tensor,从而享受GPU带来的加速运算。1
2
3
4if t.cuda.is_available():
x = x.cuda()
y = y.cuda()
x + y
torch.unsqueeze
torch.unsqueeze(input, dim, out=None)
返回一个新的张量,对输入的指定位置插入维度 1
注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。
如果dim为负,则将会被转化dim+input.dim()+1
参数:
tensor (Tensor) – 输入张量
dim (int) – 插入维度的索引
out (Tensor, optional) – 结果张量1
2
3
4
5
6
7
8
9
10>>> x = torch.Tensor([1, 2, 3, 4])
>>> torch.unsqueeze(x, 0)
1 2 3 4
[torch.FloatTensor of size 1x4]
>>> torch.unsqueeze(x, 1)
1
2
3
4
[torch.FloatTensor of size 4x1]
autograd
在机器学习中,我们通常使用梯度下降(gradient descent)来更新模型参数从而求解。
损失函数关于模型参数的梯度指向一个可以降低损失函数值的方向,我们不断地沿着梯度的方向更新模型从而最小化损失函数。
虽然梯度计算比较直观,但对于复杂的模型,例如多达数十层的神经网络,手动计算梯度非常困难。
PyTorch中提供给了我们自动化求导的包 —— autograd
导入之后,我们用一个 Variable X 对象来包装Tensor变量。然后 X.data 就是该Tensor变量,X.grad 就是梯度1
2
3
4
5
6
7
8
9
10
11
12
13import torch
from torch.autograd import Variable
def fn(x):
y = 3 * x.pow(3) + 4 * x.pow(2) + 6
return y
x1 = Variable(torch.Tensor([1]), requires_grad=True)
y1 = fn(x1)
print(y1)
y1.backward() # 自动求导
print(x1.grad) # 查看梯度
需要注意的一点是:如果我们输入的 Tensor 不是一个标量,而是矢量(多个值)。
那么,我们在调用backward()之前,需要让结果变成标量,才能求出导数。1
2
3
4
5
6
7
8
9
10
11import torch
from torch.autograd import Variable
def fn(x):
y = 3 * x.pow(3) + 4 * x.pow(2) + 6
return y
x2 = Variable(torch.Tensor([[1, 2], [3, 4]]), requires_grad=True)
y2 = fn(x2).mean() # 将结果变成标量,这样就不会报错了
y2.backward() # 自动求导
print(x2.grad)
Variable和Tensor具有近乎一致的接口,在实际使用中可以无缝切换。1
2
3x = Variable(t.ones(4,5))
y = t.cos(x)
x_tensor_cos = t.cos(x.data)
定义网络
把网络中具有可学习参数的层放在构造函数init中。如果某一层(如ReLU)不具有可学习的参数,
则既可以放在构造函数中,也可以不放,但建议不放在其中,而在forward中使用nn.functional代替。
网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。
forward函数的输入和输出都是Variable,只有Variable才具有自动求导功能,
而Tensor是没有的,所以在输入时,需把Tensor封装成Variable。
简单例子
下面我们来尝试实现对CIFAR-10数据集的分类,步骤如下:
- 使用torchvision加载并预处理CIFAR-10数据集
- 定义网络
- 定义损失函数和优化器
- 训练网络并更新网络参数
- 测试网络
CIFAR-10数据加载及预处理
CIFAR-10^3是一个常用的彩色图片数据集,它有10个类别: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。每张图片都是 3×32×323×32×32 ,也即3-通道彩色图片,分辨率为 32×3232×32 。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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
show = ToPILImage() # 可以把Tensor转成Image,方便可视化
# 第一次运行程序torchvision会自动下载CIFAR-10数据集,
# 大约100M,需花费一定的时间,
# 如果已经下载有CIFAR-10,可通过root参数指定
# 定义对数据的预处理
transform = transforms.Compose([
transforms.ToTensor(), # 转为Tensor
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), # 归一化
])
# 训练集
trainset = tv.datasets.CIFAR10(
root='/home/cy/tmp/data/',
train=True,
download=True,
transform=transform)
trainloader = t.utils.data.DataLoader(
trainset,
batch_size=4,
shuffle=True,
num_workers=2)
# 测试集
testset = tv.datasets.CIFAR10(
'/home/cy/tmp/data/',
train=False,
download=True,
transform=transform)
testloader = t.utils.data.DataLoader(
testset,
batch_size=4,
shuffle=False,
num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# Dataset对象是一个数据集,可以按下标访问,返回形如(data, label)的数据。
(data, label) = trainset[100]
print(classes[label])
# (data + 1) / 2是为了还原被归一化的数据
show((data + 1) / 2).resize((100, 100))
# Dataloader是一个可迭代的对象,它将dataset返回的每一条数据拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对dataset的所有数据遍历完一遍之后,相应的对Dataloader也完成了一次迭代。
dataiter = iter(trainloader)
images, labels = dataiter.next() # 返回4张图片及标签
print(' '.join('%11s'%classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid((images+1)/2)).resize((400,100))
常用神经网络层
图像相关层
图像相关层主要包括卷积层(Conv)、池化层(Pool)等,这些层在实际使用中可分为一维(1D)、二维(2D)、三维(3D),
池化方式又分为平均池化(AvgPool)、最大值池化(MaxPool)、自适应池化(AdaptiveAvgPool)等。1
2
3
4
5
6
7from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
to_tensor = ToTensor() # img -> tensor
to_pil = ToPILImage()
lena = Image.open('imgs/lena.png')
# 输入是一个batch,batch_size=1
input = to_tensor(lena).unsqueeze(0)
池化层可以看作是一种特殊的卷积层,用来下采样。但池化层没有可学习参数,其weight是固定的。
除了卷积层和池化层,深度学习中还将常用到以下几个层:
- Linear:全连接层。
- BatchNorm:批规范化层,分为1D、2D和3D。除了标准的BatchNorm之外,还有在风格迁移中常用到的InstanceNorm层。
- Dropout:dropout层,用来防止过拟合,同样分为1D、2D和3D。 下面通过例子来说明它们的使用。