转:Pytorch模型小例子

原文链接:五分钟内编写Pytorch模型

如果你想在五分钟内编写Pytorch模型,需要完成四个步骤:

  1. 导入和预处理(数据集)数据,并对其进行批处理(数据加载器)

  2. 使用神经网络建立模型。

  3. 编写一个训练循环并运行它。

  4. 验证集上的验证。

由于MNIST已经做得非常彻底,我们将介绍如何导入torchvision数据集,并在五分钟内编写一些代码。出于这个原因,它不会很漂亮,但会起作用。

1、下载和导入数据

因为MNIST已经做得很死了,我们将搜索标准的torchvision数据集,看看是否还有其他我们想要尝试和预测的东西。

让我们来看Kuzushiji MNIST,它是由70000张图像组成的MNIST数据集的平假名替代品。

首先,我们找到每个通道的平均值和标准偏差。这背后的原因是,我们希望对训练数据进行归一化,但Pytorch变换需要提前给出归一化平均值和标准偏差。因此,我们将使用数据集找到这些值,然后重新导入它,并用预定义的值传递一个归一化转换。

from torchvision import datasets
from torchvision import transforms
# 指定数据集路径为data,如果数据集不存在则进行下载
# 通过train=False获取测试集
data_path = "data/"
kmnist = datasets.KMNIST(data_path, train=True, download=True,
                         transform=transforms.Compose([
                             transforms.ToTensor()]))

kmnist_val = datasets.KMNIST(data_path, train=False, download=True,
                             transform=transforms.ToTensor())

上述代码的Kuzushiji MNIST数据会自动下载,结果如下:

请注意,kmnist是一个数据集,因此循环它会为我们提供每一个图像和每一个标签。因此,如果我们在数据集中的每个图像上循环,并沿着额外的第四维叠加它们,我们将得到所有图像的张量。

# t.stack:扩维堆叠
imgs = torch.stack([img_t for img_t, _ in kmnist], dim=3)

print(f"imgs.shape: {imgs.shape}")
# torch.Size([1, 28, 28, 60000])

现在我们计算每个通道的平均值。请注意,调用imgs.view(1,-1)将把所有的张量压缩到第二维度。我们在这个第二维度上取像素值的平均值(因此dim=1)和标准差。

# compute mean and std per channel
mean = imgs.view(1, -1).mean(dim=1)
std = imgs.view(1, -1).std(dim=1)
print(f"mean:{mean},std:{std}")
# mean:tensor([0.1918]),std:tensor([0.3483])

我们现在可以重新导入数据,使用Normalize转换以及将数组转换为张量。请注意,Normalize将像素值的平均值和标准偏差作为参数。

# 我们现在可以重新导入数据,使用Normalize转换以及将数组转换为张量。
# 请注意,Normalize将像素值的平均值和标准偏差作为参数。
# normalised data
t_kmnist = datasets.KMNIST(data_path, train=True, download=False,
                           transform=transforms.Compose([
                               transforms.ToTensor(),
                               # transforms.Normalize(0.1307,0.3081),
                               transforms.Normalize(mean, std)
                           ]))

t_kmnist_val = datasets.KMNIST(data_path, train=False, download=False,
                               transform=transforms.Compose([
                                   transforms.ToTensor()]))

现在我们有了数据集,我们需要将这些数据输入数据加载器进行批处理。如果你使用的是CPU,请确保设置较小的批处理大小,并将num_workers=1(这是一个GPU问题,不要太担心)。

# 数据加载器
train_loader = torch.utils.data.DataLoader(kmnist, batch_size=128,
                                           shuffle=True,
                                           num_workers=1)

val_loader = torch.utils.data.DataLoader(kmnist_val, batch_size=128,
                                         shuffle=False,
                                         num_workers=1)

我们可以从数据集中查看一些样本。

# 遍历
figure = plt.figure(figsize=(10, 8))

cols, rows = 5, 5

for i in range(1, cols * rows + 1):
    # randint返回tensor,size=(1,)一维一列,item返回数值
    sample_idx = torch.randint(len(t_kmnist), size=(1,)).item()
    img, label = t_kmnist[sample_idx]
    figure.add_subplot(rows, cols, i)

    plt.title(label)

    plt.axis("off")

    plt.imshow(img.squeeze(), cmap="gray")

plt.show()

构建模型

这不是一篇关于如何从理论上构建深度学习模型的教程。因此,我们将在这里介绍模型实现。

