基于BP神经网络的图像分类

1 数据集

本次BP神经网络分类实验所用数据集为MNIST手写数据集。

MNIST 数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST)。训练集 (training set) 由来自 250 个不同人的手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员。测试集(test set) 也是同样比例的手写数字数据。

训练数据集包含60000个样本, 测试数据集包含10000样本。在 MNIST 数据集中,每张图片由28×28个像素点构成, 每个像素点用一个灰度值表示。使用时,将28×28的像素展开成一个维度为784的一维的向量, 向量中的值对应图片中的像素值。此外,还有一个标签数组,对应每张图片所属的手写数字类别(整数0-9)。

本次实验基于Pytorch框架,使用Pytorch自带工具包可完成MNIST数据集的下载及预处理。下图所示代码分别完成了训练数据和测试数据的下载,预处理及加载。

#1.Load the train datasets and test datasets    
train_transformations = transforms.Compose([        
    transforms.ToTensor(),        
    transforms.Normalize((0.5,), (0.5,))    
])    
train_set = MNIST(root="./data", train=True, transform=train_transformations, download=True)    
train_loader = DataLoader(train_set,batch_size=128,shuffle=True,num_workers=4)    
test_transformations = transforms.Compose([        
    transforms.ToTensor(),        
    transforms.Normalize((0.5,), (0.5,))    
])    
test_set = MNIST(root="./data", train=False, transform=test_transformations, download=True)    
test_loader = DataLoader(test_set,batch_size=128,shuffle=False,num_workers=0)

观察MNIST单个样本数据时,发现其中的像素值范围在(0,1)之间,说明已经被整除256,后续无需再重复这一操作。先将28×28的二维数据转换成张量Tensor的形式,再进行标准化处理,将数据映射到(-1,1)之间,最后将数据进行批处理,每批包含128(2的倍数,方便GPU处理)个样本,完成数据的加载。加载时设置打乱训练样本的顺序,不打乱测试样本的顺序。

2 分类网络设计

本次分类BP神经网络共有5层,包括1个输入层,3个隐藏层和一个输出层。层与层之间均采用全连接的形式。输入层有784个节点,对应MNIST单个样本的像素值总数;隐藏层的节点数分别为1024,1024,2048;输出层共有10个节点,用于分类,输出结果为(0-9)的预测概率。详细网络结构图如下所示。
基于BP神经网络的图像分类_第1张图片

3 训练过程

训练过程主要分为以下四个步骤,分别为:

  1. 加载训练数据集和测试数据集;

  2. 初始化模型;

  3. 定义优化器和损失函数;

  4. 训练模型及保存权重参数;

下面对每个步骤详细展开介绍。

3.1 加载训练数据集和测试数据集

数据集的加载包括训练数据集和测试数据集的加载,训练数据集用于训练模型,测试数据集用于训练过程中对模型的性能进行实时监测。本次所用数据集为MNIST书写数字数据集,包括60000个训练样本和10000个测试样本,具体加载方式在第一章已详细介绍。

3.2 初始化模型

本次分类BP神经网络共有5层,包括1个输入层,3个隐藏层和一个输出层。层与层之间均采用全连接的形式。代码实现时,只需要实现3个隐藏层和1个输出层的设计即可。

每个隐藏层都由一个线性全连接层和一个激活函数构成,为了方便起见,自定义一个隐藏层模块,主要是定义线性全连接层的输入和输出节点数,激活函数的选取,实现过程如下所示。

# Build the neural network
class Unit(nn.Module):    
    def __init__(self, in_features, out_features):        
        super(Unit, self).__init__()
        
        self.fc=nn.Linear(in_features, out_features, True)        
        self.relu = nn.ReLU()
        
    def forward(self, input):        
        output = self.fc(input)        
        output = self.relu(output)        
        return output

接着,调用上面定义好的隐藏层模块实现整个网络的设计。将3个隐藏层根据其输入和输出节点数一一定义出来,再将这3个隐藏层组合成一个小网络。然后根据分类的类别数定义出输出层,也就是最后一个全连接层。最后在前向传播函数中,将这些层的输入输出拼接起来,实现整个网络的结构设计。代码实现如下所示。

