Day.47

冻结卷积:

if freeze_epochs > 0:

        model = freeze_model(model, freeze=True)

    for epoch in range(epochs):

        if epoch == freeze_epochs:

            model = freeze_model(model, freeze=False)

            optimizer.param_groups[0]['lr'] = 1e-4  

        model.train()  

        running_loss = 0.0

        correct_train = 0

        total_train = 0

        for batch_idx, (data, target) in enumerate(train_loader):

            data, target = data.to(device), target.to(device)

            optimizer.zero_grad()

            output = model(data)

            loss = criterion(output, target)

            loss.backward()

            optimizer.step()

            iter_loss = loss.item()

            all_iter_losses.append(iter_loss)

            iter_indices.append(epoch * len(train_loader) + batch_idx + 1)

            running_loss += iter_loss

            _, predicted = output.max(1)

            total_train += target.size(0)

            correct_train += predicted.eq(target).sum().item()

            if (batch_idx + 1) % 100 == 0:

                print(f"Epoch {epoch+1}/{epochs} | Batch {batch_idx+1}/{len(train_loader)} "

                      f"| 单Batch损失: {iter_loss:.4f}")

tensorboard:

log_dir = "runs/cifar10_cnn_exp"
if os.path.exists(log_dir): 
    version = 1 
    while os.path.exists(f"{log_dir}_v{version}"): 
        version += 1 
    log_dir = f"{log_dir}_v{version}" 
writer = SummaryWriter(log_dir) 
print(f"TensorBoard 日志目录: {log_dir}") # 所以第一次是cifar10_cnn_exp、第二次是cifar10_cnn_exp_v1
def train(model, train_loader, test_loader, criterion, optimizer, scheduler, device, epochs, writer):
    model.train()
    global_step = 0  

    # 记录模型结构和训练图像
    dataiter = iter(train_loader)
    images, labels = next(dataiter)
    images = images.to(device)
    writer.add_graph(model, images)
    
    img_grid = torchvision.utils.make_grid(images[:8].cpu())
    writer.add_image('原始训练图像(增强前)', img_grid, global_step=0)

    for epoch in range(epochs):
        running_loss = 0.0
        correct = 0
        total = 0
        
        for batch_idx, (data, target) in enumerate(train_loader):
            data, target = data.to(device), target.to(device)
            
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()

            # 统计准确率
            running_loss += loss.item()
            _, predicted = output.max(1)
            total += target.size(0)
            correct += predicted.eq(target).sum().item()

            # 记录每个 batch 的损失、准确率和学习率
            batch_acc = 100. * correct / total
            writer.add_scalar('Train/Batch Loss', loss.item(), global_step)
            writer.add_scalar('Train/Batch Accuracy', batch_acc, global_step)
            writer.add_scalar('Train/Learning Rate', optimizer.param_groups[0]['lr'], global_step)

            # 每 200 个 batch 记录一次参数直方图
            if (batch_idx + 1) % 200 == 0:
                for name, param in model.named_parameters():
                    writer.add_histogram(f'Weights/{name}', param, global_step)
                    if param.grad is not None:
                        writer.add_histogram(f'Gradients/{name}', param.grad, global_step)

            global_step += 1

        # 计算 epoch 级训练指标
        epoch_train_loss = running_loss / len(train_loader)
        epoch_train_acc = 100. * correct / total
        writer.add_scalar('Train/Epoch Loss', epoch_train_loss, epoch)
        writer.add_scalar('Train/Epoch Accuracy', epoch_train_acc, epoch)

        # 测试阶段
        model.eval()
        test_loss = 0
        correct_test = 0
        total_test = 0
        wrong_images = []
        wrong_labels = []
        wrong_preds = []

        with torch.no_grad():
            for data, target in test_loader:
                data, target = data.to(device), target.to(device)
                output = model(data)
                test_loss += criterion(output, target).item()
                _, predicted = output.max(1)
                total_test += target.size(0)
                correct_test += predicted.eq(target).sum().item()

                # 收集错误预测样本
                wrong_mask = (predicted != target)
                if wrong_mask.sum() > 0:
                    wrong_batch_images = data[wrong_mask][:8].cpu()
                    wrong_batch_labels = target[wrong_mask][:8].cpu()
                    wrong_batch_preds = predicted[wrong_mask][:8].cpu()
                    wrong_images.extend(wrong_batch_images)
                    wrong_labels.extend(wrong_batch_labels)
                    wrong_preds.extend(wrong_batch_preds)

        # 计算 epoch 级测试指标
        epoch_test_loss = test_loss / len(test_loader)
        epoch_test_acc = 100. * correct_test / total_test
        writer.add_scalar('Test/Epoch Loss', epoch_test_loss, epoch)
        writer.add_scalar('Test/Epoch Accuracy', epoch_test_acc, epoch)

        # 可视化错误预测样本
        if wrong_images:
            wrong_img_grid = torchvision.utils.make_grid(wrong_images)
            writer.add_image('错误预测样本', wrong_img_grid, epoch)
            wrong_text = [f"真实: {classes[wl]}, 预测: {classes[wp]}" 
                         for wl, wp in zip(wrong_labels, wrong_preds)]
            writer.add_text('错误预测标签', '\n'.join(wrong_text), epoch)

        # 更新学习率调度器
        scheduler.step(epoch_test_loss)
        print(f'Epoch {epoch+1}/{epochs} 完成 | 测试准确率: {epoch_test_acc:.2f}%')

    writer.close()
    return epoch_test_acc

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 执行训练
epochs = 20
print("开始使用CNN训练模型...")
print("训练后执行: tensorboard --logdir=runs 查看可视化")

