用PyTorch对以MNIST数据集进行卷积神经网络

PyTorch构建深度学习网络一般步骤

  1. 加 载 数 据 集 \color{red}加载数据集
  2. 定 义 网 络 结 构 模 型 \color{red}定义网络结构模型
  3. 定 义 损 失 函 数 \color{red}定义损失函数
  4. 定 义 优 化 算 法 \color{red}定义优化算法
  5. 迭 代 训 练 \color{red}迭代训练
    在训练阶段主要分为四个部分。
    1. 前向过程,计算输入到输出的结果
    2. 由结果和labels计算损失函数的值
    3. 后向过程,由损失计算各个变量的梯度
    4. 优化器根据梯度进行参数的更新
  6. 测 试 集 验 证 \color{red}测试集验证

1. 配置库

#配置库
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

解释:

  1. optim是优化器模块,里面包括具体的优化算法有:SGD、Momentum、RMSProp、AdaGrad和Adam

    其中Momentum是加速梯度下降算法,其他三个是改进学习率。常用的有:SGD和Adam。

  2. Variable是对tensor的封装,用于放入计算图进行前向传播、反向传播和自动求导,是一个非常最重要的基本对象。包含三个重要属性:datagradcreator。其中:
    1. data表示Tensor的本身;
    2. grad表示传播方向的梯度,
    3. creator是创建这个Variable的Function引用,该引用用于回溯整个创建链路。

    如果是用户创建的Variable,则creator为None,同时这种Variable称为Leaf Variable,autograd只会给这种Variable分配梯度。

  3. DataLoaderDataset是一个包装类,用来将数据包装为Dataset类,然后传入DataLoader中,我们再使用DataLoader这个类来更加快捷的对数据进行操作。

    具体来说,就是将数据集包装成特定的格式,便于处理和引用。

  4. torchvision.transforms是pytorch中的图像预处理模块,包含了很多种对图像数据进行变换的函数,这些都是在进行图像数据读入步骤中必不可少的。
  5. datasets顾名思义,是一系列数据集,可以通过相应的命令加载诸如MNIST等的数据集。

2. 配置超参数

#配置参数
torch.manual_seed(1)
Learning_rate = 1e-2
Batch_Size = 16   # 没批处理的数据
Device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 用CPU还是GPU
Epochs = 100     # 训练数据集的轮次

解释:

  1. torch.manual_seed()是设置随机数种子,保证程序数字的可重复性,便于测试等。
  2. batch_size是批处理大小(批尺寸),即每次处理的数据量。 迭代次数 = 样本总数 批尺寸 \color{red}\text{迭代次数}=\frac{\text{样本总数}}{\text{批尺寸}} 迭代次数=批尺寸样本总数;在一定范围内,批尺寸越大,跑完一次全数据集(一个epoch)需要的迭代次数越少,处理数据速度也越快。但盲目增大会导致达到一定精度所需时间更大。
  3. learning_rate为学习率。学习率越小,每次梯度下降的步伐越小,要达到目标精度所需训练次数也更多;但学习率过大会导致梯度下降不收敛,达不到学习的目的。
  4. num_epocher 是数据集训练次数,即跑几遍数据集。

3. 加载数据集

## 3.构建pipeline,对图像做处理,transform
pipeline = transforms.Compose([
    transforms.ToTensor(),   # 将图片转换成Tensor
    transforms.Normalize((0.1307,), (0.3081, )) # 正则化,降低模型的复杂度
])

## 4.下载、加载数据
# 下载数据集
train_set = datasets.MNIST(root="data_Mnist", train=True, download=True, transform=pipeline)
test_set = datasets.MNIST(root="data_Mnist", train=False, download=True, transform=pipeline)

# 加载数据
train_loader = DataLoader(train_set, batch_size=Batch_Size, shuffle=True)    # 加载图片,进行打乱
test_loader = DataLoader(test_set, batch_size=Batch_Size, shuffle=True)

解释:

  1. 加载datasets中的MNIST手写数字数据集,可以加载本地下载好的数据集。
    • root为数据集存放的位置;
    • train表示是否是训练集;
    • transform.ToTensor()是将数据集数据归一化,将值为[0,255]的Image转化为[0,1.0]的Tensor数据,归一化能够提高梯度下降算法的收敛速度;
    • downlaod代表是否需要在线下载数据集,若本地没有则需要为True。
      加载测试集,参数设置与训练集类似。
  2. DataLoader用于包装数据,并提供对数据的快捷处理,其中shuffle参数为是否打乱顺序,训练集必须要打乱数据的次序,防止过拟合。

