基于迁移学习实现宠物分类(oxford-iiit-pet数据集)任务

一、项目概述

1.1 项目背景

宠物分类是计算机视觉中的重要应用场景,旨在自动识别不同品种的猫狗。本项目使用Oxford-IIIT Pet数据集,通过迁移学习技术构建高效准确的宠物分类模型。

1.2 数据集介绍

Oxford-IIIT Pet数据集包含:

  • 37个宠物类别(12种猫+25种狗)

  • 7349张图片(训练集:3680,测试集:3669)

  • 每张图片包含:

    • 宠物主体分割掩码

    • 头部区域标注

    • 品种标签(如"Abyssinian", "Persian"等)

1.3 项目目标

  1. 实现多类别宠物品种分类

  2. 达到90%以上的分类准确率

  3. 展示迁移学习在实际应用中的优势

二、技术架构

2.1 核心思想:迁移学习

复制代码
迁移学习的优势:
1. 利用在大规模数据集(如ImageNet)上预训练的模型
2. 只需要少量数据即可达到良好效果
3. 减少训练时间和计算资源需求

2.2 技术栈

  • 深度学习框架:PyTorch / TensorFlow

  • 预训练模型

    • ResNet50/101

    • EfficientNet

    • VGG16

    • MobileNetV2(轻量化部署)

  • 数据增强:Albumentations或Torchvision Transforms

  • 评估指标:准确率、F1-score、混淆矩阵

三、项目实施步骤

Resnet预训练模型介绍

Resnet是计算机视觉(CV)领域著名的预训练模型,ResNet50是计算机视觉领域最具影响力的深度学习架构之一,由何恺明等人在2015年提出。它解决了深度神经网络中的退化问题,使得训练极深的网络成为可能。下面详细介绍ResNet50的架构与设计思想.

  • 传统网络:直接学习目标映射 H(x)
  • 残差网络:学习残差映射 F(x) = H(x) - x,最终输出为 F(x) + x

这种设计允许梯度直接流过恒等映射(identity mapping)路径,有效缓解了梯度消失问题,使得训练上百层的网络成为可能。

ResNet50总共有50层(按权重层计算),由以下部分组成:

  1. 初始层:7×7卷积 + 最大池化
  2. 4个残差阶段(Stage),每个阶段包含多个残差块
  3. 全局平均池化层
  4. 1000类分类器(原始版本用于ImageNet)

ResNet50使用Bottleneck结构作为基本单元,每个Bottleneck包含3个卷积层:

  1. 1×1卷积:降维,减少计算量
  2. 3×3卷积:空间特征提取
  3. 1×1卷积:升维,恢复通道数

具体公式:y = F(x, {Wi}) + x

  • F(x)是残差函数
  • x是输入
  • y是输出
  • "+"操作通过元素相加实现,要求F(x)和x维度相同

基于Resnet50和Resnet101实现宠物分类任务

下面给出基于Resnet50实现cifar-10数据集分类任务的具体实现代码:

1.导包与预训练模型的导入

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, random_split
import os
from tqdm import tqdm
import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import seaborn as sns
import time

# 设置随机种子以保证可重复性
def set_seed(seed=42):
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    np.random.seed(seed)

set_seed(42)

# 检查是否有可用的GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")

2.数据准备和增强

