Pytorch学习笔记(六)

简单的LeNet网络模型

torchvision.datasets

torchvision是pytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。

以下是torchvision的构成:

  • torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
  • torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;
  • torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
  • torchvision.utils: 其他的一些有用的方法。

这里只讲下面需要使用的部分:

下面我们介绍一下使用torchvision.datasets中自带的Fashion-MNIST数据集。

这个数据集包含10种样本,按照标签从小到大的顺序对应的图像分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。训练集中每种样本6000张图片,所以训练集总共6w张图片,测试集中每种样本1000张图片,所以测试集总共1w张图片。每幅图片都是28×28的像素数组,每个像素的值为0~255之间的8位无符号整数(uint8),使用三维张量存储,第一维表示通道个数。由于为灰度图像,故通道数为1,即1×28×28。

Pytorch学习笔记(六)_第1张图片

修正了下面“REF”中的第一个参考博客中的部分错误内容。

实现步骤

  • 导入库
import torch
import torchvision
import torchvision.transforms as transforms
import torch.utils.data as Data
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
  • 下载FashionMNIST数据集
"""
torchvision.datasets中有很多自带的数据集。
root参数指明我下载的这些数据集要保存在哪个路径下;
train参数为True表示下载(保存)的是训练集,False表示测试集
download参数为True,从互联网下载数据集后将其放在root中。 如果数据集已经下载,则不是再次下载。
transform参数(可调用,可选):接受PIL图像并返回转换后版本的函数/转换。 例如,ToTensor()就是转换为张量形式并进行归一化
"""
fashion_mnist_train = torchvision.datasets.FashionMNIST(root='./', train=True, download=True, transform=transforms.ToTensor())
fashion_mnist_test = torchvision.datasets.FashionMNIST(root='./', train=False, download=True, transform=transforms.ToTensor())

在我执行torchvision.datasets.FashionMNIST(root='./', train=True, download=True, transform=transforms.ToTensor())torchvision.datasets.FashionMNIST(root='./', train=False, download=True, transform=transforms.ToTensor())命令后,会自动构建如下的FashionMNIST文件夹及其内部文件:

Pytorch学习笔记(六)_第2张图片
“实现多层感知器的学习过程”文件夹是我自己创建的,MLP.py是存放代码的,".pt"类型的文件是pytorch类型的文件,是调用上面两条语句后自动生成的。

对比一下两条语句返回的对象与自动生成的.pt文件的区别,首先说明,返回的对象和.pt文件都是数据集,只是存储的格式不同。

① fashion_mnist_train 和 fashion_mnist_test 的第一维表示样本个数,即fashion_mnist_train[0]表示第一个样本,为一个元组,元组的第一维度是特征,第二维度是标签。这与DataSet类型的表示方式是一致的。float类型。

② 而.pt文件保存的一个元组,元组的第一维和第二维均为张量;第一维的张量是全部样本的特征信息,特征信息张量的第一维就是样本数;第二维的张量是全部样本的标签。int类型。

建议自己使用type、dtype等方式输出一下试试,自己观察一遍才能真正理解。

  • 加载数据集

写下面代码时还未阅读到“REF”[1]博客,所以代码中的数据集是从test.pt和train.pt中获取的,而这两个文件是调用torchvision.datasets.FashionMNIST自动生成的。其实使用返回对象访问数据集更为简单,feature, lable = mnist_train[0]就可以分别获取到训练集的特征与标签。

# 加载数据
train_dataset = torch.load('./FashionMNIST/processed/training.pt')    # 60000个样本
test_dataset = torch.load('./FashionMNIST/processed/test.pt')         # 10000个样本

train_dataset 是个元组,两个元素均为张量,第一个张量是全部样本的特征张量,大小为60000×28×28;第二个张量是全部样本的标签张量,一维张量大小为60000。

# 由初始训练数据集格式转换为我们方便处理的格式,即分离特征与标签
x = train_dataset[0][:1000].unsqueeze(dim=1).float()       # 训练集特征 28×28
y = train_dataset[1][:1000]                                # 训练集标签
  1. train_dataset[0]:特征

  2. train_dataset[1]:标签

  3. [:1000]:取前1000个样本,6w个样本CPU根本跑不出来

  4. .unsqueeze(dim=1):因为要求conv2d是对(N, C, H, W)的四维张量进行操作的,而数据集是(N, H, W)的,即少一个单通道维度,该函数就是在第二维加一个维度将(N, H, W)变为(N, 1, H, W)

  5. .float():将数据类型从int转换为float,如果不转换会出现”RuntimeError: expected scalar type Byte but found Float“的问题,因为model()中的输入需要是浮点才能计算梯度等信息,所以在传入全部数据集进行输出损失值时会报如上错误,但是由于DataLoader生成的batch自动将int转为了float,所以训练的时候并不会报错。

