基于小土堆入门教程的pytorch训练神经网络方法实现

成果展示

在学习吴恩达机器学习和小土堆入门教程的基础上,完成了该实验,目前可以实现标准数据集的加载、网络模型的搭建及训练、数据可视化、GPU加速功能,是机器学习理论的初步实践

import torch
import torchvision.datasets
from torch import nn
from torch.utils.data import DataLoader
import time

from torch.utils.tensorboard import SummaryWriter

# 获取训练集
train_data = torchvision.datasets.CIFAR10(root="./data", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
# 获取测试集
test_data = torchvision.datasets.CIFAR10(root="./data", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)
# 加载训练数据
train_dataloader = DataLoader(train_data, batch_size=16)
# 加载测试数据
test_dataloader = DataLoader(test_data, batch_size=16)

train_data_size=len(train_data)
test_data_size=len(test_data)
# 搭建网络
class NetWork(nn.Module):
    def __init__(self):
        super(NetWork, self).__init__()
        # 网络模型构建
        self.module = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64 * 4 * 4, 64),
            nn.Linear(64, 10)
        )

    # 向前传播
    def forward(self, x):
        return self.module(x)


# 实例化网络对象
NW = NetWork().cuda()       # cuda加速

# 验证网络正确性,查看卷积后尺寸
# input = torch.ones((64, 3, 32, 32)).cuda()
# output = NW(input)
# print(output.shape)
# print(output.argmax(1))

# 创建损失函数
loss_fn = nn.CrossEntropyLoss().cuda()  # cuda加速

# 创建优化器
optimizer = torch.optim.SGD(NW.parameters(), lr=0.01)

# 训练过程
NW.train() # 开启训练模式,但并不必要,仅对特定层起作用
epoch = 10  # 训练轮数

for i in range(epoch):
    start_time = time.time()
    print("-------第", i + 1, "轮训练--------")
    for data in train_dataloader:
        img, target = data      # 分别获取图像和标签
        img=img.cuda()
        target=target.cuda()    # cuda加速
        output=NW(img)          # 输出为经过向前传播的图像

        # 调优过程
        loss=loss_fn(output,target)    # 根据预测和标签求损失函数
        optimizer.zero_grad()   # 梯度清零,避免干扰
        loss.backward()         # 反向传播,计算参数的梯度
        optimizer.step()        # 调整优化参数

        cost_time=time.time()-start_time
    print("第",i+1,"轮训练完成,训练用时:",cost_time,"s")
    # 测试步骤
    NW.eval()   # 验证模式
    with torch.no_grad():       # 模块下计算不用于梯度求导,即只测试,不调参
        total_test_loss=0
        total_accuracy=0    # 总正确率
        for data in test_dataloader:
            imgs,targets=data
            imgs=imgs.cuda()
            targets=targets.cuda() # cuda加速
            output=NW(imgs)
            loss=loss_fn(output,targets)
            total_test_loss+=loss.item() # 将loss从tensor类型转为常规类型
            accuracy=(output.argmax(1)==targets).sum()  # 单次验证集的正确数量
            total_accuracy=total_accuracy+accuracy      # 总正确数量
        print("总损失为:",total_test_loss)
        print("正确率为:", total_accuracy.item()/test_data_size)

输出示例为:
网络输出

数据集

数据集加载

主要有标准数据集和自制数据两种方法,标准数据以目标检测为例,有VOCCOCO等,自制数据集方法见此处,本文主要介绍读取加载方法。

pytroch中提供DataSet基类用于获取内容和标签,可以重写该类用于读取自定义的数据,示例如下:

import torch
import torch.utils.data.dataset as Dataset
import numpy as np

#创建子类
class New_Dataset(Dataset.Dataset):
    # 初始化内容和标签
    def __init__(self, Data, Label):
        self.Data = Data
        self.Label = Label
    # 返回数据集大小
    def __len__(self):
        return len(self.Data)
    # 获取数据内容及标签并转为Tensor类型
    def __getitem__(self, index):
        data = torch.Tensor(self.Data[index])
        label = torch.Tensor(self.Label[index])
        return data, label

Dataloader用于打包数据,给网络提供不同形式的数据,使用方法为数据名=DataLoader(dataset数据集,batch_size=每批取的单元,drop_last=True舍去最后不足的数据,shuffle=True 随机选取)

标准数据集

本章使用标准数据集CIFAR10进行实验,使用方法为train_data = torchvision.datasets.CIFAR10(root="./data", train=True,download=True),将训练数据下载到当前目录的data文件夹下,没有文件时会自动下载,获取测试数据将train参数设置为False即可。

该数据集为60000张32×32的图像,其中50000张训练图像,10000张测试图像,使用该数据集的好处就是小,下载演示都比较方便,生产实践中真正搞得数据集动辄几十上百G,暂时还没这个需求。
数据集
对第一个加载数据读取情况如下:
读取加载内容

数据预处理

Transformor

主要用于图像预处理和相关变换,包含在torchvision.transforms 模块下,一些常用方法如下:

ToTensor:输入PIL的Image图像格式或 numpy.ndarray的数组,将其转化为包含反向传播所需参数的tensor类型,使用示例为t=transforms.ToTensor(Image.open(path))
ToPILImage:将tensor类型转回PIL的Image格式
Resize:拉伸压缩调整图像大小,而不分割,使用方法为tr=torchvision.transforms.Resize(size=(宽,高))若输入为一个数,则图像的短边将与此匹配,其他详细参数见该文章
Normalize:使用平均值和标准差对图像进行均一化处理,输入每个通道对应的均值和标准差作为参数,函数会利用这两个参数分别将每层标准化(使数据均值为0,方差为1)后输出。
Compose:组合多个变换,使用方法示例为transforms.Compose([ transforms.CenterCrop(10),transforms.PILToTensor(),transforms.ConvertImageDtype(torch.float) ])
RandomCrop:随机裁剪,输入大小,返回随机裁剪后的图像

Tensorboard

用于解析训练相关事件,将图像和训练信息可视化,库文件使用from torch.utils.tensorboard import SummaryWriter导入,创建实例writer=SummaryWriter(".\logs"),要显示图像使用writer.add_image(标题,tensor_img),最常用的绘图使用add_scaler(图标名称,纵坐标,横坐标),实例代码及效果如下:

for n_iter in range(100):
    writer.add_scalar('Loss/train', np.random.random(), n_iter)
    writer.add_scalar('Loss/test', np.random.random(), n_iter)
    writer.add_scalar('Accuracy/train', np.random.random(), n_iter)
    writer.add_scalar('Accuracy/test', np.random.random(), n_iter)

多个图像会自动根据标签字符归类,该方法常用于绘制学习曲线,绘制完成后在终端输入tensorboard --logdir="文件路径\logs --port=新端口",在弹出的链接出可查看。
折线图绘制
也可以使用该工具绘制模型,通过writer.add_graph(模型名,输入)
绘制模型
使用后必须使用Writer.close()将内容写入文件中,类似U盘和读写文件操作时的最后一步。

网络搭建

骨架—nn.Module

pytorch中的网络构建通过nn库实现,其中Module为骨架,作为容器组合隐藏层及输出层,示例代码如下:

import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 20, 5)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        return F.relu(self.conv2(x))

调用该模块时自动执行forward函数,构造函数中定义神经网络需要的隐藏层。

卷积层Convolution Layers

nn.Conv1d代表一维卷积,nn.Conv2d代表二维卷积,常用输入包括输入通道in_channel、输出通道out_channel、卷积核大小kernel_size、填充padding和步长stride,由于卷积核也是训练的变量之一,故该方法并不能手动设置卷积核,若要手动设置卷积核,需使用orch.nn.functional.conv2d方法,在参数的weight中设置。

池化层Pooling layers

为了保留输入特征,但减少数据量而增加的层,如最大池化,选出卷积核对应的最大值MaxPool方法,输入有池化窗kernel_size,步长stride,填充padding和空洞卷积dilated即卷积核有空格,和cellmode是否保留超出核范围的值。

非线性激活Non-linear Activations

给数据引入非线性特质,如RELU截断非负数值、sigmoid分类方法等。

线性层Linear Layers

对传入数据进行线性变换wx+b,参数为输入特征的个数和输出特征的个数,即Linear(x,y),可将x个特征输出为y个预测。

构建序列

上述隐藏层输出层都写在构造函数并命名太繁琐,调用也很不方便,可以使用Sequential进行组合,将这些层共同组成模型,示例如下:

model = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )

在后续的forward函数仅需调用self.model()方法即可实现数据的正向传播。

网络模型

网络模型示例图
记不清为啥了,反正当时就是用了这个网络,可能是简单网络中效果较好的一种吧,输入三通道32×32的图像,最终输出10个可能特征的预测,中间分别经过的层列表如下:

5×5卷积核的卷积层输入通道为3,输出通道为32
2×2窗口的最大池化层原有图像缩减16×16
5×5卷积核的卷积层输入通道为32,输出通道为32
2×2窗口的最大池化层原有图像缩减至8×8
5×5卷积核的卷积层输入通道为32,输出通道为64
2×2窗口的最大池化层原有图像缩再减4×4
Falttern层数据一维化,图像处理成64×4×4=1024个样本
全连接层输入1024个样本输出64
全连接层输入64个样本输出10个预测特征

该步的重点在于计算卷积层的参数,32×32的图像经过5×5的卷积核,步长为1的情况下该输出28×28的矩阵,而我们仍需要输出32×32的图像,则需要对原图像进行填充,填充数值可经过如下公式计算:
填充计算公式
空洞卷积我们不使用,故内核间距dilation为默认的1,可得padding=2,该步在调用Conv2d时除了输入输出通道数和卷积核大小外,需要设置该参数。

损失函数

用于衡量预测与实际的差距,为更新输出提供依据(通过反向传播实现),根据模型和预测需要的不同可以选择多种损失函数,MSE平方差,交叉熵等,具体损失函数可见官方文档

