pytorch CV入门3-预训练模型与迁移学习
专栏链接:https://blog.csdn.net/qq_33345365/category_12578430.html
初次编辑:2024/3/7;最后编辑:2024/3/8
参考网站-微软教程:https://learn.microsoft.com/en-us/training/modules/intro-computer-vision-pytorch
更多的内容可以参考本作者其他专栏:
Pytorch基础:https://blog.csdn.net/qq_33345365/category_12591348.html
Pytorch NLP基础:https://blog.csdn.net/qq_33345365/category_12597850.html
使用预训练的网络进行迁移学习
预训练模型与迁移学习 Pre-trained models and transfer learning
训练卷积神经网络可能需要大量时间,而且需要大量数据。然而,许多时间都花在了学习网络用于从图像中提取模式的最佳低级滤波器上。一个自然的问题是 - 我们是否可以使用一个在一个数据集上训练过的神经网络,将其适应于对不同图像进行分类而无需完全训练过程?
这种方法被称为迁移学习(transfer learning),因为从一个神经网络模型向另一个模型转移了一些知识。在迁移学习中,我们通常从一个预训练模型开始,该模型已经在一些大型图像数据集(例如ImageNet)上进行了训练。这些模型已经能够很好地从通用图像中提取不同的特征,在许多情况下,只需在这些提取的特征之上构建一个分类器就可以得到很好的结果。
使用以下python代码来帮助构建代码:
# Script file to hide implementation details for PyTorch computer vision moduleimport builtins
import torch
import torch.nn as nn
from torch.utils import data
import torchvision
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import glob
import os
import zipfiledefault_device = 'cuda' if torch.cuda.is_available() else 'cpu'def load_mnist(batch_size=64):builtins.data_train = torchvision.datasets.MNIST('./data',download=True, train=True, transform=ToTensor())builtins.data_test = torchvision.datasets.MNIST('./data',download=True, train=False, transform=ToTensor())builtins.train_loader = torch.utils.data.DataLoader(data_train, batch_size=batch_size)builtins.test_loader = torch.utils.data.DataLoader(data_test, batch_size=batch_size)def train_epoch(net, dataloader, lr=0.01, optimizer=None, loss_fn=nn.NLLLoss()):optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)net.train()total_loss, acc, count = 0, 0, 0for features, labels in dataloader:optimizer.zero_grad()lbls = labels.to(default_device)out = net(features.to(default_device))loss = loss_fn(out, lbls) # cross_entropy(out,labels)loss.backward()optimizer.step()total_loss += loss_, predicted = torch.max(out, 1)acc += (predicted == lbls).sum()count += len(labels)return total_loss.item() / count, acc.item() / countdef validate(net, dataloader, loss_fn=nn.NLLLoss()):net.eval()count, acc, loss = 0, 0, 0with torch.no_grad():for features, labels in dataloader:lbls = labels.to(default_device)out = net(features.to(default_device))loss += loss_fn(out, lbls)pred = torch.max(out, 1)[1]acc += (pred == lbls).sum()count += len(labels)return loss.item() / count, acc.item() / countdef train(net, train_loader, test_loader, optimizer=None, lr=0.01, epochs=10, loss_fn=nn.NLLLoss()):optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)res = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}for ep in range(epochs):tl, ta = train_epoch(net, train_loader, optimizer=optimizer, lr=lr, loss_fn=loss_fn)vl, va = validate(net, test_loader, loss_fn=loss_fn)print(f"Epoch {ep:2}, Train acc={ta:.3f}, Val acc={va:.3f}, Train loss={tl:.3f}, Val loss={vl:.3f}")res['train_loss'].append(tl)res['train_acc'].append(ta)res['val_loss'].append(vl)res['val_acc'].append(va)return resdef train_long(net, train_loader, test_loader, epochs=5, lr=0.01, optimizer=None, loss_fn=nn.NLLLoss(), print_freq=10):optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)for epoch in range(epochs):net.train()total_loss, acc, count = 0, 0, 0for i, (features, labels) in enumerate(train_loader):lbls = labels.to(default_device)optimizer.zero_grad()out = net(features.to(default_device))loss = loss_fn(out, lbls)loss.backward()optimizer.step()total_loss += loss_, predicted = torch.max(out, 1)acc += (predicted == lbls).sum()count += len(labels)if i % print_freq == 0:print("Epoch {}, minibatch {}: train acc = {}, train loss = {}".format(epoch, i, acc.item() / count,total_loss.item() / count))vl, va = validate(net, test_loader, loss_fn)print("Epoch {} done, validation acc = {}, validation loss = {}".format(epoch, va, vl))def plot_results(hist):plt.figure(figsize=(15, 5))plt.subplot(121)plt.plot(hist['train_acc'], label='Training acc')plt.plot(hist['val_acc'], label='Validation acc')plt.legend()plt.subplot(122)plt.plot(hist['train_loss'], label='Training loss')plt.plot(hist['val_loss'], label='Validation loss')plt.legend()def plot_convolution(t, title=''):with torch.no_grad():c = nn.Conv2d(kernel_size=(3, 3), out_channels=1, in_channels=1)c.weight.copy_(t)fig, ax = plt.subplots(2, 6, figsize=(8, 3))fig.suptitle(title, fontsize=16)for i in range(5):im = data_train[i][0]ax[0][i].imshow(im[0])ax[1][i].imshow(c(im.unsqueeze(0))[0][0])ax[0][i].axis('off')ax[1][i].axis('off')ax[0, 5].imshow(t)ax[0, 5].axis('off')ax[1, 5].axis('off')# plt.tight_layout()plt.show()def display_dataset(dataset, n=10, classes=None):fig, ax = plt.subplots(1, n, figsize=(15, 3))mn = min([dataset[i][0].min() for i in range(n)])mx = max([dataset[i][0].max() for i in range(n)])for i in range(n):ax[i].imshow(np.transpose((dataset[i][0] - mn) / (mx - mn), (1, 2, 0)))ax[i].axis('off')if classes:ax[i].set_title(classes[dataset[i][1]])def check_image(fn):try:im = Image.open(fn)im.verify()return Trueexcept:return Falsedef check_image_dir(path):for fn in glob.glob(path):if not check_image(fn):print("Corrupt image: {}".format(fn))os.remove(fn)def common_transform():std_normalize = torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])trans = torchvision.transforms.Compose([torchvision.transforms.Resize(256),torchvision.transforms.CenterCrop(224),torchvision.transforms.ToTensor(),std_normalize])return transdef load_cats_dogs_dataset():if not os.path.exists('data/PetImages'):with zipfile.ZipFile('data/kagglecatsanddogs_5340.zip', 'r') as zip_ref:zip_ref.extractall('data')check_image_dir('data/PetImages/Cat/*.jpg')check_image_dir('data/PetImages/Dog/*.jpg')dataset = torchvision.datasets.ImageFolder('data/PetImages', transform=common_transform())trainset, testset = torch.utils.data.random_split(dataset, [20000, len(dataset) - 20000])trainloader = torch.utils.data.DataLoader(trainset, batch_size=32)testloader = torch.utils.data.DataLoader(trainset, batch_size=32)return dataset, trainloader, testloader
版本要求:
torchvision==0.13.0
torch==1.12.0
创建一个新文件,加载类
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from torchinfo import summary
import numpy as np
import osfrom pytorchcv import train, plot_results, display_dataset, train_long, check_image_dir
Cats vs. Dogs 数据集
本单元将解决一个现实生活中的猫和狗图像分类问题。为此,将使用 Kaggle Cats vs. Dogs Dataset这个数据集,下载完成将压缩包放到data目录下。
数据集网址:https://www.microsoft.com/en-us/download/details.aspx?id=54765
添加如下代码来解压缩:
import zipfile
if not os.path.exists('data/PetImages'):with zipfile.ZipFile('data/kagglecatsanddogs_5340.zip', 'r') as zip_ref:zip_ref.extractall('data')
很遗憾,数据集中存在一些损坏的图像文件。我们需要快速清理以检查损坏的文件。为了不干扰本教程,我们将验证数据集的代码移到一个模块中,并在此处调用它。check_image_dir
会逐个图像地检查整个数据集,尝试加载图像并检查是否可以正确加载。所有损坏的图像都会被删除。
check_image_dir('data/PetImages/Cat/*.jpg')
check_image_dir('data/PetImages/Dog/*.jpg')
输出是:
Corrupt image: data/PetImages/Cat\666.jpg
Corrupt image: data/PetImages/Dog\11702.jpg
接下来,将图像加载到PyTorch数据集中,将它们转换为张量并进行一些归一化。我们通过使用Compose
组合几个基本变换来定义图像变换管道:
Resize
将图像调整为 256x256 尺寸CenterCrop
获取尺寸为 224x224 的图像中心部分。预训练的VGG网络是在 224x224 的图像上进行训练的,因此我们需要将我们的数据集调整到这个尺寸。ToTensor
将像素强度归一化为 0 到 1 的范围,并将图像转换为 PyTorch 张量std_normalize
变换是VGG网络的附加归一化步骤。在训练VGG网络时,ImageNet中的原始图像经过了颜色平均强度的减法和颜色标准差的除法(也是按颜色进行的)。因此,我们需要对我们的数据集应用相同的变换,以便所有图像都被正确处理。
我们将图像调整为尺寸为 256,然后裁剪为 224 像素有几个原因:
- 我们想要展示更多可能的转换。
- 宠物通常在图像的中心部分,因此通过更多地关注中心部分,我们可以提高分类效果。
- 由于一些图像不是正方形的,我们最终会有一些填充部分不包含任何有用的图像数据,稍微裁剪图像会减少填充部分。
std_normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])
trans = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(), std_normalize])
dataset = torchvision.datasets.ImageFolder('data/PetImages',transform=trans)
trainset, testset = torch.utils.data.random_split(dataset,[20000,len(dataset)-20000])display_dataset(dataset)
预训练模型
torchvision模块中提供了许多不同的预训练模型,甚至还可以在互联网上找到更多模型。让我们看看如何加载和使用最简单的VGG-16模型。首先,我们将下载在本地存储的VGG-16模型的权重。
- 创建文件夹:mkdir -p models
- 下载模型:wget -P models https://github.com/MicrosoftDocs/pytorchfundamentals/raw/main/computer-vision-pytorch/vgg16-397923af.pth
接下来,我们将使用load_state_dict
方法将权重加载到预训练的VGG-16模型中。然后,使用eval
方法将模型设置为推断模式。
file_path = 'models/vgg16-397923af.pth'vgg = torchvision.models.vgg16()
vgg.load_state_dict(torch.load(file_path))
vgg.eval()sample_image = dataset[0][0].unsqueeze(0)
res = vgg(sample_image)
print(res[0].argmax())
输出:
tensor(282)
我们收到的结果是ImageNet类的编号,可以在这里查找。我们可以使用以下代码自动加载这个类表并返回结果:
import json, requests
class_map = json.loads(requests.get("https://raw.githubusercontent.com/MicrosoftDocs/pytorchfundamentals/main/computer-vision-pytorch/imagenet_class_index.json").text)
class_map = { int(k) : v for k,v in class_map.items() }class_map[res[0].argmax().item()]
输出是:
['n02123045', 'tabby']
打印出VGG-16网络的架构:
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
VGG -- --
├─Sequential: 1-1 [1, 512, 7, 7] --
│ └─Conv2d: 2-1 [1, 64, 224, 224] 1,792
│ └─ReLU: 2-2 [1, 64, 224, 224] --
│ └─Conv2d: 2-3 [1, 64, 224, 224] 36,928
│ └─ReLU: 2-4 [1, 64, 224, 224] --
│ └─MaxPool2d: 2-5 [1, 64, 112, 112] --
│ └─Conv2d: 2-6 [1, 128, 112, 112] 73,856
│ └─ReLU: 2-7 [1, 128, 112, 112] --
│ └─Conv2d: 2-8 [1, 128, 112, 112] 147,584
│ └─ReLU: 2-9 [1, 128, 112, 112] --
│ └─MaxPool2d: 2-10 [1, 128, 56, 56] --
│ └─Conv2d: 2-11 [1, 256, 56, 56] 295,168
│ └─ReLU: 2-12 [1, 256, 56, 56] --
│ └─Conv2d: 2-13 [1, 256, 56, 56] 590,080
│ └─ReLU: 2-14 [1, 256, 56, 56] --
│ └─Conv2d: 2-15 [1, 256, 56, 56] 590,080
│ └─ReLU: 2-16 [1, 256, 56, 56] --
│ └─MaxPool2d: 2-17 [1, 256, 28, 28] --
│ └─Conv2d: 2-18 [1, 512, 28, 28] 1,180,160
│ └─ReLU: 2-19 [1, 512, 28, 28] --
│ └─Conv2d: 2-20 [1, 512, 28, 28] 2,359,808
│ └─ReLU: 2-21 [1, 512, 28, 28] --
│ └─Conv2d: 2-22 [1, 512, 28, 28] 2,359,808
│ └─ReLU: 2-23 [1, 512, 28, 28] --
│ └─MaxPool2d: 2-24 [1, 512, 14, 14] --
│ └─Conv2d: 2-25 [1, 512, 14, 14] 2,359,808
│ └─ReLU: 2-26 [1, 512, 14, 14] --
│ └─Conv2d: 2-27 [1, 512, 14, 14] 2,359,808
│ └─ReLU: 2-28 [1, 512, 14, 14] --
│ └─Conv2d: 2-29 [1, 512, 14, 14] 2,359,808
│ └─ReLU: 2-30 [1, 512, 14, 14] --
│ └─MaxPool2d: 2-31 [1, 512, 7, 7] --
├─AdaptiveAvgPool2d: 1-2 [1, 512, 7, 7] --
├─Sequential: 1-3 [1, 1000] --
│ └─Linear: 2-32 [1, 4096] 102,764,544
│ └─ReLU: 2-33 [1, 4096] --
│ └─Dropout: 2-34 [1, 4096] --
│ └─Linear: 2-35 [1, 4096] 16,781,312
│ └─ReLU: 2-36 [1, 4096] --
│ └─Dropout: 2-37 [1, 4096] --
│ └─Linear: 2-38 [1, 1000] 4,097,000
==========================================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
Total mult-adds (G): 15.48
==========================================================================================
Input size (MB): 0.60
Forward/backward pass size (MB): 108.45
Params size (MB): 553.43
Estimated Total Size (MB): 662.49
==========================================================================================
除了我们已经了解的层之外,还有另一种称为Dropout的层类型。这些层作为正则化(regularization)技术。正则化对学习算法进行轻微修改,使模型更好地泛化。在训练过程中,Dropout层会舍弃前一层中的一定比例(大约30%)的神经元,并且在没有它们的情况下进行训练。这有助于使优化过程摆脱局部最小值,并将决策能力分布在不同的神经路径之间,从而提高网络的整体稳定性。
GPU计算
深度神经网络,例如VGG-16和其他更现代的架构,需要相当大的计算能力才能运行。如果可用的话,使用GPU加速是有意义的。为了做到这一点,我们需要显式地将参与计算的所有张量移动到GPU上。通常的做法是在代码中检查GPU的可用性,并定义一个指向计算设备(GPU或CPU)的设备变量。
device = 'cuda' if torch.cuda.is_available() else 'cpu'print('Doing computations on device = {}'.format(device))vgg.to(device)
sample_image = sample_image.to(device)vgg(sample_image).argmax()
提取VGG特征
如果我们想要使用VGG-16来从我们的图像中提取特征,我们需要没有最终分类层的模型。实际上,可以使用vgg.features
方法来获取这个“特征提取器”。
res = vgg.features(sample_image).cpu()
plt.figure(figsize=(15,3))
plt.imshow(res.detach().view(-1,512))
print(res.size())
输出是:
torch.Size([1, 512, 7, 7])
特征张量的维度是512x7x7,但为了可视化它,必须将其重塑为2D形式。
现在让我们尝试看看这些特征是否可以用来对图像进行分类。让我们手动选择一些图像的部分(在我们的案例中为800张),并预先计算它们的特征向量。我们将结果存储在一个称为feature_tensor
的大张量中,并将标签存储在label_tensor
中:
bs = 8
dl = torch.utils.data.DataLoader(dataset,batch_size=bs,shuffle=True)
num = bs*100
feature_tensor = torch.zeros(num,512*7*7).to(device)
label_tensor = torch.zeros(num).to(device)
i = 0
for x,l in dl:with torch.no_grad():f = vgg.features(x.to(device))feature_tensor[i:i+bs] = f.view(bs,-1)label_tensor[i:i+bs] = li+=bsprint('.',end='')if i>=num:break
现在我们可以定义vgg_dataset
,从这个张量中获取数据,使用random_split
函数将其分割成训练集和测试集,并在提取的特征之上训练一个小型单层密集分类器网络。
vgg_dataset = torch.utils.data.TensorDataset(feature_tensor,label_tensor.to(torch.long))
train_ds, test_ds = torch.utils.data.random_split(vgg_dataset,[700,100])train_loader = torch.utils.data.DataLoader(train_ds,batch_size=32)
test_loader = torch.utils.data.DataLoader(test_ds,batch_size=32)net = torch.nn.Sequential(torch.nn.Linear(512*7*7,2),torch.nn.LogSoftmax()).to(device)history = train(net,train_loader,test_loader)
输出:
Epoch 0, Train acc=0.879, Val acc=0.990, Train loss=0.110, Val loss=0.007
Epoch 1, Train acc=0.981, Val acc=0.980, Train loss=0.015, Val loss=0.021
Epoch 2, Train acc=0.999, Val acc=0.990, Train loss=0.001, Val loss=0.002
Epoch 3, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
Epoch 4, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
Epoch 5, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
Epoch 6, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
Epoch 7, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
Epoch 8, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
Epoch 9, Train acc=1.000, Val acc=0.980, Train loss=0.000, Val loss=0.002
结果很好,我们几乎可以以98%的概率区分猫和狗!然而,我们只是在所有图像的一个小子集上测试了这种方法,因为手动特征提取似乎需要很长时间。
使用一个VGG网络进行迁移学习
我们也可以通过在训练过程中使用原始的VGG-16网络作为一个整体来避免手动预先计算特征。让我们看看VGG-16的对象结构:
print(vgg)
输出是:
VGG((features): Sequential((0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(1): ReLU(inplace=True)(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(3): ReLU(inplace=True)(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(6): ReLU(inplace=True)(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(8): ReLU(inplace=True)(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(11): ReLU(inplace=True)(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(13): ReLU(inplace=True)(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(15): ReLU(inplace=True)(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(18): ReLU(inplace=True)(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(20): ReLU(inplace=True)(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(22): ReLU(inplace=True)(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(25): ReLU(inplace=True)(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(27): ReLU(inplace=True)(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(29): ReLU(inplace=True)(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False))(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))(classifier): Sequential((0): Linear(in_features=25088, out_features=4096, bias=True)(1): ReLU(inplace=True)(2): Dropout(p=0.5, inplace=False)(3): Linear(in_features=4096, out_features=4096, bias=True)(4): ReLU(inplace=True)(5): Dropout(p=0.5, inplace=False)(6): Linear(in_features=4096, out_features=1000, bias=True))
)
网络包含以下组件:
- 特征提取器(
features
),由多个卷积层和池化层组成 - 平均池化层(
avgpool
) - 最终的
classifier
,由几个密集层组成,将25088个输入特征转换为1000个类别(这是ImageNet中的类别数)
为了训练能够对我们的数据集进行分类的端到端模型,我们需要:
- 替换最终的分类器,使用一个能够产生所需类别数的分类器。在我们的情况下,我们可以使用一个具有25088个输入和2个输出神经元的
Linear
层。 - 冻结卷积特征提取器的权重,以防它们被训练。建议最初执行此冻结,因为否则未经训练的分类器层可能会破坏卷积特征提取器的原始预训练权重。可以通过将所有参数的
requires_grad
属性设置为False
来冻结权重。
vgg.classifier = torch.nn.Linear(25088,2).to(device)for x in vgg.features.parameters():x.requires_grad = Falsesummary(vgg,(1, 3,244,244))
输出是:
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
VGG -- --
├─Sequential: 1-1 [1, 512, 7, 7] --
│ └─Conv2d: 2-1 [1, 64, 244, 244] (1,792)
│ └─ReLU: 2-2 [1, 64, 244, 244] --
│ └─Conv2d: 2-3 [1, 64, 244, 244] (36,928)
│ └─ReLU: 2-4 [1, 64, 244, 244] --
│ └─MaxPool2d: 2-5 [1, 64, 122, 122] --
│ └─Conv2d: 2-6 [1, 128, 122, 122] (73,856)
│ └─ReLU: 2-7 [1, 128, 122, 122] --
│ └─Conv2d: 2-8 [1, 128, 122, 122] (147,584)
│ └─ReLU: 2-9 [1, 128, 122, 122] --
│ └─MaxPool2d: 2-10 [1, 128, 61, 61] --
│ └─Conv2d: 2-11 [1, 256, 61, 61] (295,168)
│ └─ReLU: 2-12 [1, 256, 61, 61] --
│ └─Conv2d: 2-13 [1, 256, 61, 61] (590,080)
│ └─ReLU: 2-14 [1, 256, 61, 61] --
│ └─Conv2d: 2-15 [1, 256, 61, 61] (590,080)
│ └─ReLU: 2-16 [1, 256, 61, 61] --
│ └─MaxPool2d: 2-17 [1, 256, 30, 30] --
│ └─Conv2d: 2-18 [1, 512, 30, 30] (1,180,160)
│ └─ReLU: 2-19 [1, 512, 30, 30] --
│ └─Conv2d: 2-20 [1, 512, 30, 30] (2,359,808)
│ └─ReLU: 2-21 [1, 512, 30, 30] --
│ └─Conv2d: 2-22 [1, 512, 30, 30] (2,359,808)
│ └─ReLU: 2-23 [1, 512, 30, 30] --
│ └─MaxPool2d: 2-24 [1, 512, 15, 15] --
│ └─Conv2d: 2-25 [1, 512, 15, 15] (2,359,808)
│ └─ReLU: 2-26 [1, 512, 15, 15] --
│ └─Conv2d: 2-27 [1, 512, 15, 15] (2,359,808)
│ └─ReLU: 2-28 [1, 512, 15, 15] --
│ └─Conv2d: 2-29 [1, 512, 15, 15] (2,359,808)
│ └─ReLU: 2-30 [1, 512, 15, 15] --
│ └─MaxPool2d: 2-31 [1, 512, 7, 7] --
├─AdaptiveAvgPool2d: 1-2 [1, 512, 7, 7] --
├─Linear: 1-3 [1, 2] 50,178
==========================================================================================
Total params: 14,764,866
Trainable params: 50,178
Non-trainable params: 14,714,688
Total mult-adds (G): 17.99
==========================================================================================
Input size (MB): 0.71
Forward/backward pass size (MB): 128.13
Params size (MB): 59.06
Estimated Total Size (MB): 187.91
==========================================================================================
从摘要中可以看出,这个模型总共包含大约1500万个参数,但其中只有50万个是可训练的 - 这些是分类层的权重。这是好事,因为我们可以用更少的示例来微调更少的参数。
现在让我们使用我们的原始数据集来训练模型。这个过程会花费很长时间,所以我们将使用train_long
函数,它会在等待周期结束之前打印一些中间结果。强烈建议在启用GPU的计算机上运行这个训练!
trainset, testset = torch.utils.data.random_split(dataset,[20000,len(dataset)-20000])
train_loader = torch.utils.data.DataLoader(trainset,batch_size=16)
test_loader = torch.utils.data.DataLoader(testset,batch_size=16)train_long(vgg,train_loader,test_loader,loss_fn=torch.nn.CrossEntropyLoss(),epochs=1,print_freq=90)
输出:
Epoch 0, minibatch 180: train acc = 0.9582182320441989, train loss = 0.12481052967724879
Epoch 0, minibatch 270: train acc = 0.9587177121771218, train loss = 0.14185787918822793
Epoch 0, minibatch 360: train acc = 0.9634695290858726, train loss = 0.14566257719848294
Epoch 0, minibatch 450: train acc = 0.966879157427938, train loss = 0.13402751914149114
Epoch 0, minibatch 540: train acc = 0.9686922365988909, train loss = 0.13931148902766144
Epoch 0, minibatch 630: train acc = 0.9694928684627575, train loss = 0.1386710044510202
Epoch 0, minibatch 720: train acc = 0.970613730929265, train loss = 0.13363790313678375
Epoch 0, minibatch 810: train acc = 0.9709463625154131, train loss = 0.1342217084364885
Epoch 0, minibatch 900: train acc = 0.9721143174250833, train loss = 0.13233261023721474
Epoch 0, minibatch 990: train acc = 0.9726286579212916, train loss = 0.1334670727957871
Epoch 0, minibatch 1080: train acc = 0.9733464384828863, train loss = 0.13777193110039893
Epoch 0, minibatch 1170: train acc = 0.9734735269000854, train loss = 0.14239378162778207
Epoch 0 done, validation acc = 0.9671868747499, validation loss = 0.25287964306816474
看起来我们已经获得了相当准确的猫与狗分类器!让我们把它保存起来以备将来使用!
torch.save(vgg,'data/cats_dogs.pth')
我们随时可以从文件中加载模型。如果下一个实验破坏了模型,您可能会发现这很有用 - 您不必从头开始重新启动。
vgg = torch.load('data/cats_dogs.pth')
微调迁移学习
在上一节中,我们训练了最终的分类器层来对我们自己数据集中的图像进行分类。然而,我们没有重新训练特征提取器,并且我们的模型依赖于模型在ImageNet数据上学到的特征。如果您的对象在视觉上与普通的ImageNet图像不同,那么这些特征的组合可能效果不佳。因此,开始训练卷积层也是有意义的。
为了做到这一点,我们可以解冻之前冻结的卷积滤波器参数。
**注意:**重要的是,您首先冻结参数并进行几个周期的训练,以稳定分类层中的权重。如果您立即开始对解冻参数进行端到端网络的训练,很可能会因为大误差而破坏卷积层中的预训练权重。
for x in vgg.features.parameters():x.requires_grad = True
在解冻后,我们可以进行几个额外的周期训练。您还可以选择较低的学习率,以最小化对预训练权重的影响。然而,即使采用较低的学习率,您可以预期在训练开始时准确度会下降,直到最终达到略高于固定权重情况下的水平。
**注意:**这种训练速度要慢得多,因为我们需要将梯度传播回网络的许多层!您可能希望观察前几个小批次的趋势,然后停止计算。
train_long(vgg,train_loader,test_loader,loss_fn=torch.nn.CrossEntropyLoss(),epochs=1,print_freq=90,lr=0.0001)
输出:
Epoch 0, minibatch 0: train acc = 1.0, train loss = 0.0
Epoch 0, minibatch 90: train acc = 0.8990384615384616, train loss = 0.2978392171335744
Epoch 0, minibatch 180: train acc = 0.9060773480662984, train loss = 0.1658294214069514
Epoch 0, minibatch 270: train acc = 0.9102859778597786, train loss = 0.11819224340009514
Epoch 0, minibatch 360: train acc = 0.9191481994459834, train loss = 0.09244130522920814
Epoch 0, minibatch 450: train acc = 0.9261363636363636, train loss = 0.07583886292451236
Epoch 0, minibatch 540: train acc = 0.928373382624769, train loss = 0.06537413817456822
Epoch 0, minibatch 630: train acc = 0.9318541996830428, train loss = 0.057419379426257924
Epoch 0, minibatch 720: train acc = 0.9361130374479889, train loss = 0.05114534460059813
Epoch 0, minibatch 810: train acc = 0.938347718865598, train loss = 0.04657612246737968
Epoch 0, minibatch 900: train acc = 0.9407602663706992, train loss = 0.04258851655712403
Epoch 0, minibatch 990: train acc = 0.9431130171543896, train loss = 0.03927870595491257
Epoch 0, minibatch 1080: train acc = 0.945536540240518, train loss = 0.03652716609309053
Epoch 0, minibatch 1170: train acc = 0.9463065755764304, train loss = 0.03445258006186286
Epoch 0 done, validation acc = 0.974389755902361, validation loss = 0.005457923144233279
其他CV模型
VGG-16是最简单的计算机视觉架构之一。torchvision
包提供了许多更多预训练网络。其中最常用的是由Microsoft开发的ResNet架构和由Google开发的Inception。例如,让我们探索最简单的ResNet-18模型的架构(ResNet是一个具有不同深度的模型系列,您可以尝试使用ResNet-151来查看真正深度模型的外观)。
resnet = torchvision.models.resnet18()
print(resnet)
输出为:
ResNet((conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)(layer1): Sequential((0): BasicBlock((conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(1): BasicBlock((conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(layer2): Sequential((0): BasicBlock((conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(downsample): Sequential((0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(1): BasicBlock((conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(layer3): Sequential((0): BasicBlock((conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(downsample): Sequential((0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(1): BasicBlock((conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(layer4): Sequential((0): BasicBlock((conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(downsample): Sequential((0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(1): BasicBlock((conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(relu): ReLU(inplace=True)(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))(fc): Linear(in_features=512, out_features=1000, bias=True)
)
如您所见,该模型包含相同的构建块:特征提取器和最终分类器(fc
)。这使得我们可以以与我们一直在使用VGG-16进行迁移学习相同的方式使用该模型。您可以尝试使用上面的代码,将不同的ResNet模型作为基础模型,并查看准确率如何变化。
批量归一化 Batch Normalization
该网络还包含另一种类型的层:批量归一化。批量归一化的思想是将通过神经网络流动的值调整到正确的区间。通常情况下,当所有值都在[-1,1]或[0,1]的范围内时,神经网络的工作效果最佳,这也是我们相应地缩放/规范化输入数据的原因。然而,在训练深度网络时,可能会出现值明显超出此范围的情况,这会使训练变得困难。批量归一化层计算当前小批量的所有值的平均值和标准差,并使用它们在通过神经网络层之前对信号进行归一化。这显着提高了深度网络的稳定性。
总结
使用迁移学习,我们能够快速地为我们的自定义对象分类任务组建一个分类器,并实现高准确率。然而,这个例子并不完全公平,因为原始的VGG-16网络是预先训练的,用于识别猫和狗,因此我们只是重复使用了网络中已经存在的大部分模式。您可以预期在更奇特的领域特定对象上,例如工厂生产线上的细节或不同的树叶上,准确率会更低。
您可以看到,我们现在解决的更复杂的任务需要更高的计算能力,并且不能轻易在CPU上解决。在下一个单元中,我们将尝试使用更轻量级的实现来使用更低的计算资源训练相同的模型,这将导致略低的准确率。
相关文章:

pytorch CV入门3-预训练模型与迁移学习
专栏链接:https://blog.csdn.net/qq_33345365/category_12578430.html 初次编辑:2024/3/7;最后编辑:2024/3/8 参考网站-微软教程:https://learn.microsoft.com/en-us/training/modules/intro-computer-vision-pytorc…...

Swift SwiftUI 学习笔记 2024
Swift SwiftUI 学习笔记 2024 一、资源 视频资源 StanfordUnivercity 公开课 2023: https://cs193p.sites.stanford.edu/2023 教程 Swift 初识:基础语法:https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/…...

【Stable Diffusion】入门:原理简介+应用安装(Windows)+生成步骤
【Stable Diffusion】入门:原理简介应用安装(Windows)生成步骤 原理简介应用安装 原理简介 稳定扩散生成模型(Stable Diffusion)是一种潜在的文本到图像扩散模型,能够在给定任何文本输入的情况下生成照片般逼真的图像。 应用安…...
【栈】第十二届蓝桥杯省赛第一场C++ B组/研究生组《双向排序》(c++)
【题目描述】 给定序列 (a1,a2,⋅⋅⋅,an)(1,2,⋅⋅⋅,n),即 aii。 小蓝将对这个序列进行 m 次操作,每次可能是将 a1,a2,⋅⋅⋅,aqi 降序排列,或者将 aqi,aqi1,⋅⋅⋅,an 升序排列。 请求出操作完成后的序列。 【输入格式】 输入的第一行…...
Gitea 安装和配置
Gitea 安装和配置: http://coffeelatte.vip.cpolar.top/post/software/applications/gitea/gitea_安装和配置/ 文章目录 Gitea 安装和配置: <http://coffeelatte.vip.cpolar.top/post/software/applications/gitea/gitea_%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AE/>…...
CEF JS与c++能够交互的原理 以及 JS 调用C++的流程分析
相关章节:CEF 之 Render进程 与 Browser进程通信 目录 一、JS与c++能够交互的原理 二、JS调用C++ 流程梳理...

关于比特币的AI对话
【ChatGPT】 比特币源码开源吗? 是的,比特币的源码是开源的。比特币项目是在MIT许可证下发布的,这意味着任何人都可以查看、修改、贡献和分发代码。比特币的源码托管在GitHub上,可以通过下面的链接进行访问: https://g…...

Linux查看磁盘命令df-h详解
df -h 是一个常用的 Linux 命令,用于查看文件系统的磁盘使用情况并以易于阅读的方式显示。以下是 df -h 命令的详细解释: -h:以人类可读的格式显示磁盘空间大小。例如,使用 GB、MB、KB 等单位代替字节。 执行 df -h 命令后&…...

nginx-排查一次大文件无法正常下载问题
目录 问题现象&报错信息 问题现象以及分析 nginx报错信息 问题解决 方法1:配置proxy_max_temp_file_size 方法2:关闭proxy_buffering 参考文档 问题现象&报错信息 问题现象以及分析 文件正常从后端服务器直接下载时,一切正常…...

基于yolov5的草莓成熟度检测系统,可进行图像目标检测,也可进行视屏和摄像检测(pytorch框架)【python源码+UI界面+功能源码详解】
功能演示: 基于yolov5的草莓成熟度检测系统,系统既能够实现图像检测,也可以进行视屏和摄像实时检测_哔哩哔哩_bilibili (一)简介 基于yolov5的草莓成熟度系统是在pytorch框架下实现的,这是一个完整的项目…...

Kubesphere 保姆级分析
应用场景 KubeSphere 适用于多种场景,为企业提供容器化的环境,借助完善的管理和运维功能,让企业在数字化转型过程中从容应对各种挑战和各类业务场景,如多云多集群管理、敏捷软件开发、自动化运维、微服务治理、流量管理以及 DevO…...

力扣hot100:240.搜索二维矩阵II(脑子)
吉大21级算法分析与设计的一道大题,由于每一行都是排好序的直接逐行二分 可以达到:O(mlogn)。但是这里追求更广的思路可以使用其他方法。 矩阵四分: 在矩阵中用中心点比较,如果target大于中心点的值,则由于升序排列&am…...
Apache Hive(三)
一、Apache Hive 1、ETL数据清洗 数据问题 问题1:当前数据中,有一些数据的字段为空,不是合法数据 解决:where 过滤 问题2:需求中,需要统计每天、每个小时的消息量,但是数据中没有天和小时字段…...
ORM(对象关系映射)的概念,并说明在Python中如何使用
ORM(对象关系映射)的概念,并说明在Python中如何使用 ORM(对象关系映射)是一种编程技术,它实现了将关系型数据库中的数据映射到程序中的对象模型,使得开发者能够使用面向对象的方式来操作数据…...
Br 算法
基于google的brotli开源,实现Br算法。 #include <brotli/encode.h> #include <brotli/decode.h>namespace br {/*compress unsigned char* content,if ok return non empty unsigned char * */std::string compress_string(const std::string& c…...
GPT实战系列-一种构建LangChain自定义Tool工具的简单方法
GPT实战系列-一种构建LangChain自定义Tool工具的简单方法 LLM大模型: GPT实战系列-探究GPT等大模型的文本生成 GPT实战系列-Baichuan2等大模型的计算精度与量化 GPT实战系列-GPT训练的Pretraining,SFT,Reward Modeling,RLHF …...
【Docker】Memcached 容器化部署
Memcached环境标准软件基于Bitnami Memcached 构建。当前版本为1.6.24 你可以通过轻云UC部署工具直接安装部署,也可以手动按如下文档操作,该项目已经全面开源,可以从如下环境获取 配置文件地址: https://gitee.com/qingplus/qingcloud-platf…...

Langchain-Chatchat本地搭建ChatGLM3模型和提取PDF内容
文章目录 1、软件要求2、安装CUDA2.1、安装gcc2.2、安装CUDA 3、安装Anaconda33.1、下载Anaconda33.2、创建python虚拟环境 4、部署系统4.1、下载源码4.2、安装依赖4.3、下载模型4.4、初始化配置和知识库4.4.1、初始化配置4.4.2、初始化知识库 4.5、运行4.6、运行4.6.1、启动4.…...

案例分析篇03:一篇文章搞定软考设计模式考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)
专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…...

套接字的地址结构,IP地址转换函数,网络编程的接口
目录 一、套接字的地址结构 1.1 通用socket地址结构 1.2 专用socket地址结构 1.2.1 tcp协议族 1.2.3 IP协议族 二、IP地址转换函数 三、网络编程接口 3.1 socket() 3.2 bind() 3.3 listen() 3.4 accept() 3.5 connect() 3.6 close() 3.7 recv()、send() 3.8 recv…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

边缘计算网关提升水产养殖尾水处理的远程运维效率
一、项目背景 随着水产养殖行业的快速发展,养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下,而且难以实现精准监控和管理。为了提升尾水处理的效果和效率,同时降低人力成本,某大型水产养殖企业决定…...
算法250609 高精度
加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...

RabbitMQ 各类交换机
为什么要用交换机? 交换机用来路由消息。如果直发队列,这个消息就被处理消失了,那别的队列也需要这个消息怎么办?那就要用到交换机 交换机类型 1,fanout:广播 特点 广播所有消息:将消息…...

MCP和Function Calling
MCP MCP(Model Context Protocol,模型上下文协议) ,2024年11月底,由 Anthropic 推出的一种开放标准,旨在统一大模型与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而…...