class SimpleNet(nn.Module):    
    def __init__(self, num_classes=10):        
        super(SimpleNet, self).__init__()
        
        self.unit1 = Unit(in_features=784,out_features=1024)        
        self.unit2 = Unit(in_features=1024, out_features=1024)        
        self.unit3 = Unit(in_features=1024, out_features=2048)        
        self.net = nn.Sequential(self.unit1, self.unit2, self.unit3)        
        self.fc = nn.Linear(in_features=2048,out_features=num_classes)
        
    def forward(self, input):        
        output = self.net(input)        
        output = self.fc(output)        
        return output

完成网络结构设计后,通过实例化这个类来初始化训练模型。为了加速训练过程,选择在GPU上进行模型的训练和测试,具体过程如下所示。

#2.Initialize model    
cuda_avail = torch.cuda.is_available()    
model = SimpleNet(num_classes=10)    
cuda_avail = torch.cuda.is_available()    
if cuda_avail:        
    model.cuda()

3.3 定义优化器和损失函数

本次实验选用Adam优化器,它结合AdaGrad和RMSProp两种优化算法的优点。对梯度的一阶矩估计(First Moment Estimation,即梯度的均值)和二阶矩估计(Second Moment Estimation,即梯度的未中心化的方差)进行综合考虑,计算出更新步长。设置初始学习率为0.001,权重衰减值为0.0001。

损失函数选用交叉熵损失函数,它是一种专门用于处理分类问题的损失函数。

#3. Define the optimizer and loss function     
optimizer = Adam(model.parameters(), lr=0.001, weight_decay=0.0001)    
loss_fn = nn.CrossEntropyLoss()

3.4 训练模型及保存权重参数

定义训练过程,遍历整个训练集100轮。每遍历一轮训练集,根据训练轮次,不断调整学习率的大小,并计算模型在测试集上的准确率,若本轮训练后的测试准确率高于之前轮次,则保存本轮训练得到的权重参数。具体实现如下所示。

#4.Training    
num_epochs = 100    
best_acc = 0.0    
for epoch in range(num_epochs):        
    train_acc = 0.0        
    train_loss = 0.0        
    train_acc,train_loss = train(model,train_acc,train_loss,train_loader,
                    optimizer,loss_fn,cuda_avail)        
    adjust_learning_rate(optimizer, epoch)        
    test_acc = test(model,test_loader,cuda_avail)        
    if test_acc > best_acc:            
        save_models(model,epoch)            
        best_acc = test_acc        
    print("Epoch {}, Train Accuracy: {} , TrainLoss: {} , Test Accuracy: {}".format(           
        epoch, train_acc, train_loss, test_acc))

训练过程如下所示。每一轮次训练结束均会打印训练精度,训练损失值和测试精度。

在这里插入图片描述

4 完整代码

import torch
import torch.nn as nn
import os
from torch.autograd import Variable
from torchvision.datasets import MNIST
from torchvision.transforms import transforms
from torch.utils.data import DataLoader
from torch.optim import Adamimport numpy
#Set GPU id
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
# Build the neural network
class Unit(nn.Module):    
    def __init__(self, in_features, out_features):        
        super(Unit, self).__init__()
        
        self.fc=nn.Linear(in_features, out_features, True)        
        self.relu = nn.ReLU()
    def forward(self, input):        
        output = self.fc(input)        
        output = self.relu(output)       
        return output
        
class SimpleNet(nn.Module):    
    def __init__(self, num_classes=10):        
        super(SimpleNet, self).__init__()
        
        self.unit1 = Unit(in_features=784,out_features=1024)        
        self.unit2 = Unit(in_features=1024, out_features=1024)        
        self.unit3 = Unit(in_features=1024, out_features=2048)        
        self.net = nn.Sequential(self.unit1, self.unit2, self.unit3)        
        self.fc = nn.Linear(in_features=2048,out_features=num_classes)
        
    def forward(self, input):        
        output = self.net(input)        
        output = self.fc(output)        
        return output