# 显示一下,看看对不对
# plt.imshow(x[0][0]) # 获取第一个样本的第一个通道
# plt.show() # 显示的是“靴子”
  • 定义网络结构

这部分对于我们来说已经太熟练了。

这里我们不再使用add_module,因为使用add_module需要加名字参数,太麻烦了,所以直接使用如下方式创建。

class LeNet(nn.Module) :
    def __init__(self):
        super(LeNet, self).__init__()
        
        self.layer1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=25, kernel_size=(3, 3)),
            nn.BatchNorm2d(num_features=25),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
        )

        self.layer2 = nn.Sequential(
            nn.Conv2d(in_channels=25, out_channels=50, kernel_size=(3, 3)),
            nn.BatchNorm2d(num_features=50),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
        )

        self.layer3 = nn.Sequential(
            nn.Linear(in_features=50*5*5, out_features=1024),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=1024, out_features=128),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=128, out_features=10)
        )

    def forward(self, x) :
        conv1 = self.layer1(x)
        conv2 = self.layer2(conv1)
        input = conv2.view(conv2.size(0), -1)
        y_hat = self.layer3(input)
        return y_hat

实例化模型

model = LeNet()
  • 规定一些参数,定义小批量迭代器、优化器和损失函数
batch_size = 50
num_epochs = 100
num_examples = 60000
learning_rate = 0.01

# 用选取的样本进行训练
dataset = Data.TensorDataset(x, y)

# 实现小批量的迭代器
train_loader = Data.DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)

# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 定义损失函数
loss = nn.CrossEntropyLoss()
  • 训练模型

训练速度很慢,因为使用的是CPU,所以也不必等到训练完,可以正常执行完一个epoch、理解过程即可。

for epoch in range(1, num_epochs+1) :
    for step, (batch_x, batch_y) in enumerate(train_loader) :

        optimizer.zero_grad()
        batch_y_hat = model(batch_x)
        loss_value = loss(batch_y_hat, batch_y)
        loss_value.backward()
        optimizer.step()

    loss_value = loss(model(x), y)
    print('Epoch %d, Loss %f' % (epoch, loss_value.item()))
  • 测试模型

训练完成后选取测试集进行测试,判断模型预测的标签与真实标签是否一致,计算准确率。

(上面训练模型只选取了1k个样本,因为我的GPU太小了,内存不够)

以上代码按照在本文的出现顺序复制执行即可。

使用GPU的简单LeNet网络模型

想来想去还是觉得不行,需要用GPU跑跑试试,于是就去网上学习。

我将全部代码分成了三部分,LeNet_Network.py存放定义的网络结构,LeNet_Train.py存放模型训练的代码,LeNet_Test.py存放模型测试的代码,TrainedModel文件夹存放LeNet.pkl文件,该文件保存训练好的网络模型参数,这使得每次使用模型去测试时不用去训练了,直接向模型中导入模型参数即可。

如果你在import自定义的模块时出现错误,而且感觉不应该出现这种低级的问题,那么不妨尝试一下在pycharm中右键这两个模块所在的文件夹,点击“Mark Directory as”,点击“Sources root”,OK了。

LeNet_Network.py

这部分没变。

import torch.nn as nn

"""
定义网络结构
"""
class LeNet(nn.Module) :
    def __init__(self):
        super(LeNet, self).__init__()
        # 不使用add_module,因为使用add_module需要加名字参数
        self.layer1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=25, kernel_size=(3, 3)),
            nn.BatchNorm2d(num_features=25),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
        )

        self.layer2 = nn.Sequential(
            nn.Conv2d(in_channels=25, out_channels=50, kernel_size=(3, 3)),
            nn.BatchNorm2d(num_features=50),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))
        )

        self.layer3 = nn.Sequential(
            nn.Linear(in_features=50*5*5, out_features=1024),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=1024, out_features=128),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=128, out_features=10)
        )

    def forward(self, x) :
        conv1 = self.layer1(x)
        conv2 = self.layer2(conv1)
        input = conv2.view(conv2.size(0), -1)
        y_hat = self.layer3(input)
        return y_hat

LeNet_Train.py

大部分代码都没变,多了几个.to(device)语句和.cuda()语句。

下面代码的“定义模型”部分,如果本设备存在可以使用的GPU,则device为GPU,否则为CPU,.to(device)将网络放在GPU上。

同样的,下面生成batch的迭代中,也要将生成的batch(数据)放在GPU上,所以使用.cuda()。由于我们还要使用全部数据集来观察损失值,所以x.cuda()y.cuda()也不可缺少。

