前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【AICAMP —— Pytorch】看完就去搭网络!

【AICAMP —— Pytorch】看完就去搭网络!

作者头像
灿视学长
发布2021-05-28 11:21:58
9160
发布2021-05-28 11:21:58
举报
文章被收录于专栏:灿视学长

Pytorch 入门教程

Pytorch是一个基于python的科学计算库,致力于为两类用户提供服务:

  1. 一些想要找到 Numpy 搭建神经网络替代品的用户;
  2. 寻找一个可提供极强的可拓展性和运行速度的深度学习研究平台;

1. 张量的概念和生成

张量和Numpyndarrays的概念很相似,有了这个作为基础,张量也可以被运行在GPU上来加速计算,下面直接可以感受下张量的创建与Numpy有什么样的区别。

代码语言:javascript
复制
>>> import torch
>>> #  这个是用来生成一个未初始化的5*3的张量,切记不是全零
>>> x = torch.empty(5, 3)
>>> print(x)
tensor([[1.6795e+08, 4.7423e+30, 4.7393e+30],
        [9.5461e-01, 4.4377e+27, 1.7975e+19],
        [4.6894e+27, 7.9463e+08, 3.2604e-12],
        [1.7743e+28, 2.0535e-19, 5.9682e-02],
        [7.0374e+22, 3.8946e+21, 4.4650e+30]])

>>> x = torch.rand(5, 3)
>>> print(x)
tensor([[0.6174, 0.5861, 0.0551],
        [0.0682, 0.7835, 0.1476],
        [0.9313, 0.4109, 0.5852],
        [0.5924, 0.8216, 0.5282],
        [0.1268, 0.6657, 0.8975]])
>>> # 这个是初始化一个全零张量,可以指定每个元素的类型。
>>> x = torch.zeros(5, 3, dtype=torch.long)
>>> print(x)
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])
>>> #从已有列表转化为张量
>>> x = torch.tensor([5.5, 3])
>>> print(x)
tensor([5.5000, 3.0000])
>>> #新建一个与之前tensor一样shape的tensor
>>> y = torch.randn_like(x, dtype=torch.float32)
>>> print(y)
tensor([-0.1764, -0.4671])
>>> print(x.size)
torch.Size([2])

2. 张量的操作

在这里,先了解下张量的四则运算。包括加、剪、乘、除等具体的实现代码!

  • 加法的具体实现:
代码语言:javascript
复制
>>> import torch
>>> x = torch.rand(5, 3)
>>> y = torch.randn(5, 3)
>>> print(x)
tensor([[ 0.5597, -0.7536,  0.2511],
        [-1.8233, -0.0448,  0.8358],
        [ 1.6649, -0.2385, -0.5507],
        [-2.2102, -0.8068,  0.6075],
        [-0.9757, -1.4153,  1.6784]])
>>>print(y)
tensor([[-1.2461,  0.5389,  0.2612],
        [-2.3689, -1.8445,  0.6520],
        [ 0.0917,  1.0162,  0.0165],
        [ 2.0018, -1.1325, -0.2471],
        [-0.4413, -0.5396, -1.6062]])
>>> # 加法1
>>> z = x + y
>>> print(z)
tensor([[-0.6864, -0.2147,  0.5123],
        [-4.1921, -1.8892,  1.4879],
        [ 1.7566,  0.7777, -0.5343],
        [-0.2084, -1.9393,  0.3604],
        [-1.4170, -1.9549,  0.0722]])
>>> # 加法2
>>> z = torch.add(x, y)
>>> print(z)
tensor([[-0.6864, -0.2147,  0.5123],
        [-4.1921, -1.8892,  1.4879],
        [ 1.7566,  0.7777, -0.5343],
        [-0.2084, -1.9393,  0.3604],
        [-1.4170, -1.9549,  0.0722]])
>>> # 加法三,使用一个新的变量来保存
>>> result = torch.empty(5, 3)
>>> torch.add(x, y, out=result)
>>> print(result)
tensor([[-0.6864, -0.2147,  0.5123],
        [-4.1921, -1.8892,  1.4879],
        [ 1.7566,  0.7777, -0.5343],
        [-0.2084, -1.9393,  0.3604],
        [-1.4170, -1.9549,  0.0722]])
>>> # 加法四,在本身基础之上进行相加
>>> y.add_(x)
>>> print(y)
tensor([[-0.6864, -0.2147,  0.5123],
        [-4.1921, -1.8892,  1.4879],
        [ 1.7566,  0.7777, -0.5343],
        [-0.2084, -1.9393,  0.3604],
        [-1.4170, -1.9549,  0.0722]])
  • 减法的具体实现
代码语言:javascript
复制
>>> import torch
>>> c = x - y
>>> print(c)
tensor([[ 1.8059, -1.2925, -0.0102],
        [ 0.5456,  1.7997,  0.1838],
        [ 1.5732, -1.2546, -0.5672],
        [-4.2119,  0.3256,  0.8546],
        [-0.5344, -0.8758,  3.2846]])
>>> c = torch.sub(x, y)
>>> print(c)
tensor([[ 1.8059, -1.2925, -0.0102],
        [ 0.5456,  1.7997,  0.1838],
        [ 1.5732, -1.2546, -0.5672],
        [-4.2119,  0.3256,  0.8546],
        [-0.5344, -0.8758,  3.2846]])
  • 对于元素相乘
代码语言:javascript
复制
>>> z = x * y
>>> print(z)
tensor([[-0.6975, -0.4061,  0.0656],
        [ 4.3191,  0.0825,  0.5450],
        [ 0.1526, -0.2423, -0.0091],
        [-4.4242,  0.9137, -0.1501],
        [ 0.4306,  0.7636, -2.6959]])