4. 定义卷积网络结构模型(重点)

##5. 构建网络模型
class Digit(nn.Module):
    def __init__(self):  # 定义自己的构造方法
        super().__init__()  # 继承构造方法
        self.conv1 = nn.Conv2d(1, 10, 5)  # 1:灰度图片的通道    10:输出通道    5:kernel  padding默认为0,输出:10*26*26
        self.conv2 = nn.Conv2d(10, 20, 3)  # 10:输入通道    20:输出通道      3:Kernel   padding默认为0,输出:20*24*24
        self.fc1 = nn.Linear(20 * 10 * 10, 500)  # 20*10*10:输入通道     500:输出通道
        self.fc2 = nn.Linear(500, 10)  # 500:输入通道   10:输出通道

    def forward(self, x):
        input_size = x.size(0)          # batch_size
        x = self.conv1(x)               # 输入:batch*1*28*28, 输出:batch*10*24*24(28-5+1=24)
        x = F.relu(x)                   # 激活函数shape不变
        x = F.max_pool2d(x, 2, 2)       # 输入:batch*10*24*24  输出:batch*10*12*12
        x = self.conv2(x)               # 输入:batch*10*12*12 输出:batch*20*10*1012-3+1=10)
        x = F.relu(x)  #
        x = x.view(input_size, -1)      # view:拉平,-1 自动计算维度:20*10*10 = 2000
        x = self.fc1(x)                 # 输入:batch*2000 输出:batch*500
        x = F.relu(x)                   # 保持shape不变
        x = self.fc2(x)                 # 输入: batch*500 输出:batch*10
        output = F.log_softmax(x, dim=1)  # 计算分类后每个数字的概率值

        return output

解释:

  1. nn.Moudle是十分重要的类,包含网络各层的定义,以及forword函数的定义 自 定 义 的 网 络 结 构 模 型 都 需 要 继 承 M o u d l e 类 \color{red}自定义的网络结构模型都需要继承Moudle类 Moudle

    自 定 义 网 络 结 构 \color{blue}自定义网络结构

    1. 需要继承nn.Module类,并实现forward方法。
    2. 一般把网络中具有可学习参数的层放在构造函数__init__()中,
    3. 不具有可学习参数的层(如ReLU)可放在构造函数中,也可不放在构造函数中(而在forward中使用nn.functional来代替)
    4. 只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现,而不需要像forword那样需要重新定义。
  2. super类的作用是 继 承 \color{red}继承 ,调用含super的各个的基类__init__函数。

    如果不使用super,就不会调用这些类的__init__函数,除非显式声明。而且使用super可以避免基类被重复调用。

  3. nn.sequential官方的解释是一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数。在这里的作用是构造神经网络结构。

  4. nn.Conv2d是卷积神经网络的卷积层函数,其默认参数如下:

    nn.Conv2d(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True))
    

    程序中的参数解释:
      - in_channels: 输入数据通道数,RGB图片通道数为3,黑白图片的通道数1,MNIST数据集中是黑白图像,参数为1;
      - out_channel: 输出数据通道数,也称为卷积核数量,和提取的特征数量相关,这里取6;
      - kennel_size: 卷积核大小;这里取卷积核大小为3*3;
      - stride:步长,默认为1;
      - padding:填充数量,为保持输出尺寸不变,这里取1。