python 复制代码
# 1. 数据准备和增强
def get_data_transforms():
    """
    定义训练和验证的数据转换
    """
    # 图像大小调整到适合预训练模型
    IMG_SIZE = 224
    
    train_transform = transforms.Compose([
        transforms.RandomResizedCrop(IMG_SIZE, scale=(0.8, 1.0)),
        transforms.RandomHorizontalFlip(p=0.5),
        transforms.RandomRotation(15),
        transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
        transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    
    val_transform = transforms.Compose([
        transforms.Resize((IMG_SIZE, IMG_SIZE)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    
    return train_transform, val_transform

# 2. 加载Oxford-IIIT Pet数据集
def load_pet_dataset(batch_size=32):
    """
    加载和准备宠物数据集
    """
    train_transform, val_transform = get_data_transforms()
    
    # 下载并加载数据集
    train_val_dataset = torchvision.datasets.OxfordIIITPet(
        root='./data/oxford_pets',
        split='trainval',
        target_types='category',
        download=False,
        transform=train_transform  # 暂时使用train_transform,后面会分割
    )
    
    test_dataset = torchvision.datasets.OxfordIIITPet(
        root='./data/oxford_pets',
        split='test',
        target_types='category',
        download=False,
        transform=val_transform
    )
    
    # 分割训练集和验证集
    dataset_size = len(train_val_dataset)
    train_size = int(0.85 * dataset_size)  # 85%用于训练
    val_size = dataset_size - train_size
    
    train_dataset, val_dataset = random_split(train_val_dataset, [train_size, val_size])
    
    # 为训练集应用训练变换,验证集应用验证变换
    # 注意:我们需要手动设置变换,因为random_split会保留原变换
    train_dataset.dataset.transform = train_transform
    val_dataset.dataset.transform = val_transform
    
    print(f"训练集大小: {len(train_dataset)}")
    print(f"验证集大小: {len(val_dataset)}")
    print(f"测试集大小: {len(test_dataset)}")
    print(f"类别数量: {len(train_val_dataset.classes)}")
    
    # 创建数据加载器
    train_loader = DataLoader(
        train_dataset,
        batch_size=batch_size,
        shuffle=True,
        num_workers=4,
        pin_memory=True
    )
    
    val_loader = DataLoader(
        val_dataset,
        batch_size=batch_size,
        shuffle=False,
        num_workers=4,
        pin_memory=True
    )
    
    test_loader = DataLoader(
        test_dataset,
        batch_size=batch_size,
        shuffle=False,
        num_workers=4,
        pin_memory=True
    )
    
    return train_loader, val_loader, test_loader, train_val_dataset.classes

3.构建网络模型

python 复制代码
# 3. 定义模型架构(使用预训练的ResNet50)
class PetClassifier(nn.Module):
    def __init__(self, num_classes=37, dropout_rate=0.5):
        super(PetClassifier, self).__init__()
        
        # 使用预训练的ResNet50作为特征提取器
        self.backbone = torchvision.models.resnet50(pretrained=True)
        
        # 冻结前几层(可选,可根据需要调整)
        for param in list(self.backbone.parameters())[:100]:
            param.requires_grad = False
        
        # 获取ResNet的特征维度
        num_features = self.backbone.fc.in_features
        
        # 替换最后的全连接层
        self.backbone.fc = nn.Sequential(
            nn.Dropout(dropout_rate),
            nn.Linear(num_features, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(),
            nn.Dropout(dropout_rate/2),
            nn.Linear(512, 256),
            nn.BatchNorm1d(256),
            nn.ReLU(),
            nn.Dropout(dropout_rate/3),
            nn.Linear(256, num_classes)
        )
        
    def forward(self, x):
        return self.backbone(x)

4.编写训练函数和推理函数

python 复制代码
# 3. 定义模型架构(使用预训练的ResNet50)
class PetClassifier(nn.Module):
    def __init__(self, num_classes=37, dropout_rate=0.5):
        super(PetClassifier, self).__init__()
        
        # 使用预训练的ResNet50作为特征提取器
        self.backbone = torchvision.models.resnet50(pretrained=True)
        
        # 冻结前几层(可选,可根据需要调整)
        for param in list(self.backbone.parameters())[:100]:
            param.requires_grad = False
        
        # 获取ResNet的特征维度
        num_features = self.backbone.fc.in_features
        
        # 替换最后的全连接层
        self.backbone.fc = nn.Sequential(
            nn.Dropout(dropout_rate),
            nn.Linear(num_features, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(),
            nn.Dropout(dropout_rate/2),
            nn.Linear(512, 256),
            nn.BatchNorm1d(256),
            nn.ReLU(),
            nn.Dropout(dropout_rate/3),
            nn.Linear(256, num_classes)
        )
        
    def forward(self, x):
        return self.backbone(x)

# 4. 训练和评估函数
class PetTrainer:
    def __init__(self, model, device, num_classes=37):
        self.model = model.to(device)
        self.device = device
        self.num_classes = num_classes
        
        # 使用交叉熵损失
        self.criterion = nn.CrossEntropyLoss(label_smoothing=0.1)  # 标签平滑防止过拟合
        
        # 使用AdamW优化器(比Adam更好)
        self.optimizer = optim.AdamW(
            filter(lambda p: p.requires_grad, self.model.parameters()),
            lr=0.0001,
            weight_decay=1e-4
        )
        
        # 使用余弦退火学习率调度器
        self.scheduler = optim.lr_scheduler.CosineAnnealingLR(
            self.optimizer, 
            T_max=20,  # 20个epoch后重置
            eta_min=1e-6
        )
        
        # 添加早停机制
        self.best_val_acc = 0.0
        self.patience = 10
        self.counter = 0
        
    def train_epoch(self, train_loader):
        self.model.train()
        running_loss = 0.0
        all_preds = []
        all_labels = []
        
        # 使用tqdm显示训练进度
        pbar = tqdm(train_loader, desc="训练中", leave=False)
        for batch_idx, (inputs, labels) in enumerate(pbar):
            inputs, labels = inputs.to(self.device), labels.to(self.device)
            
            # 梯度清零
            self.optimizer.zero_grad()
            
            # 前向传播
            outputs = self.model(inputs)
            loss = self.criterion(outputs, labels)
            
            # 反向传播和优化
            loss.backward()
            
            # 梯度裁剪防止梯度爆炸
            torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)
            
            self.optimizer.step()
            
            # 统计
            running_loss += loss.item()
            _, predicted = outputs.max(1)
            all_preds.extend(predicted.cpu().numpy())
            all_labels.extend(labels.cpu().numpy())
            
            # 更新进度条
            pbar.set_postfix({'loss': loss.item()})
        
        # 计算准确率
        train_acc = accuracy_score(all_labels, all_preds)
        train_loss = running_loss / len(train_loader)
        
        return train_loss, train_acc
    
    def validate(self, val_loader):
        self.model.eval()
        running_loss = 0.0
        all_preds = []
        all_labels = []
        
        with torch.no_grad():
            pbar = tqdm(val_loader, desc="验证中", leave=False)
            for inputs, labels in pbar:
                inputs, labels = inputs.to(self.device), labels.to(self.device)
                
                outputs = self.model(inputs)
                loss = self.criterion(outputs, labels)
                
                running_loss += loss.item()
                _, predicted = outputs.max(1)
                all_preds.extend(predicted.cpu().numpy())
                all_labels.extend(labels.cpu().numpy())
        
        val_acc = accuracy_score(all_labels, all_preds)
        val_loss = running_loss / len(val_loader)
        
        return val_loss, val_acc
    
    def test(self, test_loader):
        self.model.eval()
        all_preds = []
        all_labels = []
        all_probs = []
        
        with torch.no_grad():
            pbar = tqdm(test_loader, desc="测试中")
            for inputs, labels in pbar:
                inputs, labels = inputs.to(self.device), labels.to(self.device)
                
                outputs = self.model(inputs)
                probs = torch.nn.functional.softmax(outputs, dim=1)
                _, predicted = outputs.max(1)
                
                all_preds.extend(predicted.cpu().numpy())
                all_labels.extend(labels.cpu().numpy())
                all_probs.extend(probs.cpu().numpy())
        
        test_acc = accuracy_score(all_labels, all_preds)
        
        return test_acc, all_preds, all_labels, all_probs
    
    def train(self, train_loader, val_loader, num_epochs=30):
        history = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}
        
        print("开始训练...")
        start_time = time.time()
        
        for epoch in range(num_epochs):
            print(f"\nEpoch {epoch+1}/{num_epochs}")
            print("-" * 50)
            
            # 训练一个epoch
            train_loss, train_acc = self.train_epoch(train_loader)
            
            # 验证
            val_loss, val_acc = self.validate(val_loader)
            
            # 更新学习率
            self.scheduler.step()
            
            # 保存历史记录
            history['train_loss'].append(train_loss)
            history['train_acc'].append(train_acc)
            history['val_loss'].append(val_loss)
            history['val_acc'].append(val_acc)
            
            # 打印结果
            current_lr = self.optimizer.param_groups[0]['lr']
            print(f"训练损失: {train_loss:.4f}, 训练准确率: {train_acc:.4f}")
            print(f"验证损失: {val_loss:.4f}, 验证准确率: {val_acc:.4f}")
            print(f"学习率: {current_lr:.6f}")
            
            # 早停检查和保存最佳模型
            if val_acc > self.best_val_acc:
                self.best_val_acc = val_acc
                self.counter = 0
                # 保存最佳模型
                torch.save({
                    'epoch': epoch,
                    'model_state_dict': self.model.state_dict(),
                    'optimizer_state_dict': self.optimizer.state_dict(),
                    'val_acc': val_acc,
                }, 'best_pet_classifier.pth')
                print(f"保存最佳模型,验证准确率: {val_acc:.4f}")
            else:
                self.counter += 1
                if self.counter >= self.patience:
                    print(f"早停触发,在epoch {epoch+1}停止训练")
                    break
        
        # 加载最佳模型
        checkpoint = torch.load('best_pet_classifier.pth')
        self.model.load_state_dict(checkpoint['model_state_dict'])
        
        training_time = time.time() - start_time
        print(f"\n训练完成!总共用时: {training_time:.2f}秒")
        print(f"最佳验证准确率: {self.best_val_acc:.4f}")
        
        return history

5.编写可视化函数

python 复制代码
# 5. 可视化函数
def plot_training_history(history):
    """绘制训练历史曲线"""
    fig, axes = plt.subplots(1, 2, figsize=(15, 5))
    
    # 绘制损失曲线
    axes[0].plot(history['train_loss'], label='训练损失')
    axes[0].plot(history['val_loss'], label='验证损失')
    axes[0].set_xlabel('Epoch')
    axes[0].set_ylabel('损失')
    axes[0].set_title('训练和验证损失曲线')
    axes[0].legend()
    axes[0].grid(True)
    
    # 绘制准确率曲线
    axes[1].plot(history['train_acc'], label='训练准确率')
    axes[1].plot(history['val_acc'], label='验证准确率')
    axes[1].set_xlabel('Epoch')
    axes[1].set_ylabel('准确率')
    axes[1].set_title('训练和验证准确率曲线')
    axes[1].legend()
    axes[1].grid(True)
    
    plt.tight_layout()
    plt.savefig('training_history.png', dpi=100)
    plt.show()

def plot_confusion_matrix(y_true, y_pred, class_names):
    """绘制混淆矩阵"""
    cm = confusion_matrix(y_true, y_pred)
    
    plt.figure(figsize=(12, 10))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
                xticklabels=class_names, yticklabels=class_names)
    plt.title('混淆矩阵')
    plt.xlabel('预测标签')
    plt.ylabel('真实标签')
    plt.xticks(rotation=45, ha='right')
    plt.tight_layout()
    plt.savefig('confusion_matrix.png', dpi=100)
    plt.show()

def visualize_predictions(model, test_loader, class_names, device, num_samples=10):
    """可视化部分预测结果"""
    model.eval()
    images_shown = 0
    
    with torch.no_grad():
        for inputs, labels in test_loader:
            inputs, labels = inputs.to(device), labels.to(device)
            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)
            probs = torch.nn.functional.softmax(outputs, dim=1)
            
            inputs = inputs.cpu()
            labels = labels.cpu()
            preds = preds.cpu()
            probs = probs.cpu()
            
            fig, axes = plt.subplots(2, 5, figsize=(15, 6))
            axes = axes.ravel()
            
            for idx in range(min(num_samples, len(inputs))):
                # 反标准化图像
                img = inputs[idx].numpy().transpose(1, 2, 0)
                mean = np.array([0.485, 0.456, 0.406])
                std = np.array([0.229, 0.224, 0.225])
                img = std * img + mean
                img = np.clip(img, 0, 1)
                
                axes[idx].imshow(img)
                true_label = class_names[labels[idx]]
                pred_label = class_names[preds[idx]]
                prob = probs[idx][preds[idx]].item()
                
                color = 'green' if labels[idx] == preds[idx] else 'red'
                axes[idx].set_title(f"真实: {true_label}\n预测: {pred_label}\n置信度: {prob:.2f}", 
                                  color=color, fontsize=9)
                axes[idx].axis('off')
                
                images_shown += 1
                if images_shown >= num_samples:
                    break
            
            plt.tight_layout()
            plt.savefig('sample_predictions.png', dpi=100)
            plt.show()
            break

6.集成学习增强(可选)

python 复制代码
# 6. 集成学习增强(可选)
class EnsembleModel:
    def __init__(self, model_paths, num_classes=37, device='cuda'):
        self.models = []
        self.device = device
        
        for path in model_paths:
            model = PetClassifier(num_classes=num_classes)
            checkpoint = torch.load(path, map_location=device)
            model.load_state_dict(checkpoint['model_state_dict'])
            model.to(device)
            model.eval()
            self.models.append(model)
    
    def predict(self, inputs):
        all_probs = []
        
        with torch.no_grad():
            for model in self.models:
                outputs = model(inputs)
                probs = torch.nn.functional.softmax(outputs, dim=1)
                all_probs.append(probs)
        
        # 平均所有模型的概率
        avg_probs = torch.stack(all_probs).mean(0)
        _, predictions = torch.max(avg_probs, 1)
        
        return predictions, avg_probs

7.编写主函数

python 复制代码
# 7. 主程序
def main():
    # 参数设置
    BATCH_SIZE = 32
    NUM_EPOCHS = 40
    NUM_CLASSES = 37
    
    print("=" * 60)
    print("Oxford-IIIT Pet 数据集分类任务")
    print("=" * 60)
    
    # 加载数据
    print("\n1. 加载数据集...")
    train_loader, val_loader, test_loader, class_names = load_pet_dataset(BATCH_SIZE)
    
    # 创建模型
    print("\n2. 创建模型...")
    model = PetClassifier(num_classes=NUM_CLASSES, dropout_rate=0.5)
    
    # 打印模型摘要
    total_params = sum(p.numel() for p in model.parameters())
    trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print(f"总参数: {total_params:,}")
    print(f"可训练参数: {trainable_params:,}")
    
    # 创建训练器并训练
    print("\n3. 开始训练...")
    trainer = PetTrainer(model, device, NUM_CLASSES)
    history = trainer.train(train_loader, val_loader, NUM_EPOCHS)
    
    # 绘制训练历史
    print("\n4. 绘制训练历史...")
    plot_training_history(history)
    
    # 在测试集上评估
    print("\n5. 在测试集上评估模型...")
    test_acc, test_preds, test_labels, test_probs = trainer.test(test_loader)
    print(f"测试集准确率: {test_acc:.4f}")
    
    # 生成分类报告
    print("\n6. 生成分类报告...")
    report = classification_report(test_labels, test_preds, target_names=class_names)
    print(report)
    
    # 绘制混淆矩阵
    print("\n7. 绘制混淆矩阵...")
    plot_confusion_matrix(test_labels, test_preds, class_names)
    
    # 可视化一些预测结果
    print("\n8. 可视化预测结果...")
    visualize_predictions(model, test_loader, class_names, device, num_samples=10)
    
    # 性能分析
    print("\n9. 性能分析...")
    if test_acc >= 0.95:
        print(f"✅ 目标达成!测试准确率: {test_acc:.4f} (> 0.95)")
    else:
        print(f"⚠️ 未达到目标,测试准确率: {test_acc:.4f} (< 0.95)")
    
    # 保存最终模型
    torch.save({
        'model_state_dict': model.state_dict(),
        'class_names': class_names,
        'test_accuracy': test_acc
    }, 'final_pet_classifier.pth')
    print("\n✅ 模型已保存为 'final_pet_classifier.pth'")

8.优化版

python 复制代码
# 8. 进阶技巧:微调和数据增强增强版
def train_with_advanced_techniques():
    """
    使用更高级的技术训练模型
    """
    print("\n使用进阶技巧训练模型...")
    
    # 使用更大的批处理大小和更复杂的增强
    BATCH_SIZE = 48
    
    # 定义更复杂的数据增强
    IMG_SIZE = 256
    
    advanced_train_transform = transforms.Compose([
        transforms.RandomResizedCrop(IMG_SIZE, scale=(0.7, 1.0)),
        transforms.RandomHorizontalFlip(p=0.5),
        transforms.RandomVerticalFlip(p=0.2),
        transforms.RandomRotation(20),
        transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.2),
        transforms.RandomAffine(degrees=0, translate=(0.15, 0.15), scale=(0.9, 1.1)),
        transforms.RandomPerspective(distortion_scale=0.2, p=0.3),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
        transforms.RandomErasing(p=0.2, scale=(0.02, 0.2), ratio=(0.3, 3.3))
    ])
    
    val_transform = transforms.Compose([
        transforms.Resize((IMG_SIZE, IMG_SIZE)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    
    # 加载数据
    train_val_dataset = torchvision.datasets.OxfordIIITPet(
        root='./data/oxford_pets',
        split='trainval',
        target_types='category',
        download=False,
        transform=advanced_train_transform
    )
    
    test_dataset = torchvision.datasets.OxfordIIITPet(
        root='./data/oxford_pets',
        split='test',
        target_types='category',
        download=False,
        transform=val_transform
    )
    
    # 分割数据集
    dataset_size = len(train_val_dataset)
    train_size = int(0.9 * dataset_size)  # 90%用于训练
    val_size = dataset_size - train_size
    
    train_dataset, val_dataset = random_split(train_val_dataset, [train_size, val_size])
    val_dataset.dataset.transform = val_transform
    
    # 创建数据加载器
    train_loader = DataLoader(
        train_dataset,
        batch_size=BATCH_SIZE,
        shuffle=True,
        num_workers=6,
        pin_memory=True,
        drop_last=True
    )
    
    val_loader = DataLoader(
        val_dataset,
        batch_size=BATCH_SIZE,
        shuffle=False,
        num_workers=6,
        pin_memory=True
    )
    
    test_loader = DataLoader(
        test_dataset,
        batch_size=BATCH_SIZE,
        shuffle=False,
        num_workers=6,
        pin_memory=True
    )
    
    # 使用更大的模型(ResNet101)
    model = torchvision.models.resnet101(pretrained=True)
    num_features = model.fc.in_features
    
    # 更复杂的分类头
    model.fc = nn.Sequential(
        nn.Dropout(0.5),
        nn.Linear(num_features, 1024),
        nn.BatchNorm1d(1024),
        nn.ReLU(),
        nn.Dropout(0.4),
        nn.Linear(1024, 512),
        nn.BatchNorm1d(512),
        nn.ReLU(),
        nn.Dropout(0.3),
        nn.Linear(512, 256),
        nn.BatchNorm1d(256),
        nn.ReLU(),
        nn.Dropout(0.2),
        nn.Linear(256, 37)
    )
    
    model = model.to(device)
    
    # 使用SWA(随机权重平均)技术
    optimizer = optim.AdamW(model.parameters(), lr=0.0001, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(
        optimizer, 
        T_0=10,  # 初始周期长度
        T_mult=2,  # 周期倍增因子
        eta_min=1e-6
    )
    
    # 训练循环(简化版)
    best_acc = 0.0
    for epoch in range(30):
        print(f"\nEpoch {epoch+1}/30")
        
        # 训练阶段
        model.train()
        train_loss = 0.0
        train_correct = 0
        train_total = 0
        
        pbar = tqdm(train_loader, desc=f"训练 Epoch {epoch+1}")
        for inputs, labels in pbar:
            inputs, labels = inputs.to(device), labels.to(device)
            
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = nn.CrossEntropyLoss()(outputs, labels)
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            optimizer.step()
            
            train_loss += loss.item()
            _, predicted = outputs.max(1)
            train_total += labels.size(0)
            train_correct += predicted.eq(labels).sum().item()
            
            pbar.set_postfix({'loss': loss.item(), 'acc': train_correct/train_total})
        
        scheduler.step()
        
        # 验证阶段
        model.eval()
        val_correct = 0
        val_total = 0
        
        with torch.no_grad():
            for inputs, labels in val_loader:
                inputs, labels = inputs.to(device), labels.to(device)
                outputs = model(inputs)
                _, predicted = outputs.max(1)
                val_total += labels.size(0)
                val_correct += predicted.eq(labels).sum().item()
        
        val_acc = val_correct / val_total
        print(f"验证准确率: {val_acc:.4f}")
        
        # 保存最佳模型
        if val_acc > best_acc:
            best_acc = val_acc
            torch.save(model.state_dict(), 'advanced_model.pth')
            print(f"保存最佳模型,准确率: {val_acc:.4f}")
    
    return best_acc

9.运行代码:

python 复制代码
if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"发生错误: {e}")
        print("尝试使用进阶技巧...")
        
        # 如果主方法失败,尝试进阶方法
        try:
            acc = train_with_advanced_techniques()
            print(f"进阶方法训练完成,最佳准确率: {acc:.4f}")
            if acc >= 0.95:
                print("✅ 使用进阶技巧达到目标准确率!")
        except Exception as e2:
            print(f"进阶方法也失败: {e2}")
            print("请检查数据下载或安装必要的包")

10.运行结果展示

mlstat) [haichao@node01 demo1]$ python demo4.py

使用设备: cpu

============================================================

Oxford-IIIT Pet 数据集分类任务

============================================================

  1. 加载数据集...

训练集大小: 3128

验证集大小: 552

测试集大小: 3669

类别数量: 37

  1. 创建模型...

/home/haichao/anaconda/anaconda_install/envs/mlstat/lib/python3.11/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.

warnings.warn(

/home/haichao/anaconda/anaconda_install/envs/mlstat/lib/python3.11/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ResNet50_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet50_Weights.DEFAULT` to get the most up-to-date weights.

warnings.warn(msg)

Downloading: "https://download.pytorch.org/models/resnet50-0676ba61.pth" to /home/haichao/.cache/torch/hub/checkpoints/resnet50-0676ba61.pth

100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 97.8M/97.8M [01:32<00:00, 1.11MB/s]

总参数: 24,699,493

可训练参数: 19,509,797

  1. 开始训练...

开始训练...

Epoch 1/40


训练损失: 2.8773, 训练准确率: 0.4012

验证损失: 1.8949, 验证准确率: 0.7989

学习率: 0.000099

保存最佳模型,验证准确率: 0.7989

Epoch 2/40


训练损失: 1.7745, 训练准确率: 0.7874

验证损失: 1.3117, 验证准确率: 0.8859

学习率: 0.000098

保存最佳模型,验证准确率: 0.8859

Epoch 3/40


训练损失: 1.3021, 训练准确率: 0.8795

验证损失: 1.1134, 验证准确率: 0.8967

学习率: 0.000095

保存最佳模型,验证准确率: 0.8967

Epoch 4/40


训练损失: 1.0909, 训练准确率: 0.9105

验证损失: 1.0414, 验证准确率: 0.8841

学习率: 0.000091

Epoch 5/40


训练损失: 0.9758, 训练准确率: 0.9313

验证损失: 1.0194, 验证准确率: 0.8877

学习率: 0.000086

Epoch 6/40


训练损失: 0.8985, 训练准确率: 0.9511

验证损失: 1.1017, 验证准确率: 0.8533

学习率: 0.000080

Epoch 7/40


训练损失: 0.8466, 训练准确率: 0.9655

验证损失: 1.0448, 验证准确率: 0.8895

学习率: 0.000073

Epoch 8/40


训练损失: 0.8186, 训练准确率: 0.9696

验证损失: 0.9821, 验证准确率: 0.9076

学习率: 0.000066

保存最佳模型,验证准确率: 0.9076

Epoch 9/40


训练损失: 0.7915, 训练准确率: 0.9786

验证损失: 0.9865, 验证准确率: 0.8967

学习率: 0.000058

Epoch 10/40


训练损失: 0.7790, 训练准确率: 0.9840

验证损失: 1.0045, 验证准确率: 0.8986

学习率: 0.000051

Epoch 11/40


训练损失: 0.7580, 训练准确率: 0.9872

验证损失: 0.9778, 验证准确率: 0.9040

学习率: 0.000043

Epoch 12/40


训练损失: 0.7432, 训练准确率: 0.9910

验证损失: 1.0484, 验证准确率: 0.9004

学习率: 0.000035

Epoch 13/40


训练损失: 0.7395, 训练准确率: 0.9917

验证损失: 0.9999, 验证准确率: 0.9130

学习率: 0.000028

保存最佳模型,验证准确率: 0.9130

Epoch 14/40


训练损失: 0.7299, 训练准确率: 0.9923

验证损失: 1.0175, 验证准确率: 0.9130

学习率: 0.000021

Epoch 15/40


训练损失: 0.7217, 训练准确率: 0.9958

验证损失: 0.9745, 验证准确率: 0.9312

学习率: 0.000015

保存最佳模型,验证准确率: 0.9312

Epoch 16/40


训练损失: 0.7198, 训练准确率: 0.9958

验证损失: 0.9395, 验证准确率: 0.9330

学习率: 0.000010

保存最佳模型,验证准确率: 0.9330

Epoch 17/40


训练损失: 0.7161, 训练准确率: 0.9974

验证损失: 0.9662, 验证准确率: 0.9185

学习率: 0.000006

Epoch 18/40


训练损失: 0.7121, 训练准确率: 0.9984

验证损失: 0.9268, 验证准确率: 0.9239

学习率: 0.000003

Epoch 19/40


训练损失: 0.7118, 训练准确率: 0.9981

验证损失: 0.9353, 验证准确率: 0.9221

学习率: 0.000002

Epoch 20/40


训练损失: 0.7149, 训练准确率: 0.9968

验证损失: 0.9530, 验证准确率: 0.9203

学习率: 0.000001

Epoch 21/40


训练损失: 0.7125, 训练准确率: 0.9984

验证损失: 1.0208, 验证准确率: 0.9130

学习率: 0.000002

Epoch 22/40


训练损失: 0.7104, 训练准确率: 0.9984

验证损失: 0.9540, 验证准确率: 0.9221

学习率: 0.000003

Epoch 23/40


训练损失: 0.7108, 训练准确率: 0.9984

验证损失: 0.9207, 验证准确率: 0.9348

学习率: 0.000006

保存最佳模型,验证准确率: 0.9348

Epoch 24/40


训练损失: 0.7081, 训练准确率: 0.9984

验证损失: 0.9419, 验证准确率: 0.9293

学习率: 0.000010

Epoch 25/40


训练损失: 0.7099, 训练准确率: 0.9987

验证损失: 1.0030, 验证准确率: 0.9167

学习率: 0.000015

Epoch 26/40


训练损失: 0.7163, 训练准确率: 0.9962

验证损失: 0.9952, 验证准确率: 0.9130

学习率: 0.000021

Epoch 27/40


训练损失: 0.7101, 训练准确率: 0.9974

验证损失: 0.9511, 验证准确率: 0.9203

学习率: 0.000028

Epoch 28/40


训练损失: 0.7103, 训练准确率: 0.9981

验证损失: 0.9734, 验证准确率: 0.9149

学习率: 0.000035

Epoch 29/40


训练损失: 0.7149, 训练准确率: 0.9962

验证损失: 1.0185, 验证准确率: 0.9094

学习率: 0.000043

Epoch 30/40


训练损失: 0.7138, 训练准确率: 0.9965

验证损失: 1.0045, 验证准确率: 0.9094

学习率: 0.000050

Epoch 31/40


训练损失: 0.7162, 训练准确率: 0.9946

验证损失: 1.0169, 验证准确率: 0.9094

学习率: 0.000058

Epoch 32/40


训练损失: 0.7264, 训练准确率: 0.9923

验证损失: 1.0629, 验证准确率: 0.9022

学习率: 0.000066

Epoch 33/40


训练损失: 0.7261, 训练准确率: 0.9914

验证损失: 1.0436, 验证准确率: 0.8949

学习率: 0.000073

早停触发,在epoch 33停止训练

发生错误: Weights only load failed. This file can still be loaded, to do so you have two options, do those steps only if you trust the source of the checkpoint.

(1) In PyTorch 2.6, we changed the default value of the `weights_only` argument in `torch.load` from `False` to `True`. Re-running `torch.load` with `weights_only` set to `False` will likely succeed, but it can result in arbitrary code execution. Do it only if you got the file from a trusted source.

(2) Alternatively, to load with `weights_only=True` please check the recommended steps in the following error message.

WeightsUnpickler error: Unsupported global: GLOBAL numpy.core.multiarray.scalar was not an allowed global by default. Please use `torch.serialization.add_safe_globals([scalar])` or the `torch.serialization.safe_globals([scalar])` context manager to allowlist this global if you trust this class/function.

Check the documentation of torch.load to learn more about types accepted by default with weights_only https://pytorch.org/docs/stable/generated/torch.load.html.

尝试使用进阶技巧...

使用进阶技巧训练模型...

/home/haichao/anaconda/anaconda_install/envs/mlstat/lib/python3.11/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.

warnings.warn(

/home/haichao/anaconda/anaconda_install/envs/mlstat/lib/python3.11/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ResNet101_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet101_Weights.DEFAULT` to get the most up-to-date weights.

warnings.warn(msg)

Epoch 1/30

训练 Epoch 1: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:07<00:00, 3.59s/it, loss=3.08, acc=0.0848]

验证准确率: 0.3804

保存最佳模型,准确率: 0.3804

Epoch 2/30

训练 Epoch 2: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:08<00:00, 3.60s/it, loss=2.35, acc=0.391]

验证准确率: 0.5435

保存最佳模型,准确率: 0.5435

Epoch 3/30

训练 Epoch 3: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:09<00:00, 3.62s/it, loss=1.81, acc=0.617]

验证准确率: 0.7473

保存最佳模型,准确率: 0.7473

Epoch 4/30

训练 Epoch 4: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:08<00:00, 3.61s/it, loss=1.38, acc=0.761]

验证准确率: 0.8179

保存最佳模型,准确率: 0.8179

Epoch 5/30

训练 Epoch 5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:09<00:00, 3.62s/it, loss=1.02, acc=0.821]

