大家好!我是菜菜卷。
之前两篇我们用传统的机器学习方法实现了无监督和半监督的异常检测,今天我们开始进行异常检测的深度学习实战(监督学习)。
在有标签的情况下,其实异常检测就变成了一个简单的分类任务,所有数据都可以被划分为正常类和异常类中的一种,因此我们今天使用MNIST
数据集和pytorch
框架来实现一个简单的图像分类算法。
今天我们使用的数据集是非常经典的图像分类入门数据集MNIST
,MNIST
是一个手写数字数据集,每张图像都是32*32
分辨率的灰度图组成的,训练集有5w
张图像,测试集有1w
张图像。一共有0-9
共十个类别。
其数据集都已经集成在pytorch
官方文档中了,所以不需要我们单独去下载,直接调用pytorch
中的API就可以完成数据的下载和加载(下面会详细介绍)。
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
这里就不做过多的介绍了,就是torch
和torchvision
已经可能用到的一些包的导入,关于包的详细作用,后面用到的时候再详细介绍。
我们使用以下代码来指定使用哪个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,所有的数据图像都遍历过一次称为一个epoch
;num_classes
表示我们一共要识别的类别数目,因为我们要识别的是0-9的数字,所以一共是10类;batch_size
表示在每次训练的时候,我们一次性输送多少数目的图像进入模型;lr
是learning rate
,表示在反向传播更新模型参数时,参数更新的步长。
首先我们使用如下代码构建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
。
我们使用一个由两层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
在训练模型的时候,除了dataloader
和已经创建好的model
还是不够的,我们还需要一些辅助优化的东西。比如我们需要使用优化器(例如SGD
、adam
)来决定如何更新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)
将model
,image
和label
放到对应的GPU
上;loss_function = nn.CrossEntropyLoss()
表示我们该图像分类任务使用的是torch
内置的交叉熵loss
;optimizer = optim.Adam(model.parameters(), lr=lr)
,表明我们使用的是Adam
优化器来优化模型的参数,第一个参数表示想优化的是model
的哪些参数(本实例中我们对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')
我是菜菜卷,我们下篇再见!!