首先,需要将模型实例化为nn.Module的实例。其次,你需要使用常用的Python方法初始化类。最后,你需要一个模型初始化,在这里我们定义了所有的模型层,然后是一个forward方法,在这里我们告诉模型如何获取输入并将其传递给这些层。


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()

        self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5,
                               stride=1, padding=2)
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1,
                               padding=2)
        self.layer1 = nn.Sequential(self.conv1, nn.ReLU(), nn.MaxPool2d(kernel_size=2))
        self.layer2 = nn.Sequential(self.conv2, nn.ReLU(), nn.MaxPool2d(kernel_size=2))
        self.out = nn.Linear(32 * 7 * 7, 10)  # output 10 classes

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        # flatten the output of conv2 to (batch_size, 32*7*7)
        x = x.view(x.size(0), -1)
        output = self.out(x)
        return output

在这个阶段,通过从dataloader中给出一个示例来调试模型总是很重要的。然后,我们将该图像传递给模型,并检查它是否输出了正确大小的内容。

img, _ = next(iter(train_loader))

img = img[0]

model = Net()

model(img.unsqueeze(0)).shape

>>> torch.Size([1, 10])

完美的我们构建了一个模型,该模型采用K-MNIST图像,并输出10个类,代表每个可能的数字0到9的10种不同概率。

编写和运行训练循环

像往常一样,我们的训练步骤是相似的。前向传播,计算损失,重置梯度(Pytorch)。反向传播以计算有关损失的梯度。用这些梯度更新我们的权重。

def training_loop(n_epochs, model, loss_fn, optimiser, train_loader):
    
    model.train()
    
    total_step = len(train_loader)
    
    for n in range(1, n_epochs+1):
        
        for i, (imgs, labels) in enumerate(train_loader):
            
            output = model(Variable(imgs)) # forward pass
            
            loss = loss_fn(output, Variable(labels)) # compute loss
            
            optimiser.zero_grad() # reset gradients
            
            loss.backward() # backpropagation
            
            optimiser.step() # update the weights
            
            if (i+1) % (n_epochs/10) == 0:
                
                print ('{} Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}' 
                       .format(datetime.datetime.now(), n, n_epochs, 
                               i + 1, total_step, loss.item()))

然后,我们实例化我们的模型,设置Adam优化器,并使用交叉熵损失(因为这是一个多类分类问题)。

 
 

model = Net()

optimiser = torch.optim.Adam(model.parameters(), lr=1e-2)

loss_fn = nn.CrossEntropyLoss()

然后把这些参数传递给训练循环。

 
 

training_loop(
    
    n_epochs = 10,
    
    model = model,
    
    loss_fn = loss_fn,
    
    optimiser = optimiser,
    
    train_loader = train_loader
)

验证模型

迭代验证数据加载器中的图像和标签,前向传播,通过在输出张量中找到值最高的索引(记住,我们输出了10个概率的向量)得到预测。data.squeeze()以获取实际的标量本身。最后,统计预测值与标签相等的样本数量,除以标签总数。

 
 

def validate(model, train_loader, val_loader):       
    
    model.eval() # set to eval mode to avoid batchnorm
    
    with torch.no_grad(): # avoid calculating gradients
        
        correct, total = 0, 0
        
        for images, labels in val_loader:
            
            test_output = model(images)
            
            pred_y = torch.max(test_output, 1)[1].data.squeeze()
            
            accuracy = (pred_y == labels).sum().item() / float(labels.size(0))
            
    print('VALIDATION SET ACCURACY: %.2f' % accuracy)
    
validate(model, train_loader, val_loader)

>>> VALIDATION SET ACCURACY: 0.95

验证集准确率为95%。

结论

让模型变得更好:

  • 在模型训练时打印验证集指标:显然,很高兴看到训练损失随着每个epoch而减少。但是,直到我们在训练后对模型进行验证,我们才真正了解模型的性能。如果在运行过程中打印验证准确性,你将更好地了解模型的成功。

  • 早停:一旦验证的准确性在一定时期内(称为耐心)没有提高,就回到表现最好的epoch并使用这些权重。

  • 看看其他指标:例如曲线下面积(AUC)。

  • 实现其他网络架构:自CNN引入以来,计算机视觉已经取得了长足的进步。你可以尝试其他体系结构来提高性能。

源码Kuzushiji.py如下:

import datetime

from matplotlib import pyplot as plt
from torch import nn
from torch.autograd import Variable
from torchvision import datasets
from torchvision import transforms
import torch


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()

        self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5,
                               stride=1, padding=2)
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1,
                               padding=2)
        self.layer1 = nn.Sequential(self.conv1, nn.ReLU(), nn.MaxPool2d(kernel_size=2))
        self.layer2 = nn.Sequential(self.conv2, nn.ReLU(), nn.MaxPool2d(kernel_size=2))
        self.out = nn.Linear(32 * 7 * 7, 10)  # output 10 classes

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        # flatten the output of conv2 to (batch_size, 32*7*7)
        x = x.view(x.size(0), -1)
        output = self.out(x)
        return output