>>> z = torch.mul(x, y)
>>> print(z)
tensor([[-0.6975, -0.4061,  0.0656],
        [ 4.3191,  0.0825,  0.5450],
        [ 0.1526, -0.2423, -0.0091],
        [-4.4242,  0.9137, -0.1501],
        [ 0.4306,  0.7636, -2.6959]])
  • 对于元素除法
代码语言:javascript
复制
>>> d = x / y
>>> print(d)
tensor([[-4.4917e-01, -1.3984e+00,  9.6110e-01],
        [ 7.6968e-01,  2.4264e-02,  1.2819e+00],
        [ 1.8158e+01, -2.3467e-01, -3.3461e+01],
        [-1.1041e+00,  7.1247e-01, -2.4586e+00],
        [ 2.2111e+00,  2.6231e+00, -1.0449e+00]])
>>> d = torch.div(x, y)
>>> print(d)
tensor([[-4.4917e-01, -1.3984e+00,  9.6110e-01],
        [ 7.6968e-01,  2.4264e-02,  1.2819e+00],
        [ 1.8158e+01, -2.3467e-01, -3.3461e+01],
        [-1.1041e+00,  7.1247e-01, -2.4586e+00],
        [ 2.2111e+00,  2.6231e+00, -1.0449e+00]])
  • 矩阵乘法
代码语言:javascript
复制
>>> #二维矩阵乘法
>>> # 二维矩阵乘法运算操作包括torch.mm()、torch.matmul()、@,
>>> a = torch.ones(2, 1)
>>> b = torch.ones(1, 2)
>>> print(torch.mm(a, b).shape)
torch.Size([2, 2])
>>> print(torch.matmul(a, b).shape)
torch.Size([2, 2])
>>> print((a @ b).shape)
torch.Size([2, 2])
  • 多维矩阵乘法 在神经网络中,我们主要是多维的张量来进行数据的拟合,基本的乘法如下:
代码语言:javascript
复制
>>> c = torch.rand(4, 3, 28, 64)
>>> d = torch.rand(4, 3, 64, 32)
>>> print(torch.matmul(c, d).shape)
torch.Size([4, 3, 28, 32])
  • 幂运算
代码语言:javascript
复制
>>> a = torch.full([2, 2], 3)
>>> print(a)
tensor([[3., 3.],
        [3., 3.]])
>>> b = a.pow(2)
>>> print(b)
tensor([[9., 9.],
        [9., 9.]])
  • 开方运算
代码语言:javascript
复制
>>> c = b ** (0.5)  # 也可以a**(0.5)
>>> print(c)
tensor([[3., 3.],
        [3., 3.]])
  • 指数与对数运算
代码语言:javascript
复制
>>> import torch
>>> a = torch.exp(torch.ones(2, 2))  # 得到2*2的全是e的Tensor
>>> print(a)
tensor([[2.7183, 2.7183],
        [2.7183, 2.7183]])
>>> print(torch.log(a))  # 取自然对数
tensor([[1., 1.],
        [1., 1.]])
  • 近似值运算
代码语言:javascript
复制
>>> a = torch.tensor(3.14)
>>> print(a.floor(), a.ceil(), a.trunc(), a.frac())  # 取下,取上,取整数,取小数
>>> tensor(3.) tensor(4.) tensor(3.) tensor(0.1400)
>>> b = torch.tensor(3.49)
>>> c = torch.tensor(3.5)
>>> print(b.round(), c.round())  # 四舍五入
tensor(3.) tensor(4.)
  • 其它常用用法