卷积的计算公式:
Input: ( N , C in  , H in  , W in  ) \left(N, C_{\text {in }}, H_{\text {in }}, W_{\text {in }}\right) (N,Cin ,Hin ,Win )
Output: ( N , C out  , H out  , W out  ) \left(N, C_{\text {out }}, H_{\text {out }}, W_{\text {out }}\right) (N,Cout ,Hout ,Wout ) where
H out  = ⌊ H in  + 2 ×  padding  [ 0 ] −  dilation  [ 0 ] × ( k e r n e l    s i z e [ 0 ] − 1 ) − 1  stride  [ 0 ] + 1 ⌋ . W out  = ⌊ W in  + 2 ×  padding  [ 1 ] −  dilation  [ 1 ] × ( k e r n e l    s i z e [ 1 ] − 1 ) − 1  stride  [ 1 ] + 1 ⌋ . \begin{aligned} H_{\text {out }} &=\left\lfloor\frac{H_{\text {in }}+2 \times \text { padding }[0]-\text { dilation }[0] \times(kernel\;size[0]-1)-1}{\text { stride }[0]}+1\right\rfloor.\\ W_{\text {out }} &=\left\lfloor\frac{W_{\text {in }}+2 \times \text { padding }[1]-\text { dilation }[1] \times(kernel\;size[1]-1)-1}{\text { stride }[1]}+1\right\rfloor. \end{aligned} Hout Wout = stride [0]Hin +2× padding [0] dilation [0]×(kernelsize[0]1)1+1.= stride [1]Win +2× padding [1] dilation [1]×(kernelsize[1]1)1+1.

  1. nn.Relu是激活函数,具有减少计算量,缓解过拟合的作用。

  2. nn.MaxPool2d是卷积神经网络的池化层,具有降采样,减少计算量的功能。这里采用的是最大池化层,卷积核大小为2*2,步长为2。池化层输出尺寸计算方法为:

    W:图像宽,H:图像高,D:图像深度(通道数)F:卷积核宽高,S:步长
    池化后输出图像大小:
    W = ( W − F ) S + 1 H = ( H − F ) S + 1 W=\frac{(W-F)}{S}+1\\H=\frac{(H-F)}{S}+1 W=S(WF)+1H=S(HF)+1
    输出的图像通道数(深度)不变。

    • 因此,程序中经过池化层后,图片尺寸为6×14×14,可见减少了计算量。
  3. 经过两次卷积和池化操作后,图片最终输出尺寸为16×5×5的三维数组。接下来是 全 连 接 层 ( n n . l i n e r ) \color{red}全连接层(nn.liner) nn.liner。之前是三维结构的特征数据,Liner层将之综合起来。

    • 全连接层的每一个结点都与上一层的所有结点相连,用来把前边提取到的特征综合起来。由于其全相连的特性,一般全连接层的参数也是最多的。
    • 在程序中,由于前面传递过来了共16×5×5个节点,故其输入为400;由于手写数字需要分为10类,故最终输出节点数为10
  4. forward函数用于前向传播,将数据流前向传播。forward函数定义后,反向传播函数backward()自动被定义,而不需要像前向传播一样显式定义,可直接调用。

  5. model = Cnn(1,10) 将参数传入模型,并命名。

5.模型训练

## 6.定义优化器
model = Digit().to(Device)

optimizer = optim.Adam(model.parameters())

## 7.定义训练方法
def train_model(model, device, train_loader, optimizer, epoch):
    # 模型训练
    model.train()
    for batch_index, (data, target) in enumerate(train_loader):
        # 数据部署到device上去
        data, target = data.to(device), target.to(device)
        # 梯度初始化为0
        optimizer.zero_grad()
        # 训练后的结果
        output = model(data)
        # 计算损失
        loss = F.cross_entropy(output, target)
        # 找到最大的概率值,即最大的下标
        pred = output.max(1, keepdim = True)   # pred = output.argmax(dim=1)
        # 反向传播
        loss.backward()
        # 参数优化
        optimizer.step()
        if batch_index % 3000 == 0:
            print("Train Epoch:{} \t Loss: {:.6f}".format(epoch, loss.item()))
  1. nn.CrossEntropyLoss是交叉熵损失函数,用于计算损失。

  2. Adam函数是一个优化器,主要作用是优化我们的神经网络,使之在我们的训练过程中运算速度快起来,节省时间。

    model.parameters()是用于获取我们的神经网络参数。

  3. enumerate()是python的内置函数,用于 将 一 个 可 遍 历 的 数 据 对 象 ( 如 列 表 、 元 组 或 字 符 串 等 ) 组 合 为 一 个 索 引 序 列 \color{blue}将一个可遍历的数据对象(如列表、元组或字符串等)组合为一个索引序列 () 同 时 列 出 数 据 和 数 据 下 标 \color{blue}同时列出数据和数据下标

    enumerate(a,start)
    a是可迭代对象,start是计数起始数字,程序中是遍历训练集,从1开始计算。

  4. Variable在第一节中讲了,是对tensor的封装,用于放入计算图进行前向传播、反向传播和自动求导,这里是将img和label两个变量进行封装。
    out = model(img)是将数据传入模型,进行前向传播。

  5. loss = criterion(out,label)这里是将计算结果与标签比较,计算损失。

  6. running_loss += loss.item() * label.size(0)这里将损失累加,留作均值计算整个epoch的损失。在多维度时,loss为多个维度的均值,故需要先乘以维度数。

  7. torch.max(a,1) 返回每一行中最大值的那个元素,且返回其索引(返回最大元素在这一行的列索引),这里的pred为返回的预测结果。

  8. optimizer.zero_grad() ,清除掉前面累加的梯度,便于后面的反向传播。

  9. loss.backward(),反向传播。

  10. optimizer.step(),利用反向传播,更新网络模型内部的权值,从而使得模型的loss减小。


