1.pytorch介绍
PyTorch是由Facebook基于人工智能研究小组的开发Lua编写的Torch库的Python目前,深度学习库已广泛应用于学术界和工业界Caffe2项目并入Pytorch, Pytorch开始影响到TensorFlow在深度学习应用框架领域的地位。总的来说,PyTorch是目前罕见的简洁、优雅、高效、快速的框架。
张量的基本概念
几何代数中定义的张量是基于向量和矩阵的推广。例如,我们可以将标量视为零阶张量,矢量视为一阶张量,矩阵视为二阶张量。在深度学习中,我们通常以张量的形式表示数据。例如,我们用三维张量表示一个RGB图像,四维张量表示视频。
张量是现代机器学习的基础。它的核心是一个数据容器。在大多数情况下,它包含数字,有时它包含字符串,但这种情况相对较少。所以你可以把它想象成一个数字桶。
有一些公共数据集类型存储在各种类型的张量:
例个图像可以用三个字段来表示:
(width, height, channel) = 3D
然而,在机器学习中,我们经常需要处理不止一张图片或文档——我们需要处理一个集合。我们可能有1万张郁金香图片,这意味着我们将使用4张D张量:
(, width, height, channel) = 4D
与TensorFlow差别
PyTorch学习资源
-
Awesome-pytorch-list:目前已获12K Star,包含了NLP,CV,常见库、论文实现和Pytorch其他项目。
-
PyTorch官方文件:官方文件,非常丰富。
-
Pytorch-handbook:GitHub上已经收获14.8K,pytorch手中书。
-
PyTorch官方社区:PyTorch拥有一个活跃的社区,在这里你可以和开发pytorch人们交流。
-
PyTorch官方tutorials:官方编写的tutorials,可以结合colab边动手边学习
-
实践学习深度学习:实践学习深度学习是由李木先生讲授的一门深度学习入门课,拥有成熟的图书资源和课程资源,在B站,Youtube均有回放。
-
Awesome-PyTorch-Chinese:中文常见品质高PyTorch资源
安装
推荐搭配: IDE(如Spyder、PyCharm)
可参考安装过程:https://datawhalechina.github.io/thorough-pytorch/第一章/1.2 PyTorch的安装.html#
2.pytorch基本定义和操作
在PyTorch中,torch.Tensor
它是存储和转换数据的主要工具。假如你以前用过NumPy
,你会发现Tensor
和NumPy多维数组非常相似。Tensor
提供GPU更多的功能,如计算和自动梯度要求Tensor
这种数据类型更适合深度学习。
2.1创建tensor
(1)随机初始化矩阵 我们可以通过torch.rand()
构建随机初始化矩阵的方法
import torch x = torch.rand(4, 3) print(x)
(2)构建全0矩阵 我们可以通过torch.zeros()
构建所有矩阵 0,并且通过dtype
设置数据类型为 long。此外,我们还可以通过torch.zero_()和torch.zeros_like()将现有矩阵转换为全0矩阵.
import torch x = torch.zeros(4, 3, dtype=torch.long) print(x)
(3)直接构建张量。 我们可以通过torch.tensor()
直接使用数据构建张量:
import torch x = torch.tensor([5.5, 3]) print(x) # tensor([5.5000, 3.0000]) print(x.size()) # torch.Size([2]) x = torch.tensor([[5.5, 3],]) print(x) # tensor([[5.5000, 3.0000]]) print(x.size()) # torch.Size([1, 2]) x = torch.tensor([[5.5], [3]]) print(x) # tensor([[5.5000], # [3.0000]]) print(x.size()) # torch.Size([2, 1])
(4)基于现有的存在 tensor,创建一个 tensor :
x = x.new_ones(4, 3, dtype=torch.double) # 创建新的全1矩阵tensor,返回的tensor默认情况是一样的torch.dtype和torch.device # 也可以像之前的写法 x = torch.ones(4, 3, dtype=torch.double) x = torch.randn_like(x, dtype=torch.float) # 重置数据类型,同样的生成size随机数矩阵 print(x) print(x.size()) # torch.Size([4, 3]) print(x.shape) # torch.Size([4, 3]) print(x.shape[0]) # 2
(5)常见的构造Tensor的方法: import torch
函数 |
功能 |
---|---|
Tensor(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份 |
rand/randn(sizes) |
rand均匀分布为[0,1);randn是服从N正态分布(0,1) |
normal(mean,std) |
正态分布(均值为mean,标准差是std) |
randperm(m) |
随机排列 |
2.2tensor操作
PyTorch中的 Tensor 支持转移、索引、切片、数学运算、线性代数、随机数等100多种操作。具体使用方法可参考官方文件。
(1)加法操作:以下三种方法计算结果一致
import torch # 方式1 y = torch.rand(4, 3) print(x y) # 方式
print(torch.add(x, y))
# 方式3 in-place,原值修改
y.add_(x)
print(y)
(2)索引操作:(类似于numpy)
import torch
x = torch.rand(4,3)
# 取第二列
print(x[:, 1]) # tensor([-0.0720, 0.0666, 1.0336, -0.6965])
y = x[0,:]
y += 1
print(y) # tensor([3.7311, 0.9280, 1.2497])
print(x[0, :]) # tensor([3.7311, 0.9280, 1.2497])。源tensor也被改了了
(3)维度变换 张量的维度变换常见的方法有torch.view()
和torch.reshape()
,下面我们将介绍第一中方法torch.view()
:
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # -1是指这一维的维数由其他维度决定
print(x.size(), y.size(), z.size()) # torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
x += 1
print(x)
print(y) # 也加了了1
注: 。(顾名思义,view()仅仅是改变了对这个张量的观察角度)
上面我们说过torch.view()会改变原始张量,但是很多情况下,我们希望原始张量和变换后的张量互相不影响。为为了使创建的张量和原始张量不共享内存,我们需要使用第二种方法torch.reshape()
, 同样可以改变张量的形状,但是此函数并不能保证返回的是其拷贝值,所以官方不推荐使用。。
注:使用 clone()
还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor 。
(4)取值操作 如果我们有一个元素 tensor
,我们可以使用 .item()
来获得这个 value
,而不获得其他性质:
import torch
x = torch.randn(1)
print(type(x)) # <class 'torch.Tensor'>
print(type(x.item())) # <class 'float'>
2.3广播机制
当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:。
x = torch.arange(1, 3).view(1, 2)
print(x) # tensor([[1, 2]])
y = torch.arange(1, 4).view(3, 1)
print(y) # tensor([[1],
# [2],
# [3]])
print(x + y) # tensor([[2, 3],
# [3, 4],
# [4, 5]])
2.4自动求导:Autograd
PyTorch 中,所有神经网络的核心是 autograd
包。autograd包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义 (define-by-run)的框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
个包的核心类。如果设置它的属性 .requires_grad
为 True
,那么它将会追踪对于该张量的所有操作。当完成计算后可以通过调用 .backward()
,来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad
属性。
注意:在 y.backward() 时,如果 y 是标量,则不需要为 backward() 传入任何参数;否则,需要传入一个与 y 同形的Tensor。
要阻止一个张量被跟踪历史,可以调用.detach()
方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad():
中。在评估模型时特别有用,因为模型可能具有 requires_grad = True
的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。
还有一个类对于autograd
的实现非常重要:Function
。Tensor
和 Function
互相连接生成了一个无环图 (acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn
属性,该属性引用了创建 Tensor
自身的Function
(除非这个张量是用户手动创建的,即这个张量的grad_fn
是 None
)。下面给出的例子中,张量由用户手动创建,因此grad_fn返回结果是None。
3.利用pytorch进行深度学习的基础模块
在完成一项机器学习任务时的步骤,首先,其中重要的步骤包括数据格式的统一和必要的数据变换,同时。接下来,并设定,以及对应的(当然可以使用sklearn这样的机器学习库中模型自带的损失函数和优化器)。最后用模型去拟合训练集数据,并在。
3.1深度学习和机器学习 实现模块对比
深度学习和机器学习在流程上类似,但在代码实现上有较大的差异。首先,,因此深度学习在数据加载上需要有专门的设计。
在模型实现上,深度学习和机器学习也有很大差异。由于深度神经网络层数往往较多,同时会有一些用于实现特定功能的层(如卷积层、池化层、批正则化层、LSTM层等),因此。这种“定制化”的模型构建方式能够充分保证模型的灵活性,也对代码实现提出了新的要求。
接下来是损失函数和优化器的设定。这部分和经典机器学习的实现是类似的。但由于模型设定的灵活性,。
上述步骤完成后就可以开始训练了。我们前面介绍了GPU的概念和GPU用于并行计算加速的功能,不过,因此在代码实现中,需要把模型和数据“放到”GPU上去做运算,同时还需要保证损失函数和优化器能够在GPU上工作。如果使用多张GPU进行训练,还需要考虑模型和数据分配、整合的问题。此外,后续计算一些指标还需要把数据“放回”CPU。这里涉及到了一系列。
3.2基本配置:导包、超参数初始化、GPU设置
import os
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import torch.optim as optimizer
GPU的设置有两种常见的方式:
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
有如下几个超参数可以统一设置,方便后续调试时修改:
-
batch size
-
初始学习率(初始)
-
训练次数(max_epochs)
batch_size = 16
# 批次的大小
lr = 1e-4
# 优化器的学习率
max_epochs = 100
3.3数据读入:根据需要自定义导入函数
,Dataset定义好数据的格式和数据变换形式,DataLoader用iterative的方式不断读入批次数据。
我们可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数:
-
__init__
: 用于向类中传入外部参数,同时定义样本集 -
__getitem__
: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据 -
__len__
: 用于返回数据集的样本数
下面以cifar10数据集为例给出构建Dataset类的方式:
import torch
from torchvision import datasets
train_data = datasets.ImageFolder(train_path, transform=data_transform)
val_data = datasets.ImageFolder(val_path, transform=data_transform)
这里使用了的用于读取按一定结构存储的图片数据(path对应图片存放的目录,目录下包含若干子目录,每个子目录对应属于同一个类的图片)。其中"data_transform”可以对图像进行一定的变换,如翻转、裁剪等操作,可自己定义。这里我们会在下一章通过实战加以介绍。
这里另外给出一个例子,其中图片存放在一个文件夹,另外有一个csv文件给出了图片名称对应的标签。这种情况下需要自己来定义Dataset类:
class MyDataset(Dataset):
def __init__(self, data_dir, info_csv, image_list, transform=None):
"""
Args:
data_dir: path to image directory.
info_csv: path to the csv file containing image indexes
with corresponding labels.
image_list: path to the txt file contains image names to training/validation set
transform: optional transform to be applied on a sample.
"""
label_info = pd.read_csv(info_csv)
image_file = open(image_list).readlines()
self.data_dir = data_dir
self.image_file = image_file
self.label_info = label_info
self.transform = transform
def __getitem__(self, index):
"""
Args:
index: the index of item
Returns:
image and its labels
"""
image_name = self.image_file[index].strip('\n')
raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
label = raw_label.iloc[:,0]
image_name = os.path.join(self.data_dir, image_name)
image = Image.open(image_name).convert('RGB')
if self.transform is not None:
image = self.transform(image)
return image, label
def __len__(self):
return len(self.image_file)
构建好Dataset后,就可以使用DataLoader来按批次读入数据了,实现代码如下:
from torch.utils.data import DataLoader
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
其中:
-
batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数
-
num_workers:有多少个进程用于读取数据
-
shuffle:是否将读入的数据打乱
-
drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练
这里可以看一下我们的加载的数据。PyTorch中的DataLoader的读取可以使用next和iter来完成
import matplotlib.pyplot as plt
images, labels = next(iter(val_loader))
print(images.shape)
plt.imshow(images[0].transpose(1,2,0))
plt.show()
3.4模型构建:神经网络的构造
3.4.1构件基本的神经网络:torch.nn
。下面继承 Module 类构造多层感知机。前向计算也即正向传播。
import torch
from torch import nn
class MLP(nn.Module):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs):
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Linear(784, 256)
self.act = nn.ReLU()
self.output = nn.Linear(256,10)
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
o = self.act(self.hidden(x))
return self.output(o)
以上的 MLP 类中须定义反向传播函数。系统将通过求梯度自动生成反向传播所需的 backward 函数。
我们可以实例化 MLP 类得到模型变量 net 。下述的代码初始化 net 并传入输入数据 X 做一次前向计算。其中, net(X) 会调用 MLP 继承自 Module 类的 函数,这个函数将调用 MLP 类定义的forward 函数来完成前向计算。
X = torch.rand(2,784)
net = MLP()
print(net)
'''
net(X)
MLP(
(hidden): Linear(in_features=784, out_features=256, bias=True)
(act): ReLU()
(output): Linear(in_features=256, out_features=10, bias=True)
)
tensor([[ 0.0149, -0.2641, -0.0040, 0.0945, -0.1277, -0.0092, 0.0343, 0.0627,
-0.1742, 0.1866],
[ 0.0738, -0.1409, 0.0790, 0.0597, -0.1572, 0.0479, -0.0519, 0.0211,
-0.1435, 0.1958]], grad_fn=<AddmmBackward>)
'''
注意,。
3.4.2 神经网络中常见的层+自定义层
深度学习的一个魅力在于神经网络中各式各样的层,例如全连接层、卷积层、池化层与循环层等等。虽然PyTorch提供了大量常用的层,但有时候我们依然希望自定义层。这里我们会介绍如何使用 Module 来自定义层,从而可以被反复调用。
我们先介绍如何定义一个不含模型参数的自定义层。下⾯构造的 MyLayer 类通过继承 Module 类自定义了一个的层,并将层的计算定义在了 forward 函数里。这个层里不含模型参数。
import torch
from torch import nn
class MyLayer(nn.Module):
def __init__(self, **kwargs):
super(MyLayer, self).__init__(**kwargs)
def forward(self, x):
return x - x.mean()
测试,实例化该层,然后做前向计算
layer = MyLayer()
layer(torch.tensor([1, 2, 3, 4, 5], dtype=torch.float))
# tensor([-2., -1., 0., 1., 2.])
我们还可以。
Parameter 类其实是 Tensor 的子类,如果一 个 Tensor 是 Parameter ,那么它会自动被添加到模型的参数列表里。所以 ,。
class MyListDense(nn.Module):
def __init__(self):
super(MyListDense, self).__init__()
self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
self.params.append(nn.Parameter(torch.randn(4, 1)))
def forward(self, x):
for i in range(len(self.params)):
x = torch.mm(x, self.params[i])
return x
net = MyListDense()
print(net)
class MyDictDense(nn.Module):
def __init__(self):
super(MyDictDense, self).__init__()
self.params = nn.ParameterDict({
'linear1': nn.Parameter(torch.randn(4, 4)),
'linear2': nn.Parameter(torch.randn(4, 1))
})
self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增
def forward(self, x, choice='linear1'):
return torch.mm(x, self.params[choice])
net = MyDictDense()
print(net)
下面给出常见的神经网络的一些层,比如卷积层、池化层,以及较为基础的AlexNet,LeNet等。
二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。
import torch
from torch import nn
# 卷积运算(二维互相关)
def corr2d(X, K):
h, w = K.shape
X, K = X.float(), K.float()
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
return Y
# 二维卷积层
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
卷积窗口形状为 p×q 的卷积层称为 p×q 卷积层。同样, p×q 卷积或 p×q 卷积核说明卷积核的高和宽分别为 p 和 q。
填充(padding)是指在输入高和宽的两侧填充元素(通常是0元素)。
下面的例子里我们创建一个高和宽为3的二维卷积层,然后设输入高和宽两侧的填充数分别为1。给定一个高和宽为8的输入,我们发现输出的高和宽也是8。
import torch
from torch import nn
# 定义一个函数来计算卷积层。它对输入和输出做相应的升维和降维
import torch
from torch import nn
# 定义一个函数来计算卷积层。它对输入和输出做相应的升维和降维
def comp_conv2d(conv2d, X):
# (1, 1)代表批量大小和通道数
X = X.view((1, 1) + X.shape)
Y = conv2d(X)
return Y.view(Y.shape[2:]) # 排除不关心的前两维:批量和通道
# 注意这里是两侧分别填充1⾏或列,所以在两侧一共填充2⾏或列
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3,padding=1)
X = torch.rand(8, 8)
comp_conv2d(conv2d, X).shape
# torch.Size([8, 8])
。
# 使用高为5、宽为3的卷积核。在⾼和宽两侧的填充数分别为2和1
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
torch.Size([8, 8])
在二维互相关运算中,。。
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
torch.Size([2, 2])
填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。
步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的 ( 为大于1的整数)。
。不同于卷积层里计算输入和核的互相关性,。该运算也分别叫做最大池化或平均池化。在二维最大池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当池化窗口滑动到某个位置时,
下面把池化层的前向计算实现在pool2d
函数里。
import torch
from torch import nn
def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=torch.float)
pool2d(X, (2, 2)) # tensor([[4., 5.],
# [7., 8.]])
pool2d(X, (2, 2), 'avg') # tensor([[2., 3.],
# [5., 6.]])
我们可以使用torch.nn
包来构建神经网络。我们已经介绍了autograd
包,。一个nn.Module
包含各个层和一个forward(input)
方法,该方法返回output
。
3.4.3模型示例:
这是一个简单的前馈神经网络 (feed-forward network)(LeNet)。它接受一个输入,然后将它送入下一层,一层接一层的传递,最后给出输出。
一个神经网络的典型训练过程如下:
-
定义包含一些可学习参数(或者叫权重)的神经网络
-
在输入数据集上迭代
-
通过网络处理输入
-
计算 loss (输出和正确答案的距离)
-
将梯度反向传播给网络的参数
-
更新网络的权重,一般使用一个简单的规则:
weight = weight - learning_rate * gradient
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 输入图像channel:1;输出channel:6;5x5卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 2x2 Max pooling
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果是方阵,则可以只使用一个数字进行定义
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # 除去批处理维度的其他所有维度
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
'''
Net(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
'''
我们只需要定义 forward
函数,backward
函数会在使用autograd
时自动定义,backward
函数用来计算导数。我们可以在 forward
函数中使用任何针对张量的操作和计算。
一个模型的可学习参数可以通过net.parameters()
返回
params = list(net.parameters())
print(len(params)) # 10
print(params[0].size()) # conv1的权重,torch.Size([6, 1, 5, 5])
让我们尝试一个随机的 32x32 的输入。注意:这个网络 (LeNet)的期待输入是 32x32 的张量。如果使用 MNIST 数据集来训练这个网络,要把图片大小重新调整到 32x32。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
清零所有参数的梯度缓存,然后进行随机梯度的反向传播:
net.zero_grad()
out.backward(torch.randn(1, 10))
注意:torch.nn
只支持小批量处理 (mini-batches)。整个 torch.nn
包只支持小批量样本的输入,不支持单个样本的输入。比如,nn.Conv2d
接受一个4维的张量,即nSamples x nChannels x Height x Width
如果是一个单独的样本,只需要使用input.unsqueeze(0)
来添加一个“假的”批大小维度。
-
torch.Tensor
- 一个多维数组,支持诸如backward()
等的自动求导操作,同时也保存了张量的梯度。 -
nn.Module
- 神经网络模块。是一种方便封装参数的方式,具有将参数移动到GPU、导出、加载等功能。 -
nn.Parameter
- 张量的一种,当它作为一个属性分配给一个Module
时,它会被自动注册为一个参数。 -
autograd.Function
- 实现了自动求导前向和反向传播的定义,每个Tensor
至少创建一个Function
节点,该节点连接到创建Tensor
的函数并对其历史进行编码。
3.4.4模型示例:AlexNet
下面再介绍一个比较基础的案例AlexNet
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
net = AlexNet()
print(net)
'''
AlexNet(
(conv): Sequential(
(0): Conv2d(1, 96, kernel_size=(11, 11), stride=(4, 4))
(1): ReLU()
(2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(96, 256, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(4): ReLU()
(5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): ReLU()
(8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(9): ReLU()
(10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU()
(12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc): Sequential(
(0): Linear(in_features=6400, out_features=4096, bias=True)
(1): ReLU()
(2): Dropout(p=0.5)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU()
(5): Dropout(p=0.5)
(6): Linear(in_features=4096, out_features=10, bias=True)
)
)
'''
3.5模型初始化
3.6损失函数
3.7训练和评估、可视化
3.8Pytorch优化器
4.case:FashionMNIST时装分类
Reference
深入浅出PyTorch — 深入浅出PyTorch