代码语言:javascript
复制
>>> x = torch.randn(4, 4)
>>> y = x.view(16)
>>> z = x.view(-1, 8)  # the size -1 is inferred from other dimensions
>>> print(x.size(), y.size(), z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
>>> x = torch.randn(1)
>>> print(x)
tensor([-1.4743])
>>> print(x.item())
-1.4742881059646606

3. 张量与 Numpy 之间的转换

  • Tensor 至 Numpy,内存共享,改变其中一个的值,另一个值也会改变!
代码语言:javascript
复制
>>> a = torch.ones(5)
>>> print(a)
tensor([1., 1., 1., 1., 1.])
>>> b = a.numpy()
>>> print(b)
[1. 1. 1. 1. 1.]
>>> a.add_(1)
>>> print(a)
tensor([2., 2., 2., 2., 2.])
>>> print(b)
[2. 2. 2. 2. 2.]
  • Numpy 至 Tensor 同样!
代码语言:javascript
复制
>>> import numpy as np
>>> a = np.ones(5)
>>> b = torch.from_numpy(a)
>>> np.add(a, 1, out=a)
>>> print(a)
[2. 2. 2. 2. 2.]
>>> print(b)
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
  • GPU 下的转换
代码语言:javascript
复制
>>> if torch.cuda.is_available(): #判断机器中有没有GPU
...    device = torch.device("cuda")          # 初始化一个device的对象
...    y = torch.ones_like(x, device=device)  # 直接在GPU中初始化一个tensor对象
...    x = x.to(device)                       # 使用.to方法,将tensor移动到GPU中
...    z = x + y
...    print(z)
...    print(z.to("cpu", torch.double))       # 将z张量移动到cpu中
tensor([-0.4743], device='cuda:0')
tensor([-0.4743], dtype=torch.float64)

4. 梯度与反向传播

在其他的文章中你可能会看到说将Tensor包裹到Variable中提供自动梯度计算,Variable这个在 0.41 版中已经被标注为过期了,现在可以直接使用Tensor

在这里,我们先回顾下Pytorch中的Tensor初始化的实现:

代码语言:javascript
复制
torch.tensor(data, *, dtype=None, device=None, requires_grad=False, pin_memory=False) → Tensor

我们想直接创建一个Tensor,那么我们需要使用torch.tensor(),其中的具体参数如下:

  • data是张量本身,可以是列表、元组、NumPyndarray,标量等等。
  • dtype是数据类型,比如float32等。
  • device指定存储该张量的的设备。这个变量主要是针对 CPU/GPU 异构编程,在 CUDA 这种异构编程体系里,CPU 被称作主机(Host),某张 GPU 卡被称作设备(Device)。
  • requires_grad表示是否需要求梯度(Gradient)或者说是否需要求导。如果设置为 True,表示需要求导。默认这个值是 False。

关于梯度的使用可以看下面的代码段示例:

代码语言:javascript
复制
>>> import torch
>>> #如果你设置了它的参数 ‘.requires_grad=true’ 的话,它将会开始去追踪所有的在这个张量上面的运算。
>>> #当你完成你的计算的时候,你可以调用’.backwward()来计算所有的微分。这个向量的梯度将会自动被保存在’grad’这个属性里面。
>>> x = torch.ones(2, 2, requires_grad=True)
>>> print(x)
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
>>> y = x + 2
>>> print(y)
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
>>> print(y.grad_fn)
<AddBackward0 object at 0x7fc6bd199ac8>
>>> z = y * y * 3
>>> out = z.mean()
>>> print(z, out)
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
>>> a = torch.randn(2, 2)
>>> a = ((a * 3) / (a - 1))
>>> print(a.requires_grad)
False
>>> a.requires_grad_(True)
>>> print(a.requires_grad)
True
>>> b = (a * a).sum()
>>> print(b.grad_fn)
<SumBackward0 object at 0x7fc6bd1b02e8>
>>> out.backward()
>>> print(x.grad)
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

我们可以看一下线性回归的案例,来学习下,如何使用自动求导来学习参数:在这个例子中,我们随机初始化了输入x和输出y,分别作为模型的特征和要拟合的目标值。这个模型有两层,第一层是输入层,第二层为隐藏层,模型的前向传播如下所示:

H = ReLU(W_{1}*X)
Y = W_{2}*H

在案例中,我们需要网络学习到

W_{1}

W_{2}

的具体值:

代码语言:javascript
复制
import torch
dtype = torch.float
device = torch.device("cpu") # 使用CPU
# device = torch.device("cuda:0") # 如果使用GPU,请打开注释

# N: batch size
# D_in: 输入维度
# H: 隐藏层
# D_out: 输出维度
N, D_in, H, D_out = 64, 1000, 100, 10

# 初始化随机数x, y
# x, y用来模拟机器学习的输入和输出
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 初始化模型的参数w1和w2
# 均设置为 requires_grad=True
# PyTorch会跟踪w1和w2上的计算,帮我们自动求导
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    # 前向传播过程:
    # h1 = relu(x * w1)
    # y = h1 * w2
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # 计算损失函数loss
    # loss是误差的平方和
    loss = (y_pred - y).pow(2).sum()
    if t % 100 == 99:
        print(t, loss.item())

    # 反向传播过程:
    # PyTorch会对设置了requires_grad=True的Tensor自动求导,本例中是w1和w2
    # 执行完backward()后,w1.grad 和 w2.grad 里存储着对于loss的梯度
    loss.backward()

    # 根据梯度,更新参数w1和w2
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 将 w1.grad 和 w2.grad 中的梯度设为零
        # PyTorch的backward()方法计算梯度会默认将本次计算的梯度与.grad中已有的梯度加和
        # 必须在下次反向传播前先将.grad中的梯度清零
        w1.grad.zero_()
        w2.grad.zero_()

在这个例子中,我们对

w_{1}

w_{2}

设置了requires*grad=True,损失函数是lossPyTorch会自动跟踪这两个变量上的计算,当执行backward()时,PyTorch帮我们计算了loss对于

w_{1}

w_{2}

的导数。每次迭代后,我们都要根据导数,更新

w_{1}

w_{2}

PyTorchbackward()方法计算梯度会默认将本次计算的梯度与.grad中已有的梯度加和,下次迭代前,需要将.grad中的梯度清零,否则影响下一轮迭代的梯度值。经过多轮训练,模型的loss不断变小。

5. 数据处理

Pytorch中,PyTorch通过torch.utils.data对一般常用的数据加载进行了封装,可以很容易地实现多线程数据预读和批量加载。并且torchvision已经预先实现了常用图像数据集,包括如CIFAR-10,ImageNet、COCO、MNIST、LSUN等数据集,可通过torchvision.datasets方便的调用

5.1 Dataset

Dataset是一个抽象类, 为了能够方便的读取,需要将要使用的数据包装为Dataset类。自定义的Dataset需要继承它并且实现两个成员方法:

  • __getitem__() 该方法定义用索引(0 到 len(self))获取一条数据或一个样本。
  • __len__() 该方法返回数据集的总长度。

数据集展示

如上图所示,是我们常用来作为分类数据集的标签案例,前面是路径,后面为标签的具体值。我们使用Dataset来进行数据加载。如下代码所示:

代码语言:javascript
复制
from torch.utils.data import Dataset

#定义一个数据集
class MyDataSet(Dataset):
    """ 数据集演示 """
    def __init__(self, file_name):
        """实现初始化方法,在初始化的时候将数据读载入"""
        self.file=[line.strip() for line in open(file_name)]
    def __len__(self):
        '''
        返回df的长度
        '''
        return len(self.file)
    def __getitem__(self, idx):
        '''
        根据 idx 返回一行数据
        '''
        return self.file[idx]

执行下面的代码:

代码语言:javascript
复制
data = MyDataSet('./data.txt')
print(len(data))

返回数据集的长度为3

5.2 DataLoader

DataLoader为我们提供了对Dataset的读取操作,常用参数有:batch_size(每个batch的大小), shuffle(是否进行shuffle操作), num_workers(加载数据的时候使用几个子进程)。

如下代码所示:

代码语言:javascript
复制
import torch
dataloader = torch.utils.data.DataLoader(data, batch_size=1, shuffle=True, num_workers=0)
#我们可以调整其中的参数,来进行数据部分代码修改
for i, data in enumerate(dataloader):
    print(i,data)

返回结果如下:

代码语言:javascript
复制
0 ['/data/3.jpg 0']
1 ['/data/1.jpg 1']
2 ['/data/2.jpg 2']

5.3 torchvision

  • torchvision.datasets 可以理解为PyTorch团队自定义的dataset,这些dataset帮我们提前处理好了很多的图片数据集,我们拿来就可以直接使用,如下几种数据集:
  1. MNIST
  2. COCO
  3. Captions
  4. Detection
  5. LSUN
  6. ImageFolder
  7. Imagenet-12
  8. CIFAR

我们可以直接使用,示例如下:

代码语言:javascript
复制
import torchvision.datasets as datasets
trainset = datasets.MNIST(root='./data', # 表示 MNIST 数据的加载的目录
                          train=True,  # 表示是否加载数据库的训练集,false的时候加载测试集
                          download=True, # 表示是否自动下载 MNIST 数据集
                          transform=None) # 表示是否需要对数据进行预处理,None为不进行预处理
  • torchvision.models

torchvision不仅提供了常用图片数据集,还提供了训练好的模型,可以加载之后,直接使用,或者在进行迁移学习torchvision.models模块的子模块中包含以下模型结构案例等。

  1. AlexNet
  2. VGG
  3. ResNet
  4. SqueezeNet
  5. DenseNet
代码语言:javascript
复制
#初始化resnet18模型
import torchvision.models as models
resnet18 = models.resnet18(pretrained=True)
print(resnet18)

这里可以看到输出的网络结构如下:

代码语言:javascript
复制
Downloading: "https://download.pytorch.org/models/resnet18-5c106cde.pth" to C:\Users\lenovo/.cache\torch\checkpoints\resnet18-5c106cde.pth
100%|██████████| 44.7M/44.7M [00:04<00:00, 11.2MB/s]
ResNet(
  (conv1): Conv2d(3, 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=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=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)
    )
    (1): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=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)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=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)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=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)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (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): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