验证准确率: 0.8397

保存最佳模型,准确率: 0.8397

Epoch 6/30

训练 Epoch 6: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:10<00:00, 3.64s/it, loss=0.97, acc=0.864]

验证准确率: 0.8370

Epoch 7/30

训练 Epoch 7: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:05<00:00, 3.56s/it, loss=0.817, acc=0.896]

验证准确率: 0.8859

保存最佳模型,准确率: 0.8859

Epoch 8/30

训练 Epoch 8: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:09<00:00, 3.62s/it, loss=0.659, acc=0.912]

验证准确率: 0.8967

保存最佳模型,准确率: 0.8967

Epoch 9/30

训练 Epoch 9: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:13<00:00, 3.68s/it, loss=0.583, acc=0.926]

验证准确率: 0.9022

保存最佳模型,准确率: 0.9022

Epoch 10/30

训练 Epoch 10: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:11<00:00, 3.64s/it, loss=0.633, acc=0.944]

验证准确率: 0.9049

保存最佳模型,准确率: 0.9049

Epoch 11/30

训练 Epoch 11: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:12<00:00, 3.66s/it, loss=0.698, acc=0.885]

验证准确率: 0.8370

Epoch 12/30

训练 Epoch 12: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:06<00:00, 3.58s/it, loss=0.671, acc=0.886]

