【深度学习实验】—— DenseNet 算法实战
2026/7/4 4:23:29 网站建设 项目流程
  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

文章目录

  • 1. 简介
  • 2. 环境
  • 3. 代码实现
    • 3.1 前期准备
      • 3.1.1 设置GPU & 导入库
      • 3.1.2 数据加载
    • 3.2 模型建立与训练
      • 3.2.1 定义 DenseNet 网络模型
      • 3.2.2 模型结构概览
      • 3.2.3 定义训练和测试函数
      • 3.2.4 训练模型
      • 训练配置:
      • 训练策略:
  • 4. 模型评估
    • 4.1 可视化训练过程
    • 4.2 加载最优模型并评估
  • 5.总结

1. 简介

项目内容
模型DenseNet-121(growth_rate=32, block_config=(6,12,24,16))
任务三分类图像分类(Normal / Mild / Severe)
数据集1661 张图像,80/20 划分
最优性能测试准确率 94.3%,测试损失 0.162

2. 环境

  • 语言环境:Python 3.14.6
  • 编译器:Jupyter Notebook
  • 深度学习环境:PyTorch ( torch 2.12.1 + torchvision 0.27.1 )

3. 代码实现

3.1 前期准备

3.1.1 设置GPU & 导入库

导入 PyTorch、torchvision 等深度学习库,配置 matplotlib 中文字体,自动选择 GPU/CPU 设备.

importtorchimporttorch.nnasnnimporttorch.optimasoptimimporttorch.nn.functionalasFfromtorchvisionimporttransforms,datasetsimportwarningsimportcopyimportmatplotlib.pyplotaspltfromdatetimeimportdatetimefromtorchsummaryimportsummary warnings.filterwarnings("ignore")plt.rcParams["figure.dpi"]=100plt.rcParams['font.sans-serif']=['SimHei']plt.rcParams['axes.unicode_minus']=Falsedevice=torch.device("cuda"iftorch.cuda.is_available()else"cpu")device

3.1.2 数据加载

data_dir='./Data/data/'train_transforms=transforms.Compose([transforms.Resize((224,224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])])total_data=datasets.ImageFolder(data_dir,transform=train_transforms)print(f"Classes:{total_data.class_to_idx}")print(f"Total samples:{len(total_data)}")train_size=int(0.8*len(total_data))test_size=len(total_data)-train_size train_dataset,test_dataset=torch.utils.data.random_split(total_data,[train_size,test_size])batch_size=16train_dl=torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True)test_dl=torch.utils.data.DataLoader(test_dataset,batch_size=batch_size)forX,yintest_dl:print(f"Batch shape:{X.shape}, Labels:{y.shape}")break

3.2 模型建立与训练

3.2.1 定义 DenseNet 网络模型

实现经典的DenseNet(Densely Connected Convolutional Network)架构,其核心思想是密集连接——每一层都接收前面所有层的特征图作为输入。

四个核心组件

