vgg16添加注意力模块做图像分类

1、传统VGG有以下几个版本:

我们拿出vgg16举例,其模型实现如下:

import torch
import torch.nn as nn

# 定义VGG16模型
class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x

# 创建VGG16模型实例
model = VGG16()

# 打印模型结构
print(model)

受现代transformers思路启发,我打算在VGG16模型的特定层之间添加自注意力模块,以使模型能够自动学习图像中的关键区域。这样做需要对VGG16模型的结构进行修改,并添加自注意力模块的定义和连接,使用注意力机制替代VGG16模型中的卷积层。

import torch
import torch.nn as nn

# 定义自注意力层
class SelfAttention(nn.Module):
    def __init__(self, in_channels):
        super(SelfAttention, self).__init__()
        self.query_conv = nn.Conv2d(in_channels, in_channels // 8, kernel_size=1)
        self.key_conv = nn.Conv2d(in_channels, in_channels // 8, kernel_size=1)
        self.value_conv = nn.Conv2d(in_channels, in_channels, kernel_size=1)
        self.gamma = nn.Parameter(torch.zeros(1))

    def forward(self, x):
        batch_size, channels, height, width = x.size()
        query = self.query_conv(x).view(batch_size, -1, height * width).permute(0, 2, 1)
        key = self.key_conv(x).view(batch_size, -1, height * width)
        energy = torch.bmm(query, key)
        attention = torch.softmax(energy, dim=-1)
        value = self.value_conv(x).view(batch_size, -1, height * width)
        out = torch.bmm(value, attention.permute(0, 2, 1))
        out = out.view(batch_size, channels, height, width)
        out = self.gamma * out + x
        return out

# 定义VGG16模型
class VGG16Attention(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16Attention, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            SelfAttention(64),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            SelfAttention(128),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)
        )
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x

# 创建VGG16Attention模型实例
model = VGG16Attention()

# 打印模型结构
print(model)

它的结构图大概如下:

​
Input (224x224x3)

Convolutional Layer: 64 filters, 3x3 kernel, padding=1
ReLU Activation

Self-Attention Layer

Convolutional Layer: 64 filters, 3x3 kernel, padding=1
ReLU Activation

Max Pooling Layer: 2x2 pool size, stride=2

Convolutional Layer: 128 filters, 3x3 kernel, padding=1
ReLU Activation

Self-Attention Layer

Convolutional Layer: 128 filters, 3x3 kernel, padding=1
ReLU Activation

Max Pooling Layer: 2x2 pool size, stride=2

Convolutional Layer: 256 filters, 3x3 kernel, padding=1
ReLU Activation

Convolutional Layer: 256 filters, 3x3 kernel, padding=1
ReLU Activation

Convolutional Layer: 256 filters, 3x3 kernel, padding=1
ReLU Activation

Max Pooling Layer: 2x2 pool size, stride=2

Convolutional Layer: 512 filters, 3x3 kernel, padding=1
ReLU Activation

Convolutional Layer: 512 filters, 3x3 kernel, padding=1
ReLU Activation

Convolutional Layer: 512 filters, 3x3 kernel, padding=1
ReLU Activation

Max Pooling Layer: 2x2 pool size, stride=2

Convolutional Layer: 512 filters, 3x3 kernel, padding=1
ReLU Activation

Convolutional Layer: 512 filters, 3x3 kernel, padding=1
ReLU Activation

Convolutional Layer: 512 filters, 3x3 kernel, padding=1
ReLU Activation

Max Pooling Layer: 2x2 pool size, stride=2

Flatten

Fully Connected Layer: 4096 units
ReLU Activation

Dropout

Fully Connected Layer: 4096 units
ReLU Activation

Dropout

Fully Connected Layer: num_classes units

Output

​

接着我们用添加注意力机制的VGG16做图像分类训练:

​
import torch.optim as optim
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.utils.data
import torch.utils.data.distributed
import torchvision.transforms as transforms
from dataset.dataset import SeedlingData
from torch.autograd import Variable
from torchvision.models import vgg16

# 设置全局参数
modellr = 1e-4
BATCH_SIZE = 32
EPOCHS = 100
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 数据预处理

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])

])
transform_test = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
dataset_train = SeedlingData('data/train', transforms=transform, train=True)
dataset_test = SeedlingData("data/train", transforms=transform_test, train=False)
# 读取数据
print(dataset_train.imgs)

# 导入数据
train_loader = torch.utils.data.DataLoader(dataset_train, batch_size=BATCH_SIZE, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset_test, batch_size=BATCH_SIZE, shuffle=False)

# 实例化模型并且移动到GPU
criterion = nn.CrossEntropyLoss()
model_ft = VGG16Attention()
model_ft.classifier = classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 3),
        )
model_ft.to(DEVICE)
optimizer = optim.Adam(model_ft.parameters(), lr=modellr)


