异常检测第三篇:MNIST数据集分类(pytorch实现)

大家好!我是菜菜卷。

之前两篇我们用传统的机器学习方法实现了无监督和半监督的异常检测,今天我们开始进行异常检测的深度学习实战(监督学习)。

在有标签的情况下,其实异常检测就变成了一个简单的分类任务,所有数据都可以被划分为正常类和异常类中的一种,因此我们今天使用MNIST数据集和pytorch框架来实现一个简单的图像分类算法。

所用数据集

今天我们使用的数据集是非常经典的图像分类入门数据集MNISTMNIST是一个手写数字数据集,每张图像都是32*32分辨率的灰度图组成的,训练集有5w张图像,测试集有1w张图像。一共有0-9共十个类别。
其数据集都已经集成在pytorch官方文档中了,所以不需要我们单独去下载,直接调用pytorch中的API就可以完成数据的下载和加载(下面会详细介绍)。

项目实战

1、所用包的导入

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torch.optim as optim
import torch.nn.functional as F

import torchvision
from torchvision import transforms
from torchvision import datasets

这里就不做过多的介绍了,就是torchtorchvision已经可能用到的一些包的导入,关于包的详细作用,后面用到的时候再详细介绍。

2、指定一些超参数

我们使用以下代码来指定使用哪个GPU来训练和模型的一些超参数:

    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

    num_epochs = 15
    num_classes = 10
    batch_size = 128
    lr = 0.001

因为深度学习需要大量计算,所以推荐使用GPU来进行训练,所以在最开始的时候我们要指定具体使用什么来训练(使用cpu还是gpu,使用gpu的话,使用哪块GPU),device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')的意思是,如果GPU可用的话,就是用0号GPU,否则使用CPU;然后num_epochs表示我们一共需要训练多少个epoch,所有的数据图像都遍历过一次称为一个epochnum_classes表示我们一共要识别的类别数目,因为我们要识别的是0-9的数字,所以一共是10类;batch_size表示在每次训练的时候,我们一次性输送多少数目的图像进入模型;lrlearning rate,表示在反向传播更新模型参数时,参数更新的步长。

3、dataset和dataloader的构建

首先我们使用如下代码构建dataset的训练集和测试集:

    # load MINST data set
    train_dataset = datasets.MNIST(root='data/',
                                   train=True,
                                   transform=transforms.ToTensor(),
                                   download=True,
                                  )
    test_dataset = datasets.MNIST(root='data/',
                                   train=False,
                                   transform=transforms.ToTensor(),
                                   )

其中root中的路径表示我们将MNIST数据集下载到哪个路径下,本实例中我们将其放在与主文件同目录的data文件夹下。
使用pytorch的时候,仅仅使用dataset来读入数据是不够的,我们还需要一个“外壳”对dataset进行包装,使其数据和标签可以更方便以batch size的大小逐批次取出进行模型训练,我们使用的这个外壳是torch.utils.data.DataLoader,其具体用法如下所示:

    # create dataloader
    train_loader = DataLoader(dataset=train_dataset,
                              batch_size=batch_size,
                              shuffle=True,
                              )
    test_loader = DataLoader(dataset=test_dataset,
                              batch_size=batch_size,
                              shuffle=False,
                              )

可以看到,我们的参数除了放入数据和指定batch的大小以外,还有一个参数shuffle,他执行的功能就是是否随机打乱原数据集中数据的顺序,一般来说,对于训练集来说,我们设置shuffle=True,对于测试集来说,我们设置shuffle=False

4、model的构建

我们使用一个由两层CNN和两层全连接层(FC层)构成的网络结构来实现对MNIST的分类,其具体结构如下所示:

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)

        self.fc1 = nn.Linear(14*14*64, 128)
        self.fc2 = nn.Linear(128, num_classes)

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2, 2)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.dropout(x, 0.25)
        x = x.view(-1, 14*14*64)

        x = self.fc1(x)
        x = F.relu(x)
        x = F.dropout(x, 0.5)

        x = self.fc2(x)
        out = F.log_softmax(x, dim=1)
        return out

5、开始model训练

在训练模型的时候,除了dataloader和已经创建好的model还是不够的,我们还需要一些辅助优化的东西。比如我们需要使用优化器(例如SGDadam)来决定如何更新model的参数;还需要使用loss来衡量我们的模型预测和真实的label到底有多么的不相似,从而决定模型参数的更新方向(根据梯度更新),具体实现如下所示:

    # create model
    model = CNN()
    model = model.to(device)
    loss_function = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=lr)
    total_iter = len(train_loader)

    # training
    print('---------------------------start training----------------------')
    for epoch in range(num_epochs):
        for i, (images, labels) in enumerate(train_loader):
            images = images.to(device)
            labels = labels.to(device)

            outputs = model(images)
            loss = loss_function(outputs, labels)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            if i % 100 == 99:
                print('epoch:[{}/{}], iters:[{}/{}], loss:{}'.format(epoch + 1,num_epochs, i + 1, total_iter, loss))

