作者 | Pandeynandancse
来源 | Medium
编辑 | 代码医生团队
本教程的数据摘自Kaggle,该数据最初由Intel在analytics-vidhya上发布,以举办图像分类挑战赛。
https://www.kaggle.com/puneet6060/intel-image-classification
关于数据集
该数据包含大约65,000幅大小为150x150的25,000张图像。
{ ‘buildings’ : 0,‘forest’ : 1,‘glacier’ : 2,‘mountain’ : 3,‘sea’ : 4,‘street’ : 5 }
训练,测试和预测数据在每个zip文件中分开。训练中大约有14k图像,测试中有3k,预测中有7k。
挑战
这是一个多类图像分类问题。目的是将这些图像更准确地分类为正确的类别。
先决条件
基本了解python,pytorch和分类问题。
方法
1.导入库
首先,导入所有重要的库。
import os
import torch
import tarfile
import torchvision
import torch.nn as nn
from PIL import Image
import matplotlib.pyplot as plt
import torch.nn.functional as F
from torchvision import transforms
from torchvision.utils import make_grid
from torch.utils.data import random_split
from torchvision.transforms import ToTensor
from torchvision.datasets import ImageFolder
from torch.utils.data import Dataset, DataLoader
from torchvision.datasets.utils import download_url
2.图像文件夹到数据集
由于我们的数据存在于文件夹中,因此让我们将其转换为数据集。
transform_train = transforms.Compose([
transforms.Resize((150,150)), #becasue vgg takes 150*150
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.ToTensor(),
transforms.Normalize((.5, .5, .5), (.5, .5, .5))
])
#Augmentation is not done for test/validation data.
transform_test = transforms.Compose([
transforms.Resize((150,150)), #becasue vgg takes 150*150
transforms.ToTensor(),
transforms.Normalize((.5, .5, .5), (.5, .5, .5))
])
train_ds = ImageFolder('../input/intel-image-classification/seg_train/seg_train', transform=transform_train)
test_ds = ImageFolder('../input/intel-image-classification/seg_test/seg_test', transform=transform_test)
pred_ds = ImageFolder('/kaggle/input/intel-image-classification/seg_pred/', transform=transform_test)
3.探索性数据分析(EDA)
在这里作为EDA的一部分回答一些问题,但是EDA并没有广泛涉及。
继续回答一些问题。
a)数据集中有多少张图片?
答:
这意味着有14034张图像用于训练,3000张图像用于测试/验证以及7301张图像用于预测。
b)你能告诉我图像尺寸吗?
答:
这意味着图像大小为150 * 150,具有三个通道,其标签为0。
c)您可以打印一批训练图像吗?
答:创建数据加载器后将给出此问题的答案,因此请等待并继续下面给出的下一个标题。
4.创建一个DataLoader
为将批量加载数据的所有数据集创建一个数据加载器。
batch_size=128
train_dl = DataLoader(train_ds, batch_size, shuffle=True, num_workers=4, pin_memory=True)
val_dl = DataLoader(test_ds, batch_size, num_workers=4, pin_memory=True)
pred_dl = DataLoader(pred_ds, batch_size, num_workers=4, pin_memory=True)
接下来,创建一个数据加载器,该数据加载器可用于打印上述问题中要求的一批图像。
batch_size=128
train_dl = DataLoader(train_ds, batch_size, shuffle=True, num_workers=4, pin_memory=True)
val_dl = DataLoader(test_ds, batch_size, num_workers=4, pin_memory=True)
pred_dl = DataLoader(pred_ds, batch_size, num_workers=4, pin_memory=True)
5.生成类名称
尽管可以通过在此处查看文件夹名称来手动列出类名称,但是作为一种好的做法,应该为此编写代码。
6.创建精度函数
定义一个可以计算模型精度的函数。
7.下载预训练的模型
下载选择的任何预训练模型,可以随意选择任何模型。在这里,选择了两个模型VGG和ResNet50进行实验。移动并下载模型。
8.冻结所有图层
下载模型后,可以根据需要训练整个体系结构。一种可能的策略是,可以训练某些层的预训练模型,而有些则不能。在这里选择了这样一种策略,即在对新输入进行模型训练时,不需要对任何现有层进行训练,因此可以通过将模型的每个参数的require_grad设置为False来保持所有层冻结。
如果require_grad为True,则意味着更新可以计算其导数的参数。
9.添加自己的分类器层
现在,要使用下载的预训练模型作为您自己的分类器,必须对其进行一些更改,因为要预测的类别数量可能与训练模型所依据的类别数量不同。另一个原因是(几乎在每种情况下)都有可能训练模型来检测某些特定类型的事物,但是希望使用该模型来检测不同的事物。
因此模型的一些变化是可以有您自己的分类层,该层将根据要求执行分类。
因此要在预训练模型中添加哪种架构完全取决于您。在这里选择了人们最常用的策略,那就是用自己的分类层替换模型的最后一层。
其他策略是您可以从最后一个删除一些图层,例如已经删除了最后三个图层并添加了自己的分类图层。
为了更好地理解,请参见下文
预训练的VGG模型:
上图中显示了VGG模型的最后两层(avgpool和classsifer)。可以看到,该经过预训练的模型旨在用于对1000个班级进行分类。但是只需要6类分类,因此可以稍微更改此模型。
替换最后一层后的新模型:
已经用自己的分类器层替换了,因为可以看到有6个out_features表示6个输出,但是在预训练模型中还有另一个数字,因为模型经过训练可以对这些分类进行分类。
为什么分类器层内部的某些功能和out_features已更改,为什么?
因此回答这个问题。可以为它们选择任何数字,但请记住,第一个线性层内部的in_features必须为25088,因为它是输出层的数目,不能更改,该数字必须是不变的。
与ResNet50相同:
预训练模型(最后两层)
更换最后一层后的新模型
请注意,第一个Linear层中的in_features与2048相同,最后一个Linear层中的out_features为6。
除上面提到的外,任何in_features和out_features均可根据选择进行更改。
10.创建基类
创建一个基类,其中将包含将来要使用的所有有用函数,并且这样做只是为了确保DRY(不要重复自己)的概念,因为这两个模型都将需要该类内部的函数,因此必须如果未在此处实现而违反DRY概念,则分别为每个函数定义这些功能。
class ImageClassificationBase(nn.Module):
def training_step(self, batch):
images, labels = batch
out = self(images) # Generate predictions
loss = F.cross_entropy(out, labels) # Calculate loss
return loss
def validation_step(self, batch):
images, labels = batch
out = self(images) # Generate predictions
loss = F.cross_entropy(out, labels) # Calculate loss
acc = accuracy(out, labels) # Calculate accuracy
return {'val_loss': loss.detach(), 'val_acc': acc}
def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs]
epoch_loss = torch.stack(batch_losses).mean() # Combine losses
batch_accs = [x['val_acc'] for x in outputs]
epoch_acc = torch.stack(batch_accs).mean() # Combine accuracies
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}
def epoch_end(self, epoch, result):
print("Epoch [{}], train_loss: {:.4f}, val_loss: {:.4f}, val_acc: {:.4f}".format(
epoch, result['train_loss'], result['val_loss'], result['val_acc']))
11.继承基类
通过继承基类为每个模型创建一个类,该基类具有训练任何模型期间所需的所有有用功能。
12.创建继承类的对象
实例化课程
13.检查装置
创建一个将检查当前设备的功能。如果存在GPU,则选择它,否则选择CPU作为工作设备。
在这里使用GPU,因此将设备类型显示为CUDA。
14.移至设备
创建一个可以将张量和模型移动到特定设备的函数。
15. DeviceDataLoader
创建一个DeviceDataLoader类,该类包装DataLoader以将数据移动到特定设备,然后可以从该设备产生一批数据。
在这里,可以看到张量和两个模型都已发送到当前存在的适当设备。该设备是GPU。
16.评估和拟合函数
定义评估函数,该函数对看不见的数据评估模型的性能,并定义可用于训练模型的拟合函数。
class ImageClassificationBase(nn.Module):
def training_step(self, batch):
images, labels = batch
out = self(images) # Generate predictions
loss = F.cross_entropy(out, labels) # Calculate loss
return loss
def validation_step(self, batch):
images, labels = batch
out = self(images) # Generate predictions
loss = F.cross_entropy(out, labels) # Calculate loss
acc = accuracy(out, labels) # Calculate accuracy
return {'val_loss': loss.detach(), 'val_acc': acc}
def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs]
epoch_loss = torch.stack(batch_losses).mean() # Combine losses
batch_accs = [x['val_acc'] for x in outputs]
epoch_acc = torch.stack(batch_accs).mean() # Combine accuracies
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}
def epoch_end(self, epoch, result):
print("Epoch [{}], train_loss: {:.4f}, val_loss: {:.4f}, val_acc: {:.4f}".format(
epoch, result['train_loss'], result['val_loss'], result['val_acc']))
17.训练(第一阶段)
训练模型,即VGG的一些时期。
num_epochs = 10
opt_func = torch.optim.Adam
lr = 0.00001
history = fit(num_epochs, lr, model, train_dl, val_dl, opt_func)
Epoch [0], train_loss: 0.8719, val_loss: 0.3769, val_acc: 0.8793
Epoch [1], train_loss: 0.4265, val_loss: 0.3104, val_acc: 0.8942
Epoch [2], train_loss: 0.3682, val_loss: 0.2884, val_acc: 0.9016
Epoch [3], train_loss: 0.3354, val_loss: 0.2819, val_acc: 0.8988
Epoch [4], train_loss: 0.3205, val_loss: 0.2704, val_acc: 0.9033
Epoch [5], train_loss: 0.2977, val_loss: 0.2722, val_acc: 0.9021
Epoch [6], train_loss: 0.2853, val_loss: 0.2629, val_acc: 0.9068
Epoch [7], train_loss: 0.2784, val_loss: 0.2625, val_acc: 0.9045
Epoch [8], train_loss: 0.2697, val_loss: 0.2623, val_acc: 0.9033
Epoch [9], train_loss: 0.2530, val_loss: 0.2629, val_acc: 0.9018
18.训练(第二阶段)
训练更多的时期,并评估该模型。
19.训练(第三阶段)
训练模型2,即ResNet50,以了解一些时期
num_epochs = 10
opt_func = torch.optim.Adam
lr = 0.00001
history = fit(num_epochs, lr, model2, train_dl, val_dl, opt_func)
Epoch [0], train_loss: 1.6437, val_loss: 1.4135, val_acc: 0.7686
Epoch [1], train_loss: 1.2088, val_loss: 0.9185, val_acc: 0.8582
Epoch [2], train_loss: 0.8531, val_loss: 0.6467, val_acc: 0.8594
Epoch [3], train_loss: 0.6709, val_loss: 0.5129, val_acc: 0.8640
Epoch [4], train_loss: 0.5773, val_loss: 0.4416, val_acc: 0.8693
Epoch [5], train_loss: 0.5215, val_loss: 0.4002, val_acc: 0.8739
Epoch [6], train_loss: 0.4796, val_loss: 0.3725, val_acc: 0.8767
Epoch [7], train_loss: 0.4582, val_loss: 0.3559, val_acc: 0.8795
Epoch [8], train_loss: 0.4391, val_loss: 0.3430, val_acc: 0.8819
Epoch [9], train_loss: 0.4262, val_loss: 0.3299, val_acc: 0.8823
num_epochs = 5
opt_func = torch.optim.Adam
lr = 0.0001
history = fit(num_epochs, lr, model2, train_dl, val_dl, opt_func)
Epoch [0], train_loss: 0.4183, val_loss: 0.3225, val_acc: 0.8753
Epoch [1], train_loss: 0.3696, val_loss: 0.2960, val_acc: 0.8855
Epoch [2], train_loss: 0.3533, val_loss: 0.2977, val_acc: 0.8814
Epoch [3], train_loss: 0.3382, val_loss: 0.2970, val_acc: 0.8891
Epoch [4], train_loss: 0.3289, val_loss: 0.2849, val_acc: 0.8933
20.训练(第四阶段)
训练更多的时期,并评估该模型。
21.预测单个图像
定义模型可以用来预测单个图像的函数。
def predict_single(input,label, model):
input = to_device(input,device)
inputs = input.unsqueeze(0) # unsqueeze the input i.e. add an additonal dimension
predictions = model(inputs)
prediction = predictions[0].detach().cpu()
print(f"Prediction is {np.argmax(prediction)} of Model whereas given label is {label}")
22.做预测
可以看出,尽管VGG具有良好的验证准确性(val_acc),但目前VGG给出了错误的预测,而ResNet给出了正确的预测,但不能说它会在每幅图像上正确预测。
因此训练两个模型以获取更多的时期,以便使错误最小化,即可以最大程度地减少val_loss,并且两个模型都可以更准确地执行。
现在,该轮到预测整个pred文件夹/数据集了。
提示:使用pred_dl作为数据加载器可以批量加载pred数据以进行预测。进行练习,并尝试使用集合预测的概念来获得更多正确的预测数。
23.保存模型
很好地训练模型后,保存它以便可以将其用作下一个标题中的未来工作。
24.未来的工作
合并两个模型的预测,进行最终预测,然后使用保存的模型将此项目转换为flask / stream-lit Web应用程序。
资源资源
如果想要笔记本,可以在这里获得。
https://www.kaggle.com/awadhi123/intel-multiclass-classification?scriptVersionId=37760533