PyTorch新手教程--构建神经网络

261 阅读12分钟

PyTorch提供了许多方法来创建不同类型的神经网络。在这篇文章中,我们创建了两种类型的神经网络用于图像分类。第一个是只使用简单的前馈神经网络建立的,第二个是卷积神经网络

在这篇文章中,我们涵盖:

1.深度学习的直觉

2.数据集和先决条件

3.用PyTorch构建前馈神经网络

4.使用PyTorch构建卷积神经网络

1.深度学习的直觉

深度学习和神经网络是这十年来的大热门词汇。神经网络是基于生物神经系统的元素,它们试图模仿其行为。它们由小型处理单元--神经元和它们之间的加权连接组成。

连接的权重模拟了神经元之间传输的神经递质的数量。在数学上,我们可以将神经网络定义为一个排序的三要素 (N,C,w),其中N是神经元的集合,C{(i,j)|i,j∈N}的集合,其元素是神经元ij之间的连接 w(i,j)是神经元 ij之间连接的权重。

Recommendation Systems

通常情况下,一个神经元从许多其他神经元接收输出作为其输入。传播函数将它们转化为对该神经元的所谓输入网络的连接权重的考虑。通常,这个传播函数只是加权输入的总和--加权总和。在数学上,它是这样定义的:net = Σ (i*w),其中net是输入,i是每个单独输入的值,w是输入值通过的连接的权重。

之后,该输入被处理为激活函数。这个函数定义了将在神经元的输出上显示的内容。神经元被构造成层,其中一个层的每个神经元都与邻近层的所有神经元相连。了解更多关于神经网络如何运作和学习的信息 这里.

2.数据集和先决条件

在这篇文章中,我们实现了用于图像分类的神经网络。 时尚MNIST数据集.因此,让我们来了解一下这个数据集的情况。另外,让我们学习一下如何安装PyTorch

2.1 时尚MNIST数据集

该数据集是标准MNIST数据集的一个 "替代版本",经常被用作 "Hello world"的例子。事实上,时尚MNIST数据集与MNIST数据集的结构相同,即它有一个由60,000个样本组成的训练集和一个由10,000张衣服图像组成的测试集。

衣服有10个类别。神经网络的目标是学习这些类别,并能够成功地对新图像进行分类。数据集中的所有图像都已经过尺寸标准化和居中处理。图像的大小被固定为28×28,因此预处理的图像数据被最小化。下面是这个样子的。

Pytorch Installation

2.2 安装PyTorch

安装PyTorch 是很容易的。你所需要做的就是输入你的环境细节并运行该命令。下面是我为我的机器所做的设置。

Pytorch Installation

为了验证你是否正确安装了它,运行Jupyter Notebook 并尝试导入torch

import torch

torch.__version__
'1.9.0'

3.用PyTorch构建前馈神经网络

最后,让我们开始用PyTorch 实现神经网络。首先,我们需要导入所有必要的模块。

import torch
import torchvision
import numpy as np
import matplotlib.pyplot as plt
import torch.nn as nn
import torch.nn.functional as F
from torchvision.datasets import FashionMNIST
from torchvision.transforms import ToTensor
from torchvision.utils import make_grid
from torch.utils.data.dataloader import DataLoader
from torch.utils.data import random_split
%matplotlib inline

你可以看到,我们几乎只使用PyTorch模块(除了NumPyMatplotlib)。使用nn模块,我们能够创建不同的神经网络层,使用nn.functional我们可以实现不同的激活函数。除了PyTorch库之外,我们还使用了torchvision库的一些模块。也就是说,我们使用了Fashion MNIST模块,它包含了FashionMNIST数据。

3.1 用PyTorch加载数据

那么,让我们来加载这些数据。

data = FashionMNIST(root='data/', download=True, transform=ToTensor())

validation_size = 10000
train_size = len(data) - validation_size

train_data, val_data = random_split(data, [train_size, validation_size])

batch_size=128

train_loader = DataLoader(train_data, batch_size, shuffle=True, num_workers=4, pin_memory=True)
val_loader = DataLoader(val_data, batch_size*2, num_workers=4, pin_memory=True)

首先,我们创建一个Fashion MNIST类的对象,它基本上包含所有必要的数据。我们这些数据分成训练集和验证集。训练数据是在监督学习的训练过程中使用的,这是一种神经网络用来从数据中学习的方法。

验证数据也在训练过程中使用,以评估神经网络的表现如何。通常情况下,我们也会创建一个测试集,用于最终评估神经网络在迄今为止未见过的数据上的表现,但对于这个简单的教程,这就足够了。然后,我们使用DataLoader类来清洗数据,并将其分成若干批,在每个训练步骤中送入神经网络。

Decision Tree

3.2 使用PyTorch的前馈神经网络

