> 文章列表 > 构建一个pytorch手写网络的基本流程

构建一个pytorch手写网络的基本流程

构建一个pytorch手写网络的基本流程

文章目录

  • 从简单手写数字识别开始基本网络框架的使用
    • 使用pytorch构建网络——基本组件
    • 定义一个简单的网络
    • 使用pytorch加载和处理数据
      • 数据流的基本单位:Tensor
      • 下载数据集 & 加载
    • 使用pytorch训练及保存模型
      • 训练的基本逻辑——梯度下降 & 反向传播
      • 训练代码
      • 模型参数
    • 使用pytorch进行推理及验证

从简单手写数字识别开始基本网络框架的使用

使用pytorch构建网络——基本组件

  • 卷积——conv层
    在pytorch里面,nn.conv2d就是用于构建网络中的卷积层组建,我自己经常用到的有下面几个参数,也就是前几个行参:

    • in_channels
    • out_channels
    • kenel_size:卷积核大小
    • stride:步长
    • padding,边缘,默认为0

    首先需要理解一下channel的概念,在RGB图像里,通道就是每个颜色分量。在pytorch里面,每个卷积核对一个通道做完卷积操作之后就可以形成一个通道,那么如果输出通道是32的话,就是32个卷积核对图像去做卷积操作。
    卷积的基本作用就是提取图像中的特征,32个卷积通道我理解就是32个特征。

    我自己使用的卷积层定义为:
    nn.conv2d(3, 32, 3, 1)
    这句代码的意思就是用32个大小为 3*3的卷积核,对一个通道数为3的图像,以padding为1的step去做卷积操作。

    卷积之后的张量大小会发生改变:
    图像卷积尺寸的计算公式:
    N = ( W − F + 2 P ) / S + 1 N = (W − F + 2P )/S+1 N=(WF+2P)/S+1

    W为原尺寸大小

    F为卷积核大小

    P为padding大小

    S为步长Stride

    所以我卷积出来的尺寸是

    X 方向上: ( 702 − 3 + 2 ∗ 0 ) / 1 + 1 = 700 X方向上:(702 - 3 + 2*0)/1 + 1 = 700 X方向上:(7023+20)/1+1=700
    Y 方向上: ( 587 − 3 + 2 ∗ 0 ) / 1 + 1 = 585 Y方向上:(587 - 3 + 2*0)/1 + 1 = 585 Y方向上:(5873+20)/1+1=585

    如果一个702 * 587像素大小的3通道图像,经过卷积之后就会变成一个[32, 585, 700]的张量(tensor)

  • ReLU层:
    一个被称为线性整流的函数,小于0的数全部变为0,大于0的数等于自己
    R e L U = { 0 , x < 0 x , x > 0 ReLU = \\begin{cases} 0, x<0 \\\\ x, x>0 \\end{cases} ReLU={0,x<0x,x>0
    用于把非0的值过滤掉。

    在之前的一篇文章中也提到过:https://blog.csdn.net/pcgamer/article/details/122704136?spm=1001.2014.3001.5502

    在pytorch中的组件为nn.ReLU

  • 池化层:同样在https://blog.csdn.net/pcgamer/article/details/122704136?spm=1001.2014.3001.5502这片文章中提到过

    demo中用到了最大池化层,也就是一个感受野中取最大值。这个组件只有一个行参是必须手动赋值的,也就是kernelSize,感受野的大小。
    其实这个层也可以理解为一种降采样

    pytorch中的2维池化层的组件是nn.MaxPool2d

  • Dropout层,是一种抑制过拟合的方法,用于将一些权重参数按照一定的几率变成0,防止过拟合。参数P指的是被置为0的概率。

  • FLatten层,用于减少张量的维数,pytorch中的组件为nn.Flatten,需要制定的参数是降到哪个维度,从0开始,0表示降到1维,1表示降到2维,以此类推。另外,这个是从最低维往上合并,比如一个shape = [2, 3, 4]的三维向量,如果经过Flatten(1)的一个网络层,就会变成shape = [2, 12]的一个二维向量。

  • Linear层,线性回归层。我理解就是全链接层。因为这个pytorch组件nn.Linear的三个参数就是

    • in_features, 输入的神经元个数
    • out_features, 输出的神经元个数
    • bias,偏移量

    也就是把前一层的所有输出值,通过
    y = w x i + b y = wx_i + b y=wxi+b
    的方式输出到后一层的一个神经元节点。out_features就是表示后一层有多少个节点。那么连接数就是 i n _ f e a t u r e s ∗ o u t _ f e a t u r e s in\\_features * out\\_features in_featuresout_features这么多。

  • Softmax层,激活层。使用的函数为:
    s o f t m a x ( x i ) = e x p ( x i ) ∑ j e x p ( x j ) softmax(x_i) = \\frac{exp(x_i)}{\\sum_jexp(x_j)} softmax(xi)=jexp(xj)exp(xi)
    在pytorch中的组件是nn.softmax,这个组件初始化的时候接受一个行参dim,如果dim=0,表示对矩阵中以行为单位进行一个一维数组进行计算,如果dim=1,则表示对矩阵中以列尾单位进行一个以为数组进行计算。

    import torch 
    import torch.nn as nn
    input_0 = torch.Tensor([1,2,3,4])
    input_1 = torch.Tensor([[1,2,3,4],[5,6,7,8]])
    #规定不同方向的softmax
    softmax_0 = nn.Softmax(dim=0)
    softmax_1 = nn.Softmax(dim=1 )
    #对不同维度的张量试验
    output_0 = softmax_0(input_0)
    output_1 = softmax_1(input_1)
    output_2 = softmax_0(input_1)
    #输出
    print(output_0)
    print(output_1)
    print(output_2)

    输入结果为:

    tensor([0.0321, 0.0871, 0.2369, 0.6439])
    tensor([[0.0321, 0.0871, 0.2369, 0.6439],[0.0321, 0.0871, 0.2369, 0.6439]])
    tensor([[0.0180, 0.0180, 0.0180, 0.0180],[0.9820, 0.9820, 0.9820, 0.9820]])

那么,在我们的MINIST手写数字的识别例子中,最后对全连接层输出的一个10长度的数组进行运算,实际上就是算这10个值的概率大小。

定义一个简单的网络

在pytorch框架里,定义自己的网络框架一般分为两个部分,网络结构和推理过程。
上面已经简单说了没一个组件层的作用,直接把这些层拼到一起。
代码如下,定义一个自己的网络,这个网络是继承nn.Module这个python类,我自己是定义的MINISTNet:

from torch import nn
import torch.nn.functionalclass MINISTNet(nn.Module):def __init__(self):super(MINISTNet, self).__init__()self.model = nn.Sequential(nn.Conv2d(1, 32, 3, 1),nn.ReLU(),nn.Conv2d(32, 64, 3, 1),nn.ReLU(),nn.MaxPool2d(2),nn.Dropout(0.25),nn.Flatten(1),nn.Linear(9216, 128),nn.Linear(128, 10),nn.Softmax(dim=1))def forward(self, x):output = self.model(x)return output

网络结构如下:

  • 首先把一个三通道图像进入一个卷积层,输出一个32个通道的3维向量
  • 然后把这个卷积层的负值去掉。
  • 重复再做一次卷积,扩展到64个通道,并去掉负值
  • 做一次池化,或者说最大降采样
  • 做一次降维,降到2维,因为进入模型的张量第一维是batchSize,图像数据到第二维的Channel就行了。
  • 做两次全连接,第一次全连接的输入是降维操作,在网络里,前一个出来的是卷积之后的池化,需要根据前面的公式进行一下推理和计算
  • 概率计算

推理过程就是把一个图像从头到尾进行一次正向计算的过程,重载的forward函数就是pytorch框架用于前向推理的基本操作。

使用pytorch加载和处理数据

把网络结构定义好之后,就可以使用这个网络结构进行预测和推理了,当然,此时完全是一种随机猜测,但是可以运行的。
在这里先提一下,pytorch网络处理的基本单位:张量——Tensor

数据流的基本单位:Tensor

pytorch中的Tensor就是一个向量的概念,可以有多维向量。在手写图像的训练数据集中,一般使用的是一个4维向量,一般的图像处理任务也是这4个维度.
[batch, channel, height, weight]

  • BatchSize,训练的批次
  • channel,图像的通道数
  • height,图像的高度,相当于y坐标
  • weight,图像的宽度,相当于x坐标

下载数据集 & 加载

from torchvision import datasets, transforms
import torchtransform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])
ministDataset = datasets.MNIST(root="data/MINIST", train=True, download=True, transform=transform)
self.train_loader = torch.utils.data.DataLoader(ministDataset)
  • torchvision中的dataset库提供了MNIST数据集的下载和加载类,如果提供的root目录没有数据,就会从网上把数据下载下来。
  • dataset库中加载数据的时候,可以使用一个transform类将数据初始化成Tensor。
  • transform类的compose方法简单来说就是把几个转换方法顺序执行
    • transform.ToTensor方法是把输入转换成pytorch的Tensor类,对于图像来说,最主要的操作就是把Weight * Height * Channel的格式变成Channel * Height * Weight的格式
    • 标准化操作Normalize,这个操作有两个参数:(mean, std),均值与方差。mean和std都可以是一个数组,这就对应图像中的各个通道,如果是3通道图,就需要有三个值。每个数据集都会提供自己的这两个参数,可以加快模型的收敛速度。

