@[toc]
论文来源:(2017)Densely Connected Convolutional Networks
作者:Gao Huang 等人
一、摘要
- 最近的工作表明,如果卷积网络在靠近输入的层和靠近输出的层之间包含较短的连接,则它们可以更深、更准确和更有效地训练。
- 在本文中,我们接受了这一观察并介绍了密集卷积网络(DenseNet),它以前馈方式将每一层连接到其他每一层。具有 L 层的传统卷积网络有 L 个连接——每层与其后续层之间有一个连接——我们的网络有 L*(L+1) /2 个直接连接。 F 或每一层,所有前面的层的特征图被用作输入,它自己的特征图被用作所有后续层的输入。
- DenseNets 有几个引人注目的优势:它们缓解了梯度消失问题,加强了特征传播,鼓励特征重用,并大大减少了参数的数量。
- 我们在四个竞争激烈的对象识别基准任务(CIF AR-10、CIF AR-100、SVHN 和 ImageNet)上评估我们提出的架构。
- DenseNets 在大多数情况下都比最先进的技术获得了显着改进,同时需要更少的计算来实现高性能。
二、Dense Net 网络结构
三、Dense Block
在Dense Block中,每一层都与后面的层有跳连接
为了更好的保存低层网络的特征,DenseNet 使用的是将不同层的输出拼接在一起,而在残差网络中使用的是单位加操作。以上便是DenseNet算法的动机
在Dsense Block中的每一层,都有1×1和3×3两个卷积核
论文中指出:”虽然每一层只产生 k 个输出特征图,但它通常有更多的输入。可以在每个 3×3 卷积之前引入一个 1×1 卷积作为瓶颈层,以减少输入特征图的数量,从而提高计算效率。我们发现这种设计对 DenseNet 特别有效,我们将我们的网络称为具有这样一个瓶颈层的网络,即 H' 的 BN-ReLU-Conv(1×1)-BN-ReLU-Conv(3×3) 版本“
四、PyTorch-GPU代码实现
MNIST手写数据集的图片大小为28×28,如果采用论文里的结构,在第四层pooling时会出现0×0尺寸的情况,为了避免这个情况,我在第四层前的1×1的conv层设置了padding=(1,1),故下面的代码结构不和论文里完全一致
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.optim as optim
import torch.nn.functional as F
def conv_block(in_channels, out_channels):
block = nn.Sequential(nn.BatchNorm2d(in_channels),
nn.ReLU(),
nn.Conv2d(in_channels, out_channels, kernel_size=(3, 3), padding=(1, 1)))
return block
class DenseBlock(nn.Module):
def __init__(self, in_channels, out_channels, cnt):
super(DenseBlock, self).__init__()
net = []
for i in range(cnt):
in_c = in_channels + i * out_channels
net.append(conv_block(in_c, out_channels))
self.net = nn.ModuleList(net)
self.out_channels = in_channels + cnt * out_channels # 计算输出通道数
def forward(self, x):
for blk in self.net:
y = blk(x)
x = torch.cat((x, y), dim=1) # 在通道维上将输入和输出拼接
return x
class DenselyNet(torch.nn.Module):
def __init__(self):
super(DenselyNet, self).__init__()
self.conv7x7 = nn.Conv2d(1, 16, kernel_size=(7, 7), padding=(3, 3), stride=(2, 2))
self.conv1x1_1 = nn.Conv2d(112, 16, kernel_size=(1, 1))
self.conv1x1_2 = nn.Conv2d(208, 16, kernel_size=(1, 1))
self.conv1x1_3 = nn.Conv2d(400, 16, kernel_size=(1, 1),padding=(1,1))
self.max_pooling3x3 = nn.MaxPool2d(kernel_size=(3, 3), stride=(2, 2))
self.avg_pooling2x2 = nn.AvgPool2d(kernel_size=(2, 2), stride=(2, 2))
self.dense_block1 = DenseBlock(16, 16, 6)
self.dense_block2 = DenseBlock(16, 16, 12)
self.dense_block3 = DenseBlock(16, 16, 24)
self.dense_block4 = DenseBlock(16, 16, 16)
self.fc = nn.Linear(272, 10)
def forward(self, x):
in_size = x.size(0)
x = torch.relu(self.conv7x7(x))
x = torch.relu(self.max_pooling3x3(x))
x = self.dense_block1(x)
x = torch.relu(self.conv1x1_1(x))
x = torch.relu(self.avg_pooling2x2(x))
x = self.dense_block2(x)
x = torch.relu(self.conv1x1_2(x))
x = torch.relu(self.avg_pooling2x2(x))
x = self.dense_block3(x)
x = torch.relu(self.conv1x1_3(x))
x = torch.relu(self.avg_pooling2x2(x))
x = self.dense_block4(x)
x = torch.relu(F.adaptive_avg_pool2d(x, output_size=(1, 1)))
x = x.view(in_size, -1)
x = self.fc(x)
return x
# 单次训练函数
def train(epoch, criterion):
running_loss = 0.0
for batch_idx, data in enumerate(train_loader, 0):
inputs, target = data
# 将inputs, target转移到Gpu或者Cpu上
inputs, target = inputs.to(device), target.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
if batch_idx % 300 == 299:
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0
# 单次测试函数
def ttt():
correct = 0.0
total = 0.0
with torch.no_grad():
for data in test_loader:
images, labels = data
# 将images, labels转移到Gpu或者Cpu上
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, dim=1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))
if __name__ == '__main__':
batch_size = 64
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_dataset = datasets.MNIST(root='../dataset/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 声明模型
model = DenselyNet()
# 将模型转移道Gpu或者Cpu上
model.to(device)
# 定义损失函数
criterion = torch.nn.CrossEntropyLoss()
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
for epoch in range(10):
train(epoch, criterion)
ttt()
输出:
[1, 300] loss: 1.274
[1, 600] loss: 0.321
[1, 900] loss: 0.179
Accuracy on test set: 96 % [9699/10000]
[2, 300] loss: 0.114
[2, 600] loss: 0.115
[2, 900] loss: 0.099
Accuracy on test set: 98 % [9813/10000]
[3, 300] loss: 0.080
[3, 600] loss: 0.068
[3, 900] loss: 0.069
Accuracy on test set: 98 % [9864/10000]
[4, 300] loss: 0.053
[4, 600] loss: 0.056
[4, 900] loss: 0.056
Accuracy on test set: 98 % [9873/10000]
[5, 300] loss: 0.041
[5, 600] loss: 0.044
[5, 900] loss: 0.041
Accuracy on test set: 98 % [9882/10000]
[6, 300] loss: 0.033
[6, 600] loss: 0.035
[6, 900] loss: 0.034
Accuracy on test set: 98 % [9864/10000]
[7, 300] loss: 0.033
[7, 600] loss: 0.028
[7, 900] loss: 0.033
Accuracy on test set: 99 % [9903/10000]
[8, 300] loss: 0.024
[8, 600] loss: 0.027
[8, 900] loss: 0.031
Accuracy on test set: 98 % [9883/10000]
[9, 300] loss: 0.020
[9, 600] loss: 0.022
[9, 900] loss: 0.027
Accuracy on test set: 99 % [9901/10000]
[10, 300] loss: 0.019
[10, 600] loss: 0.022
[10, 900] loss: 0.021
Accuracy on test set: 98 % [9883/10000]