验证准确率: 0.8261

Epoch 13/30

训练 Epoch 13: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:07<00:00, 3.59s/it, loss=0.6, acc=0.885]

验证准确率: 0.8288

Epoch 14/30

训练 Epoch 14: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:10<00:00, 3.63s/it, loss=0.43, acc=0.901]

验证准确率: 0.8424

Epoch 15/30

训练 Epoch 15: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:11<00:00, 3.65s/it, loss=0.441, acc=0.906]

验证准确率: 0.8505

Epoch 16/30

训练 Epoch 16: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:07<00:00, 3.59s/it, loss=0.56, acc=0.911]

验证准确率: 0.8614

Epoch 17/30

训练 Epoch 17: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:16<00:00, 3.72s/it, loss=0.484, acc=0.927]

验证准确率: 0.8804

Epoch 18/30

训练 Epoch 18: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:32<00:00, 3.94s/it, loss=0.287, acc=0.929]

验证准确率: 0.8370

Epoch 19/30

训练 Epoch 19: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [04:26<00:00, 3.86s/it, loss=0.158, acc=0.932]

验证准确率: 0.8750

Epoch 20/30

训练 Epoch 20: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:48<00:00, 3.31s/it, loss=0.247, acc=0.949]

验证准确率: 0.8723

Epoch 21/30

训练 Epoch 21: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:28<00:00, 3.02s/it, loss=0.27, acc=0.953]

验证准确率: 0.8886

Epoch 22/30

训练 Epoch 22: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:28<00:00, 3.02s/it, loss=0.125, acc=0.958]

验证准确率: 0.9076

保存最佳模型,准确率: 0.9076

Epoch 23/30

训练 Epoch 23: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:26<00:00, 3.00s/it, loss=0.165, acc=0.963]

验证准确率: 0.8913

Epoch 24/30

训练 Epoch 24: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:29<00:00, 3.04s/it, loss=0.111, acc=0.971]

验证准确率: 0.8913

Epoch 25/30

训练 Epoch 25: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:25<00:00, 2.97s/it, loss=0.168, acc=0.971]

验证准确率: 0.9266

保存最佳模型,准确率: 0.9266

Epoch 26/30

训练 Epoch 26: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:26<00:00, 2.99s/it, loss=0.307, acc=0.98]

验证准确率: 0.9293

保存最佳模型,准确率: 0.9293

Epoch 27/30

训练 Epoch 27: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:27<00:00, 3.01s/it, loss=0.147, acc=0.978]

验证准确率: 0.8967

Epoch 28/30

训练 Epoch 28: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:25<00:00, 2.98s/it, loss=0.179, acc=0.981]

验证准确率: 0.9212

Epoch 29/30

训练 Epoch 29: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:35<00:00, 3.12s/it, loss=0.104, acc=0.982]

验证准确率: 0.9130

Epoch 30/30

训练 Epoch 30: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 69/69 [03:40<00:00, 3.19s/it, loss=0.1, acc=0.979]

验证准确率: 0.9103

进阶方法训练完成,最佳准确率: 0.9293

这个代码还有继续优化的空间,感兴趣的友友可以在这个基础上进行模型微调及参数优化。

最终模型的大小为244MB

总用量 3.2G

drwxrwxr-x 15 haichao haichao 4.0K 1月 22 15:13 .

drwx------. 37 haichao haichao 4.0K 1月 22 18:15 ..

-rw-rw-r-- 1 haichao haichao 387K 1月 20 16:23 adaptive_rk_variable_stiffness.png

-rw-rw-r-- 1 haichao haichao 174M 1月 22 15:36 advanced_model.pth

drwxrwxr-x 2 haichao haichao 4.0K 1月 5 13:51 atomsk

-rw-rw-r-- 1 haichao haichao 244M 1月 22 13:38 best_pet_classifier.pth

drwxrwxr-x 3 haichao haichao 4.0K 1月 19 20:29 data

-rw-rw-r-- 1 haichao haichao 1.6G 1月 20 10:28 data.zip

-rw-rw-r-- 1 haichao haichao 20K 1月 20 16:23 demo10.py

-rw-rw-r-- 1 haichao haichao 106K 1月 20 15:49 demo1.png

-rw-rw-r-- 1 haichao haichao 251 11月 18 21:31 demo1.py

-rw-rw-r-- 1 haichao haichao 102 11月 28 15:19 demo2.py