# 指定数据集路径为data,如果数据集不存在则进行下载
# 通过train=False获取测试集
data_path = "data/"
kmnist = datasets.KMNIST(data_path, train=True, download=True,
                         transform=transforms.Compose([
                             transforms.ToTensor()]))

kmnist_val = datasets.KMNIST(data_path, train=False, download=True,
                             transform=transforms.ToTensor())
# t.stack:扩维堆叠
imgs = torch.stack([img_t for img_t, _ in kmnist], dim=3)

print('Running3', __name__)
print(f"imgs.shape: {imgs.shape}")
# torch.Size([1, 28, 28, 60000])

# compute mean and std per channel
mean = imgs.view(1, -1).mean(dim=1)
std = imgs.view(1, -1).std(dim=1)
print(f"mean:{mean},std:{std}")
# mean:tensor([0.1918]),std:tensor([0.3483])

# 我们现在可以重新导入数据,使用Normalize转换以及将数组转换为张量。
# 请注意,Normalize将像素值的平均值和标准偏差作为参数。
# normalised data
t_kmnist = datasets.KMNIST(data_path, train=True, download=False,
                           transform=transforms.Compose([
                               transforms.ToTensor(),
                               # transforms.Normalize(0.1307,0.3081),
                               transforms.Normalize(mean, std)
                           ]))

t_kmnist_val = datasets.KMNIST(data_path, train=False, download=False,
                               transform=transforms.Compose([
                                   transforms.ToTensor()]))

# # 遍历 查看样本
# # figure = plt.figure(3,figsize=(10, 8))
# figure = plt.figure(6, None, None, '#FFD700', '#FF0000')
# plt.title("figure32")
# cols, rows = 5, 5
#
# for i in range(1, cols * rows + 1):
#     # randint返回tensor,size=(1,)一维一列,item返回数值
#     # sample_idx = torch.randint(low = 0,len(t_kmnist), size=(1,)).item()
#     sample_idx = torch.randint(len(t_kmnist), size=(1,)).item()
#     img, label = t_kmnist[sample_idx]
#     figure.add_subplot(rows, cols, i)
#
#     plt.title(label)
#
#     plt.axis("off")
#
#     plt.imshow(img.squeeze(), cmap="gray")
#
#  plt.show()

print('Running1', __name__)
# 数据加载器
train_loader = torch.utils.data.DataLoader(kmnist, batch_size=128,
                                           shuffle=True,
                                           num_workers=0)

val_loader = torch.utils.data.DataLoader(kmnist_val, batch_size=128,
                                         shuffle=False,
                                         num_workers=0)


# 从dataloader中给出一个示例来调试模型
# imgText, _ = next(iter(train_loader))
# imgT = imgText[0]
# model = Net()
# kll = model(imgT.unsqueeze(0)).shape
# print('Running2', __name__)
# print ("__name__:",__name__)
# print(f"img[0].shape: {kll}")
# # torch.Size([1, 28, 28, 60000])

def training_loop(n_epochs, model, loss_fn, optimiser, train_loader):
    model.train()

    total_step = len(train_loader)
    print('start Running1', __name__)
    for n in range(1, n_epochs + 1):

        for i, (imgs, labels) in enumerate(train_loader):

            output = model(Variable(imgs))  # forward pass

            loss = loss_fn(output, Variable(labels))  # compute loss

            optimiser.zero_grad()  # reset gradients

            loss.backward()  # backpropagation

            optimiser.step()  # update the weights

            if (i + 1) % (n_epochs / 10) == 0:
                print('{} Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
                      .format(datetime.datetime.now(), n, n_epochs,
                              i + 1, total_step, loss.item()))


def validate(model, train_loader, val_loader):
    model.eval()  # set to eval mode to avoid batchnorm

    with torch.no_grad():  # avoid calculating gradients
        correct, total = 0, 0
        for images, labels in val_loader:
            test_output = model(images)
            pred_y = torch.max(test_output, 1)[1].data.squeeze()
            accuracy = (pred_y == labels).sum().item() / float(labels.size(0))

    print('VALIDATION SET ACCURACY: %.2f' % accuracy)

if __name__ == '__main__':
    # 实例化模型
    model = Net()
    # 设置Adam优化器
    optimiser = torch.optim.Adam(model.parameters(), lr=1e-2)
    # 设置交叉熵损失(因为这是一个多类分类问题)
    loss_fn = nn.CrossEntropyLoss()
    # 训练循环
    training_loop(
        n_epochs=10,
        model=model,
        loss_fn=loss_fn,
        optimiser=optimiser,
        train_loader=train_loader
    )
    # 验证模型
    # validate(model, train_loader, val_loader)

猜你喜欢

转载自blog.csdn.net/qimo601/article/details/125282280