图像分类篇:pytorch实现VGG

一、VGG网络详解及感受野计算

VGG在2014年由牛津大学著名研究组VGG(Visual Geometry Group)提出,斩获该年ImageNet竞赛中Localization Task(定位任务)第一名和Classification Task(分类任务)第二名。VGG有两种结构,分别是VGG16和VGG19,两者并没有本质上的区别,只是网络深度不一样。

VGG网络的创新点:通过堆叠多个小卷积核来代替大尺度卷积核,可以减少训练参数,同时能保证相同的感受野。

1.CNN感受野

在卷积神经网络中,决定某一层输出结果中一个元素所对应的输入层的区域大小,被称作感受野(receptive field),通俗的解释是,输出feature map上的一个单元对应输入层的区域大小。

  以上图为例,输出层layer3中一个单元对应输入层layer2上区域大小为2x2(池化操作),对应输入层layer1上大小为5x5,可以理解为,layer2中2x2区域中的每一块对应一个3x3的卷积核,又因为stride=2,所以layer1的感受野为5x5.

感受野的计算公式为:

F(i)=(F(i+1)-1)*Stride +Ksize

  •  F(i)为第i层感受野
  • Stride为第i层的步距
  • Ksize为卷积核或池化核尺寸

以上图为例:

Feature map:F(3)=1

Pool1:F(2)=(1-1)* 2 + 2=2

Conv1:F(1)=(2-1)* 2 + 3=5

2.小卷积核

在VGG网络中的创新点:通过堆叠多个小卷积核来代替大尺度卷积核,可以减少训练参数,同时能保证相同的感受野。

1)堆叠两个3x3的卷积核替代5x5的卷积核,堆叠三个3x3的卷积核替代7x7的卷积核。比较替代前后感受野是否相等。(在VGG网络中卷积的Stride默认为1)

  • Feature map:F=1
  • Conv3x3(3):F=(1-1)x1+3=3
  • Conv3x3(2):F=(3-1)x1+3=5
  • Conv3x3(1):F=(5-1)x1+3=7(7x7的卷积核感受野)

2)是否减少了训练的参数?

CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度

使用7x7卷积核所需的参数,与堆叠三个3x3卷积核所需参数(假设输入输出channel为C)

  • 7*7*C*C=49C^{2}
  • 3*3*C*C+3*3*C*C+3*3*C*C=27C^{2}

由上式可见,训练参数是减少了。

3.VGG-16

我们最常用的VGG模型就是VGG-16模型,其网络结构如下图所示:

 通过计算可以得出,经过3x3卷积的特征矩阵的尺寸是不改变的

二、pytorch搭建VGG网络

1.model.py

跟上一篇AlexNet中网络模型的定义一样,VGG网络也是分为 卷积层提取特征 和 全连接层进行分类 这两个模块:

import torch.nn as nn
import torch

class VGG(nn.Module):
    def __init__(self, features, num_classes=1000, init_weights=False):
        super(VGG, self).__init__()
        self.features = features			# 卷积层提取特征
        self.classifier = nn.Sequential(	# 全连接层进行分类
            nn.Dropout(p=0.5),
            nn.Linear(512*7*7, 2048),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(True),
            nn.Linear(2048, num_classes)
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.features(x)
        # N x 512 x 7 x 7
        x = torch.flatten(x, start_dim=1)
        # N x 512*7*7
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

 不同的是,VGG网络有 VGG-13、VGG-16等多种网络结构,能不能将这几种结构统一成一个模型呢?
以上图的A、B、D、E模型为例,其全连接层完全一样,卷积层只有卷积核个数稍有不同

# vgg网络模型配置列表,数字表示卷积核个数,'M'表示最大池化层
cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],											# 模型A
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],									# 模型B
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],					# 模型D
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'], 	# 模型E
}

# 卷积层提取特征
def make_features(cfg: list): # 传入的是具体某个模型的参数列表
    layers = []
    in_channels = 3		# 输入的原始图像(rgb三通道)
    for v in cfg:
        # 最大池化层
        if v == "M":
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        # 卷积层
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            layers += [conv2d, nn.ReLU(True)]
            in_channels = v
    return nn.Sequential(*layers)  # 单星号(*)将参数以元组(tuple)的形式导入


def vgg(model_name="vgg16", **kwargs):  # 双星号(**)将参数以字典的形式导入
    try:
        cfg = cfgs[model_name]
    except:
        print("Warning: model number {} not in cfgs dict!".format(model_name))
        exit(-1)
    model = VGG(make_features(cfg), **kwargs)
    return model

训练结果截图:

训练10次共花费时间32分钟

预测结果:为郁金香的概率最高:63.4%,预测时间7秒

 

 

数据集:花分类数据集,3306张图片作为训练集,364张图片作为测试集

**参考:Python 函数参数前面一个星号(*)和两个星号(**)的区别

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用 VGG 算法进行 CIFAR-10 图像分类PyTorch 代码: ```python import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as datasets import torchvision.transforms as transforms # 定义 VGG 网络 class VGG(nn.Module): def __init__(self): super(VGG, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(128, 256, kernel_size=3, padding=1) self.conv4 = nn.Conv2d(256, 512, kernel_size=3, padding=1) self.conv5 = nn.Conv2d(512, 512, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(kernel_size=2, stride=2) self.fc1 = nn.Linear(512 * 4 * 4, 4096) self.fc2 = nn.Linear(4096, 4096) self.fc3 = nn.Linear(4096, 10) self.relu = nn.ReLU(inplace=True) self.dropout = nn.Dropout() def forward(self, x): x = self.conv1(x) x = self.relu(x) x = self.pool(x) x = self.conv2(x) x = self.relu(x) x = self.pool(x) x = self.conv3(x) x = self.relu(x) x = self.conv4(x) x = self.relu(x) x = self.pool(x) x = self.conv5(x) x = self.relu(x) x = self.pool(x) x = x.view(-1, 512 * 4 * 4) x = self.fc1(x) x = self.relu(x) x = self.dropout(x) x = self.fc2(x) x = self.relu(x) x = self.dropout(x) x = self.fc3(x) return x # 数据预处理 transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 加载数据集 trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2) testset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test) testloader = torch.utils.data.DataLoader(testset, batch_size=100, shuffle=False, num_workers=2) # 定义模型、损失函数和优化器 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") net = VGG().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4) # 训练模型 for epoch in range(100): running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100)) running_loss = 0.0 # 测试模型 correct = 0 total = 0 with torch.no_grad(): for data in testloader: inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) outputs = net(inputs) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total)) ``` 在上面的代码中,我们定义了一个 VGG 网络,然后使用 CIFAR-10 数据集进行训练。我们使用了数据增强技术,包括随机裁剪和随机水平翻转,以提高模型的泛化能力。我们将模型的参数保存在 GPU 上,如果 GPU 可用的话。我们使用随机梯度下降(SGD)作为优化器,并使用交叉熵损失作为损失函数。在训练过程中,我们每训练 100 个批次就输出一次损失值。最后,我们使用测试集评估模型的准确率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值