def adjust_learning_rate(optimizer, epoch):
    """Sets the learning rate to the initial LR decayed by 10 every 30 epochs"""
    modellrnew = modellr * (0.1 ** (epoch // 50))
    print("lr:", modellrnew)
    for param_group in optimizer.param_groups:
        param_group['lr'] = modellrnew


# 定义训练过程

def train(model, device, train_loader, optimizer, epoch):
    model.train()
    sum_loss = 0
    total_num = len(train_loader.dataset)
    print(total_num, len(train_loader))
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = Variable(data).to(device), Variable(target).to(device)
        output = model(data)
        loss = criterion(output, target)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        print_loss = loss.data.item()
        sum_loss += print_loss
        if (batch_idx + 1) % 10 == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, (batch_idx + 1) * len(data), len(train_loader.dataset),
                       100. * (batch_idx + 1) / len(train_loader), loss.item()))
    ave_loss = sum_loss / len(train_loader)
    print('epoch:{},loss:{}'.format(epoch, ave_loss))


# 验证过程
def val(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    total_num = len(test_loader.dataset)
    print(total_num, len(test_loader))
    with torch.no_grad():
        for data, target in test_loader:
            data, target = Variable(data).to(device), Variable(target).to(device)
            output = model(data)
            loss = criterion(output, target)
            _, pred = torch.max(output.data, 1)
            correct += torch.sum(pred == target)
            print_loss = loss.data.item()
            test_loss += print_loss
        correct = correct.data.item()
        acc = correct / total_num
        avgloss = test_loss / len(test_loader)
        print('\nVal set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
            avgloss, correct, len(test_loader.dataset), 100 * acc))


# 训练

for epoch in range(1, EPOCHS + 1):
    adjust_learning_rate(optimizer, epoch)
    train(model_ft, DEVICE, train_loader, optimizer, epoch)
    val(model_ft, DEVICE, test_loader)
torch.save(model_ft, 'model.pth')

​

还可以在VGG16模型中添加Triplet Attention模块。Triplet Attention由3个平行的Branch组成,其中两个负责捕获通道C和空间H或W之间的跨维交互。最后一个Branch类似于CBAM,用于构建Spatial Attention。最终3个Branch的输出使用平均进行聚合。这是通过使用三分支结构捕捉交叉维度交互来计算注意权重的新方法。对于输入张量,Triplet Attention通过旋转操作和残差变换建立维度间依赖关系,并以可忽略的计算开销对通道间和空间信息进行编码。(如图)

具体实现代码:

import torch
import torch.nn as nn
import torch.nn.functional as F

class TripletAttention(nn.Module):
    def __init__(self, in_channels):
        super(TripletAttention, self).__init__()
        self.conv_query = nn.Conv2d(in_channels, in_channels // 8, kernel_size=1)
        self.conv_key = nn.Conv2d(in_channels, in_channels // 8, kernel_size=1)
        self.conv_value = nn.Conv2d(in_channels, in_channels, kernel_size=1)

    def forward(self, x):
        query = self.conv_query(x)
        key = self.conv_key(x)
        value = self.conv_value(x)

        query = query.view(query.size(0), -1, query.size(2) * query.size(3))
        key = key.view(key.size(0), -1, key.size(2) * key.size(3))
        value = value.view(value.size(0), -1, value.size(2) * value.size(3))

        attention_map = torch.bmm(query.permute(0, 2, 1), key)
        attention_map = F.softmax(attention_map, dim=-1)

        attended_features = torch.bmm(value, attention_map.permute(0, 2, 1))
        attended_features = attended_features.view(x.size())

        return attended_features

Triplet Attention模块可以与VGG模型结合使用。下面是如何将Triplet Attention模块添加到VGG模型的某些层中:

import torch
import torch.nn as nn
import torchvision.models as models

class VGGWithTripletAttention(nn.Module):
    def __init__(self, attention_layers):
        super(VGGWithTripletAttention, self).__init__()
        self.vgg = models.vgg16(pretrained=True).features
        self.attention_layers = attention_layers

        for layer_idx in attention_layers:
            setattr(self, f"attention_{layer_idx}", TripletAttention(256))

    def forward(self, x):
        features = []
        for idx, module in enumerate(self.vgg):
            x = module(x)
            if idx in self.attention_layers:
                attention_module = getattr(self, f"attention_{idx}")
                x = attention_module(x)
                features.append(x)
        
        return features

# Example usage
vgg_model = VGGWithTripletAttention(attention_layers=[10, 17, 24])
input_tensor = torch.randn((1, 3, 224, 224))
output_features = vgg_model(input_tensor)

在构造函数中,我们首先加载了预训练的VGG模型,并指定了我们要在哪些层上添加Triplet Attention模块(在这里是第10、17和24层)。

以上为全部内容!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值