PyTorch 官方文档:pytorch.org/docs/stable/index.html
PyTorch 中文文档:github.com/zergtant/pytorch-handbook
PyTorch 里自带的一些重要工具包:
数据加载:from torch.utils.data import DataLoader
enumerate(self.trainloader)
时调用自定义数据处理的 __getitem__
方法,对训练数据进行读取数据转换:from torchvision import transforms
self.composed_transforms_tr = transforms.Compose
中进行定义transforms.py
调用数据转换数学计算
torch.abs(input)
:计算输入张量的每个元素绝对值
torch.acos(input)
:返回一个新张量,包含输入张量每个元素的反余弦
参考文章:PyTorch(1) torch.nn与torch.nn.functional之间的区别和联系
(1)nn.Conv2d
2d
就是二维,用于对 2d 图像数据的卷积操作,其基本定义为:
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
参考官方文档:conv2d
输入参数分别为:
*in_channels
输入通道数*out_channels
输出通道数*kernel_size
卷积核大小stride
卷积步长(默认为1)padding
填充的圈数(zero-padding,默认不填充)dilation
带孔卷积的扩散率(默认为1,即普通的卷积)groups
分组卷积(默认为1,即不分组)bias
是否加偏置项(默认True)其中,输入、输出通道数 和 卷积核大小是必须设置的,也就是前三项,而后面的参数均有默认值,如果不设置的话就使用默认值啦。
(2)nn.BatchNorm2d
常用于卷积网络中防止梯度消失或爆炸,其基本定义为:
nn.BatchNorm2d(num_features, eps=1e-05,momentum=0.1,affine=True)
输入参数分别为:
*num_features
输入通道数eps
用于保持数据稳定性的一个参数,加在分母上(默认为 1e-5)momentum
用于 running_mean 和 running_var 的计算(默认为 0.1)affine
若为 True,则网络包含该可学习参数# with learnable parameters
m = nn.BatchNorm2d(100)
# without learnable parameters
m = nn.BatchNorm2d(100, affine=False)
(3)nn.ReLU
ReLU 为激活函数,基本定义如下:
nn.ReLU(inplace=True)
参数 inplace
默认为 True, 当设为 True 时,会改变输入的数据。其实 inplace 是 True 还是 False 对计算结果没有影响,设置为 True 在计算时可以节省内(显)存,同时还可以省去反复申请和释放内存的时间。但是会对原变量覆盖,只要不带来错误就用。
# 设置 inplace=True 的效果
import torch
import torch.nn as nn
out = nn.ReLU(inplace=True)
input = torch.randn(5)
print("input:")
print(input)
output = out(input)
print("ReLU output:")
print(output)
# 改变了原值
print("input:")
print(input)
>>>
input:
tensor([-0.2954, -0.2941, 0.2327, -0.8194, -0.7024])
ReLU output:
tensor([0.0000, 0.0000, 0.2327, 0.0000, 0.0000])
input:
tensor([0.0000, 0.0000, 0.2327, 0.0000, 0.0000])
(4)nn.MaxPool2d
最大池化层,基本定义如下:
nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
输入参数分别为:
*kernel_size
为池化的窗口大小stride
为池化窗口的移动步长,默认等于池化窗口大小padding
为填充圈数(zero-padding)dilation
和带孔卷积有关,但是池化并没有可学习参数return_indices
如果等于 True,会返回输出最大值的序号,这样对上采样操作有帮助ceil_mode
如果等于 True,则在计算输出信号大小时会使用向上取整操作,默认的 False 是向下取整假设现在有大小为 32 x 32
的图片样本,输入样本的 channels = 1
,该图片可能属于 10 个类中的某一类,网络结构使用 [conv + relu + pooling] * 2 + FC * 3
,那么 CNN 框架定义如下:
class CNN(nn.Module):
def __init__(self):
nn.Model.__init__(self)
# 输入通道数=1,输出通道数=6,卷积核大小=5
self.conv1 = nn.Conv2d(1, 6, 5)
# 输入通道数=6,输出通道数=16,卷积核大小=5
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(5 * 5 * 16, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
## 由于relu和maxpooling都没有可学习的参数,故可以不在init中定义
def forward(self,x):
# 输入x -> conv1 -> relu -> 2x2 maxpooling
x = self.conv1(x) # stride默认为1
x = F.relu(x)
x = F.max_pool2d(x, 2) # kernel=2
# 输入x -> conv2 -> relu -> 2x2窗口的最大池化
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, 2)
# view函数将张量x变形成一维向量形式,总特征数不变,为全连接层做准备
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
注意: 在 PyTorch 中,池化操作默认的 stride 大小与卷积核的大小一致。
设置 GPU:
# Set gpu_id to -1 to run in CPU mode, otherwise set the id of the corresponding gpu
gpu_id = 1
device = torch.device("cuda:"+str(gpu_id) if torch.cuda.is_available() else "cpu")
if torch.cuda.is_available():
print('Using GPU: {} '.format(gpu_id))
# Network definition
net = ...
# 将网络和数据都放到 GPU 上
net.to(device)
【1】torch.device
代表将 torch.Tensor
分配到的设备对象,可以通过字符串或字符串+设备序号来进行实现:
# 通过字符串
>>> torch.device('cuda:0')
device(type='cuda', index=0)
>>> torch.device('cpu')
device(type='cpu')
>>> torch.device('cuda') # current cuda device
device(type='cuda')
# 通过字符串和设备序号
>>> torch.device('cuda', 0)
device(type='cuda', index=0)
>>> torch.device('cpu', 0)
device(type='cpu', index=0)
【2】torch.cuda.is_available()
用于验证 pytorch 是否能正确地使用 GPU 加速运算,只要安装没问题,其返回值就是 True:
>>> torch.cuda.is_available()
True
保存/加载模型和参数:
参考:PyTorch学习:加载模型和参数
PyTorch 模型的保存和参数的保存是分开的,可以分别保存或加载模型和参数。
保存方式有两种:
加载方式也有两种:
# 1.同时加载模型和参数
torch.save(model_object, 'resnet.pth')
model = torch.load('resnet.pth')
# 2.分别加载网络结构和参数
# 将my_resnet模型储存为my_resnet.pth
torch.save(my_resnet.state_dict(), "my_resnet.pth")
# 加载resnet,模型存放在my_resnet.pth
my_resnet.load_state_dict(torch.load("my_resnet.pth"))
# cpu->cpu
checkpoint = torch.load('model.pth')
model.load_state_dict(checkpoint)
# cpu->gpu
torch.load('model.pth', map_location=lambda storage, loc: storage.cuda(1))
# gpu1->gpu0
torch.load('model.pth', map_location={'cuda:1':'cuda:0'})
# gpu->cpu
torch.load('model.pth', map_location=lambda storage, loc: storage))
关于 train() 和 eval()
参考:torch.nn.Module.eval
当网络中有 BN 层或 Dropout 层时,训练过程前面会加上 *.train()
,测试过程前面会加上 *.eval()
import torch
# 1. Data preparation: get_data
# 2. Creating learnable parameters: get_weights
# 3. Network model: simple_network
# 4. Loss: loss_fn
# 5. Optimizer: optimize
# ============================ Data Preparation ============================ #
# 1.Scalar(0-D tensors)
# type: FloatTensor or LongTensor
print('------------- Scalar -------------')
x = torch.rand(3)
print(x) # Output: tensor([0.6788, 0.3105, 0.3672])
print(x.size()) # Output: torch.Size([3])
# 2.Vectors(1-D tensors)
print('------------- Vectors -------------')
temp = torch.FloatTensor([23, 24, 24.5, 27.2, 23.0])
print(temp) # Output: tensor([23.0000, 24.0000, 24.5000, 27.2000, 23.0000])
print(temp.size()) # Output: torch.Size([5])
# 3.Matrix(2-D tensors)
# convert numpy array into a torch tensor: form_numpy()
from sklearn import datasets
print('------------- Matrix -------------')
boston = datasets.load_boston()
boston_tensor = torch.from_numpy(boston.data)
print(boston_tensor.size())
print(boston_tensor[:2])
# 4.3-D Tensors
# 3-D tensors is used to represent data-like images
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
print('------------- 3-D Tensors -------------')
# read a panda image from disk using a library called PIL and convert it to numpy array
panda = np.array(Image.open('panda.jpg').resize((224,224)))
panda_tensor = torch.from_numpy(panda)
print(panda_tensor.size())
# Display panda
plt.imshow(panda)
# plt.show()
# 5.Slicing tensors
print('------------- Slicing tensors -------------')
# sales[:slice_index], where slice_index represents the index where you want to slice the tensor
sales = torch.FloatTensor([1000.0, 323.2, 333.4, 444.5, 1000.0, 323.2, 333.4, 444.5])
print(sales[:5])
print(sales[:-5])
# show image only one channel
plt.imshow(panda_tensor[:,:,0].numpy())
#plt.show()
# show image with some specific region
plt.imshow(panda_tensor[25:175,60:130,0].numpy())
#plt.show()
# 6.4-D Tensors
print('------------- 4-D Tensors -------------')
# 4-D tensor usually represents a batch of images
import glob
# read cat images from disk
data_path = 'cat/'
cats = glob.glob(data_path + '*.jpg')
# convert images into numpy arrays
cat_imgs = np.array([np.array(Image.open(cat).resize((224,224))) for cat in cats[:3]])
cat_imgs = cat_imgs.reshape(-1,224,224,3)
cat_tensors = torch.from_numpy(cat_imgs)
print(cat_tensors.size())
# 7.5-D Tensors
print('------------- 5-D Tensors -------------')
# 5-D tensor usually represents video data
# ========================================================================== #
# ========================= Tensors on GPU and CPU ========================= #
print('------------- Tensors on GPU and CPU -------------')
# tensor addition(+)
a = torch.rand(2,2)
b = torch.rand(2,2)
c = a + b # method 1
d = torch.add(a,b) # method 2
e = a.add_(b) # method 3(in-place addition)
print('a + b = ', c)
print('torch.add(a,b) = ', d)
print('a.add_(b) = ', e)
# tensor multiply(*)
c = a * b # method 1
d = a.mul(b) # method 2
e = a.mul_(b) # method 3(in-place multiplication)
print('a * b = ', c)
print('a.mul(b) = ', d)
print('a.mul_(b) = ', e)
# tensor matrix multiply(compare on CPU and GPU)
import time
a = torch.rand(10000,10000)
b = torch.rand(10000,10000)
# CPU
tic = time.time()
a.matmul(b)
toc = time.time()
#print('Time taken: ', toc-tic, ' s')
# GPU
a = a.cuda()
b = b.cuda()
tic = time.time()
a.matmul(b)
toc = time.time()
#print('Time taken: ', toc-tic, ' s')
# ========================================================================== #
# ================================ Variable ================================ #
# Variable class components: data, grad, creator
print('------------- Variable -------------')
from torch.autograd import Variable
x = Variable(torch.ones(2,2), requires_grad=True)
y = x.mean()
y.backward()
print('x:', x)
print('x.data: ', x.data)
print('x.grad: ', x.grad)
# grad_fn: 'None' for user created, function reference for other
print('x.grad_fn', x.grad_fn)
print('y.grad_fn', y.grad_fn) # MeanBackward
# ========================================================================== #
# ============================== Neural network ============================ #
# Creating data for neural network(fixed parameters x,y)
def get_data():
train_X = np.asarray([3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.167,7.042,
10.791,5.313,7.997,5.654,9.27,3.1])
train_Y = np.asarray([1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.221,2.827,
3.465,1.65,2.904,2.42,2.94,1.3])
dtype = torch.FloatTensor
x = Variable(torch.from_numpy(train_X).type(dtype), requires_grad=False).view(17,1)
y = Variable(torch.from_numpy(train_Y).type(dtype), requires_grad=False)
return x, y
# Creating learnable parameters(learnable parameters w,b)
def get_weights():
w = Variable(torch.randn(1), requires_grad=True)
b = Variable(torch.randn(1), requires_grad=True)
return w, b
# Network implementation
def simple_network():
y_pred = torch.matmul(x,w) + b
# Much simpler
# f = nn.Linear(17,1)
return y_pred
# Loss function
def loss_fn(y, y_pred):
# sum of squared error(SSE) for regression problem
loss = (y_pred-y).pow(2).sum()
for param in [w,b]:
if not param.grad is None: param.grad.data.zero_()
loss.backward()
return loss.data[0]
# Optimize the neural network
def optimize(learning_rate):
w.data -= learning_rate * w.grad.data
b.data -= learning_rate * b.grad.data
# Dataset class
# two important function: __len__(self) and __getitem__(self, idx)
from torch.utils.data import Dataset
class DogsAndCatsDataset(Dataset):
def __init__(self,):
pass # init do any initialization
def __len__(self):
pass # len return the maximum number of elements in dataset
def __getitem__(self, idx):
pass # getitem return an element based on the idx every time it is called
class DogsAndCatsDataset(Dataset):
def __init__(self, root_dir, size=(224,224)):
self.files = glob.glob(root_dir)
self.size = size
def __len__(self):
return len(self.files)
def __getitem__(self, idx):
img = np.asarray(Image.open(self.files[idx]).resuze(self.size))
label = self.files[idx].split('/')[-2]
return img, label
# DataLoader class
from torch.utils.data import DataLoader
dataloader = DataLoader(dogsdset, batch_size=32, num_workers=2)
for imgs, labels in dataloader:
# apply your DL on the dataset
pass
# imgs contain a tensor of shape (batch_size, height, weight, channels)
# ========================================================================== #
Output:
------------- Scalar -------------
tensor([0.8906, 0.5367, 0.2124])
torch.Size([3])
------------- Vectors -------------
tensor([23.0000, 24.0000, 24.5000, 27.2000, 23.0000])
torch.Size([5])
------------- Matrix -------------
torch.Size([506, 13])
tensor([[6.3200e-03, 1.8000e+01, 2.3100e+00, 0.0000e+00, 5.3800e-01, 6.5750e+00,
6.5200e+01, 4.0900e+00, 1.0000e+00, 2.9600e+02, 1.5300e+01, 3.9690e+02,
4.9800e+00],
[2.7310e-02, 0.0000e+00, 7.0700e+00, 0.0000e+00, 4.6900e-01, 6.4210e+00,
7.8900e+01, 4.9671e+00, 2.0000e+00, 2.4200e+02, 1.7800e+01, 3.9690e+02,
9.1400e+00]], dtype=torch.float64)
------------- 3-D Tensors -------------
torch.Size([224, 224, 3])
QXcbConnection: Failed to initialize XRandr
------------- Slicing tensors -------------
tensor([1000.0000, 323.2000, 333.4000, 444.5000, 1000.0000])
tensor([1000.0000, 323.2000, 333.4000])
------------- 4-D Tensors -------------
torch.Size([3, 224, 224, 3])
------------- 5-D Tensors -------------
------------- Tensors on GPU and CPU -------------
a + b = tensor([[0.8486, 1.1625],
[0.7530, 0.2172]])
torch.add(a,b) = tensor([[0.8486, 1.1625],
[0.7530, 0.2172]])
a.add_(b) = tensor([[0.8486, 1.1625],
[0.7530, 0.2172]])
a * b = tensor([[3.8537e-01, 1.1534e+00],
[3.8019e-02, 9.6186e-04]])
a.mul(b) = tensor([[3.8537e-01, 1.1534e+00],
[3.8019e-02, 9.6186e-04]])
a.mul_(b) = tensor([[3.8537e-01, 1.1534e+00],
[3.8019e-02, 9.6186e-04]])
------------- Variable -------------
x: tensor([[1., 1.],
[1., 1.]], requires_grad=True)
x.data: tensor([[1., 1.],
[1., 1.]])
x.grad: tensor([[0.2500, 0.2500],
[0.2500, 0.2500]])
x.grad_fn None
y.grad_fn
【1】在训练前如何设置GPU
【2】加载预训练模型
【3】网络定义及常用模块
【4】nn.Sequential() 模块
【5】nn.ModuleList() 模块
【1】设置 GPU:
# Set gpu_id to -1 to run in CPU mode, otherwise set the id of the corresponding gpu
gpu_id = 1
device = torch.device("cuda:"+str(gpu_id) if torch.cuda.is_available() else "cpu")
if torch.cuda.is_available():
print('Using GPU: {} '.format(gpu_id))
# Network definition
net = ...
# 将网络和数据都放到 GPU 上
net.to(device)
【1】torch.device
代表将 torch.Tensor
分配到的设备对象,可以通过字符串或字符串+设备序号来进行实现:
# 通过字符串
>>> torch.device('cuda:0')
device(type='cuda', index=0)
>>> torch.device('cpu')
device(type='cpu')
>>> torch.device('cuda') # current cuda device
device(type='cuda')
# 通过字符串和设备序号
>>> torch.device('cuda', 0)
device(type='cuda', index=0)
>>> torch.device('cpu', 0)
device(type='cpu', index=0)
【2】torch.cuda.is_available()
用于验证 pytorch 是否能正确地使用 GPU 加速运算,只要安装没问题,其返回值就是 True:
>>> torch.cuda.is_available()
True
【2】 加载模型:
# cpu->cpu
checkpoint = torch.load('model.pth')
model.load_state_dict(checkpoint)
# cpu->gpu
torch.load('model.pth', map_location=lambda storage, loc: storage.cuda(1))
# gpu1->gpu0
torch.load('model.pth', map_location={'cuda:1':'cuda:0'})
# gpu->cpu
torch.load('model.pth', map_location=lambda storage, loc: storage))
【3】定义网络:
参考:
【1】nn.Module模块
【2】PyTorch中的nn.Conv1d与nn.Conv2d
torch.nn
是专门为神经网络设计的模块化接口。nn
构建于 autograd
之上,可以用来定义和运行神经网络。nn.Module
是 nn
中十分重要的类,包含网络各层的定义及 forward 方法。
在定义自己的网络时,需要继承 nn.Module
类,并实现 forward
方法。一般把网络中具有可学习参数的层放在构造函数 __init__()
中,不具有可学习参数的层(如ReLU)既可放在构造函数中,也可不放在构造函数中(在forward中使用nn.functional来代替)。只要在 nn.Module
的子类中定义了 forward
函数,backward
函数就会被自动实现(利用Autograd)
class LeNet(nn.Module):
def __init__(self):
# nn.Module的子函数必须在构造函数中继承父类的构造函数
# 这句是定义网络时要写的标准语句
# 等价于 nn.Module.__init__()
super(LeNet, self).__init__()
# nn.Conv2d返回的是一个Conv2d class的一个对象,该类中包含forward函数的实现
# 当调用self.conv1(input)的时候,就会调用该类的forward函数
# output (N, C_{out}, H_{out}, W_{out})
self.conv1 = nn.Conv2d(1, 6, (5, 5))
self.conv2 = nn.Conv2d(6, 16, (5, 5))
self.fc1 = nn.Linear(256, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# F.max_pool2d的返回值是一个 Variable
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), (2, 2))
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
# 返回值也是一个Variable对象
return x
def output_name_and_params(net):
for name, parameters in net.named_parameters():
print('name: {}, param: {}'.format(name, parameters))
if __name__ == '__main__':
net = LeNet()
print('net: {}'.format(net))
params = net.parameters() # generator object
print('params: {}'.format(params))
output_name_and_params(net)
input_image = torch.FloatTensor(10, 1, 28, 28)
# 与tensorflow不一样,pytorch中模型的输入是一个Variable,而且是Variable在图中流动,不是Tensor。
# 这可以从forward中每一步的执行结果可以看出
input_image = Variable(input_image)
output = net(input_image)
print('output: {}'.format(output))
print('output.size: {}'.format(output.size()))
(1)nn.Conv2d
中 2d
就是二维,用于对图像数据的卷积操作,其基本定义为:
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
参考官方文档:conv2d
输入参数分别为:in_channels
输入通道数,out_channels
输出通道数,kernel_size
卷积核大小,stride
卷积步长(默认为1),padding
填充的圈数(zero-padding,默认不填充),dilation
带孔卷积的扩散率(默认为1,即普通的卷积),groups
分组卷积(默认为1,即不分组),bias
是否加偏置项(默认True)。
其中,输入、输出通道数 和 卷积核大小是必须设置的,也就是前三项,而后面的参数均有默认值,如果不设置的话就使用默认值啦。
假设现在有大小为 32 x 32
的图片样本,输入样本的 channels = 1
,该图片可能属于 10 个类中的某一类,网络结构使用 [conv + relu + pooling] * 2 + FC * 3
,那么 CNN 框架定义如下:
class CNN(nn.Module):
def __init__(self):
nn.Model.__init__(self)
# 输入通道数=1,输出通道数=6,卷积核大小=5
self.conv1 = nn.Conv2d(1, 6, 5)
# 输入通道数=6,输出通道数=16,卷积核大小=5
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(5 * 5 * 16, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
## 由于relu和maxpooling都没有可学习的参数,故可以不在init中定义
def forward(self,x):
# 输入x -> conv1 -> relu -> 2x2 maxpooling
x = self.conv1(x) # stride默认为1
x = F.relu(x)
x = F.max_pool2d(x, 2) # kernel=2
# 输入x -> conv2 -> relu -> 2x2窗口的最大池化
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, 2)
# view函数将张量x变形成一维向量形式,总特征数不变,为全连接层做准备
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
注意: 在 PyTorch 中,池化操作默认的 stride 大小与卷积核的大小一致。
(2)nn.BatchNorm2d
常用于卷积网络中防止梯度消失或爆炸,其基本定义为:
nn.BatchNorm2d(num_features, eps=1e-05,momentum=0.1,affine=True)
输入参数分别为:num_features
输入通道数;eps
用于保持数据稳定性的一个参数,加在分母上,默认为 1e-5;momentum
用于 running_mean 和 running_var 的计算,默认为 0.1;affine
若为 True,则网络包含该可学习参数
# with learnable parameters
m = nn.BatchNorm2d(100)
# without learnable parameters
m = nn.BatchNorm2d(100, affine=False)
(3)nn.ReLU
基本定义如下:
nn.ReLU(inplace=True)
参数 inplace
默认为 True, 当设为 True 时,会改变输入的数据。其实用不同 inplace 对计算结果没有影响,利用它计算可以节省内(显)存,同时还可以省去反复申请和释放内存的时间。但是会对原变量覆盖,只要不带来错误就用。
import torch
import torch.nn as nn
out = nn.ReLU(inplace=True)
input = torch.randn(5)
print("input:")
print(input)
output = out(input)
print("ReLU output:")
print(output)
print("input:")
print(input)
>>>
input:
tensor([-0.2954, -0.2941, 0.2327, -0.8194, -0.7024])
ReLU output:
tensor([0.0000, 0.0000, 0.2327, 0.0000, 0.0000])
input:
tensor([0.0000, 0.0000, 0.2327, 0.0000, 0.0000])
(4)nn.MaxPool2d
基本定义如下:
nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
kernel_size
为池化的窗口大小,stride
为池化窗口的移动步长,默认等于池化窗口大小,padding
为填充圈数(zero-padding),dilation
和带孔卷积有关,但是池化并没有可学习参数,return_indices
如果等于 True,会返回输出最大值的序号,这样对上采样操作有帮助,ceil_mode
如果等于 True,则在计算输出信号大小时会使用向上取整操作,默认的 False 是向下取整
(5)在定义网络时,还可以在类中定义一些私有方法用来模块化一些操作,比如在 ResNet 中定义了 _make_layer
来构建ResNet网络中的4个blocks。
输入参数:block
用于选择 BasicBlock 还是 Bottleneck 类,planes
是当前 block 的输出通道数,blocks
是每个 block 中包含多少个卷积层,它是一个列表,比如在 ResNet101 中定义:
model = ResNet(Bottleneck, [3, 4, 23, 3], n_classes, nInputChannels=nInputChannels,
classifier=classifier, dilations=dilations, strides=strides, _print=True)
这里的 [3, 4, 23, 3]
传给 ResNet 的 layers 参数,然后用 _make_layer
创建 block:
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=strides[2])
self.layer3 = self._make_layer(block, 256, layers[2], stride=strides[3], dilation__=dilations[0])
self.layer4 = self._make_layer(block, 512, layers[3], stride=strides[4], dilation__=dilations[1])
上面的 layers
参数值传给 _make_layer
的 blocks
,生成每个 block 的卷积层关键代码就在 for
循环中
def _make_layer(self, block, planes, blocks, stride=1, dilation__=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion or dilation__ == 2 or dilation__ == 4:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(planes * block.expansion, affine=affine_par),
)
for i in downsample._modules['1'].parameters():
i.requires_grad = False
layers = [block(self.inplanes, planes, stride, dilation_=dilation__, downsample=downsample)]
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes, dilation_=dilation__))
return nn.Sequential(*layers)
_make_layer
会为每个 block 会创建 layer[*]
个 Bottleneck
模块,根据 Bottleneck
的定义,其中包含了三个卷积层,每个卷积层后面跟着一个 BN,最后一个卷积后除了 BN 还有 ReLU 和 下采样。
看一下 ResNet 101 网络参数:这里只放了前两个 Block,分别有 3 个和 4 个 Bottleneck 模块,可以看到 Bottleneck 发挥的作用,降低参数量 ~
ResNet(
(conv1): Conv2d(4, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
(conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(downsample): Sequential(
(0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
)
(2): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
)
)
(layer2): Sequential(
(0): Bottleneck(
(conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
)
(2): Bottleneck(
(conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
)
(3): Bottleneck(
(conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
)
)
在上面 ResNet101 的网络参数定义中,看到一个叫 Sequential()
的东西,里面包含了一个 conv 和 norm 层,很神奇。
【4】nn.Sequential() 模块
查到它的定义是这样的:Sequential 是一个有序的容器,神经网络模块将按照传入该容器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数。
啊,说人话就是在 Sequential 里可以声明好多层,声明的顺序就是最终神经网络参数传递的顺序,如果把每个 layer 看作一个有特定工作的工人,那就可以把 Sequential 看作是一个工厂,把工人们按流水线的顺序安排在工厂里就可以了,这样做算是一种简化方式吧。
使用 nn.Sequential()
,必须确保前一层的输出大小与下一层的输入大小相匹配,使用该模块有几种方式:
# 方法一:先定义对象,再使用 add_module 添加层
model = nn.Sequential()
model.add_module('conv', nn.Conv2d(3, 3, 3))
model.add_module('batchnorm', nn.BatchNorm2d(3))
model.add_module('activation_layer', nn.ReLU())
# 方法二:直接定义
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
# 方法三:结合 OrderedDict 食用
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
# 实例
class Net(nn.Module):
def __init__(self, inplanes, n_hidden_1, n_hidden_2, planes):
super().__init__()
self.layer = nn.Sequential(
nn.Linear(in_dim, n_hidden_1),
nn.ReLU(True),
nn.Linear(n_hidden_1, n_hidden_2),
nn.ReLU(True),
nn.Linear(n_hidden_2, out_dim)
)
def forward(self, x):
x = self.layer(x)
return x
# 查看模型直接输出即可
print('model:', model)
【5】nn.ModuleList() 模块
nn.ModuleList
用来存储任意数量的 nn. module
当添加 nn.ModuleList
作为 nn.Module
对象的一个成员时(即当我们添加模块到我们的网络时),所有 nn.ModuleList
内部的 nn.Module 的 parameter 也被添加作为网络的 parameter。
class MyModule(nn.Module):
def __init__(self):
super(MyModule, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
def forward(self, x):
# ModuleList can act as an iterable, or be indexed using ints
for i, l in enumerate(self.linears):
x = self.linears[i // 2](x) + l(x)
return x
定义了 nn.ModuleList
对象后,可以使用 extend
添加另一个 modulelist,或使用 append
向当前 modulelist 添加另一个 module
class LinearNet(nn.Module):
def __init__(self, input_size, num_layers, layers_size, output_size):
super(LinearNet, self).__init__()
self.linears = nn.ModuleList([nn.Linear(input_size, layers_size)])
self.linears.extend([nn.Linear(layers_size, layers_size) for i in range(1, self.num_layers-1)])
self.linears.append(nn.Linear(layers_size, output_size)
def forward()
pass
和 nn.Sequential
不同的是,nn.ModuleList
没有自动 forward 功能,所以需要自己定义。
【1】torchvision.transforms
【2】__init__ 和 __call__
【1】torchvision.transforms
torchvision.transforms
是 PyTorch 中的图像预处理包,一般会用 transforms.Compose
将多个处理步骤整合到一起,比如:
from torchvision import transforms
composed_transforms_tr = transforms.Compose([
transforms.CenterCrop(10),
transforms.ToTensor()
])
其他预处理函数:
Resize:把给定的图片resize到指定大小
Normalize:对图像进行标准化
ToTensor:将像素值在范围[0,255]内的图像转换为范围在[0.0,1.0]的torch.Tensor
ToPILImage:将tensor转换为PIL图像
CenteCrop:在图片的中间区域进行裁剪
RandomCrop:在一个随机的位置进行裁剪
RandomHorizontalFlip:以0.5的概率水平翻转给定的PIL图像
RandomVerticalFlip:以0.5的概率竖直翻转给定的PIL图像
RandomResizedCrop:将PIL图像裁剪成任意大小和纵横比
Grayscale:将图像转换为灰度图像
RandomGrayscale:将图像以一定的概率转换为灰度图像
FiceCrop:把图像裁剪为四个角和一个中心
ColorJitter:随机改变图像的亮度对比度和饱和度
【2】__init__ 和 __call__
__init__
类的初始化函数,__call__
使类具有类似于函数的功能。
class Cat():
def __init__(self, name, init_age):
super().__init__
self.name = name
self.age = init_age
print("{} is playing".format(self.name))
print("{} is {} year-old".format(self.name, self.age))
def __call__(self, add_age):
cur_age = self.age + add_age
print("Now {} is {} year-old".format(self.name, cur_age))
cat = Cat('kamiya', 2)
cat(1)
>>>
kamiya is playing
kamiya is 2 year-old
Now kamiya is 3 year-old
参考文章:
https://zhuanlan.zhihu.com/p/30934236
https://blog.csdn.net/rogerfang/article/details/82291464
https://blog.csdn.net/zhenaoxi1077/article/details/80953227
参考文章:
https://zhuanlan.zhihu.com/p/27382990
参考文章:
https://ptorch.com/news/57.html
https://blog.csdn.net/e01528/article/details/84397174
参考文章:
https://www.zhihu.com/question/67726969
https://www.cnblogs.com/marsggbo/p/10962763.html
【创建tensor】
# 创建张量
torch.Tensor([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
# 根据 A 创建张量
torch.rand_like(A, dtype=torch.float)
# h*w 的 1 矩阵
torch.ones(h, w)
# h*w 的 0 矩阵
torch.zeros(h, w)
# 与 A 维度相同的 1 矩阵
torch.oness_like(A)
# 与 A 维度相同的 0 矩阵
torch.zeros_like(A)
# 对角矩阵
torch.diag(torch.from_numpy(np.array([1, 2, 3, 4, 5])))
# h*w 的随机矩阵
torch.rand(h, w)
# h*w 的符合正态分布的随机矩阵
torch.randn(h, w)
# h*w 的空矩阵
torch.empty(h, w)
torch.mul(a,b) 矩阵a和b对应位相乘,a和b的维度必须一致
torch.mat(a,b) 矩阵a和b相乘
tensor 到 numpy:
a = torch.ones(5)
>>> tensor([1., 1., 1., 1., 1.])
b = a.numpy()
>>> [1. 1. 1. 1. 1.]
注意:转换后的tensor与numpy指向同一地址,所以,对一方的值改变另一方也随之改变
对于训练时带有梯度信息的 tensor,在转换为 numpy 时,需要先从 CUDA tensor 转换到 CPU,再去除梯度信息:
b = a.cpu().detach().numpy()
numpy 到 tensor:
a = np.ones(5)
>>> [2. 2. 2. 2. 2.]
b = torch.from_numpy(a)
>>> tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
【获取张量维度】
获取名为 tensor 的张量维度
tensor.size()
>>> torch.Size([2, 2])
【拼接张量】
假设 A 和 B 都是维度为 (12,1,224,224)
的 tensor,要将它们在第二个维度上进行拼接,形成 (12,2,224,224)
的 tensor,可以利用 torch.cat()
进行拼接:
torch.cat((A,B), 1)
注意: 在拼接 tensor 时,除了被拼接的维度数值可以不同外,其余维度上的数值均需要相同。
【扩展/压缩维度】
x = x.unsqueeze(1) # 扩展维度
x = x.squeeze() # 压缩维度
第一行扩展维度,实现的效果是将维度为 (8,224,224)
的 tensor 在第一维上增加维度,变成 (8,1,224,224)
的 tensor。
第二行压缩维度,将 tensor 中维度为 1 的去除,比如维度为 (8,1,224,224)
的张量经过压缩操作,形状就会变成 (8,224,224)
。
【改变张量形状】
将张量 reshape 到 (h,w)
大小,其中某一位可以置 -1
,此时置为 -1 的维度由另一个维度和总维度计算得到。
tensor.view(h,w)
tensor.view(h,-1)
【张量类型转换】
tensor.long() # 将tensor投射为long类型
tensor.half() # 将tensor投射为半精度浮点类型
tensor.int() # 将tensor投射为int类型
tensor.double() # 将tensor投射为double类型
tensor.float() # 将tensor投射为float类型
tensor.char() # 将tensor投射为char类型
tensor.byte() # 将tensor投射为byte类型
tensor.short() # 将tensor投射为short类型