5.4 transforms

变换是常见的图像变换。它们可以使用链接在一起Compose。此外,还有torchvision.transforms.functional模块。功能转换可以对转换进行细粒度控制。如果您必须构建更复杂的转换管道(例如,在分段任务的情况下),这将非常有用。

这里主要有四类数据增强,如下:

  • 裁剪(Crop) 中心裁剪:transforms.CenterCrop 随机裁剪:transforms.RandomCrop 随机长宽比裁剪:transforms.RandomResizedCrop 上下左右中心裁剪:transforms.FiveCrop 上下左右中心裁剪后翻转,transforms.TenCrop
  • 翻转和旋转(Flip and Rotation) 依概率p水平翻转:transforms.RandomHorizontalFlip(p=0.5) 依概率p垂直翻转:transforms.RandomVerticalFlip(p=0.5) 随机旋转:transforms.RandomRotation
  • 图像变换(transforms.Resize) 标准化:transforms.Normalize 转为tensor,并归一化至[0-1]:transforms.ToTensor 填充:transforms.Pad 修改亮度、对比度和饱和度:transforms.ColorJitter 转灰度图:transforms.Grayscale 线性变换:(transforms.LinearTransformation())仿射变换:transforms.RandomAffine 依概率p转为灰度图:transforms.RandomGrayscale 将数据转换为PILImagetransforms.ToPILImage
  • transforms操作,使数据增强更灵活 从给定的一系列transforms中选一个进行操作:transforms.RandomChoice(transforms), 给一个transform加上概率,依概率进行操作:transforms.RandomApply(transforms, p=0.5), 将transforms中的操作随机打乱:transforms.RandomOrder

在数据增强的一系列使用中,我们可以按照如下的代码进行:

代码语言:javascript
复制
import os
from PIL import  Image
import numpy as np
from torchvision import transforms as T

transform = T.Compose([
    T.Resize(224), # 缩放图片(Image),保持长宽比不变,最短边为224像素
    T.CenterCrop(224), # 从图片中间切出224*224的图片
    T.ToTensor(), # 将图片(Image)转成Tensor,归一化至[0, 1]
    T.Normalize(mean=[.5, .5, .5], std=[.5, .5, .5]) # 标准化至[-1, 1],规定均值和标准差
])
img = Image.open('./image.jpg')
img = transform(img)

6. 神经网络 nn 层使用

在前面的部分内容,我们可以使用伴有梯度的来实现深度学习模型,但其抽象程度较低,如果用其来实现深度学习模型,则需要编写的代码量极大。因此,torch.nn应运而生,其是专门为深度学习而设计的模块。torch.nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络层。

6.1 全连接层

首先了解下,全连接层是个啥,长啥样呢?看下图所示:

全连接层主要在应用中是将中间的特征维度进行调整,调整到类别数的这一操作。

先来看看如何用nn.Module实现自己的全连接层。

如何用nn.Module实现自己的全连接层?输出

\textbf{y}

和输入

\textbf{x}

满足

\textbf{y=Wx+b}

\textbf{W}

\textbf{b}

是可学习的参数。

代码语言:javascript
复制
import torch as t
from torch import nn


class Linear(nn.Module): # 继承nn.Module
    def __init__(self, in_features, out_features):
        super(Linear, self).__init__() # 等价于nn.Module.__init__(self)
        self.w = nn.Parameter(t.randn(in_features, out_features)) #nn.Parameter是将其包装成一个参数对象,可以用来进行训练
        self.b = nn.Parameter(t.randn(out_features)) #bias,可训练
    
    def forward(self, x):
        x = x.mm(self.w)
        return x + self.b.expand_as(x) #使用了广播机制