6.测试集中测试模型的识别率

def test_model(model, device, test_dataloader):
    # 模型验证
    model.eval()
    # 正确率
    correct = 0.0
    # 测试损失
    test_loss = 0.0
    with torch.no_grad():#不会计算梯度,也不会进行方向传播
        for data,target in test_loader:
            # 部署到device上
            data, target = data.to(device), target.to(device)
            # 测试数据
            output = model(data)
            # 计算测试损失
            test_loss += F.cross_entropy(output, target).item()
            # 找到概率最大的下标
            pred = output.max(1,keepdim=True)[1]#值,索引
            #累计正确率
            correct += pred.eq(target.view_as(pred)).sum().item()
        test_loss /=len(test_loader.dataset)
        print("Test -- Average Loss:{:.4f}, Accuracy:{:.3f}\n".format(test_loss, 100.0 * correct / len(test_loader.dataset)))

7. 调用

## 9. 调用 方法7/8
for epoch in range(1, Epochs +1):
    train_model(model, Device, train_loader, optimizer, Epochs)
    test_model(model, Device, test_loader)

8. 结果

Train Epoch:100 	 Loss: 0.004024
Train Epoch:100 	 Loss: 0.000000
Test -- Average Loss:0.0038, Accuracy:98.870

Train Epoch:100 	 Loss: 0.000000
Train Epoch:100 	 Loss: 0.010223
Test -- Average Loss:0.0037, Accuracy:98.920

Train Epoch:100 	 Loss: 0.000000
Train Epoch:100 	 Loss: 0.000252
Test -- Average Loss:0.0037, Accuracy:99.050

Train Epoch:100 	 Loss: 0.149225
Train Epoch:100 	 Loss: 0.000007
Test -- Average Loss:0.0047, Accuracy:99.110

整体代码

## 加载必要库
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader


## 2. 定义超参数
Batch_Size = 16 #没批处理的数据
Device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 用CPU还是GPU
Epochs = 100 #训练数据集的轮次

## 3. 构建pipeline,对图像做处理,transform
pipeline = transforms.Compose([
    transforms.ToTensor(),   # 将图片转换成Tensor
    transforms.Normalize((0.1307,), (0.3081, )) # 正则化,降低模型的复杂度
])

## 4. 下载、加载数据
# 下载数据集
train_set = datasets.MNIST("data_Mnist",train = True, download=True, transform=pipeline)
test_set = datasets.MNIST("data_Mnist", train=False, download=True, transform=pipeline)

# 加载数据
train_loader = DataLoader(train_set, batch_size=Batch_Size, shuffle=True) # 加载图片,进行打乱

test_loader = DataLoader(test_set, batch_size=Batch_Size, shuffle=True)


##5. 构建网络模型
class Digit(nn.Module):
    def __init__(self):  # 定义自己的构造方法
        super().__init__()  # 继承构造方法
        self.conv1 = nn.Conv2d(1, 10, 5)  # 1:灰度图片的通道    10:输出通道    5:kernel
        self.conv2 = nn.Conv2d(10, 20, 3)  # 10:输入通道    20:输出通道      3:Kernel
        self.fc1 = nn.Linear(20 * 10 * 10, 500)  # 20*10*10:输入通道     500:输出通道
        self.fc2 = nn.Linear(500, 10)  # 500:输入通道   10:输出通道

    def forward(self, x):
        input_size = x.size(0)  # batch_size
        x = self.conv1(x)  # 输入:batch*1*28*28, 输出:batch*10*24*24(28-5+1=24)
        x = F.relu(x)  # 激活函数shape不变
        x = F.max_pool2d(x, 2, 2)  # 输入:batch*10*24*24  输出:batch*10*12*12
        x = self.conv2(x)  # 输入:batch*10*12*12 输出:batch*20*10*10(12-3+1=10)
        x = F.relu(x)  #
        x = x.view(input_size, -1)  # view:拉平,-1 自动计算维度:20*10*10 = 2000
        x = self.fc1(x)  # 输入:batch*2000 输出:batch*500
        x = F.relu(x)  # 保持shape不变
        x = self.fc2(x)  # 输入: batch*500 输出:batch*10
        output = F.log_softmax(x, dim=1)  # 计算分类后每个数字的概率值

        return output