好了,进入有趣的部分,让我们用PyTorch建立一个神经网络。这里是完整的FFN类。

class FFNN(nn.Module):
    """Simple Feed Forward Neural Network with n hidden layers"""
    def __init__(self, input_size, num_hidden_layers, hidden_size, out_size, accuracy_function):
        super().__init__()
        self.accuracy_function = accuracy_function
        
        # Create first hidden layer
        self.input_layer = nn.Linear(input_size, hidden_size)
        
        # Create remaining hidden layers
        self.hidden_layers = nn.ModuleList()
        for i in range(0, num_hidden_layers):
            self.hidden_layers.append(nn.Linear(hidden_size, hidden_size))
        
        # Create output layer
        self.output_layer = nn.Linear(hidden_size, out_size)
        
    def forward(self, input_image):
        # Flatten image
        input_image = input_image.view(input_image.size(0), -1)
        
        # Utilize hidden layers and apply activation function
        output = self.input_layer(input_image)
        output = F.relu(output)
        
        for layer in self.hidden_layers:
            output = layer(output)
            output = F.relu(output)
        
        # Get predictions
        output = self.output_layer(output)
        return output
    
    def training_step(self, batch):
        # Load batch
        images, labels = batch
        
        # Generate predictions
        output = self(images) 
        
        # Calculate loss
        loss = F.cross_entropy(output, labels)
        return loss
    
    def validation_step(self, batch):
        # Load batch
        images, labels = batch 

        # Generate predictions
        output = self(images) 
        
        # Calculate loss
        loss = F.cross_entropy(output, labels)

        # Calculate accuracy
        acc = self.accuracy_function(output, labels)
        
        return {'val_loss': loss, 'val_acc': acc}
        
    def validation_epoch_end(self, outputs):
        batch_losses = [x['val_loss'] for x in outputs]
        
        # Combine losses and return mean value
        epoch_loss = torch.stack(batch_losses).mean()
        
        # Combine accuracies and return mean value
        batch_accs = [x['val_acc'] for x in outputs]
        epoch_acc = torch.stack(batch_accs).mean()
        return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}
    
    def epoch_end(self, epoch, result):
        print("Epoch: {} - Validation Loss: {:.4f}, Validation Accuracy: {:.4f}".format( \ 
						epoch, result['val_loss'], result['val_acc']))

基本上,当你想用PyTorch建立一些模型时,你可以继承 nn.Module类。这样你就可以只通过重写几个方法来创建不同类型的神经网络。这是PyTorch在研究界如此受欢迎的主要原因之一,因为它为你提供了具有足够灵活性的 "预制"解决方案。

我们利用这一点来创建一个模型,通过构造器接收几个参数。它接收输入大小(即输入层的神经元数量)、隐藏层数量及其大小、输出大小(即输出层的神经元数量/类别数量),以及将在每一层使用的激活函数。

为了让PyTorch知道模型有某些层,你需要为每个层创建一个类属性。这就是为什么我们要创建self.input_layerself.output_layer属性。

注意,对于隐藏层,我们使用不同的方法。我们用nn.ModuleList()创建一个层数组,因为这是可以通过num_hidden_layers参数配置的。对于每个层,我们使用nn.Linear,它创建了一个具有定义的神经元数量的简单层。这种类型的层执行简单的y = wx + b函数。

除此以外,我们还需要覆盖一个重要的nn.Module方法--forward。这个函数定义了输入将如何在我们的神经网络中被处理。这个函数基本上连接了我们在构造函数中定义的所有层。让我们更详细地研究它。

def forward(self, input_image):
	# Flatten image
	input_image = input_image.view(input_image.size(0), -1)
	
	# Utilize hidden layers and apply activation function
	output = self.input_layer(input_image)
	output = F.relu(output)
	
	for layer in self.hidden_layers:
		output = layer(output)
		output = F.relu(output)
	
	# Get predictions
	output = self.output_layer(output)
	return output

3.3 用PyTorch训练前馈神经网络

首先,我们对图像进行扁平化处理,即把它重塑为一个数组。我们这样做是因为我们的神经网络的输入层不能接收2D输入。然后将这些信息通过每个线性层并应用整流器或ReLu激活函数。

这个激活函数最常被用于隐藏层,因为它能提供最好的结果。它的定义公式为:Relu(x) = max(0,x)。请注意,我们不在输出层后使用ReLu。这是因为在输出层我们希望得到每个类别的概率

除了正向函数之外,为了更好地控制网络的训练,我们还实现了其他各种方法。方法training_stepvalidation_step定义了在每个训练和验证过程中要做的事情。

def training_step(self, batch):
	# Load batch
	images, labels = batch
	
	# Generate predictions
	output = self(images) 
	
	# Calculate loss
	loss = F.cross_entropy(output, labels)
	return loss

