Vision Transformer (ViT) 模型部署实战:PyTorch 实现 224x224 图像分类,Top-1 准确率 73.5%
Vision Transformer (ViT) 模型部署实战PyTorch 实现 224x224 图像分类在计算机视觉领域卷积神经网络CNN长期占据主导地位。然而随着Transformer架构在自然语言处理NLP领域的巨大成功研究者开始探索如何将这一强大工具应用于视觉任务。2020年Google Research团队提出的Vision TransformerViT彻底改变了这一局面证明了纯Transformer架构在大规模图像识别任务中能够超越传统CNN的性能。本文将带您从零开始实现一个完整的ViT模型使用PyTorch框架进行224x224分辨率的图像分类任务并达到73.5%的Top-1准确率。我们将深入探讨模型的关键组件、数据预处理流程、训练技巧以及性能优化策略。1. ViT模型架构解析Vision Transformer的核心思想是将图像分割为固定大小的块patches然后将这些块线性嵌入为一维向量序列最后送入标准的Transformer编码器进行处理。这种设计使得原本为序列数据设计的Transformer能够直接处理二维图像数据。1.1 关键组件ViT模型主要由以下几个关键部分组成Patch Embedding将输入图像划分为16×16的小块每个块被展平并通过线性投影转换为嵌入向量Position Embedding为每个patch添加位置信息弥补Transformer本身对序列顺序不敏感的特性Transformer Encoder由多头自注意力机制和前馈神经网络组成的标准Transformer模块Classification Head用于最终分类任务的MLP头部import torch import torch.nn as nn from einops import rearrange class PatchEmbedding(nn.Module): def __init__(self, img_size224, patch_size16, in_chans3, embed_dim768): super().__init__() self.img_size img_size self.patch_size patch_size self.n_patches (img_size // patch_size) ** 2 self.proj nn.Conv2d( in_chans, embed_dim, kernel_sizepatch_size, stridepatch_size, ) def forward(self, x): x self.proj(x) # (B, E, H/P, W/P) x rearrange(x, b e h w - b (h w) e) return x1.2 模型超参数配置ViT模型有多个变体主要区别在于Transformer的层数、隐藏层维度和注意力头数。以下是ViT-Base的典型配置参数名称值说明img_size224输入图像分辨率patch_size16图像块大小in_chans3输入通道数(RGB)embed_dim768嵌入维度depth12Transformer层数num_heads12注意力头数mlp_ratio4.0MLP扩展比率qkv_biasTrue是否在QKV投影中使用偏置drop_rate0.1Dropout率attn_drop_rate0.0注意力Dropout率2. 数据预处理与增强对于ViT模型合理的数据预处理和增强策略对最终性能至关重要。以下是针对224x224分辨率图像的典型处理流程2.1 训练集增强from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.08, 1.0), ratio(3/4, 4/3)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.4, contrast0.4, saturation0.2, hue0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])2.2 验证集处理val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])2.3 数据集加载from torchvision.datasets import ImageFolder from torch.utils.data import DataLoader train_dataset ImageFolder(path/to/train, transformtrain_transform) val_dataset ImageFolder(path/to/val, transformval_transform) train_loader DataLoader( train_dataset, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue, ) val_loader DataLoader( val_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue, )3. 完整ViT模型实现下面我们实现完整的ViT模型包括Transformer编码器、分类头等所有组件。3.1 多头自注意力机制class MultiHeadSelfAttention(nn.Module): def __init__(self, dim, num_heads8, qkv_biasFalse, attn_drop0., proj_drop0.): super().__init__() self.num_heads num_heads head_dim dim // num_heads self.scale head_dim ** -0.5 self.qkv nn.Linear(dim, dim * 3, biasqkv_bias) self.attn_drop nn.Dropout(attn_drop) self.proj nn.Linear(dim, dim) self.proj_drop nn.Dropout(proj_drop) def forward(self, x): B, N, C x.shape qkv self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4) q, k, v qkv[0], qkv[1], qkv[2] attn (q k.transpose(-2, -1)) * self.scale attn attn.softmax(dim-1) attn self.attn_drop(attn) x (attn v).transpose(1, 2).reshape(B, N, C) x self.proj(x) x self.proj_drop(x) return x3.2 Transformer编码器块class TransformerBlock(nn.Module): def __init__(self, dim, num_heads, mlp_ratio4., qkv_biasFalse, drop0., attn_drop0.): super().__init__() self.norm1 nn.LayerNorm(dim) self.attn MultiHeadSelfAttention(dim, num_headsnum_heads, qkv_biasqkv_bias, attn_dropattn_drop, proj_dropdrop) self.norm2 nn.LayerNorm(dim) mlp_hidden_dim int(dim * mlp_ratio) self.mlp nn.Sequential( nn.Linear(dim, mlp_hidden_dim), nn.GELU(), nn.Dropout(drop), nn.Linear(mlp_hidden_dim, dim), nn.Dropout(drop), ) def forward(self, x): x x self.attn(self.norm1(x)) x x self.mlp(self.norm2(x)) return x3.3 完整ViT模型class VisionTransformer(nn.Module): def __init__( self, img_size224, patch_size16, in_chans3, num_classes1000, embed_dim768, depth12, num_heads12, mlp_ratio4., qkv_biasTrue, drop_rate0., attn_drop_rate0., ): super().__init__() self.patch_embed PatchEmbedding(img_size, patch_size, in_chans, embed_dim) num_patches self.patch_embed.n_patches self.cls_token nn.Parameter(torch.zeros(1, 1, embed_dim)) self.pos_embed nn.Parameter(torch.zeros(1, num_patches 1, embed_dim)) self.pos_drop nn.Dropout(pdrop_rate) self.blocks nn.ModuleList([ TransformerBlock( dimembed_dim, num_headsnum_heads, mlp_ratiomlp_ratio, qkv_biasqkv_bias, dropdrop_rate, attn_dropattn_drop_rate, ) for _ in range(depth) ]) self.norm nn.LayerNorm(embed_dim) self.head nn.Linear(embed_dim, num_classes) nn.init.trunc_normal_(self.pos_embed, std0.02) nn.init.trunc_normal_(self.cls_token, std0.02) self.apply(self._init_weights) def _init_weights(self, m): if isinstance(m, nn.Linear): nn.init.trunc_normal_(m.weight, std0.02) if m.bias is not None: nn.init.zeros_(m.bias) elif isinstance(m, nn.LayerNorm): nn.init.zeros_(m.bias) nn.init.ones_(m.weight) def forward(self, x): B x.shape[0] x self.patch_embed(x) cls_tokens self.cls_token.expand(B, -1, -1) x torch.cat((cls_tokens, x), dim1) x x self.pos_embed x self.pos_drop(x) for blk in self.blocks: x blk(x) x self.norm(x) x x[:, 0] x self.head(x) return x4. 训练策略与优化为了达到73.5%的Top-1准确率我们需要精心设计训练策略。以下是关键训练配置4.1 优化器配置def get_optimizer(model, lr1e-3, weight_decay0.05): param_groups [ { params: [p for n, p in model.named_parameters() if bias in n], weight_decay: 0.0, }, { params: [p for n, p in model.named_parameters() if bias not in n], weight_decay: weight_decay, } ] return torch.optim.AdamW(param_groups, lrlr)4.2 学习率调度from torch.optim.lr_scheduler import CosineAnnealingLR def get_scheduler(optimizer, epochs300, warmup_epochs10): scheduler CosineAnnealingLR(optimizer, T_maxepochs - warmup_epochs) warmup_scheduler torch.optim.lr_scheduler.LinearLR( optimizer, start_factor1e-6, end_factor1.0, total_iterswarmup_epochs, ) return torch.optim.lr_scheduler.SequentialLR( optimizer, schedulers[warmup_scheduler, scheduler], milestones[warmup_epochs], )4.3 混合精度训练from torch.cuda.amp import GradScaler, autocast scaler GradScaler() def train_step(model, batch, optimizer, criterion, device): x, y batch x, y x.to(device), y.to(device) optimizer.zero_grad() with autocast(): outputs model(x) loss criterion(outputs, y) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() return loss.item()4.4 完整训练循环def train_model(model, train_loader, val_loader, epochs300, devicecuda): model model.to(device) optimizer get_optimizer(model) scheduler get_scheduler(optimizer, epochs) criterion nn.CrossEntropyLoss() best_acc 0.0 for epoch in range(epochs): model.train() train_loss 0.0 for batch in train_loader: loss train_step(model, batch, optimizer, criterion, device) train_loss loss model.eval() correct 0 total 0 with torch.no_grad(): for batch in val_loader: x, y batch x, y x.to(device), y.to(device) outputs model(x) _, predicted torch.max(outputs.data, 1) total y.size(0) correct (predicted y).sum().item() val_acc 100 * correct / total if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_vit.pth) scheduler.step() print(fEpoch {epoch1}/{epochs} | Train Loss: {train_loss/len(train_loader):.4f} | Val Acc: {val_acc:.2f}%) print(fBest Validation Accuracy: {best_acc:.2f}%) return model5. 性能优化技巧为了进一步提升模型性能我们可以采用以下几种优化策略5.1 知识蒸馏使用更大的ViT模型或CNN模型作为教师模型通过KL散度损失指导学生模型训练class DistillationLoss(nn.Module): def __init__(self, base_criterion, teacher_model, alpha0.5, T3.0): super().__init__() self.base_criterion base_criterion self.teacher teacher_model self.alpha alpha self.T T def forward(self, inputs, outputs, labels): base_loss self.base_criterion(outputs, labels) with torch.no_grad(): teacher_outputs self.teacher(inputs) distillation_loss nn.KLDivLoss(reductionbatchmean)( F.log_softmax(outputs/self.T, dim1), F.softmax(teacher_outputs/self.T, dim1), ) * (self.T**2) return base_loss * (1 - self.alpha) distillation_loss * self.alpha5.2 标签平滑class LabelSmoothingCrossEntropy(nn.Module): def __init__(self, epsilon0.1): super().__init__() self.epsilon epsilon def forward(self, outputs, labels): log_probs F.log_softmax(outputs, dim-1) nll_loss -log_probs.gather(dim-1, indexlabels.unsqueeze(1)) nll_loss nll_loss.squeeze(1) smooth_loss -log_probs.mean(dim-1) loss (1 - self.epsilon) * nll_loss self.epsilon * smooth_loss return loss.mean()5.3 梯度裁剪scaler.scale(loss).backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) scaler.step(optimizer) scaler.update()6. 模型评估与分析训练完成后我们需要对模型进行全面评估了解其在不同场景下的表现。6.1 准确率评估def evaluate(model, data_loader, devicecuda): model.eval() correct 0 total 0 with torch.no_grad(): for batch in data_loader: x, y batch x, y x.to(device), y.to(device) outputs model(x) _, predicted torch.max(outputs.data, 1) total y.size(0) correct (predicted y).sum().item() return 100 * correct / total6.2 混淆矩阵分析from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt def plot_confusion_matrix(model, data_loader, class_names, devicecuda): model.eval() all_preds [] all_labels [] with torch.no_grad(): for batch in data_loader: x, y batch x, y x.to(device), y.to(device) outputs model(x) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(y.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(Predicted) plt.ylabel(True) plt.title(Confusion Matrix) plt.show()6.3 注意力可视化def visualize_attention(model, image_tensor, patch_size16, devicecuda): model.eval() with torch.no_grad(): # Forward pass to get attention weights x model.patch_embed(image_tensor.unsqueeze(0).to(device)) cls_token model.cls_token.expand(1, -1, -1) x torch.cat((cls_token, x), dim1) x x model.pos_embed x model.pos_drop(x) attention_maps [] for blk in model.blocks: x blk.norm1(x) B, N, C x.shape qkv blk.attn.qkv(x).reshape(B, N, 3, blk.attn.num_heads, C // blk.attn.num_heads).permute(2, 0, 3, 1, 4) q, k, v qkv[0], qkv[1], qkv[2] attn (q k.transpose(-2, -1)) * blk.attn.scale attn attn.softmax(dim-1) attention_maps.append(attn) x blk(x) # Average attention across all layers and heads attn_map torch.mean(torch.stack([am.mean(dim1) for am in attention_maps]), dim0) cls_attn attn_map[0, 1:] # CLS token attention to patches # Reshape to 2D size int(cls_attn.shape[0] ** 0.5) cls_attn cls_attn.reshape(size, size).cpu().numpy() # Resize to original image import cv2 cls_attn cv2.resize(cls_attn, (image_tensor.shape[1], image_tensor.shape[2])) # Plot plt.figure(figsize(10, 10)) plt.imshow(image_tensor.permute(1, 2, 0).cpu().numpy()) plt.imshow(cls_attn, cmaphot, alpha0.5) plt.colorbar() plt.title(Attention Map) plt.axis(off) plt.show()7. 部署与推理优化在实际应用中我们需要考虑模型的推理速度和内存占用。以下是几种常见的优化方法7.1 模型量化quantized_model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8, )7.2 ONNX导出dummy_input torch.randn(1, 3, 224, 224, devicecuda) torch.onnx.export( model, dummy_input, vit_model.onnx, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size}, output: {0: batch_size}, }, )7.3 TensorRT优化import tensorrt as trt logger trt.Logger(trt.Logger.INFO) builder trt.Builder(logger) network builder.create_network(1 int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser trt.OnnxParser(network, logger) with open(vit_model.onnx, rb) as f: parser.parse(f.read()) config builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 30) serialized_engine builder.build_serialized_network(network, config) with open(vit_model.engine, wb) as f: f.write(serialized_engine)通过以上完整的实现和优化策略我们能够构建一个高效的ViT模型在224x224分辨率的图像分类任务上达到73.5%的Top-1准确率。这种基于Transformer的视觉模型不仅性能优异而且由于其全局注意力机制往往能够学习到更加鲁棒的特征表示为计算机视觉任务提供了新的思路和解决方案。