组件作用
Bottleneck瓶颈层:BN→ReLU→1×1Conv(压缩通道至 4×growth_rate)→BN→ReLU→3×3Conv,输出与输入在通道维度拼接(torch.cat),实现密集连接
DenseBlock密集块:由多个 Bottleneck 层堆叠而成,每层输入通道数随层数线性增长(in_channels + i × growth_rate
Transition过渡层:位于两个 DenseBlock 之间,通过 1×1 卷积压缩通道数至一半,再用 2×2 平均池化缩小特征图尺寸
DenseNet整体网络:7×7Conv → MaxPool → 4个DenseBlock(中间穿插Transition)→ BN → GlobalAvgPool → FC

本实验中num_classes=3,对应三分类任务(Normal / Mild / Severe)。

classBottleneck(nn.Module):def__init__(self,in_channels,growth_rate):super(Bottleneck,self).__init__()self.bn1=nn.BatchNorm2d(in_channels)self.conv1=nn.Conv2d(in_channels,4*growth_rate,kernel_size=1,bias=False)self.bn2=nn.BatchNorm2d(4*growth_rate)self.conv2=nn.Conv2d(4*growth_rate,growth_rate,kernel_size=3,padding=1,bias=False)defforward(self,x):out=self.conv1(F.relu(self.bn1(x)))out=self.conv2(F.relu(self.bn2(out)))out=torch.cat([out,x],1)returnoutclassDenseBlock(nn.Module):def__init__(self,in_channels,num_Layers,growth_rate):super(DenseBlock,self).__init__()self.layers=nn.ModuleList([Bottleneck(in_channels+i*growth_rate,growth_rate)foriinrange(num_Layers)])defforward(self,x):forlayerinself.layers:x=layer(x)returnxclassTransition(nn.Module):def__init__(self,in_channels,out_channels):super(Transition,self).__init__()self.bn=nn.BatchNorm2d(in_channels)self.conv=nn.Conv2d(in_channels,out_channels,kernel_size=1,bias=False)self.avg_pool=nn.AvgPool2d(kernel_size=2,stride=2)defforward(self,x):out=self.conv(F.relu(self.bn(x)))out=self.avg_pool(out)returnoutclassDenseNet(nn.Module):def__init__(self,growth_rate=32,block_config=(6,12,24,16),num_classes=1000):super(DenseNet,self).__init__()self.conv1=nn.Conv2d(3,64,kernel_size=7,stride=2,padding=3,bias=False)self.bn1=nn.BatchNorm2d(64)self.max_pool=nn.MaxPool2d(kernel_size=3,stride=2,padding=1)num_features=64self.blocks=nn.ModuleList([])fori,num_Layersinenumerate(block_config):block=DenseBlock(num_features,num_Layers,growth_rate)self.blocks.append(block)num_features=num_features+num_Layers*growth_rateifi!=len(block_config)-1:trans=Transition(num_features,num_features//2)self.blocks.append(trans)num_features=num_features//2self.bn_final=nn.BatchNorm2d(num_features)self.avg_pool=nn.AdaptiveAvgPool2d((1,1))self.fc=nn.Linear(num_features,num_classes)defforward(self,x):out=self.conv1(x)out=self.bn1(out)out=F.relu(out)out=self.max_pool(out)forblockinself.blocks:out=block(out)out=self.bn_final(out)out=F.relu(out)out=self.avg_pool(out)out=out.view(out.size(0),-1)out=self.fc(out)returnout device=torch.device("cuda"iftorch.cuda.is_available()else"cpu")model=DenseNet(num_classes=3).to(device)summary(model,input_size=(3,32,32))

3.2.2 模型结构概览

通过随机输入验证模型的前向传播是否正常工作:

  • 输入(1, 3, 224, 224)— 1张 224×224 的 RGB 图像
  • 输出(1, 3)— 3个类别的预测分数(logits)

参数量统计:模型共有6,956,931个参数,且全部可训练(未使用预训练权重冻结)。相比 ResNet-50(约 2500 万参数),DenseNet-121 的参数效率更高,这得益于 1×1 卷积的通道压缩和特征复用机制。

# 测试前向传播x=torch.randn(1,3,224,224).to(device)out=model(x)print(f"Input shape:{x.shape}")print(f"Output shape:{out.shape}")# 参数量统计total_params=sum(p.numel()forpinmodel.parameters())trainable_params=sum(p.numel()forpinmodel.parameters()ifp.requires_grad)print(f"Total params:{total_params:,}")print(f"Trainable params:{trainable_params:,}")

3.2.3 定义训练和测试函数

  • train()函数 — 训练阶段:
    对每个 mini-batch 执行标准的前向传播→计算损失→反向传播→参数更新流程:

  • 累加每个 batch 的损失和正确预测数

  • 返回平均损失整体准确率(总正确数 / 总样本数)

  • test()函数 — 测试/验证阶段:

  • 使用torch.no_grad()禁用梯度计算,减少内存开销并加速推理

  • 不进行参数更新,仅评估模型在测试集上的泛化性能

  • 同样返回平均损失和整体准确率

deftrain(dataloader,model,loss_fn,optimizer):size=len(dataloader.dataset)num_batches=len(dataloader)train_loss,train_acc=0,0forX,yindataloader:X,y=X.to(device),y.to(device)pred=model(X)loss=loss_fn(pred,y)optimizer.zero_grad()loss.backward()optimizer.step()train_loss+=loss.item()train_acc+=(pred.argmax(1)==y).type(torch.float).sum().item()returntrain_loss/num_batches,train_acc/sizedeftest(dataloader,model,loss_fn):size=len(dataloader.dataset)num_batches=len(dataloader)test_loss,test_acc=0,0withtorch.no_grad():forimgs,targetindataloader:imgs,target=imgs.to(device),target.to(device)target_pred=model(imgs)loss=loss_fn(target_pred,target)test_acc+=(target_pred.argmax(1)==target).type(torch.float).sum().item()test_loss+=loss.item()returntest_loss/num_batches,test_acc/size

3.2.4 训练模型

训练配置:

  • 优化器:AdamW(带动量的自适应学习率优化器,含权重衰减正则化),学习率lr=1e-4
  • 损失函数:CrossEntropyLoss(交叉熵损失),适用于多分类任务
  • 训练轮次:10 个 Epoch

训练策略:

  • 每个 Epoch 结束后在测试集上评估,记录训练/测试的损失和准确率
  • Best Model 保存:当测试准确率超过历史最佳时,深拷贝当前模型权重
  • 训练结束后将最优模型保存至./best_resnet50v2.pth

最优模型出现在第 9 个 Epoch,测试准确率达94.3%

optimizer=torch.optim.AdamW(model.parameters(),lr=1e-4)loss_fn=nn.CrossEntropyLoss()epochs=10train_loss,train_acc=[],[]test_loss,test_acc=[],[]best_acc=0forepochinrange(epochs):model.train()train_epoch_loss,train_epoch_acc=train(train_dl,model,loss_fn,optimizer)model.eval()epoch_test_loss,epoch_test_acc=test(test_dl,model,loss_fn)ifepoch_test_acc>best_acc:best_acc=epoch_test_acc best_model_wts=copy.deepcopy(model)train_acc.append(train_epoch_acc)train_loss.append(train_epoch_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)lr=optimizer.param_groups[0]['lr']print(f"Epoch:{epoch+1:2d}, Train_acc:{train_epoch_acc*100:.1f}%, Train_loss:{train_epoch_loss:.3f}, "f"Test_acc:{epoch_test_acc*100:.1f}%, Test_loss:{epoch_test_loss:.3f}, Lr:{lr:.2E}")PATH='./best_resnet50v2.pth'torch.save(best_model_wts.state_dict(),PATH)print('Done.')
Epoch:1, Train_acc:71.5%, Train_loss:0.752, Test_acc:74.2%, Test_loss:0.649, Lr:1.00E-04 Epoch:2, Train_acc:78.7%, Train_loss:0.568, Test_acc:83.8%, Test_loss:0.425, Lr:1.00E-04 Epoch:3, Train_acc:81.6%, Train_loss:0.482, Test_acc:76.3%, Test_loss:0.546, Lr:1.00E-04 Epoch:4, Train_acc:83.3%, Train_loss:0.432, Test_acc:79.3%, Test_loss:0.596, Lr:1.00E-04 Epoch:5, Train_acc:85.5%, Train_loss:0.399, Test_acc:80.2%, Test_loss:0.438, Lr:1.00E-04 Epoch:6, Train_acc:87.3%, Train_loss:0.348, Test_acc:83.8%, Test_loss:0.375, Lr:1.00E-04 Epoch:7, Train_acc:89.6%, Train_loss:0.284, Test_acc:86.5%, Test_loss:0.312, Lr:1.00E-04 Epoch:8, Train_acc:91.4%, Train_loss:0.255, Test_acc:85.9%, Test_loss:0.389, Lr:1.00E-04 Epoch:9, Train_acc:92.2%, Train_loss:0.221, Test_acc:94.3%, Test_loss:0.162, Lr:1.00E-04 Epoch:10, Train_acc:91.7%, Train_loss:0.225, Test_acc:93.1%, Test_loss:0.203, Lr:1.00E-04 Done.

4. 模型评估

4.1 可视化训练过程

绘制训练和测试的准确率、损失曲线,并使用 Matplotlib 将训练集与验证集的准确率(Accuracy)和损失值(Loss)随时间变化的趋势绘制成了两幅直观的折线图。

current_time=datetime.now()epochs_range=range(epochs)plt.figure(figsize=(12,3))plt.subplot(1,2,1)plt.plot(epochs_range,train_acc,label='Training Accuracy')plt.plot(epochs_range,test_acc,label='Test Accuracy')plt.legend(loc='lower right')plt.title('Training and Validation Accuracy')plt.xlabel(current_time)plt.subplot(1,2,2)plt.plot(epochs_range,train_loss,label='Training Loss')plt.plot(epochs_range,test_loss,label='Test Loss')plt.legend(loc='upper right')plt.title('Training and Validation Loss')plt.show()

4.2 加载最优模型并评估

加载训练过程中保存的最优模型权重,在测试集上进行最终评估,输出测试准确率和损失值。

best_model_wts.load_state_dict(torch.load(PATH,map_location=device))test_epoch_loss,test_epoch_acc=test(test_dl,best_model_wts,loss_fn)print(f"Best model - Test Acc:{test_epoch_acc*100:.1f}%, Test Loss:{test_epoch_loss:.3f}")
Best model - Test Acc:94.3%, Test Loss:0.162

5.总结

  1. DenseNet 表现优异:在仅 10 个 Epoch 的训练后,测试准确率达到94.3%,说明密集连接机制能有效提取图像特征,即使在较小的数据集上也能取得良好效果。

  2. 参数效率高:模型仅约695 万参数(远少于 ResNet-50 的 2500 万),却达到了较高的分类精度,体现了 DenseNet 通过特征复用减少冗余参数的优势。

  3. 收敛速度快:模型从第 1 个 Epoch 的 74.2% 快速提升,第 9 个 Epoch 即达到最优,说明 AdamW 优化器配合合理的学习率(1e-4)能有效加速收敛。

  4. 存在轻微过拟合迹象:训练损失持续下降而测试损失有波动(如第 8→9 Epoch 测试损失从 0.389 降至 0.162,第 10 Epoch 又回升至 0.203),建议后续可通过以下方式改善:

    • 增加数据增强(随机翻转、旋转、色彩抖动等)
    • 引入学习率调度策略(如 Cosine Annealing)
    • 增加 Dropout 或权重衰减
    • 使用交叉验证替代单次随机划分
  5. 数据规模的影响:1661 张图像的数据集相对较小,测试集仅约 333 张,测试准确率的波动可能与样本量不足有关。增加数据量或使用预训练权重微调有望进一步提升性能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询