def validation_step(self, batch):
	# Load batch
	images, labels = batch 

	# Generate predictions
	output = self(images) 
	
	# Calculate loss
	loss = F.cross_entropy(output, labels)

	# Calculate accuracy
	acc = self.accuracy_function(output, labels)
	
	return {'val_loss': loss, 'val_acc': acc}

training_step函数接收由DataLoader提供的一批图像,并将它们推送到网络中以获得预测结果。在此之下,PyTorch使用正向函数来实现。完成后,我们通过计算损失来检测神经网络的表现如何。

不同的函数可以用来衡量预测数据和真实数据之间的差异。在这个例子中,我们使用交叉熵。方法validation_step看起来很相似,但是这个方法也使用我们通过构造函数传递的accuracy_function来计算我们预测的准确性,并存储损失,并返回带有该信息的字典。

一旦验证历时结束,我们就将所有这些信息合并成一个数组,这样我们就可以看到训练过程的历史。另外,在每个历时结束时,我们会打印出返回的validation_step信息。最后两个功能是在validation_epoch_endepoch_end方法中实现的。

def validation_epoch_end(self, outputs):
	batch_losses = [x['val_loss'] for x in outputs]
	
	# Combine losses and return mean value
	epoch_loss = torch.stack(batch_losses).mean()
	
	# Combine accuracies and return mean value
	batch_accs = [x['val_acc'] for x in outputs]
	epoch_acc = torch.stack(batch_accs).mean()
	return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}

def epoch_end(self, epoch, result):
	print("Epoch: {} - Validation Loss: {:.4f}, Validation Accuracy: {:.4f}".format( \ 
						epoch, result['val_loss'], result['val_acc']))

为了使神经网络的训练过程自动化,我们再实现一个ModelTrainer类。

class ModelTrainer():   
  def fit(self, epochs, learning_rate, model, train_loader, val_loader, opt_func=torch.optim.SGD):
        history = []
        optimizer = opt_func(model.parameters(), learning_rate)

        for epoch in range(epochs):
            # Training 
            for batch in train_loader:
                loss = model.training_step(batch)
                loss.backward()
                optimizer.step()
                optimizer.zero_grad()

            # Validation
            result = self._evaluate(model, val_loader)
            model.epoch_end(epoch, result)
            history.append(result)
            
        return history

    def _evaluate(self, model, val_loader):
        outputs = [model.validation_step(batch) for batch in val_loader]
        return model.validation_epoch_end(outputs)

这个类有两个方法fit_evaluate。方法fit是用来训练的。它接收一个模型、epochs的数量(整个数据集将通过网络的次数)、学习率和数据加载器。对于每个epoch,我们从加载器中获得baches,并通过调用training_step方法在网络中运行它。然后,我们得到损失,并使用反向方法来计算 梯度.最后,我们使用优化器来更新网络的权重

好了,这些是描述一般神经网络和一般训练过程的类。我们需要更具体一些,利用这个类来解决我们的问题。要做到这一点,我们首先需要实现精度函数。