有了这些数据就可以进行训练了

使用pytorch训练及保存模型

训练的基本逻辑——梯度下降 & 反向传播

网络结构和数据都准备好了,准备开始训练。
训练的基本逻辑是反向传播和损失函数计算,具体的原理可以参考我之前的一篇文章:
https://blog.csdn.net/pcgamer/article/details/108538173?spm=1001.2014.3001.5501

通过定义一个损失函数,计算输出值与标准样本值的差值,称为损失。然后把这个损失值从网络的最后一段往前推,计算每一层上的参数的调整值,这个过程就称为反向传播。在计算损失的时候,主要是利用的梯度。

  • 定义损失函数

    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)crierion = torch.nn.CrossEntropyLoss()
    
    • CrossEntropyLoss就是定义的损失函数,称作交叉熵函数。
    • SGD是一个计算优化器,就是计算梯度和损失的时候的一种计算优化,有兴趣的朋友可以自行了解。
  • 训练过程中的反复迭代和计算

    outputs = model(inputs)loss = crierion(outputs, target)loss.backward()optimizer.step()
    
    • 输出预测值,使用CrossEntropyLoss函数计算输出与实际样本的值
    • 通过backward进行反向计算
    • 通过optimizer.step更新所有参数

训练代码

def train(epoch):running_loss = 0.0model = MINISTNet()optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)crierion = torch.nn.CrossEntropyLoss()miniLoader = MINISTLoader()miniLoader.load()for batch_idx, data in enumerate(miniLoader.train_loader, 0):inputs, target = dataoptimizer.zero_grad()# forward +backward+updateoutputs = model(inputs)loss = crierion(outputs, target)loss.backward()optimizer.step()running_loss += loss.item()if batch_idx % 300 == 299:print('[%d,%5d] loss:%.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))running_loss == 0.0torch.save(model, "result/minist_model.pth")torch.save(model.state_dict(), "result/minist_model_param.pth")