执行代码:

代码语言:javascript
复制
linear = Linear(4,3)
input = t.randn(2,4)
output = linear(input)
print(output)

结果如下:

代码语言:javascript
复制
tensor([[-1.1762,  2.0987,  4.4372],
        [-2.3790, -1.8490,  0.5742]], grad_fn=<AddBackward0>)

我们可以遍历下模型的参数:

代码语言:javascript
复制
for name, parameter in linear.named_parameters():
    print(name, parameter) # w and b
w Parameter containing:
tensor([[ 0.5792,  0.4161, -1.2512],
        [-0.2647, -0.8852, -1.5065],
        [-0.5495,  0.2031, -0.5617],
        [ 0.4093,  1.0125,  0.7929]], requires_grad=True)
b Parameter containing:
tensor([-1.3909,  0.9145,  0.6379], requires_grad=True)

对于自定义全连接层的实现,可以参考上面的代码,当然后面对于像卷积层或者整个神经网络都是类似的设计,设计学习参数,以及实现forward()函数。

  • 在构造函数init中必须自己定义可学习的参数,并封装成Parameter,如在本例中我们把
w

b

封装成parameter。其默认需要求导(requires_grad = True)。

  • Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器,前者会给每个parameter都附上名字,使其更具有辨识度。
  • 自定义层Linear必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,即super(Linear, self)._init_()nn.Module._init_(self),推荐使用第一种用法,尽管第二种写法更直观。
  • forward函数实现前向传播过程,其输入可以是一个或多个tensor

6.2 图像相关操作

对于图像处理中,主要包括卷积层(Conv)、池化层(Pool)等,这些层在实际使用中可分为一维(1D)、二维(2D)、三维(3D),池化方式又分为平均池化(AvgPool)、最大值池化(MaxPool)、自适应池化(AdaptiveAvgPool)等。而卷积层除了常用的前向卷积之外,还有转置卷积(TransposeConv)。

  1. 卷积层 首先,了解下卷积操作的过程,如下图就是,就是用一个固定大小的滤波器来进行扫射,每到一个地方,得到一个值。与全连接相比,全连接层的参数量过大,每一层的参数量是这一层的特征输出与这层特征输出的值的乘积,如VGG16第一个FC的参数量为
7 * 7 * 512 * 4096 = 102760448

个。卷积层每次用来遍历图像的这个滤波器的值是共享的,即每个位置都是一样的值,所以它的参数量会比全连接层小很多。如VGG16Conv2-1的参数量为:

3 * 3 * 64 * 128 = 73728

同时,卷积神经网络还利用了感受野的这一概念:由于图像的空间联系是局部的,每个神经元不需要对全部的图像做感受,只需要感受局部特征即可,然后在更高层将这些感受得到的不同的局部神经元综合起来就可以得到全局的信息了,这样可以减少连接的数目。

卷积过程

对于卷积,我们这里主要是采用的2D卷积来举例子!

代码语言:javascript
复制
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

其中具体的参数如下:

  • in_channels :输入数据的通道数
  • out_channels:卷积后输出数据的通道数
  • kernel_size: 卷积核的大小
  • stride:步长,控制cross-correlation的步长,可以设为1个int型数或者一个(int, int)型的tuple。
  • padding:(补0):控制zero-padding的数目。
  • dilation:(扩张):控制kernel点(卷积核点)的间距
  • groups:(卷积核个数):通常来说,卷积个数唯一,但是对某些情况,可以设置范围在1 —— in_channels中数目的卷积核:
  • bias:偏置项.

首先我们先看下举例图片:

代码语言:javascript
复制
from PIL import Image
image = Image.open('./slumdunk.jpg')
image.show()

接下来,先将图像转成tensor格式,再进行卷积操作!

代码语言:javascript
复制
from torchvision.transforms import ToTensor, ToPILImage
to_tensor = ToTensor() # img -> tensor
to_pil = ToPILImage()
from torch import nn
# 输入是一个batch,batch_size=1
input = to_tensor(image).unsqueeze(0) 
conv = nn.Conv2d(3, 1, (3, 3), 1, bias=False)
out = conv(input)
ret_img = to_pil(out.data.squeeze(0))
ret_img.show()

CNN这样不断地进行信息提取,供后面进行分类与检测任务处理。

  1. 池化层 这里,我们以AvgPool2d()方法进行讲解。官网中的文档如下:
代码语言:javascript
复制
torch.nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True, divisor_override=None)

其中的主要参数的具体作用如下:

  • kernel_size:滑动窗口的大小
  • stride:滑动窗口的步长
  • padding:(补0):控制zero-padding的数目。
  • ceil_mode:当设置为True,计算输出的形状时使用向上取整
  • count_include_pad: 当设置为True,会在计算均值的时候将zero-padding的值给一起带进来进行计算

代码如下:

代码语言:javascript
复制
avg_pool = nn.AvgPool2d(2)
pool_img = avg_pool(input)
ret_img = to_pil(pool_img.squeeze())
ret_img.show()

看看使用代码后的效果:

  1. 其它常用层
  • Linear:全连接层。 在Pytorch中,有直接可以用的全连接函数!
代码语言:javascript
复制
nn.Linear(in_features, out_features, bias=True)
  • BatchNorm:批规范化层(Batch Normalization,也叫BN),分为1D、2D和3D。还有在风格迁移中常用到的Instance Normalizaion层。

对于BN的作用,主要是为了解决在深度神经网络中,每一层的特征分布不一致,神经网络学习难度大的问题。其运算过程主要如下:

相关库函数:

代码语言:javascript
复制
torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

对于这些超参主要是框架在实现的过程中的一些技巧,如下所示:

BN的具体实现过程

网络训练中以batch_size为最小单位不断迭代,而由于每一次的batch有差异,实际是通过变量,以及滑动平均来记录均值与方差。训练完成后,推断阶段时通过

\gamma

,

\beta

,以及记录的均值与方差计算bn层输出。

举例如何使用:

代码语言:javascript
复制
import torch
feature = torch.randn(2, 128, 14, 14) #(N, C, H, W)
bn = torch.nn.BatchNorm2d(128) #使用默认参数
out = bn(feature)
  • Dropout:dropout层,用来防止过拟合,同样分为1D、2D和3D。Dropout主要是通过将神经元以固定的概率进行丢弃,从而抑制过拟合。下面通过例子来说明Dropout的简单使用。
代码语言:javascript
复制
import torch
drop = torch.nn.Dropout(0.5)
feature = torch.randn(12, 256)
out = drop(feature)

6.3 激活函数

在神经网络中,需要激活函数来搭配使用,使得网络是非线性的。如果不用激励函数(其实相当于激励函数是

f(x) = x

),在这种情况下你每一层节点的输入都是上层输出的线性函数,很容易验证,无论你神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机(Perceptron)了,那么网络的逼近能力就相当有限。正因为上面的原因,我们决定引入非线性函数作为激励函数,这样深层神经网络表达能力就更加强大(不再是输入的线性组合,而是几乎可以逼近任意函数)。

常用的激活函数有sigmoidtanhReLU等。

  1. 激活函数介绍

在这里,我们以ReLU为样例,目前大多数的网络都会主要选择ReLU来作为激活函数,因为其避免了使用sigmidtanh梯度消失的问题。

ReLU的计算公式如下:

ReLU(x)=max(0, x)

代码示例如下:

代码语言:javascript
复制
import torch
relu = torch.nn.ReLU(inplace=True)
input = torch.randn(3, 3)
print(input)
output = relu(input)
print(output) # 小于0的都被截断为0
# 等价于input.clamp(min=0)
tensor([[ 0.4869,  1.3280, -1.1800],
        [-0.5563,  0.4759, -1.5502],
        [-2.1328,  0.9588, -0.5466]])
        
tensor([[0.4869, 1.3280, 0.0000],
        [0.0000, 0.4759, 0.0000],
        [0.0000, 0.9588, 0.0000]])

ReLU函数有个inplace参数,如果设为True,它会把输出直接覆盖到输入中,这样可以节省内存/显存。之所以可以覆盖是因为在计算ReLU的反向传播时,只需根据输出就能够推算出反向传播的梯度。

  1. 激活函数与网络搭建

对于我们在搭网络的时候,我们通常会将上层的输出放入这层的输入,这样的网络叫做前馈神经网络,而这样写起来有时候回比较折腾。在此,就有两种简化方式,ModuleListSequential。其中Sequential是一个特殊的module,它包含几个子Module,前向传播时会将输入一层接一层的传递下去。ModuleList也是一个特殊的module,可以包含几个子module,可以像用list一样使用它,但不能直接把输入传给ModuleList。下面举例说明:

第一种 :

代码语言:javascript
复制
import torch
from torch import nn
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())
print(net1)

#输出结果:
Sequential(
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activation_layer): ReLU()
)

第二种:

代码语言:javascript
复制
import torch
from torch import nn
net2 = nn.Sequential(
        nn.Conv2d(3, 3, 3),
        nn.BatchNorm2d(3),
        nn.ReLU()
        )
print(net2)

#输出结果
net2: Sequential(
  (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU()
)

第三种:

代码语言:javascript
复制
from collections import OrderedDict
net3= nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(3, 3, 3)),
          ('bn1', nn.BatchNorm2d(3)),
          ('relu1', nn.ReLU())
        ]))
print('net3:', net3)
#输出结果
net3: Sequential(
  (conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu1): ReLU()
)

我们可以使用名字或者序号来取出其中的Module,如下代码:

代码语言:javascript
复制
print(net1.conv, net2[0], net3.conv1)
#输出:
(Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)))

6.4 损失函数

在神经网络训练的时候,我们需要使用损失函数来进行网络的拟合。根据不同的任务,我们选择不同的损失函数,比如在目标检测任务中,我们会有坐标回归的损失,以及分类的损失。我们常用的损失函数主要是以下几种:

  • BCE二分类
代码语言:javascript
复制
class torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
  • BCEWithLogitsLoss 将sigmoid函数和BCELoss方法结合到一个类中。
代码语言:javascript
复制
class torch.nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)

  • NLLLoss(多分类) 用于多分类的负对数似然损失函数(negative log likelihood loss)。
代码语言:javascript
复制
class torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
  • CrossEntropyLoss(多分类) 将nn.LogSoftmax()和nn.NLLLoss()方法结合到一个类中。
代码语言:javascript
复制
class torch.nn.CrossEntropyLoss(weight=None, size_average=True, ignore_index=-100, reduce=True)
  • L1Loss 创建一个criterion计算input x和target y的每个元素的平均绝对误差(mean absolute error (MAE))
代码语言:javascript
复制
class torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
  • MSELoss(L2 norm) 创建一个criterion计算input x和target y的每个元素的均方误差(mean absolute error (MAE))
  • Smooth L1 Loss
代码语言:javascript
复制
class torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean')

这里以CrossEntropyLoss来举例:

代码语言:javascript
复制
import torch
from torch import nn
# batch_size=3,计算对应每个类别的分数(只有两个类别)
score = torch.randn(3, 2)
# 三个样本分别属于1,0,1类,label必须是LongTensor
label = torch.Tensor([1, 0, 0]).long()