## 6.定义优化器
model = Digit().to(Device)

optimizer = optim.Adam(model.parameters())

## 7.定义训练方法
def train_model(model, device, train_loader, optimizer, epoch):
    # 模型训练
    model.train()
    for batch_index, (data, target) in enumerate(train_loader):
        # 数据部署到device上去
        data, target = data.to(device), target.to(device)
        # 梯度初始化为0
        optimizer.zero_grad()
        # 训练后的结果
        output = model(data)
        # 计算损失
        loss = F.cross_entropy(output, target)
        # 找到最大的概率值,即最大的下标
        pred = output.max(1, keepdim = True)   # pred = output.argmax(dim=1)
        # 反向传播
        loss.backward()
        # 参数优化
        optimizer.step()
        if batch_index % 3000 == 0:
            print("Train Epoch:{} \t Loss: {:.6f}".format(epoch, loss.item()))

## 8.定义测试方法
def test_model(model, device, test_dataloader):
    # 模型验证
    model.eval()
    # 正确率
    correct = 0.0
    # 测试损失
    test_loss = 0.0
    with torch.no_grad():#不会计算梯度,也不会进行方向传播
        for data,target in test_loader:
            # 部署到device上
            data, target = data.to(device), target.to(device)
            # 测试数据
            output = model(data)
            # 计算测试损失
            test_loss += F.cross_entropy(output, target).item()
            # 找到概率最大的下标
            pred = output.max(1,keepdim=True)[1]#值,索引
            #累计正确率
            correct += pred.eq(target.view_as(pred)).sum().item()
        test_loss /=len(test_loader.dataset)
        print("Test -- Average Loss:{:.4f}, Accuracy:{:.3f}\n".format(test_loss, 100.0 * correct / len(test_loader.dataset)))

## 9. 调用 方法7/8
for epoch in range(1, Epochs +1):
    train_model(model, Device, train_loader, optimizer, Epochs)
    test_model(model, Device, test_loader)

参考

  1. PyTorch卷积神经网络实例深度分析——以MNIST数据集为例
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用PyTorch训练MNIST数据集上的卷积神经网络可以按照以下步骤进行: 1. 导入必要的库和数据集 ```python import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as datasets import torchvision.transforms as transforms # 加载MNIST数据集 train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True) test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor(), download=True) # 定义批次大小 batch_size = 64 # 创建数据加载器 train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False) ``` 2. 定义卷积神经网络模型 ```python class ConvNet(nn.Module): def __init__(self): super(ConvNet, self).__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1) self.relu1 = nn.ReLU() self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) self.relu2 = nn.ReLU() self.pool = nn.MaxPool2d(kernel_size=2, stride=2) self.fc1 = nn.Linear(64 * 7 * 7, 128) self.relu3 = nn.ReLU() self.fc2 = nn.Linear(128, 10) def forward(self, x): out = self.conv1(x) out = self.relu1(out) out = self.conv2(out) out = self.relu2(out) out = self.pool(out) out = out.view(out.size(), -1) out = self.fc1(out) out = self.relu3(out) out = self.fc2(out) return out # 创建模型实例 model = ConvNet() ``` 3. 定义损失函数和优化器 ```python # 定义损失函数 criterion = nn.CrossEntropyLoss() # 定义优化器 optimizer = optim.Adam(model.parameters(), lr=.001) ``` 4. 训练模型 ```python # 定义训练函数 def train(model, train_loader, criterion, optimizer, num_epochs): for epoch in range(num_epochs): for i, (images, labels) in enumerate(train_loader): # 前向传播 outputs = model(images) loss = criterion(outputs, labels) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() # 每100个批次打印一次训练信息 if (i+1) % 100 == : print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, i+1, len(train_loader), loss.item())) # 开始训练 num_epochs = 5 train(model, train_loader, criterion, optimizer, num_epochs) ``` 5. 测试模型 ```python # 定义测试函数 def test(model, test_loader): # 测试模式 model.eval() # 定义变量 correct = total = # 不计算梯度 with torch.no_grad(): for images, labels in test_loader: # 前向传播 outputs = model(images) _, predicted = torch.max(outputs.data, 1) # 统计正确率 total += labels.size() correct += (predicted == labels).sum().item() # 输出测试结果 print('Accuracy of the model on the 10000 test images: {:.2f} %'.format(100 * correct / total)) # 测试模型 test(model, test_loader) ``` 以上就是使用PyTorch训练MNIST数据集上的卷积神经网络的完整代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值