模型参数

有一个问题是,模型保存下来的pth文件里面是啥。我的理解是两个:

  • 网络的结构,这个按理来说不会很大,就是层与层之间的拓扑关系
  • 网络参数,这个会很大,我们可以拿我们的这个简单模型来看一下:
    • 第一个卷积层,输入1个通道,输出32个通道,也就是这里有32个卷积核,每个卷积核是3*3,这里就是32 * 9 = 283个参数
    • 第二个卷积层,输入32个通道,输出64个通道,这里没有分组,共有64个5*5的卷积核。
    • 后面的两个全连接层,第一个就是一个9216*128的参数列表。

所以我理解占主要部分的就是参数,被数据训练出来的也是这些参数。

使用pytorch进行推理及验证

模型训练出来之后,直接就可以用来做推理了

def predict(img):model = torch.load('result/minist_model.pth')# model = MINISTNet()# model.load_state_dict('result/minist_model_param.pth')model.eval()output = model(img)print(output)img = cv2.imread('images/3.jpg')
img = cv2.resize(img, (28, 28))
img = img[:, :, 0:1]img_tensor = torch.ones(1, 1, img.shape[0], img.shape[1])tfms = transforms.Compose([transforms.ToTensor(),transforms.Normalize(0.485, 0.229)])img = tfms(img)
img_tensor[0] = img
predict(img_tensor)
  • 加载模型,同过pytorch.load把刚才的模型加载进来,并把尺寸进行一下resize
  • 调用model.eval(),使模型进入推理模式
  • 使用opencv加载图像数据,但是因为模型需要一个四维张量,所以需要先把图像的三位数据扩充到四维,然后再通过transform进行转换
  • 最后直接进入模型进行预测

英语口语听力