因为我们的训练是在GPU上进行的,所以我们要用.to(device)modelimagelabel放到对应的GPU上;loss_function = nn.CrossEntropyLoss()表示我们该图像分类任务使用的是torch内置的交叉熵lossoptimizer = optim.Adam(model.parameters(), lr=lr),表明我们使用的是Adam优化器来优化模型的参数,第一个参数表示想优化的是model的哪些参数(本实例中我们对model的所有参数进行优化),第二个参数是学习率;具体训练过程中的代码比较简洁易懂,此处不做过多介绍。

6、模型的效果测试和保存model

在第五步中我们已经完成了对模型的训练,下面我们将对其进行简单的测试:

    # testing
    preds = []
    y_true = []
    model.eval()
    with torch.no_grad():
        correct = 0
        total = 0
        for images, labels in test_loader:
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            detached_pred = predicted.detach().cpu().numpy()
            detached_label = labels.detach().cpu().numpy()

            for idx in range(len(detached_pred)):
                preds.append(detached_pred[idx])
                y_true.append(detached_label[idx])

        print('the accuracy of the model on test image is {:.2%}'.format(correct/total))

    torch.save(model.state_dict(), 'pytorch_minist_cnn.ckpt')

model.eval()表示将模型切换到推理状态,因为有些层在训练状态和推理状态的计算方式是不一样的,比如dropout层和BatchNormalizition层,所以在推理的时候一定要将模型切换到推理状态;with torch.no_grad():表示不进行梯度的计算(因为前向推理中不需要反向传播更新模型参数,所以也不用计算梯度了);detached_pred = predicted.detach().cpu().numpy()是将预测好的结果从GPU上取下并转换为numpy类型的array,因为GPU上的tensor是无法直接转换为numpy类型的,所以需要.cpu()先从gpu上的tensor转换为cpu上的tensor,再转换为numpy类型。
我们还简单的计算了一下模型的accuracy,其结果如下所示:

the accuracy of the model on test image is 98.63%

由此可知,我们的模型可以在MNIST上达到98.63%的精度,最后的torch.save(model.state_dict(), 'pytorch_minist_cnn.ckpt')表示我们把训练好的模型参数保存下来,路径为当前文件夹,并命名为pytorch_minist_cnn.ckpt

我们的整个实战任务到这里就基本上结束了,完整代码如下所示:

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torch.optim as optim
import torch.nn.functional as F

import torchvision
from torchvision import transforms
from torchvision import datasets

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)

        self.fc1 = nn.Linear(14*14*64, 128)
        self.fc2 = nn.Linear(128, num_classes)

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2, 2)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.dropout(x, 0.25)
        x = x.view(-1, 14*14*64)

        x = self.fc1(x)
        x = F.relu(x)
        x = F.dropout(x, 0.5)

        x = self.fc2(x)
        out = F.log_softmax(x, dim=1)
        return out

if __name__ == '__main__':

    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

    num_epochs = 15
    num_classes = 10
    batch_size = 128
    lr = 0.001

    # load MINST data set
    train_dataset = datasets.MNIST(root='data/',
                                   train=True,
                                   transform=transforms.ToTensor(),
                                   download=True,
                                  )
    test_dataset = datasets.MNIST(root='data/',
                                   train=False,
                                   transform=transforms.ToTensor(),
                                   )

    # create dataloader
    train_loader = DataLoader(dataset=train_dataset,
                              batch_size=batch_size,
                              shuffle=True,
                              )
    test_loader = DataLoader(dataset=test_dataset,
                              batch_size=batch_size,
                              shuffle=False,
                              )
    # create model
    model = CNN()
    model = model.to(device)
    loss_function = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=lr)
    total_iter = len(train_loader)

    # training
    print('---------------------------start training----------------------')
    for epoch in range(num_epochs):
        for i, (images, labels) in enumerate(train_loader):
            images = images.to(device)
            labels = labels.to(device)

            outputs = model(images)
            loss = loss_function(outputs, labels)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            if i % 100 == 99:
                print('epoch:[{}/{}], iters:[{}/{}], loss:{}'.format(epoch + 1,num_epochs, i + 1, total_iter, loss))

    # testing
    preds = []
    y_true = []
    model.eval()
    with torch.no_grad():
        correct = 0
        total = 0
        for images, labels in test_loader:
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            detached_pred = predicted.detach().cpu().numpy()
            detached_label = labels.detach().cpu().numpy()

            for idx in range(len(detached_pred)):
                preds.append(detached_pred[idx])
                y_true.append(detached_label[idx])

        print('the accuracy of the model on test image is {:.2%}'.format(correct/total))

    torch.save(model.state_dict(), 'pytorch_minist_cnn.ckpt')

我是菜菜卷,我们下篇再见!!

你可能感兴趣的:(pytorch,分类,深度学习,MNIST)