# loss与普通的layer无差异
criterion = nn.CrossEntropyLoss()
loss = criterion(score, label)
print(loss)
#输出
tensor(1.0429)

6.5 优化器

PyTorch将深度学习中常用的优化方法全部封装在torch.optim中,其设计十分灵活,能够很方便的扩展成自定义的优化方法。目前常用的优化器主要有以下四个:

  1. SGD
  2. Momentum
  3. RMSProp
  4. Adam

所有的优化方法都是继承基类optim.Optimizer,并实现了自己的优化步骤。下面就以最基本的优化方法——随机梯度下降法(SGD)举例说明。这里需重点掌握关于优化器的一些参数设置:

  • 优化方法的基本使用方法
  • 如何对模型的不同部分设置不同的学习率
  • 如何调整学习率

optim的使用方法如下:

代码语言:javascript
复制
#首先定义一个网络,这里以LeNet为例
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.features = nn.Sequential(
                    nn.Conv2d(3, 6, 5),
                    nn.ReLU(),
                    nn.MaxPool2d(2,2),
                    nn.Conv2d(6, 16, 5),
                    nn.ReLU(),
                    nn.MaxPool2d(2,2)
        )
        self.classifier = nn.Sequential(
            nn.Linear(16 * 5 * 5, 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, 10)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(-1, 16 * 5 * 5)
        x = self.classifier(x)
        return x

net = Net()
#优化器的定义过程
from torch import optim
optimizer = optim.SGD(params=net.parameters(), lr=0.01)
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad(),这一步至关重要!

input = t.randn(1, 3, 32, 32)
output = net(input)
output.backward(output) # fake backward

optimizer.step() # 执行优化

而神经网络的训练,有时候是需要将某些层固定较大的学习率,剩下的层使用较小的学习率,这时候,依旧可以如下设计:

代码语言:javascript
复制
# 只为两个全连接层设置较大的学习率,其余层的学习率较小
special_layers = nn.ModuleList([net.classifier[0], net.classifier[3]])
special_layers_params = list(map(id, special_layers.parameters()))
base_params = filter(lambda p: id(p) not in special_layers_params,
                     net.parameters())

optimizer = t.optim.SGD([
            {'params': base_params},
            {'params': special_layers.parameters(), 'lr': 0.01}
        ], lr=0.001 )

我们同样可以调整不同的学习率,第一种方法:

代码语言:javascript
复制
# 方法1: 新建一个optimizer
old_lr = 0.1
optimizer1 =optim.SGD([
                {'params': net.features.parameters()},
                {'params': net.classifier.parameters(), 'lr': old_lr*0.1}
            ], lr=1e-5)
optimizer1

第二种方法则为:

代码语言:javascript
复制
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 学习率为之前的0.1倍

6.6 初始化策略

参数的初始化在神经网络中十分重要,良好的初始化能让模型更快收敛,同时达到更高的水平,而糟糕的初始化则可能模型都难以收敛。PyTorchnn.Module的模块参数都采取了较为合理的初始化策略,因此一般不用我们考虑,当然我们也可以用自定义初始化去代替系统的默认初始化。而当我们在使用Parameter时,自定义初始化则尤为重要,因torch.Tensor()返回的是内存中的随机数,很可能会有极大值,这在实际训练网络中会造成溢出或者梯度消失。PyTorchnn.init模块就是专门为初始化而设计,如果某种初始化策略nn.init不提供,用户也可以自己直接初始化。

对于参数初始化,主要有:

  1. Xavier Initialization
代码语言:javascript
复制
# 默认方法
for m in model.modules():
    if isinstance(m, (nn.Conv2d, nn.Linear)):
        nn.init.xavier_uniform_(m.weight)
  1. He et. al Initialization
代码语言:javascript
复制
# he initialization
for m in model.modules():
    if isinstance(m, (nn.Conv2d, nn.Linear)):
        nn.init.kaiming_normal_(m.weight, mode='fan_in')
  1. 正交初始化(Orthogonal Initialization)
代码语言:javascript
复制
for m in model.modules():
    if isinstance(m, (nn.Conv2d, nn.Linear)):
        nn.init.orthogonal(m.weight)
  1. Batchnorm Initialization
代码语言:javascript
复制
for m in model.modules():
    if isinstance(m, nn.BatchNorm2d):
        nn.init.constant(m.weight, 1)
        nn.init.constant(m.bias, 0)

我们可以使用init来进行参数初始化:

代码语言:javascript
复制
# 利用nn.init初始化
from torch import nn
from torch.nn import init
import torch
linear = nn.Linear(3, 4)
torch.manual_seed(1)
# 等价于 linear.weight.data.normal_(0, std)
init.xavier_normal_(linear.weight)

同样,我们可以直接初始化:

代码语言:javascript
复制
# 直接初始化
import math
import torch
torch.manual_seed(1)

# xavier初始化的计算公式
std = math.sqrt(2)/math.sqrt(7.)
linear.weight.data.normal_(0,std)

6.7 nn.functional

nn中还有一个很常用的模块:nn.functionalnn中的大多数layer,在functional中都有一个与之相对应的函数。nn.functional中的函数和nn.Module的主要区别在于,用nn.Module实现的layers是一个特殊的类,都是由class layer(nn.Module)定义,会自动提取可学习的参数。而nn.functional中的函数更像是纯函数,在不需要参数的情况下,可以室友nn.functional来进行实现。

ReLU、tanh、sigmoidPool等没有学习参数的层,可以使用nn.functional来进行实现。

如:

代码语言:javascript
复制
from torch.nn import functional as F
import torch
feature = torch.randn(1, 3, 12, 12)
out1 = F.relu(feature)

6.8 搭建ResNet

这里,可以使用下上面学习到的Pytorch技巧来搭建一个ResNet网络,第一种方法如下:

代码语言:javascript
复制
from torch import  nn
import torch as t
from torch.nn import  functional as F

class ResidualBlock(nn.Module):
    '''
    残差模块的实现
    '''
    def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
        super(ResidualBlock, self).__init__()
        self.left = nn.Sequential(
                nn.Conv2d(inchannel,outchannel,3,stride, 1,bias=False),
                nn.BatchNorm2d(outchannel),
                nn.ReLU(inplace=True),
                nn.Conv2d(outchannel,outchannel,3,1,1,bias=False),
                nn.BatchNorm2d(outchannel) )
        self.right = shortcut

    def forward(self, x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)
class ResNet(nn.Module):
    '''
    实现主module:ResNet34
    ResNet34 包含多个layer,每个layer又包含多个residual block
    用子module来实现residual block,用_make_layer函数来实现layer
    '''
    def __init__(self, num_classes=1000):
        super(ResNet, self).__init__()
        # 前几层图像转换
        self.pre = nn.Sequential(
                nn.Conv2d(3, 64, 7, 2, 3, bias=False),
                nn.BatchNorm2d(64),
                nn.ReLU(inplace=True),
                nn.MaxPool2d(3, 2, 1))
        
        # 重复的layer,分别有3,4,6,3个residual block
        self.layer1 = self._make_layer( 64, 64, 3)
        self.layer2 = self._make_layer( 64, 128, 4, stride=2)
        self.layer3 = self._make_layer( 128, 256, 6, stride=2)
        self.layer4 = self._make_layer( 256, 512, 3, stride=2)

        #分类用的全连接
        self.fc = nn.Linear(512, num_classes)
    def _make_layer(self,  inchannel, outchannel, block_num, stride=1):
        '''
        构建layer,包含多个residual block
        '''
        shortcut = nn.Sequential(
                nn.Conv2d(inchannel,outchannel,1,stride, bias=False),
                nn.BatchNorm2d(outchannel))
        
        layers = []
        layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))
        
        for i in range(1, block_num):
            layers.append(ResidualBlock(outchannel, outchannel))
        return nn.Sequential(*layers)
        
    def forward(self, x):
        x = self.pre(x)
        
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = F.avg_pool2d(x, 7)
        x = x.view(x.size(0), -1)
        return self.fc(x)