final_accuracy = train(model, train_loader, test_loader, criterion, optimizer, scheduler, device, epochs, writer)
print(f"训练完成!最终测试准确率: {final_accuracy:.2f}%")

通道注意力:

class ChannelAttention(nn.Module):

    def __init__(self, in_channels, reduction_ratio=16):

            in_channels: 输入特征图的通道数

            reduction_ratio: 降维比例,用于减少参数量

        super(ChannelAttention, self).__init__()

        self.avg_pool = nn.AdaptiveAvgPool2d(1)

            self.fc = nn.Sequential(

            nn.Linear(in_channels, in_channels // reduction_ratio, bias=False),

            nn.ReLU(inplace=True),

            nn.Linear(in_channels // reduction_ratio, in_channels, bias=False),

            nn.Sigmoid()

        )

    def forward(self, x):

        """

        参数:

            x: 输入特征图,形状为 [batch_size, channels, height, width]

       

        返回:

            加权后的特征图,形状不变

        """

        batch_size, channels, height, width = x.size()

        avg_pool_output = self.avg_pool(x)

        avg_pool_output = avg_pool_output.view(batch_size, channels)

        channel_weights = self.fc(avg_pool_output)

        channel_weights = channel_weights.view(batch_size, channels, 1, 1)

        return x * channel_weights

模型定义:

class CNN(nn.Module):

    def __init__(self):

        super(CNN, self).__init__()  

       

        # ---------------------- 第一个卷积块 ----------------------

        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)

        self.bn1 = nn.BatchNorm2d(32)

        self.relu1 = nn.ReLU()

        self.ca1 = ChannelAttention(in_channels=32, reduction_ratio=16)  

        self.pool1 = nn.MaxPool2d(2, 2)  

       

        # ---------------------- 第二个卷积块 ----------------------

        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)

        self.bn2 = nn.BatchNorm2d(64)

        self.relu2 = nn.ReLU()

        self.ca2 = ChannelAttention(in_channels=64, reduction_ratio=16)  

        self.pool2 = nn.MaxPool2d(2)  

       

        # ---------------------- 第三个卷积块 ----------------------

        self.conv3 = nn.Conv2d(64, 128, 3, padding=1)

        self.bn3 = nn.BatchNorm2d(128)

        self.relu3 = nn.ReLU()

        self.ca3 = ChannelAttention(in_channels=128, reduction_ratio=16)  

        self.pool3 = nn.MaxPool2d(2)  

       

        # ---------------------- 全连接层(分类器) ----------------------

        self.fc1 = nn.Linear(128 * 4 * 4, 512)

        self.dropout = nn.Dropout(p=0.5)

        self.fc2 = nn.Linear(512, 10)

    def forward(self, x):

        # ---------- 卷积块1处理 ----------

        x = self.conv1(x)      

        x = self.bn1(x)        

        x = self.relu1(x)      

        x = self.ca1(x)  

        x = self.pool1(x)      

       

        # ---------- 卷积块2处理 ----------

        x = self.conv2(x)      

        x = self.bn2(x)        

        x = self.relu2(x)      

        x = self.ca2(x)  

        x = self.pool2(x)      

       

        # ---------- 卷积块3处理 ----------

        x = self.conv3(x)      

        x = self.bn3(x)        

        x = self.relu3(x)      

        x = self.ca3(x)  

        x = self.pool3(x)      

       

        # ---------- 展平与全连接层 ----------

        x = x.view(-1, 128 * 4 * 4)  

        x = self.fc1(x)          

        x = self.relu3(x)        

        x = self.dropout(x)      

        x = self.fc2(x)          

       

        return x  

# 重新初始化模型,包含通道注意力模块

model = CNN()

model = model.to(device)  

criterion = nn.CrossEntropyLoss()  

optimizer = optim.Adam(model.parameters(), lr=0.001)  

scheduler.step()

scheduler = optim.lr_scheduler.ReduceLROnPlateau(

    optimizer,        

    mode='min',     

    patience=3,      

    factor=0.5       @浙大疏锦行

)
 

你可能感兴趣的:(Day.47)