-rw-rw-r-- 1 haichao haichao 18K 1月 19 20:29 demo3.py

-rw-rw-r-- 1 haichao haichao 24K 1月 22 13:09 demo4.py

四、集成学习策略

集成学习的主要特点:

1. 多种预训练模型集成

  • ResNet50:经典CNN架构,平衡性能和计算成本

  • ResNet101:更深层的ResNet,更强的特征提取能力

  • EfficientNet-B3:高效的网络设计,更好的精度-效率平衡

  • DenseNet121:密集连接结构,特征重用效果好

2. 两阶段训练策略

  • 第一阶段:冻结预训练骨干网络,只训练新添加的分类头

  • 第二阶段:当准确率达到阈值后,解冻骨干网络进行微调

3. 可学习的集成权重

  • 模型自动学习各个子模型的最优权重

  • 支持均匀权重和加权平均两种集成方式

4. 测试时增强(TTA)

  • 对测试图像进行多种增强

  • 综合多个增强版本的预测结果

  • 通常能提升1-3%的准确率

5. 全面的可视化分析

  • 集成权重分布图

  • 置信度分析(正确/错误预测的置信度分布)

  • 每个子模型的独立性能对比

  • 混淆矩阵和分类报告

6. 模型性能分析

  • 计算各个子模型的独立准确率

  • 分析集成带来的性能提升

  • 生成详细的性能报告

7.实现代码:

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torchvision.models as models
from torch.utils.data import DataLoader, random_split
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import os
from sklearn.metrics import confusion_matrix, classification_report
import seaborn as sns
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

# 设置随机种子以保证可重复性
def set_seed(seed=42):
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = True
    np.random.seed(seed)

set_seed(42)

# 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")

# 数据增强
transform_train = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(15),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

transform_val = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 加载Oxford-IIIT Pet数据集
def load_dataset():
    train_dataset = datasets.OxfordIIITPet(
        root='../data/oxford_pets',
        split='trainval',
        target_types='category',
        download=False,
        transform=transform_train
    )
    
    test_dataset = datasets.OxfordIIITPet(
        root='../data/oxford_pets',
        split='test',
        target_types='category',
        download=False,
        transform=transform_val
    )
    
    # 分割训练集和验证集
    train_size = int(0.85 * len(train_dataset))
    val_size = len(train_dataset) - train_size
    train_dataset, val_dataset = random_split(train_dataset, [train_size, val_size])
    
    # 对验证集应用验证集的数据增强
    val_dataset.dataset.transform = transform_val
    
    return train_dataset, val_dataset, test_dataset

train_dataset, val_dataset, test_dataset = load_dataset()

# 创建数据加载器
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True)

print(f"训练集大小: {len(train_dataset)}")
print(f"验证集大小: {len(val_dataset)}")
print(f"测试集大小: {len(test_dataset)}")
print(f"类别数量: {len(train_dataset.dataset.classes)}")

# ==================== 定义单个模型 ====================
class PetClassifier(nn.Module):
    def __init__(self, model_name='resnet50', num_classes=37, pretrained=True):
        super(PetClassifier, self).__init__()
        self.model_name = model_name
        
        # 加载预训练模型
        if model_name == 'resnet50':
            self.backbone = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2 if pretrained else None)
            num_features = self.backbone.fc.in_features
            self.backbone.fc = nn.Identity()
            self.classifier = nn.Sequential(
                nn.Dropout(0.5),
                nn.Linear(num_features, 512),
                nn.BatchNorm1d(512),
                nn.ReLU(inplace=True),
                nn.Dropout(0.3),
                nn.Linear(512, num_classes)
            )
            
        elif model_name == 'resnet101':
            self.backbone = models.resnet101(weights=models.ResNet101_Weights.IMAGENET1K_V2 if pretrained else None)
            num_features = self.backbone.fc.in_features
            self.backbone.fc = nn.Identity()
            self.classifier = nn.Sequential(
                nn.Dropout(0.5),
                nn.Linear(num_features, 512),
                nn.BatchNorm1d(512),
                nn.ReLU(inplace=True),
                nn.Dropout(0.3),
                nn.Linear(512, num_classes)
            )
            
        elif model_name == 'efficientnet_b3':
            self.backbone = models.efficientnet_b3(weights=models.EfficientNet_B3_Weights.IMAGENET1K_V1 if pretrained else None)
            num_features = self.backbone.classifier[1].in_features
            self.backbone.classifier = nn.Identity()
            self.classifier = nn.Sequential(
                nn.Dropout(0.5),
                nn.Linear(num_features, 512),
                nn.BatchNorm1d(512),
                nn.ReLU(inplace=True),
                nn.Dropout(0.3),
                nn.Linear(512, num_classes)
            )
            
        elif model_name == 'densenet121':
            self.backbone = models.densenet121(weights=models.DenseNet121_Weights.IMAGENET1K_V1 if pretrained else None)
            num_features = self.backbone.classifier.in_features
            self.backbone.classifier = nn.Identity()
            self.classifier = nn.Sequential(
                nn.Dropout(0.5),
                nn.Linear(num_features, 512),
                nn.BatchNorm1d(512),
                nn.ReLU(inplace=True),
                nn.Dropout(0.3),
                nn.Linear(512, num_classes)
            )
        
        self._initialize_classifier()
    
    def _initialize_classifier(self):
        for m in self.classifier.modules():
            if isinstance(m, nn.Linear):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
    
    def forward(self, x):
        features = self.backbone(x)
        return self.classifier(features)
    
    def freeze_backbone(self):
        """冻结骨干网络"""
        for param in self.backbone.parameters():
            param.requires_grad = False
    
    def unfreeze_backbone(self):
        """解冻骨干网络"""
        for param in self.backbone.parameters():
            param.requires_grad = True

# ==================== 集成模型 ====================
class EnsembleModel(nn.Module):
    def __init__(self, model_names=['resnet50', 'resnet101', 'efficientnet_b3', 'densenet121'], 
                 num_classes=37, weights=None):
        super(EnsembleModel, self).__init__()
        
        self.models = nn.ModuleList()
        for model_name in model_names:
            model = PetClassifier(model_name=model_name, num_classes=num_classes, pretrained=True)
            self.models.append(model)
        
        # 设置集成权重(可以学习或固定)
        if weights is None:
            # 默认均匀权重
            self.weights = nn.Parameter(torch.ones(len(model_names)) / len(model_names), requires_grad=True)
        else:
            self.weights = nn.Parameter(torch.tensor(weights, dtype=torch.float32), requires_grad=True)
        
        self.num_models = len(model_names)
    
    def forward(self, x):
        outputs = []
        for model in self.models:
            outputs.append(model(x))
        
        # 加权平均
        weighted_outputs = []
        for i, output in enumerate(outputs):
            weighted_outputs.append(output * self.weights[i])
        
        ensemble_output = torch.sum(torch.stack(weighted_outputs), dim=0)
        return ensemble_output
    
    def predict_with_confidence(self, x):
        """返回预测结果及置信度"""
        with torch.no_grad():
            outputs = []
            for model in self.models:
                outputs.append(torch.softmax(model(x), dim=1))
            
            # 计算加权平均
            weighted_outputs = []
            for i, output in enumerate(outputs):
                weighted_outputs.append(output * self.weights[i])
            
            ensemble_probs = torch.sum(torch.stack(weighted_outputs), dim=0)
            confidence, predictions = torch.max(ensemble_probs, dim=1)
            
            return predictions, confidence
    
    def get_model_predictions(self, x):
        """获取每个模型的预测结果"""
        with torch.no_grad():
            predictions = []
            for model in self.models:
                output = model(x)
                _, pred = torch.max(output, dim=1)
                predictions.append(pred)
            return torch.stack(predictions)

# ==================== 训练单个模型 ====================
def train_single_model(model, train_loader, val_loader, model_name, num_epochs=20):
    print(f"\n{'='*60}")
    print(f"训练模型: {model_name}")
    print(f"{'='*60}")
    
    model = model.to(device)
    
    # 第一阶段:冻结骨干网络,只训练分类头
    model.freeze_backbone()
    
    criterion = nn.CrossEntropyLoss(label_smoothing=0.1)
    optimizer = optim.AdamW(model.classifier.parameters(), lr=0.001, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)
    
    best_val_acc = 0.0
    best_model_path = f'best_{model_name}.pth'
    
    for epoch in range(num_epochs):
        # 训练
        model.train()
        train_loss = 0.0
        train_correct = 0
        train_total = 0
        
        pbar = tqdm(train_loader, desc=f'Epoch {epoch+1}/{num_epochs} [Train]', leave=False)
        for inputs, targets in pbar:
            inputs, targets = inputs.to(device), targets.to(device)
            
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
            optimizer.step()
            
            train_loss += loss.item()
            _, predicted = outputs.max(1)
            train_total += targets.size(0)
            train_correct += predicted.eq(targets).sum().item()
            
            pbar.set_postfix({
                'Loss': f'{train_loss/(train_total/batch_size):.4f}',
                'Acc': f'{100.*train_correct/train_total:.2f}%'
            })
        
        train_acc = 100. * train_correct / train_total
        train_loss = train_loss / len(train_loader)
        
        # 验证
        model.eval()
        val_loss = 0.0
        val_correct = 0
        val_total = 0
        
        with torch.no_grad():
            pbar = tqdm(val_loader, desc=f'Epoch {epoch+1}/{num_epochs} [Val]', leave=False)
            for inputs, targets in pbar:
                inputs, targets = inputs.to(device), targets.to(device)
                
                outputs = model(inputs)
                loss = criterion(outputs, targets)
                
                val_loss += loss.item()
                _, predicted = outputs.max(1)
                val_total += targets.size(0)
                val_correct += predicted.eq(targets).sum().item()
                
                pbar.set_postfix({
                    'Loss': f'{val_loss/(val_total/batch_size):.4f}',
                    'Acc': f'{100.*val_correct/val_total:.2f}%'
                })
        
        val_acc = 100. * val_correct / val_total
        val_loss = val_loss / len(val_loader)
        
        # 更新学习率
        scheduler.step()
        
        # 保存最佳模型
        if val_acc > best_val_acc:
            best_val_acc = val_acc
            torch.save({
                'epoch': epoch,
                'model_state_dict': model.state_dict(),
                'val_acc': val_acc,
                'train_acc': train_acc,
            }, best_model_path)
            print(f"✓ 保存最佳模型,验证准确率: {val_acc:.2f}%")
        
        print(f"Epoch {epoch+1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}% | "
              f"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%")
        
        # 如果验证准确率达到80%,开始第二阶段微调
        if val_acc >= 80 and epoch >= 10:
            print(f"开始微调骨干网络...")
            model.unfreeze_backbone()
            optimizer = optim.AdamW(model.parameters(), lr=0.0001, weight_decay=1e-4)
            scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
    
    # 加载最佳模型
    checkpoint = torch.load(best_model_path)
    model.load_state_dict(checkpoint['model_state_dict'])
    print(f"加载最佳模型: {model_name}, 验证准确率: {checkpoint['val_acc']:.2f}%")
    
    return model

