Pytorch笔记(一)

PyTorch 官方文档:pytorch.org/docs/stable/index.html
PyTorch 中文文档:github.com/zergtant/pytorch-handbook

文章目录

  • 1. torch.nn 与 torch.functioanl 的区别
  • 2. 基本网络模块
  • 3. 基本设置
  • 4. 读取数据
  • 5. torchvision.transforms 包
  • 6. torch.nn.Sequential
  • 7. torch.nn.DataParallel
  • 8. tensor 基本计算
  • 9. torch 与 numpy 转换
  • 10. 对 tensor 的基本操作


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):返回一个新张量,包含输入张量每个元素的反余弦


1. torch.nn 与 torch.functioanl 的区别

参考文章:PyTorch(1) torch.nn与torch.nn.functional之间的区别和联系

2. 基本网络模块

(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 大小与卷积核的大小一致。

3. 基本设置

设置 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. 保存整个神经网络的结构信息和模型的参数信息
  2. 仅保存神经网络的模型参数

加载方式也有两种:

  1. 同时加载模型和参数
  2. 分别加载网络结构和参数
# 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.Modulenn 中十分重要的类,包含网络各层的定义及 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.Conv2d2d 就是二维,用于对图像数据的卷积操作,其基本定义为:

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_layerblocks,生成每个 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

4. 读取数据

参考文章:
https://zhuanlan.zhihu.com/p/30934236
https://blog.csdn.net/rogerfang/article/details/82291464
https://blog.csdn.net/zhenaoxi1077/article/details/80953227

5. torchvision.transforms 包

参考文章:
https://zhuanlan.zhihu.com/p/27382990

6. torch.nn.Sequential

参考文章:
https://ptorch.com/news/57.html
https://blog.csdn.net/e01528/article/details/84397174

7. torch.nn.DataParallel

参考文章:
https://www.zhihu.com/question/67726969
https://www.cnblogs.com/marsggbo/p/10962763.html

8. tensor 基本计算

【创建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相乘

9. torch 与 numpy 转换

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)

10. 对 tensor 的基本操作

【获取张量维度】

获取名为 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类型

你可能感兴趣的:(深度学习,#,PyTorch)