# Create a learning rate adjustment function that divides the learning rate by 10 every 30 epochs
def adjust_learning_rate(optimizer, epoch):    
    lr = 0.001     
    if epoch > 180:        
        lr = lr / 1000000    
    elif epoch > 150:        
        lr = lr / 100000    
    elif epoch > 120:        
        lr = lr / 10000    
    elif epoch > 90:        
        lr = lr / 1000    
    elif epoch > 60:        
        lr = lr / 100    
    elif epoch > 30:        
        lr = lr / 10     
    for param_group in optimizer.param_groups:        
        param_group["lr"] = lr

# Training process
def train(model,train_acc,train_loss,train_loader,optimizer,loss_fn,cuda_avail=True):    
    model.train()    
    for i, (images, labels) in enumerate(train_loader):        
        if cuda_avail:            
            images = Variable(images.cuda())            
            images = images.reshape(-1,784)            
            labels = Variable(labels.cuda())        
        optimizer.zero_grad()        
        outputs = model(images)        
        loss = loss_fn(outputs, labels)        
        loss.backward()        
        optimizer.step()        
        train_loss += loss.cpu().data.numpy() * images.size(0)        
        _, prediction = torch.max(outputs.data, 1)        
        train_acc += torch.sum(prediction == labels.data)
    train_acc = train_acc / 60000    
    train_loss = train_loss / 60000    
    return train_acc, train_loss

# Test the model while training
def test(model,test_loader,cuda_avail=True):    
    model.eval()    
    test_acc = 0.0    
    for i, (images, labels) in enumerate(test_loader):        
        if cuda_avail:            
            images = Variable(images.cuda())            
            images = images.reshape(-1,784)            
            labels = Variable(labels.cuda())
                     
        # Predict classes using images from the test set        
        outputs = model(images)        
        _, prediction = torch.max(outputs.data, 1)        
        test_acc += torch.sum(prediction == labels.data)
        
    # Compute the average acc and loss over all 10000 test images    
    test_acc = test_acc / 10000
    return test_acc

# Save the weights
def save_models(model,epoch):    
    torch.save(model.state_dict(), "MNIST_model_{}.model".format(epoch))      
    print("Chekcpoint saved")

def main():    
#1.Load the train datasets and test datasets    
    train_transformations = transforms.Compose([        
        transforms.ToTensor(),        
        transforms.Normalize((0.5,), (0.5,))    
    ])    
    train_set = MNIST(root="./data", train=True, transform=train_transformations, download=True)    
    train_loader = DataLoader(train_set,batch_size=128,shuffle=True,num_workers=4)    
    test_transformations = transforms.Compose([        
        transforms.ToTensor(),        
        transforms.Normalize((0.5,), (0.5,))    
    ])    
    test_set = MNIST(root="./data", train=False, transform=test_transformations, download=True)    
    test_loader = DataLoader(test_set,batch_size=128,shuffle=False,num_workers=0)
    #2.Initialize model    
    cuda_avail = torch.cuda.is_available()    
    model = SimpleNet(num_classes=10)    
    cuda_avail = torch.cuda.is_available()    
    if cuda_avail:        
       model.cuda()
    #3. Define the optimizer and loss function     
    optimizer = Adam(model.parameters(), lr=0.001, weight_decay=0.0001)    
    loss_fn = nn.CrossEntropyLoss()
    #4.Training    
    num_epochs = 100    
    best_acc = 0.0    
    for epoch in range(num_epochs):        
        train_acc = 0.0        
        train_loss = 0.0        
        train_acc,train_loss = train(model,train_acc,train_loss,train_loader,                
        optimizer,loss_fn,cuda_avail)        
        adjust_learning_rate(optimizer, epoch)        
        test_acc = test(model,test_loader,cuda_avail)        
        if test_acc > best_acc:            
            save_models(model,epoch)            
            best_acc = test_acc        
            print("Epoch {}, Train Accuracy: {} , TrainLoss: {} , Test Accuracy: {}".format(            
            epoch, train_acc, train_loss, test_acc))

if __name__ == "__main__":    
    main()

你可能感兴趣的:(深度学习)