优化器

pytorch实现各种优化算法的包,官方文档在此,使用过程如下:
1,构造优化器对象
2,反向传播计算梯度
3,调优参数
示例代码如下:

optimizer.zero_grad()	# 梯度清零,避免上次学习的干扰,pytorch特性是将梯度累加
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # 构建SGD优化算法的优化器,学习速率为0.01,动量为0.9
backward()				# 反向传播计算梯度
optimizer.step()		# 执行调优参数

经过两次优化的部分模型参数变化展示如下:
初始参数
新参数

模型保存与加载

torch.save(模型名,保存文件名.pth)
模型名=torch.load(文件名.pth)
使用该方法会保存模型的结构和参数,但在新文件中加载仍然需要以前的结构文件,即需要import 网络模型文件,如果有了网络模型文件就没必要从文件中加载了,而因为保存了模型文件,该方法保存的文件体积也较大,故官方推荐使用如下方法:

torch.save(模型名.state_dict(),文件名.pth)
模型.lload_state_dict(文件名.pth)
该方法只保存了模型的训练数据,减少了保存文件体积。

网络模型优化

GPU加速

首先通过两张图直观感受一下GPU的加速效果:
cpu训练
gpu训练
可以看到用了GPU能加的速不是一点半点,这也是为什么当时学习安装pytorch时要着重搞CUDA的安装了,该方法比较费硬件,也对显卡显存有一定要求,大量数据在显卡上运行时是否有和操作系统一致的保护或调度机制,目前还不了解,先这么用着吧,如果没有英伟达显卡的话也可以去谷歌找一张免费提供的显卡使用,好像效果还不错。

使用方法就是分别在模型、数据和损失函数后加上指定显卡运行标识,如NetWork=NetWork().cuda()img=img.cuda()loss=loss_fn(output,target).cuda()

这种方法不大严谨,官方推荐使用device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu",有显卡使用第一张显卡,否则使用cpu运行,可以增强程序的可移植性和健壮性,通过NetWork=NetWork.to(device)的方式给需要的信息加上标签。

输出处理

直接对经过网络的output进行输出,信息如下:
输出结果信息
该信息为16×10的矩阵序列,表示一个批次16张图像的每个结果的可能概率,我们需要的仅仅是最可能的分类结果,在该序列中选择最大的可能作为输出即可,该方法通过调用argmax()函数实现,参数为0表示横向比较,参数为1表示纵向比较,输出为比较序列最大值的序号。

通过输出可以看到每次预测和真实标签的差距:
预测和真实
统计每个批次预测正确的数量,测试集全部读取完成后与测试集总数之比即为准确率,实现代码如下:

	accuracy=(output.argmax(1)==targets).sum()  # 单次验证集的正确数量
	total_accuracy=total_accuracy+accuracy      # 总正确数量
# 循环结束后
print("正确率为:", total_accuracy.item()/test_data_size)

答疑

现在已经能构建起自己定义的网络模型并跑出还可以的数据,但在实际构建的确有和基础知识学习时的很大区别,比如:

1,基础学习时特征是经过构建好的神经网络,通过计算损失函数,不断调整神经网络的一些参数,但实验中并没有设置网络参数的过程,只给网络搭起了模型就自行运转了,网络中的参数从何而来?

2,调优过程的反向传播起了什么作用,调优器究竟整合了机器学习的什么步骤?

模型参数

模型在建立时我们并没有指定参数,这是因为pytroch定义了默认值,以Conv2d为例,继承自ConvNd类,参数初始化方法定义在其中:
继承类
初始化参数方法
模型初始化参数的方法

backward()反向传播

我们之前学过,优化成本函数的方法是梯度下降,第一步就需要计算偏导,不同函数人手算很麻烦,计算机实现更复杂,为了简化该过程,有人推导出了由损失函数计算每个结点偏导的快速方法,这种由损失函数推出偏导的方法称为反向传播,pytorch给我们封装成了函数直接使用即可,该步骤在实验中就是为了计算梯度

说白了方向传播就是求偏导,后面的step就是给新的参数进行赋值,优化器本质就是给模型更新参数的过程,从数学上来说就是实现了这两个公式:
在这里插入图片描述

总结

还是画图比较清楚,对pytorch训练神经网络的整体过程总结如下:
pytorch训练神经网络流程
该过程大致和理论学习时一致,优化器整合了梯度下降部分,一些复杂的数学原理并没有向用户过多暴露,使得我们可以很快上手构造模型,但后续调参和模型优化仍要求我们理解底层原理。

这么说来机器学习本身好像也是快上手,慢熟悉的过程,更多操作如特征缩放、判断收敛等还未使用,更不必说神经网络的底层原理,我们并没有规定特征的学习方向,网络究竟是如何通过激活函数和卷积操作等就能学习到准确的特征并判断的呢?不同参数又是如何在一个庞大的神经网络中作用的?目前只能说是稍稍入门,这些都有还待于后续的学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值