# ==================== 训练集成模型 ====================
def train_ensemble(ensemble_model, train_loader, val_loader, num_epochs=15):
    print(f"\n{'='*60}")
    print(f"训练集成模型")
    print(f"{'='*60}")
    
    ensemble_model = ensemble_model.to(device)
    
    # 先训练各个模型
    trained_models = []
    for i, model in enumerate(ensemble_model.models):
        model_name = ['resnet50', 'resnet101', 'efficientnet_b3', 'densenet121'][i]
        trained_model = train_single_model(model, train_loader, val_loader, model_name)
        trained_models.append(trained_model)
    
    # 更新集成模型中的模型
    for i, trained_model in enumerate(trained_models):
        ensemble_model.models[i].load_state_dict(trained_model.state_dict())
    
    # 训练集成权重(可选)
    print(f"\n{'='*60}")
    print(f"优化集成权重")
    print(f"{'='*60}")
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.AdamW([ensemble_model.weights], lr=0.01)
    
    best_val_acc = 0.0
    
    for epoch in range(10):
        # 训练集成权重
        ensemble_model.eval()  # 保持模型为评估模式
        
        val_correct = 0
        val_total = 0
        
        with torch.no_grad():
            pbar = tqdm(val_loader, desc=f'Epoch {epoch+1}/10 [Ensemble]', leave=False)
            for inputs, targets in pbar:
                inputs, targets = inputs.to(device), targets.to(device)
                
                predictions, confidence = ensemble_model.predict_with_confidence(inputs)
                val_total += targets.size(0)
                val_correct += predictions.eq(targets).sum().item()
                
                pbar.set_postfix({
                    'Acc': f'{100.*val_correct/val_total:.2f}%'
                })
        
        val_acc = 100. * val_correct / val_total
        print(f"Epoch {epoch+1}: 集成验证准确率: {val_acc:.2f}%")
        print(f"当前权重: {ensemble_model.weights.data.tolist()}")
        
        if val_acc > best_val_acc:
            best_val_acc = val_acc
    
    return ensemble_model

# ==================== 测试集成模型 ====================
def test_ensemble_model(ensemble_model, loader):
    ensemble_model.eval()
    
    all_predictions = []
    all_targets = []
    all_confidences = []
    
    with torch.no_grad():
        pbar = tqdm(loader, desc='[Ensemble Testing]', leave=False)
        for inputs, targets in pbar:
            inputs, targets = inputs.to(device), targets.to(device)
            
            predictions, confidence = ensemble_model.predict_with_confidence(inputs)
            
            all_predictions.extend(predictions.cpu().numpy())
            all_targets.extend(targets.cpu().numpy())
            all_confidences.extend(confidence.cpu().numpy())
            
            # 计算当前准确率
            correct = predictions.eq(targets).sum().item()
            total = targets.size(0)
            current_acc = 100. * correct / total
            
            pbar.set_postfix({'Acc': f'{current_acc:.2f}%'})
    
    accuracy = 100. * np.sum(np.array(all_predictions) == np.array(all_targets)) / len(all_targets)
    
    return accuracy, all_predictions, all_targets, all_confidences

# ==================== 可视化函数 ====================
def plot_ensemble_results(ensemble_model, all_predictions, all_targets, all_confidences, class_names):
    # 1. 绘制混淆矩阵
    cm = confusion_matrix(all_targets, all_predictions)
    
    plt.figure(figsize=(15, 12))
    sns.heatmap(cm, annot=False, fmt='d', cmap='Blues', 
                xticklabels=class_names, yticklabels=class_names)
    plt.title('Ensemble Model - Confusion Matrix', fontsize=16)
    plt.xlabel('Predicted Labels', fontsize=12)
    plt.ylabel('True Labels', fontsize=12)
    plt.xticks(rotation=90, fontsize=8)
    plt.yticks(rotation=0, fontsize=8)
    plt.tight_layout()
    plt.savefig('ensemble_confusion_matrix.png', dpi=150, bbox_inches='tight')
    plt.show()
    
    # 2. 绘制置信度分布
    plt.figure(figsize=(12, 5))
    
    plt.subplot(1, 2, 1)
    correct_confidences = [conf for pred, target, conf in zip(all_predictions, all_targets, all_confidences) if pred == target]
    incorrect_confidences = [conf for pred, target, conf in zip(all_predictions, all_targets, all_confidences) if pred != target]
    
    plt.hist(correct_confidences, bins=30, alpha=0.5, label='Correct', color='green')
    plt.hist(incorrect_confidences, bins=30, alpha=0.5, label='Incorrect', color='red')
    plt.xlabel('Confidence')
    plt.ylabel('Count')
    plt.title('Confidence Distribution')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    # 3. 绘制模型权重
    plt.subplot(1, 2, 2)
    model_names = ['ResNet50', 'ResNet101', 'EfficientNet-B3', 'DenseNet121']
    weights = ensemble_model.weights.data.cpu().numpy()
    
    plt.bar(model_names, weights)
    plt.xlabel('Models')
    plt.ylabel('Weight')
    plt.title('Ensemble Weights')
    plt.xticks(rotation=45)
    plt.grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig('ensemble_analysis.png', dpi=150, bbox_inches='tight')
    plt.show()
    
    # 4. 打印分类报告
    print("\n集成模型分类报告:")
    print(classification_report(all_targets, all_predictions, target_names=class_names, digits=3))

# ==================== 测试时增强(TTA) ====================
def predict_with_tta(ensemble_model, image, n_aug=5):
    """使用测试时增强进行预测"""
    ensemble_model.eval()
    
    # 定义不同的增强策略
    tta_transforms = [
        transforms.Compose([
            transforms.Resize((256, 256)),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
        ]),
        transforms.Compose([
            transforms.Resize((256, 256)),
            transforms.CenterCrop(224),
            transforms.RandomHorizontalFlip(p=1.0),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
        ]),
        transforms.Compose([
            transforms.Resize((256, 256)),
            transforms.RandomCrop(224),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
        ]),
    ]
    
    all_probs = []
    
    with torch.no_grad():
        for _ in range(n_aug):
            # 随机选择一种增强
            transform_idx = np.random.randint(0, len(tta_transforms))
            augmented_image = tta_transforms[transform_idx](image)
            augmented_image = augmented_image.unsqueeze(0).to(device)
            
            # 获取模型输出
            output = ensemble_model(augmented_image)
            probs = torch.softmax(output, dim=1)
            all_probs.append(probs)
    
    # 平均所有增强的预测
    avg_probs = torch.mean(torch.stack(all_probs), dim=0)
    confidence, prediction = torch.max(avg_probs, dim=1)
    
    return prediction, confidence