def accuracy(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    return torch.tensor(torch.sum(preds == labels).item() / len(preds))

另外,我们还定义了用于规划历史的辅助函数。

def plot_history(history):
    losses = [x['val_loss'] for x in history]
    plt.plot(losses, '-x')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')

    accuracies = [x['val_acc'] for x in history]
    plt.plot(accuracies, '-x')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.title('Loss and Accuracy');

3.4 一起运行

最后,我们可以把所有这些碎片放在一起,创建FFN的对象。我们创建一个有3个隐藏层的神经网络,每个隐藏层有32个神经元。注意,输入大小为28×28=784,输出大小为10,因为我们有10衣服。

input_size = 784
num_classes = 10

model = FFNN(input_size, num_hidden_layers, 32, out_size=num_classes, accuracy_function=accuracy)
print(model)
FFNN(
  (input_layer): Linear(in_features=784, out_features=32, bias=True)
  (hidden_layers): ModuleList(
    (0): Linear(in_features=32, out_features=32, bias=True)
    (1): Linear(in_features=32, out_features=32, bias=True)
    (2): Linear(in_features=32, out_features=32, bias=True)
  )
  (output_layer): Linear(in_features=32, out_features=10, bias=True)
)

让我们来训练它,并绘制历史数据和准确性。

model_trainer = ModelTrainer()

training_history = []
training_history += model_trainer.fit(5, 0.2, model, train_loader, val_loader)

plot_history(training_history)
Epoch: 0 - Validation Loss: 0.7095, Validation Accuracy: 0.7266
Epoch: 1 - Validation Loss: 0.5858, Validation Accuracy: 0.7959
Epoch: 2 - Validation Loss: 0.5417, Validation Accuracy: 0.7789
Epoch: 3 - Validation Loss: 0.4696, Validation Accuracy: 0.8247
Epoch: 4 - Validation Loss: 0.4303, Validation Accuracy: 0.8371

请注意,损失越来越低,准确率越来越高。最后,经过5个 epochs,我们的准确率达到了83%。

4.用PyTorch构建卷积神经网络

如果你想对图像进行处理和分类,最好的方法之一就是使用 卷积神经网络.这种类型的网络在某种程度上对过去几年的深度学习炒作负责。说到底,他们使用的是前馈神经网络,但他们在图像处理方面有几个小技巧。

在其核心,我们可以找到卷积过程。这个过程是用来检测图像的特征,并使用这些信息进行分类。以下是卷积神经网络的完整架构

首先,卷积层使用过滤器检测图像的特征(线条、曲线等)。它们创建所谓的特征图,其中包含关于图像中某些特征的位置的信息。这些地图被池化层进一步压缩,然后被压扁为一维阵列。最后,一个前馈网络被用于分类,在这种情况下被称为全连接

PyTorch nn模块提供了一些其他的层尝试,除了我们已经使用的Linear之外。下面是我们如何实现上述过程的。

class CNN(nn.Module):
    """Simple Convolutional Neural Network"""
    def __init__(self, accuracy_function):
        super().__init__()
        self.accuracy_function = accuracy_function

        # Create Convolutional Layers
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        
        # Create Fully Connected Layers
        self.fc1 = nn.Linear(1600, 128) 
        self.fc2 = nn.Linear(128, 10)
        
    def forward(self, input_image):
        # Convolution, ReLu and MaxPooling
        output = self.conv1(input_image)
        output = F.relu(output)
        output = F.max_pool2d(output, (2, 2))

        output = self.conv2(output)
        output = F.relu(output)
        output = F.max_pool2d(output, 2)

        # Flatten
        output = output.view(-1, self.num_flat_features(output))
        
        # Fully Connected
        output = self.fc1(output)
        output = F.relu(output)
        output = self.fc2(output)

        return output
    
    def training_step(self, batch):
        # Load batch
        images, labels = batch
        
        # Generate predictions
        output = self(images) 
        
        # Calculate loss
        loss = F.cross_entropy(output, labels)
        return loss
    
    def validation_step(self, batch):
        # Load batch
        images, labels = batch 

        # Generate predictions
        output = self(images) 
        
        # Calculate loss
        loss = F.cross_entropy(output, labels)

        # Calculate accuracy
        acc = self.accuracy_function(output, labels)
        
        return {'val_loss': loss, 'val_acc': acc}
        
    def validation_epoch_end(self, outputs):
        batch_losses = [x['val_loss'] for x in outputs]
        
        # Combine losses and return mean value
        epoch_loss = torch.stack(batch_losses).mean()
        
        # Combine accuracies and return mean value
        batch_accs = [x['val_acc'] for x in outputs]
        epoch_acc = torch.stack(batch_accs).mean()
        return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}
    
    def epoch_end(self, epoch, result):
        print("Epoch: {} - Validation Loss: {:.4f}, Validation Accuracy: {:.4f}".format( \ 
						epoch, result['val_loss'], result['val_acc']))
        
    def num_flat_features(self, image):
        size = image.size()[1:]
        num_features = 1
        for s in size:
            num_features *= s
            
        return num_features

FFN的区别在于构造函数和前进 方法。我们预先知道我们要使用哪些层,我们使用Conv2d类添加两个卷积层,像以前一样使用Linear类添加两个全连接层。

前向函数中,我们使用max_pool2d函数来进行最大池化。其他方法与FFN的实现相同。我们可以利用我们之前已经实现的ModelTrainer来训练这个网络。

input_size = 784
num_classes = 10

model = CNN(accuracy_function=accuracy)
print(model)
CNN(
  (conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=1600, out_features=128, bias=True)
  (fc2): Linear(in_features=128, out_features=10, bias=True)
)
model_trainer = ModelTrainer()

training_history = []
training_history += model_trainer.fit(5, 0.2, model, train_loader, val_loader)

plot_history(training_history)
Epoch: 0 - Validation Loss: 0.5739, Validation Accuracy: 0.7846
Epoch: 1 - Validation Loss: 0.3864, Validation Accuracy: 0.8598
Epoch: 2 - Validation Loss: 0.4431, Validation Accuracy: 0.8344
Epoch: 3 - Validation Loss: 0.3124, Validation Accuracy: 0.8859
Epoch: 4 - Validation Loss: 0.3125, Validation Accuracy: 0.8873

我们得到的结果比前馈神经网络要好一些。准确率为88%。我们可以通过添加更多的卷积层,延长网络训练时间,以及修改学习时间来进一步改善这些结果。试一试吧。

总结

在这篇文章中,我们介绍了两个使用PyTorch进行图像分类的神经网络的实现。我们探索了一些基本的神经网络概念,了解了卷积神经网络。

谢谢您的阅读!