介绍
进入 21 世纪,世界正迅速向人工智能和机器学习迈进。各种强大的 AI 模型已经被制造出来,其性能远远优于人脑,例如 deepfake 生成、图像分类、文本分类等。
我将在本文中向你展示如何使用卷积神经网络制作简单的图像分类模型。我们将对猫和狗的图像进行分类。这是一个完美的问题陈述,可以在初级水平上使用。在训练我们的模型之后,我们还将通过应用各种优化技术来优化它的性能,我将在本文后面讨论。
图像分类的应用有很多种,如计算机视觉、交通控制系统、刹车灯检测、疾病检测等。而图像分类的基础是卷积神经网络(CNN),所以让我们先介绍一下CNN。
什么是图像分类中的 CNN
卷积神经网络,通常称为 CNN ,是用于深度学习的人工神经网络的一个子集,经常用于对象和图片的识别和分类。因此,深度学习利用 CNN 来识别图片中的项目。
CNN 是一种深度学习模型,用于处理具有网格模式的数据,例如图像。CNN 的灵感来自动物视觉皮层的组织方式 [13, 14],旨在自动和自适应地学习从低级到高级模式的特征空间层次结构。
代码实现
在本节中,我们将讨论代码实现。首先,我们将导入所有必要的库。在此之后,我们将加载和预处理数据集。然后,我们将训练 CNN 模型并在测试集上计算其准确度。
最后,我们将对其应用不同的优化技术,如上所述,并比较其中最好的优化方法。
导入必要的库:
我们将导入所有必需的库,如 Numpy、Pandas、Torchvision、Keras 等。流行的数据集、模型架构和计算机视觉的典型图像修改都包含在 torchvision 包中。
import torch
from google.colab import drive
drive.mount('/content/gdrive')
输出:
Drive already mounted at /content/gdrive; to attempt to forcibly remount, call drive.mount("/content/gdrive", force_remount=True).
!unzip gdrive/MyDrive/kagglecatsanddogs_3367a.zip
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torchvision.models as models
import torch.nn as nn
import torch.optim as optim
import numpy as np
from PIL import Image
import numpy as np
import matplotlib
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from imutils import paths import shutil
加载数据集:
我们使用了猫和狗数据集,其中包含几张猫和狗的图像。你可以使用此链接下载:https://drive.google.com/file/d/1T4MsAG42TXKiRozRppdAGo2pRmYYkuUV/view?usp=sharing
该数据集包含 12501 张猫和狗的图像。
下面名为copy_images的函数用于从目录中提取图像并读取它们以使用 opencv 库放入数组中。
之后,我们将调整图像大小并执行水平和垂直翻转。它提供了更多关于我们模型的稳健性的信息。
最后,我们将此数据集划分为训练集、测试集和验证集。
neg_num, negs_num = -1, -2
import os
def copy_images(imagePaths, folder):
if not os.path.exists(folder):
os.makedirs(folder)
for path in imagePaths:
zer = 0
v = zer
try:
img = matplotlib.image.mpimg.imread(path)
except:
print("found1 ",path,"\n")
else:
one_num =1
v = one_num
if v == one_num:
imageName = path.split(os.path.sep)[neg_num]
label = path.split(os.path.sep)[negs_num]
ing, ing1 = folder, label
labelFolder = os.path.join(ing, ing1)
if not os.path.exists(labelFolder):
os.makedirs(labelFolder)
destination = os.path.join(labelFolder, imageName)
cp, dest = path, destination
shutil.copy(cp, dest)
TRAIN = "train"
VAL = "val"
INPUT_HEIGHT, INPUT_WIDTH = 224, 224
BATCH_SIZE, VAL_SPLIT = 100, 0.1
resize = transforms.Resize(size=(INPUT_HEIGHT,INPUT_WIDTH))
hFlip = transforms.RandomHorizontalFlip(p=0.25)
trainTransforms, testTransforms = transforms.Compose([resize,transforms.ToTensor()]), transforms.Compose([resize,transforms.ToTensor()])
imagePaths = list(paths.list_images("PetImages"))
np.random.shuffle(imagePaths)
num, num1 = 0.3, 0.07
val, val1 = len(imagePaths) * num, len(imagePaths) * num1
testPathsLen,valPathsLen = int(val), int(val1)
trainPathsLen = len(imagePaths) - valPathsLen - testPathsLen
trainPaths,valPaths,testPaths = imagePaths[:trainPathsLen], imagePaths[trainPathsLen:trainPathsLen+valPathsLen+1], imagePaths[trainPathsLen+valPathsLen+1:]
copy_images(trainPaths, "train")
copy_images(valPaths, "val")
copy_images(testPaths, "test")
输出:
[INFO] loading image paths...
[INFO] copying training and validation images...
found1 PetImages/Dog/11702.jpg
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 32 bytes but only got 0. Skipping tag 270
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 5 bytes but only got 0. Skipping tag 271
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 8 bytes but only got 0. Skipping tag 272
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 8 bytes but only got 0. Skipping tag 282
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 8 bytes but only got 0. Skipping tag 283
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 20 bytes but only got 0. Skipping tag 306
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 48 bytes but only got 0. Skipping tag 532
" Skipping tag %s" % (size, len(data), tag)
/usr/local/lib/python3.7/dist-packages/PIL/TiffImagePlugin.py:788: UserWarning: Corrupt EXIF data. Expecting to read 2 bytes but only got 0.
warnings.warn(str(msg))
数据可视化:现在,我们将打印两个类的随机图像以更好地可视化数据。它帮助我们找到正确和完美的算法来训练我们的模型。
![](http://qiniu.aihubs.net/413图像分类 (3).png)
我们将创建一个名为“ visualize_batch”的函数,它会一张一张地读取一些随机图像,并使用matplotlib库将它们绘制在地图中。
之后,我们将使用Data Loader分别可视化训练集、测试集和验证集。
def visualize_batch(batch, classes, dataset_type):
val = 32
hund = 100
fig = matplotlib.pyplot.figure("{} batch".format(dataset_type),
figsize=(val, val))
for i in range(0, hund):
ghr, ytu = 25,4
ax = matplotlib.pyplot.subplot(ghr, ytu, i + 1)
image = batch[0][i].cpu().numpy()
if(np.all((image == 0.0))):
print("iszero")
one, two, zer = 1,2,0
image = image.transpose((one, two, zer))
image = (image * 255.0).astype("uint8")
idx = batch[one][i]
label = classes[idx]
print(idx,label)
matplotlib.pyplot.imshow(image)
matplotlib.pyplot.title(label)
matplotlib.pyplot.axis("off")
matplotlib.pyplot.tight_layout()
matplotlib.pyplot.show()
# initialize the training and validation dataset
trainDataset, testDataset, valDataset = ImageFolder(root="train", transform=trainTransforms), ImageFolder(root="test", transform=testTransforms), ImageFolder(root="val", transform=testTransforms)
trainDataLoader,valDataLoader,testDataLoader = DataLoader(trainDataset,batch_size=BATCH_SIZE, shuffle=True), DataLoader(dataset = valDataset, batch_size=BATCH_SIZE, shuffle=True), DataLoader(testDataset, batch_size=BATCH_SIZE, shuffle=True)
from PIL import Image
trainBatch, testBatch,valBatch = next(iter(trainDataLoader)), next(iter(testDataLoader)), next(iter(valDataLoader))
visualize_batch(trainBatch, trainDataset.classes, "train")
检查 Cuda 核心的可用性:
检查你的机器是否包含 GPU。GPU 是一种图形处理单元,它是专门设计的硬件,可加速图像的渲染及其处理。使用 GPU 将加快你的训练过程。
如果你有可用的 CUDA 内核,下面的代码会将你的运行时从 CPU 转移到 GPU。
device = 'cuda' if torch.cuda.is_available() else 'cpu'
训练CNN模型:
现在我们将制作一个三层卷积神经网络来训练我们的模型。该模型包含 Conv2D 层、Max Pooling 层、Flattening 层、Dropout 层等。
我们的第一层包含一个大小为 32×32 的 Conv2D 层和一个步幅为 2 的 Max Pooling 层。layer2 和 layer3 类似,但 conv2D 层的大小不同,你可以在代码中查看。
应用卷积层后,现在我们将使用展平层将二维数组转换为一维数组。然后我们将应用密集层和dropout层的组合来获得最终分类。
最后,使用名为“test_accuracy”的函数来评估我们训练好的模型在测试集上的准确度和精度分数。
ks = 3
pads = 0
neg_one =1
strides = 2
var, var1, var2 = 32,64,128
drop_ratio = 0.5
num2 = 10
class Cnn(nn.Module):
def __init__(self):
super(Cnn,self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3,var,kernel_size=ks, padding=pads,stride=strides),
nn.MaxPool2d(strides)
)
self.layer2 = nn.Sequential(
nn.Conv2d(var, var1, kernel_size=ks, padding=pads, stride=strides),
nn.MaxPool2d(strides)
)
self.layer3 = nn.Sequential(
nn.Conv2d(var1, var2, kernel_size=ks, padding=pads, stride=strides),
nn.MaxPool2d(strides)
)
self.fc1 = nn.Linear(3*3*128,num2)
self.dropout = nn.Dropout(drop_ratio)
self.fc2 = nn.Linear(num2,strides)
self.relu = nn.ReLU()
def forward(self,x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = out.view(out.size(pads),neg_one)
out = self.relu(self.fc1(out))
out = self.fc2(out)
return out
model = Cnn().to(device)
model.train()
def trainer(model,trainDataLoader,valDataLoader,testDataLoader,optimizer,criterion,val_losses,val_acc,train_losses,train_acc):
epochs, zer = 5, 0
for epoch in range(epochs):
epoch_loss, epoch_accuracy = 0, 0
for data, label in trainDataLoader:
data, label = data.to(device), label.to(device)
output = model(data)
ops, lbs = output, label
loss = criterion(ops, lbs)
optimizer.zero_grad()
loss.backward()
optimizer.step()
acc = ((output.argmax(dim=1) == label).float().mean())
epoch_accuracy = (epoch_accuracy) + acc/len(trainDataLoader)
epoch_loss = (epoch_loss)+ loss/len(trainDataLoader)
xe, ye = loss.item(), acc.item()
train_losses.append(xe)
train_acc.append(ye)
print('Epoch : {}, train accuracy : {}, train loss : {}'.format(epoch+1, epoch_accuracy,epoch_loss))
with torch.no_grad():
epoch_val_accuracy,epoch_val_loss =0, 0
# epoch_val_loss =0
for data, label in valDataLoader:
data, label = data.to(device), label.to(device)
# label = label.to(device)
val_output = model(data)
val_loss = criterion(val_output,label)
acc = ((val_output.argmax(dim=1) == label).float().mean())
epoch_val_accuracy = (epoch_val_accuracy) + acc/ len(valDataLoader)
epoch_val_loss = (epoch_val_loss) + val_loss/ len(valDataLoader)
val_losses.append(val_loss.item())
val_acc.append(acc.item())
print('Epoch : {}, val_accuracy : {}, val_loss : {}'.format(epoch+1, epoch_val_accuracy,epoch_val_loss))
def test_accuracy(loader,model):
num_correct,num_samples = 0, 0
model.eval()
with torch.no_grad():
for x, y in loader:
x, y = x.to(device=device), y.to(device=device)
scores = model(x)
acc = (scores.argmax(dim = 1) == y).sum()
num_correct = num_correct+acc
num_samples = num_samples+y.size(0)
print(f'Got {num_correct} / {num_samples} with accuracy {float(num_correct)/float(num_samples)*200:.2f}')
model.train()
输出:
Cnn(
(layer1): Sequential(
(0): Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2))
(1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer2): Sequential(
(0): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2))
(1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer3): Sequential(
(0): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2))
(1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc1): Linear(in_features=1152, out_features=10, bias=True)
(dropout): Dropout(p=0.5, inplace=False)
(fc2): Linear(in_features=10, out_features=2, bias=True)
(relu): ReLU()
)
应用优化
在本节中,我们将应用一些优化,如 Vanilla SGD Optimizer、Minibatch with SGD Optimizer 等,以进一步提高我们之前训练的模型的准确性和精度。
我们通过优化迭代训练模型,评估最大和最小函数。
1. Vanilla SGD:
它不是纯梯度下降,因为它是标准梯度下降的变体。与更复杂的 SGD 选项(例如带有动量的 SGD)相比,这被称为“普通随机梯度下降”,因为即使是随机梯度下降也有几种变体。
我们为验证损失、训练损失和准确性制作了空列表。之后,我们将optim.SGD函数与CrossEntropyLoss()一起应用,并将完整的模型作为参数对其进行优化。
val_losses, val_acc, train_losses, train_acc = list(), list(),list(),list()
lr_val = 0.01
optimizer2 = optim.SGD(params = model.parameters(),lr=lr_val)
criterion2 = nn.CrossEntropyLoss()
bs, rt = 100, False
trainDataLoader, valDataLoader, testDataLoader = DataLoader(trainDataset,batch_size=bs, shuffle=rt), DataLoader(dataset = valDataset, batch_size=bs, shuffle=rt), DataLoader(testDataset, batch_size=bs, shuffle=rt)
trainer(model,trainDataLoader,valDataLoader,testDataLoader,optimizer2,criterion2,val_losses,val_acc,train_losses,train_acc)
损失曲线:
在这条曲线中,我们已经看到,随着我们增加迭代或 epoch 的数量,训练和验证损失都保持不变。
我们将使用matplotlib库来绘制损失图。
matplotlib.pyplot.figure(figsize=(10,5))
matplotlib.pyplot.title("Training and Validation Loss")
matplotlib.pyplot.plot(val_losses,label="Val")
matplotlib.pyplot.plot(train_losses,label="train")
matplotlib.pyplot.xlabel("iterations")
matplotlib.pyplot.ylabel("Loss")
matplotlib.pyplot.legend()
matplotlib.pyplot.show()
精度曲线:
在这条曲线中,我们已经看到,随着我们增加迭代或 epoch 的数量,训练和验证的准确性都保持不变。
matplotlib.pyplot.figure(figsize=(10,5))
matplotlib.pyplot.title("Training and Validation Accuracy")
matplotlib.pyplot.plot(val_losses,label="Val")
matplotlib.pyplot.plot(train_losses,label="train")
matplotlib.pyplot.xlabel("iterations")
matplotlib.pyplot.ylabel("accuracy")
matplotlib.pyplot.legend()
matplotlib.pyplot.show()
2. 带 SGD 的小批量:
当数据集很广泛时,可以使用 SGD。批量梯度下降迅速接近最小值。在更大数据集的情况下,SGD 收敛得更快。但是我们不能在 SGD 上使用矢量化实现,因为我们一次只使用一个样本。结果,计算可能会变慢。
损失曲线:
在这条曲线中,我们看到训练和验证损失都会随着迭代次数或时期数的增加而波动。
![](http://qiniu.aihubs.net/154图像分类 (5).png)
精度曲线:
在这条曲线中,我们已经看到,随着迭代次数或 epoch 次数的增加,训练和验证的准确性都会发生波动。
![](http://qiniu.aihubs.net/325图像分类 (1).png)
3. 带动量的小批量 SGD:
通过在正确的方向上加速梯度向量,具有动量的 SGD 是一种促进更快收敛的策略。
损失曲线:
在这条曲线中,我们看到训练和验证损失以 V 形均值的形式发生变化,然后随着我们增加迭代次数或 epoch 次数而增加并保持不变。
![](http://qiniu.aihubs.net/5143图像分类 (6).png)
准确度曲线
在这条曲线中,我们看到训练和验证准确度都随着我们增加迭代或时期的数量而增加,这是使用基本概念所预期的。
![](http://qiniu.aihubs.net/235图像分类 (2).png)
4. 带 ADAM 的小批量:
Adam 是一种自适应深度神经网络训练优化器,已成功应用于许多不同领域。然而,与随机梯度下降相比,它在图片分类问题上的泛化性能要低得多(SGD)。
损失曲线:
在这条曲线中,我们看到训练和验证损失都随着迭代次数或时期的增加而增加。
准确度曲线:
在这条曲线中,我们已经看到,随着我们增加迭代或 epoch 的数量,训练和验证的准确度都保持不变。
![](http://qiniu.aihubs.net/651图像分类 (4).png)
图像分类结果
最初,带 SGD 的小批量可以对较小的 epoch 数进行更准确的预测。尽管如此,随着我们增加 epoch 的数量,在某一点之后,我们观察到具有动量的 mini-batch 表现更好,因为动量还包括当前输出的先前变化。因此,动量优化比SGD 提供了更好的精度。
对于具有动量的小批量,我们使用 ADAM 优化,它使用动量和自适应学习率的概念(根据我们获得的损失和准确率自动设置每个 epoch 后的学习率)。这意味着 ADAM 优化将对大多数问题陈述执行得更好。
Adam 优化器将维护两个不同的超参数——alpha 和 beta,我们可以对其进行调整并找到可能的最佳精度,因为一个因素负责保持动量,另一个因素负责学习率适应。
结论
完整的代码:https://drive.google.com/file/d/16FPXlMnHokFIMr9PDFRed46QTmnN2BKq/view?usp=sharing
因此,最后,如果我们比较所有四种不同的模型,我们会在未见过的数据集上的准确度方面得到以下顺序:
带 ADAM 的小批量 > 带 SGD 的小批量 > 带动量的小批量 > Vanilla SGD(这不是硬性规定,它随数据集和问题陈述而变化,因为我们知道不存在通用分类器)。
因此,这结束了给定的图像分类问题。
本文的重要内容:
- 首先,我们加载并可视化了数据集。在此,我们从目录中读取图像,然后将它们调整为固定大小。之后,将它们分成训练集和测试集。
- 在数据预处理之后,我们训练了我们的 CNN 模型。在准备我们的模型时,我们应用了 Conv2D 层、Max Pooling Layers 等的几种组合。
- 为了进一步提高准确性,我们应用了几种优化技术,如 Vanilla SGD、Mini Batch SGD 等。
- 最后,我们讨论了最好的优化技术,即带有 ADAM 的 Mini batch。