# ==================== 主函数 ====================
def main():
    print("开始宠物分类集成学习模型训练...")
    print(f"设备: {device}")
    print(f"类别数: {len(train_dataset.dataset.classes)}")
    
    # 创建集成模型
    ensemble_model = EnsembleModel(
        model_names=['resnet50', 'resnet101', 'efficientnet_b3', 'densenet121'],
        num_classes=37
    )
    
    # 训练集成模型
    trained_ensemble = train_ensemble(ensemble_model, train_loader, val_loader)
    
    # 测试集成模型
    print("\n" + "="*60)
    print("测试集成模型性能")
    print("="*60)
    
    test_acc, all_preds, all_targets, all_confidences = test_ensemble_model(trained_ensemble, test_loader)
    print(f"\n集成模型测试准确率: {test_acc:.2f}%")
    
    # 测试每个单独模型的性能
    print("\n" + "="*60)
    print("各个模型独立性能")
    print("="*60)
    
    model_names = ['resnet50', 'resnet101', 'efficientnet_b3', 'densenet121']
    individual_accuracies = []
    
    for i, model in enumerate(trained_ensemble.models):
        model.eval()
        correct = 0
        total = 0
        
        with torch.no_grad():
            pbar = tqdm(test_loader, desc=f'Testing {model_names[i]}', leave=False)
            for inputs, targets in pbar:
                inputs, targets = inputs.to(device), targets.to(device)
                outputs = model(inputs)
                _, predicted = outputs.max(1)
                total += targets.size(0)
                correct += predicted.eq(targets).sum().item()
        
        acc = 100. * correct / total
        individual_accuracies.append(acc)
        print(f"{model_names[i]}: {acc:.2f}%")
    
    print(f"\n各个模型平均准确率: {np.mean(individual_accuracies):.2f}%")
    print(f"集成模型提升: {test_acc - np.mean(individual_accuracies):.2f}%")
    
    if test_acc >= 95:
        print(f"🎉 达到目标!集成模型测试准确率 >= 95%")
    else:
        print(f"⚠️  未达到目标,当前准确率: {test_acc:.2f}%")
        print("尝试使用测试时增强...")
        
        # 使用TTA进行预测
        tta_correct = 0
        tta_total = 0
        tta_predictions = []
        
        pbar = tqdm(test_loader, desc='Testing with TTA', leave=False)
        for inputs, targets in pbar:
            inputs, targets = inputs.to(device), targets.to(device)
            
            batch_predictions = []
            for i in range(inputs.size(0)):
                image = transforms.functional.to_pil_image(inputs[i].cpu())
                pred, _ = predict_with_tta(trained_ensemble, image, n_aug=5)
                batch_predictions.append(pred)
            
            batch_predictions = torch.stack(batch_predictions).to(device)
            tta_total += targets.size(0)
            tta_correct += batch_predictions.eq(targets).sum().item()
            tta_predictions.extend(batch_predictions.cpu().numpy())
            
            pbar.set_postfix({'Acc': f'{100.*tta_correct/tta_total:.2f}%'})
        
        tta_acc = 100. * tta_correct / tta_total
        print(f"TTA测试准确率: {tta_acc:.2f}%")
        
        if tta_acc > test_acc:
            test_acc = tta_acc
            all_preds = tta_predictions
            print(f"✅ TTA提升了 {tta_acc - test_acc:.2f}% 的准确率")
    
    # 可视化结果
    class_names = train_dataset.dataset.classes
    print("\n生成可视化图表...")
    plot_ensemble_results(trained_ensemble, all_preds, all_targets, all_confidences, class_names)
    
    # 保存集成模型
    final_model_path = 'pet_classifier_ensemble.pth'
    torch.save({
        'ensemble_state_dict': trained_ensemble.state_dict(),
        'test_acc': test_acc,
        'class_names': class_names,
        'individual_accuracies': individual_accuracies,
        'ensemble_weights': trained_ensemble.weights.data.cpu().numpy(),
        'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    }, final_model_path)
    
    print(f"\n集成模型已保存到: {final_model_path}")
    print(f"最终测试准确率: {test_acc:.2f}%")
    
    # 保存详细的性能报告
    with open('ensemble_performance_report.txt', 'w') as f:
        f.write("宠物分类集成学习模型性能报告\n")
        f.write("=" * 50 + "\n")
        f.write(f"测试日期: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write(f"测试准确率: {test_acc:.2f}%\n")
        f.write(f"集成权重: {trained_ensemble.weights.data.cpu().numpy().tolist()}\n\n")
        
        f.write("各个模型性能:\n")
        for i, model_name in enumerate(['ResNet50', 'ResNet101', 'EfficientNet-B3', 'DenseNet121']):
            f.write(f"  {model_name}: {individual_accuracies[i]:.2f}%\n")
        
        f.write(f"\n平均模型性能: {np.mean(individual_accuracies):.2f}%\n")
        f.write(f"集成提升: {test_acc - np.mean(individual_accuracies):.2f}%\n")
    
    return test_acc

if __name__ == "__main__":
    test_accuracy = main()

8.运行结果展示

(mlstat) ➜ resmble git:(master) ✗ python demo1.py

使用设备: cuda

训练集大小: 3128

验证集大小: 552

测试集大小: 3669

类别数量: 37

开始宠物分类集成学习模型训练...

设备: cuda

类别数: 37

Downloading: "https://download.pytorch.org/models/resnet50-11ad3fa6.pth" to /root/.cache/torch/hub/checkpoints/resnet50-11ad3fa6.pth

100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 97.8M/97.8M [00:02<00:00, 44.8MB/s]

Downloading: "https://download.pytorch.org/models/resnet101-cd907fc2.pth" to /root/.cache/torch/hub/checkpoints/resnet101-cd907fc2.pth

100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 171M/171M [00:04<00:00, 41.4MB/s]

Downloading: "https://download.pytorch.org/models/efficientnet_b3_rwightman-b3899882.pth" to /root/.cache/torch/hub/checkpoints/efficientnet_b3_rwightman-b3899882.pth

100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 47.2M/47.2M [00:01<00:00, 37.1MB/s]

Downloading: "https://download.pytorch.org/models/densenet121-a639ec97.pth" to /root/.cache/torch/hub/checkpoints/densenet121-a639ec97.pth

100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 30.8M/30.8M [00:00<00:00, 56.7MB/s]

============================================================

训练集成模型

============================================================

============================================================

训练模型: resnet50

============================================================

✓ 保存最佳模型,验证准确率: 79.53%

Epoch 1: Train Loss: 4.7613, Train Acc: 34.65% | Val Loss: 1.8522, Val Acc: 79.53%

✓ 保存最佳模型,验证准确率: 83.15%

Epoch 2: Train Loss: 2.8170, Train Acc: 61.73% | Val Loss: 1.8026, Val Acc: 83.15%

✓ 保存最佳模型,验证准确率: 84.06%

Epoch 3: Train Loss: 2.4843, Train Acc: 67.46% | Val Loss: 1.6643, Val Acc: 84.06%

✓ 保存最佳模型,验证准确率: 84.60%

Epoch 4: Train Loss: 2.2631, Train Acc: 70.84% | Val Loss: 1.5390, Val Acc: 84.60%

✓ 保存最佳模型,验证准确率: 85.69%

Epoch 5: Train Loss: 2.0993, Train Acc: 72.38% | Val Loss: 1.4637, Val Acc: 85.69%

Epoch 6: Train Loss: 1.9942, Train Acc: 72.92% | Val Loss: 1.4716, Val Acc: 84.42%

Epoch 7: Train Loss: 1.8311, Train Acc: 75.48% | Val Loss: 1.4411, Val Acc: 85.51%

✓ 保存最佳模型,验证准确率: 86.78%

Epoch 8: Train Loss: 1.7619, Train Acc: 77.01% | Val Loss: 1.4167, Val Acc: 86.78%

Epoch 9: Train Loss: 1.6699, Train Acc: 78.23% | Val Loss: 1.3267, Val Acc: 86.23%

✓ 保存最佳模型,验证准确率: 87.68%

Epoch 10: Train Loss: 1.6085, Train Acc: 79.00% | Val Loss: 1.2783, Val Acc: 87.68%

Epoch 11: Train Loss: 1.5653, Train Acc: 79.32% | Val Loss: 1.2785, Val Acc: 85.69%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 88.95%

Epoch 12: Train Loss: 1.4928, Train Acc: 81.49% | Val Loss: 1.2267, Val Acc: 88.95%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 90.76%

Epoch 13: Train Loss: 1.4146, Train Acc: 84.37% | Val Loss: 1.2199, Val Acc: 90.76%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 91.49%

Epoch 14: Train Loss: 1.3532, Train Acc: 85.65% | Val Loss: 1.1705, Val Acc: 91.49%

开始微调骨干网络...

Epoch 15: Train Loss: 1.3042, Train Acc: 86.76% | Val Loss: 1.1424, Val Acc: 89.86%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 92.21%

Epoch 16: Train Loss: 1.2388, Train Acc: 89.35% | Val Loss: 1.0854, Val Acc: 92.21%

开始微调骨干网络...

Epoch 17: Train Loss: 1.1659, Train Acc: 91.34% | Val Loss: 1.0691, Val Acc: 91.67%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 92.75%

Epoch 18: Train Loss: 1.1488, Train Acc: 91.05% | Val Loss: 1.0016, Val Acc: 92.75%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 93.66%

Epoch 19: Train Loss: 1.0940, Train Acc: 93.16% | Val Loss: 1.0003, Val Acc: 93.66%

开始微调骨干网络...

Epoch 20: Train Loss: 1.0928, Train Acc: 93.45% | Val Loss: 0.9844, Val Acc: 92.57%

开始微调骨干网络...

加载最佳模型: resnet50, 验证准确率: 93.66%

============================================================

训练模型: resnet101

============================================================

✓ 保存最佳模型,验证准确率: 82.43%

Epoch 1: Train Loss: 4.6327, Train Acc: 36.70% | Val Loss: 1.7731, Val Acc: 82.43%

✓ 保存最佳模型,验证准确率: 83.33%

Epoch 2: Train Loss: 2.8049, Train Acc: 64.19% | Val Loss: 1.7957, Val Acc: 83.33%

Epoch 3: Train Loss: 2.4211, Train Acc: 69.53% | Val Loss: 1.7097, Val Acc: 83.33%

✓ 保存最佳模型,验证准确率: 85.51%

Epoch 4: Train Loss: 2.1872, Train Acc: 72.54% | Val Loss: 1.5918, Val Acc: 85.51%

Epoch 5: Train Loss: 2.0162, Train Acc: 74.94% | Val Loss: 1.5028, Val Acc: 85.33%

Epoch 6: Train Loss: 1.9197, Train Acc: 75.61% | Val Loss: 1.4776, Val Acc: 85.33%

✓ 保存最佳模型,验证准确率: 88.22%

Epoch 7: Train Loss: 1.7695, Train Acc: 76.76% | Val Loss: 1.3718, Val Acc: 88.22%

Epoch 8: Train Loss: 1.6964, Train Acc: 78.39% | Val Loss: 1.3302, Val Acc: 87.32%

Epoch 9: Train Loss: 1.6190, Train Acc: 79.09% | Val Loss: 1.3631, Val Acc: 87.14%

Epoch 10: Train Loss: 1.5756, Train Acc: 79.83% | Val Loss: 1.2992, Val Acc: 85.51%

Epoch 11: Train Loss: 1.5227, Train Acc: 81.36% | Val Loss: 1.2870, Val Acc: 84.60%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 90.04%

Epoch 12: Train Loss: 1.4365, Train Acc: 83.28% | Val Loss: 1.1957, Val Acc: 90.04%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 91.49%

Epoch 13: Train Loss: 1.3334, Train Acc: 85.49% | Val Loss: 1.1073, Val Acc: 91.49%

开始微调骨干网络...

Epoch 14: Train Loss: 1.2600, Train Acc: 88.27% | Val Loss: 1.0439, Val Acc: 91.12%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 92.21%

Epoch 15: Train Loss: 1.1814, Train Acc: 90.86% | Val Loss: 1.0228, Val Acc: 92.21%

开始微调骨干网络...

Epoch 16: Train Loss: 1.1301, Train Acc: 91.94% | Val Loss: 1.0082, Val Acc: 91.30%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 92.93%

Epoch 17: Train Loss: 1.0650, Train Acc: 93.19% | Val Loss: 0.9462, Val Acc: 92.93%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 93.66%

Epoch 18: Train Loss: 1.0520, Train Acc: 94.15% | Val Loss: 0.9361, Val Acc: 93.66%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 93.84%

Epoch 19: Train Loss: 1.0103, Train Acc: 94.63% | Val Loss: 0.9533, Val Acc: 93.84%

开始微调骨干网络...

Epoch 20: Train Loss: 0.9932, Train Acc: 94.95% | Val Loss: 0.9688, Val Acc: 93.84%

开始微调骨干网络...

加载最佳模型: resnet101, 验证准确率: 93.84%

============================================================

训练模型: efficientnet_b3

============================================================

✓ 保存最佳模型,验证准确率: 66.67%

Epoch 1: Train Loss: 5.4504, Train Acc: 24.84% | Val Loss: 1.9644, Val Acc: 66.67%

✓ 保存最佳模型,验证准确率: 71.01%

Epoch 2: Train Loss: 3.3708, Train Acc: 49.62% | Val Loss: 1.9350, Val Acc: 71.01%

✓ 保存最佳模型,验证准确率: 72.46%

Epoch 3: Train Loss: 3.0523, Train Acc: 55.18% | Val Loss: 1.8518, Val Acc: 72.46%

✓ 保存最佳模型,验证准确率: 74.64%

Epoch 4: Train Loss: 2.7719, Train Acc: 59.14% | Val Loss: 1.7739, Val Acc: 74.64%

✓ 保存最佳模型,验证准确率: 76.81%

Epoch 5: Train Loss: 2.5621, Train Acc: 61.09% | Val Loss: 1.6939, Val Acc: 76.81%

✓ 保存最佳模型,验证准确率: 79.17%

Epoch 6: Train Loss: 2.4091, Train Acc: 62.40% | Val Loss: 1.6971, Val Acc: 79.17%

Epoch 7: Train Loss: 2.2682, Train Acc: 64.19% | Val Loss: 1.7452, Val Acc: 75.54%

Epoch 8: Train Loss: 2.2013, Train Acc: 65.70% | Val Loss: 1.6025, Val Acc: 76.81%

Epoch 9: Train Loss: 2.0413, Train Acc: 67.87% | Val Loss: 1.6075, Val Acc: 77.54%

✓ 保存最佳模型,验证准确率: 79.53%

Epoch 10: Train Loss: 1.9549, Train Acc: 69.31% | Val Loss: 1.4882, Val Acc: 79.53%

Epoch 11: Train Loss: 1.9288, Train Acc: 69.44% | Val Loss: 1.4771, Val Acc: 78.99%

Epoch 12: Train Loss: 1.8857, Train Acc: 69.25% | Val Loss: 1.4694, Val Acc: 78.80%

Epoch 13: Train Loss: 1.8154, Train Acc: 69.95% | Val Loss: 1.4594, Val Acc: 77.72%

Epoch 14: Train Loss: 1.7688, Train Acc: 71.00% | Val Loss: 1.4482, Val Acc: 77.54%

Epoch 15: Train Loss: 1.7351, Train Acc: 72.06% | Val Loss: 1.4621, Val Acc: 78.26%

✓ 保存最佳模型,验证准确率: 79.89%

Epoch 16: Train Loss: 1.7532, Train Acc: 70.75% | Val Loss: 1.4210, Val Acc: 79.89%

✓ 保存最佳模型,验证准确率: 80.07%

Epoch 17: Train Loss: 1.7205, Train Acc: 71.93% | Val Loss: 1.4024, Val Acc: 80.07%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 83.88%

Epoch 18: Train Loss: 1.6303, Train Acc: 74.87% | Val Loss: 1.3215, Val Acc: 83.88%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 86.78%

Epoch 19: Train Loss: 1.5164, Train Acc: 79.86% | Val Loss: 1.2681, Val Acc: 86.78%

开始微调骨干网络...

Epoch 20: Train Loss: 1.4247, Train Acc: 83.34% | Val Loss: 1.2648, Val Acc: 86.23%

开始微调骨干网络...

加载最佳模型: efficientnet_b3, 验证准确率: 86.78%

============================================================

训练模型: densenet121

============================================================

✓ 保存最佳模型,验证准确率: 73.55%

Epoch 1: Train Loss: 5.8393, Train Acc: 19.44% | Val Loss: 1.6572, Val Acc: 73.55%

✓ 保存最佳模型,验证准确率: 81.52%

Epoch 2: Train Loss: 3.5104, Train Acc: 45.75% | Val Loss: 1.6149, Val Acc: 81.52%

✓ 保存最佳模型,验证准确率: 82.97%

Epoch 3: Train Loss: 2.9466, Train Acc: 53.61% | Val Loss: 1.5911, Val Acc: 82.97%

✓ 保存最佳模型,验证准确率: 85.14%

Epoch 4: Train Loss: 2.6857, Train Acc: 57.96% | Val Loss: 1.5351, Val Acc: 85.14%

Epoch 5: Train Loss: 2.6012, Train Acc: 58.95% | Val Loss: 1.5477, Val Acc: 82.43%

✓ 保存最佳模型,验证准确率: 86.41%

Epoch 6: Train Loss: 2.4685, Train Acc: 61.32% | Val Loss: 1.4291, Val Acc: 86.41%

Epoch 7: Train Loss: 2.3199, Train Acc: 63.01% | Val Loss: 1.4478, Val Acc: 83.88%

Epoch 8: Train Loss: 2.3126, Train Acc: 62.21% | Val Loss: 1.4215, Val Acc: 84.24%

Epoch 9: Train Loss: 2.1542, Train Acc: 65.25% | Val Loss: 1.3958, Val Acc: 85.14%

Epoch 10: Train Loss: 2.0501, Train Acc: 67.55% | Val Loss: 1.3689, Val Acc: 84.60%

Epoch 11: Train Loss: 2.0299, Train Acc: 65.89% | Val Loss: 1.3182, Val Acc: 85.51%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 86.78%

Epoch 12: Train Loss: 1.8685, Train Acc: 71.84% | Val Loss: 1.3925, Val Acc: 86.78%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 88.41%

Epoch 13: Train Loss: 1.8166, Train Acc: 73.34% | Val Loss: 1.3586, Val Acc: 88.41%

开始微调骨干网络...

Epoch 14: Train Loss: 1.6926, Train Acc: 76.53% | Val Loss: 1.3231, Val Acc: 86.78%

开始微调骨干网络...

Epoch 15: Train Loss: 1.6267, Train Acc: 78.07% | Val Loss: 1.3441, Val Acc: 87.86%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 89.49%

Epoch 16: Train Loss: 1.5582, Train Acc: 79.80% | Val Loss: 1.3244, Val Acc: 89.49%

开始微调骨干网络...

Epoch 17: Train Loss: 1.5468, Train Acc: 81.04% | Val Loss: 1.2831, Val Acc: 89.49%

开始微调骨干网络...

✓ 保存最佳模型,验证准确率: 90.22%

Epoch 18: Train Loss: 1.5118, Train Acc: 81.87% | Val Loss: 1.2677, Val Acc: 90.22%

开始微调骨干网络...

Epoch 19: Train Loss: 1.4077, Train Acc: 84.97% | Val Loss: 1.2536, Val Acc: 89.86%

开始微调骨干网络...

Epoch 20: Train Loss: 1.4084, Train Acc: 84.27% | Val Loss: 1.2533, Val Acc: 90.04%

开始微调骨干网络...

加载最佳模型: densenet121, 验证准确率: 90.22%

============================================================

优化集成权重

============================================================

Epoch 1: 集成验证准确率: 94.20%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 2: 集成验证准确率: 95.11%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 3: 集成验证准确率: 94.38%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 4: 集成验证准确率: 94.38%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 5: 集成验证准确率: 94.02%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 6: 集成验证准确率: 94.93%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 7: 集成验证准确率: 94.93%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 8: 集成验证准确率: 94.38%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 9: 集成验证准确率: 94.93%

当前权重: [0.25, 0.25, 0.25, 0.25]

Epoch 10: 集成验证准确率: 95.11%

当前权重: [0.25, 0.25, 0.25, 0.25]

============================================================

测试集成模型性能

============================================================

集成模型测试准确率: 92.80%

============================================================

各个模型独立性能

============================================================

resnet50: 90.65%

resnet101: 91.06%

efficientnet_b3: 86.92%

densenet121: 87.41%

可视化展示:

相关推荐
钮钴禄·爱因斯晨3 小时前
机器学习(二):KNN算法简介及API介绍(分类、回归)
人工智能·算法·机器学习·分类·回归
KmjJgWeb12 小时前
工业零件检测与分类——基于YOLOv5的改进模型 Dysample 实现
yolo·分类·数据挖掘
数字化脑洞实验室13 小时前
智能化时代宠物护理设备的选型逻辑
宠物·机械·机械制造·制造设备
Blossom.11817 小时前
AI Agent智能办公助手:从ChatGPT到真正“干活“的系统
人工智能·分布式·python·深度学习·神经网络·chatgpt·迁移学习
ZCXZ12385296a20 小时前
基于YOLOv8-VanillaNet的章鱼图像中生物与非物体识别与分类
yolo·分类·数据挖掘
莫非王土也非王臣20 小时前
迁移学习详情介绍
人工智能·机器学习·迁移学习
翱翔的苍鹰1 天前
CIFAR-10 是一个经典的小型彩色图像分类数据集,广泛用于深度学习入门、模型验证和算法研究
深度学习·算法·分类
qwerasda1238521 天前
青豆质量分类识别_YOLOv5_SPDConv_改进算法_目标检测_深度学习_计算机视觉
算法·计算机视觉·分类
wfeqhfxz25887821 天前
香梨表面损伤检测与分类——基于改进YOLOv8-Goldyolo-ASF模型的即插即用检测方法_2
yolo·计算机视觉·分类