model = ResNet()
input  = t.randn(1, 3, 224, 224)
out = model(input)

同样,我们可以使用torchvision来搭建神经网络,如下代码:

代码语言:javascript
复制
from torchvision import models
model = models.resnet34()

8. 其它使用注意事项

8.1 多卡训练

Pytorch在实际使用过程中,往往会使用多卡来进行训练。而多卡训练也包括单机多卡,多机多卡,在这里,我们主要参考单机多卡的使用方式。

代码语言:javascript
复制
#我一般在使用多GPU的时候, 会喜欢使用os.environ['CUDA_VISIBLE_DEVICES']来限制使用的GPU个数, 例如我要使用第0和第3编号的GPU, 那么只需要在程序中这样设置:
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0,3' #第一块与第四块卡
#但是要注意的是, 这个参数的设定要保证在模型加载到gpu上之前, 我一般都是在程序开始的时候就设定好这个参数, 之后如何将模型加载到多GPU上面呢?
import torch
from torch import nn
#如果是模型, 那么需要执行下面的这几句代码:
model = MyModel()  #其中MyModel是自己实现的模型
model = nn.DataParallel(model)
model = model.cuda()
#如果是数据, 那么直接执行下面这几句代码就可以了:
inputs = inputs.cuda()
labels = labels.cuda()

8.2 固定随机种子

Pytorch在训练过程中,如果随机初始化,那么可能导致每次训练的结果不一样。因此,在每次训练的时候,需要固定住这些随机参数!代码如下:

代码语言:javascript
复制
import os
import random
import torch
def seed_torch(seed=1029):
 random.seed(seed)
 os.environ['PYTHONHASHSEED'] = str(seed) # 为了禁止hash随机化,使得实验可复现
 np.random.seed(seed)
 torch.manual_seed(seed)
 torch.cuda.manual_seed(seed)
 torch.cuda.manual_seed_all(seed) # if you are using multi-GPU.
 torch.backends.cudnn.benchmark = False
 torch.backends.cudnn.deterministic = True

seed_torch()

9.总结

Pytorch最快的学习方法往往是直接上手一个新的完整项目,学习搭建过程中的设计与代码,在后面,我们再进行Pytorch的项目实战,在实战中,再进行其中的细节讲解,并夯实基础!

10.往期原创

[AICAMP——Python入门]:

1. 环境配置

2. 基本语法

3. 函数式编程

4. 文件存储

5. 异常处理

6. 面向对象

[AICAMP——Linux入门]:Linux入门

11. 关于我

欢迎加我微信,每天16个小时在线

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 灿视学长 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Pytorch 入门教程
    • 1. 张量的概念和生成
      • 2. 张量的操作
        • 3. 张量与 Numpy 之间的转换
          • 4. 梯度与反向传播
            • 5. 数据处理
              • 5.1 Dataset
              • 5.2 DataLoader
              • 5.3 torchvision
              • 5.4 transforms
            • 6. 神经网络 nn 层使用
              • 6.1 全连接层
              • 6.2 图像相关操作
              • 6.3 激活函数
              • 6.4 损失函数
              • 6.5 优化器
              • 6.6 初始化策略
              • 6.7 nn.functional
              • 6.8 搭建ResNet
            • 8. 其它使用注意事项
              • 8.1 多卡训练
              • 8.2 固定随机种子
            • 9.总结
              • 10.往期原创
                • 11. 关于我
                相关产品与服务
                GPU 云服务器
                GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档