这里使用save将模型参数保存在TrainedModel文件夹中的LeNet.pkl文件中。

模仿着写。

import torch
import torchvision
import torchvision.transforms as transforms
import torch.utils.data as Data
import torch.optim as optim
import torch.nn as nn
import matplotlib.pyplot as plt
from LeNet_Network import LeNet

fashion_mnist_train = torchvision.datasets.FashionMNIST(root='./', train=True, download=True, transform=transforms.ToTensor())
fashion_mnist_test = torchvision.datasets.FashionMNIST(root='./', train=False, download=True, transform=transforms.ToTensor())

train_dataset = torch.load('./FashionMNIST/processed/training.pt')    # 60000个样本

x = train_dataset[0][:4000].unsqueeze(dim=1).float()       # 训练集特征 28×28
y = train_dataset[1][:4000]                                # 训练集标签

# 显示一下,看看对不对
# plt.imshow(x[0][0]) # 获取第一个样本的第一个通道
# plt.show() # 显示的是“靴子”

"""
定义模型
(并讲模型放在GPU上)
"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = LeNet().to(device)


"""
设置一些训练参数
"""
batch_size = 50
num_epochs = 10
num_examples = 60000	# 没用上
learning_rate = 0.01

# 用选取的样本进行训练
dataset = Data.TensorDataset(x, y)

# 实现小批量的迭代器
train_loader = Data.DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)

# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 定义损失函数
loss = nn.CrossEntropyLoss()

"""
开始迭代训练
"""
for epoch in range(1, num_epochs+1) :
    for step, (batch_x, batch_y) in enumerate(train_loader) :

        # 放在GPU上
        batch_x = batch_x.cuda()
        batch_y = batch_y.cuda()

        optimizer.zero_grad()
        batch_y_hat = model(batch_x)
        loss_value = loss(batch_y_hat, batch_y)
        loss_value.backward()
        optimizer.step()

    loss_value = loss(model(x.cuda()), y.cuda())
    print('Epoch %d, Loss %f' % (epoch, loss_value.item()))

torch.save(model.state_dict(),"./TrainedModel/LeNet.pkl") # 保存训练好的参数

LeNet_Test.py

定义一个空模型,即参数都是默认初始化的模型,使用 model.load_state_dict(torch.load("./TrainedModel/LeNet.pkl"))语句将保存的模型参数导入空模型中。

测试部分我选用了全部的测试集,即1w个数据进行测试,每100个数据作为一组,即一个batch,计算一组正确率进行输出,总共100组。

torch.argmax(model(batch_x), dim=1)是选取每组样本中预测值最大的索引作为标签(你可以试着输出一下model(batch_x),观察一下输出的结构,就方便理解了),argmax按照第二个维度选出最大索引。再转化为numpy,进行逐位对比判断是否相同,计算均值作为该组的准确率。

import torch
import numpy as np
import torch.utils.data as Data
from LeNet_Network import LeNet

if __name__ == '__main__' :
    """
    获取数据,处理数据
    """
    test_dataset = torch.load('./FashionMNIST/processed/test.pt')  # 10000个样本
    features = test_dataset[0].unsqueeze(dim=1).float()
    labels = test_dataset[1]

    """
    创建模型
    """
    model = LeNet()
    # print(torch.load("./TrainedModel/LeNet.pkl")) # OrderedDict形式
    model.load_state_dict(torch.load("./TrainedModel/LeNet.pkl")) # 导入模型参数

    loader = Data.DataLoader(dataset=Data.TensorDataset(features, labels), shuffle=True, batch_size=100)

    with torch.no_grad(): # 不构建计算图

        for step, (batch_x, batch_y) in enumerate(loader) :

            acc = np.mean(torch.argmax(model(batch_x), dim=1).numpy() == batch_y.numpy())
            print(f'Step {step+1}, Acc {acc}')
  • with torch.no_grad():

在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用 with torch.no_grad():,强制之后的内容不进行计算图构建。

REF

[1] 【深度学习】Fashion-MNIST数据集简介 - CSDN博客

[2] torchvision的使用(transforms用法介绍) - CSDN博客

[3] Pytorch学习基础——LeNet从训练到测试 - CSDN博客

[4] 3.5 图像分类数据集(Fashion-MNIST) - Dive-into-DL-PyTorch

[5] pytorch中with torch.no_grad(): - CSDN博客

[6] PyTorch保存网络结构以及参数【 torch.save()、torch.load() 】- CSDN博客

[7] 1.LeNet5–手写数字识别(Pytorch) - 哔哩哔哩

[8] model.parameters()与model.state_dict() - 知乎

你可能感兴趣的:(【Pytorch学习】,pytorch,深度学习,python)