d2l现代卷积神经网络(全部更新完成)

李沐动手学深度学习V2-AlexNet模型和代码实现 1. AlexNet 1.1 AlexNet概要 如果你和机器学习研究人员交谈,你会发现他们相信机器学习既重要又美丽:优雅的理论去证明各种模型的性质。机器学习是一个正在蓬勃发展、严谨且非常有用的领域。然而,如果你和计算机视觉研究人员交谈,你会听到一个完全不同的故事。他们会告诉你图像识别的诡异事实————推动领域进步的是数据特征,而不是学习算法。计算机视觉研究人员相信**,从对最终模型精度的影响来说,更大或更干净的数据集、或是稍微改进的特征提取,比任何学习算法带来的进步要大得多。** 2012年,AlexNe 阅读详情

对第七章节的AlexNet、VGG、NiN、GoogLeNet、ResNet、DenseNet进行讲解。 

目录

1.AlexNet

1.1模型概览

1.2网络实现

1.3模型输出概览

1.4实现

2.VGG

2.1模型概览

 2.2网络实现

2.2.1为什么会有两次in_c=out_c?

2.3模型输出概览

2.4实现

3.NiN

3.1模型概览

3.2代码实现

 3.2.1模型层预览

3.2.2训练命令行

4.GoogLeNet

4.1模型概览

4.1.1Inception块

 4.1.2整体

4.2代码实现 

4.2.1首先定义inception块:

4.2.2整体定义

 4.2.3模型概览

4.2.4训练命令行

5.ResNet

5.1模型概览

5.2代码实现

5.2.1残差块的实现:

5.2.2为什么要保持通道与尺寸一致?--广播机制与BN层回顾

广播机制的根本

5.2.3残差网络实现

残差模块的实现:

模型的组装:

5.2.4训练命令行

6.DenseNet

6.1模型概览与原理图

6.2代码实现

6.3构造DenseNet


1.AlexNet

1.1模型概览

  模型概览图如下所示:其实AlexNet可以看作是一个加强版的LeNet,相较前者,他的改进标记到了图中, 主要有采取dropout,引进Relu代替Sigmoid,使用maxpooling等。

1.2网络实现

  上代码:

net = nn.Sequential(
    # 这⾥,我们使⽤⼀个11*11的更⼤窗⼝来捕捉对象。
    # 同时,步幅为4,以减少输出的⾼度和宽度。
    # 另外,输出通道的数⽬远⼤于LeNet
    nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    # 减⼩卷积窗⼝,使⽤填充为2来使得输⼊与输出的⾼和宽⼀致,且增⼤输出通道数
    nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    # 使⽤三个连续的卷积层和较⼩的卷积窗⼝。
    # 除了最后的卷积层,输出通道的数量进⼀步增加。
    # 在前两个卷积层之后,汇聚层不⽤于减少输⼊的⾼度和宽度
    nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Flatten(),
    # 这⾥,全连接层的输出数量是LeNet中的好⼏倍。使⽤dropout层来减轻过拟合
    nn.Linear(6400, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    nn.Linear(4096, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    # 最后是输出层。由于这⾥使⽤Fashion-MNIST,所以⽤类别数为10,⽽⾮论⽂中的1000
    nn.Linear(4096, 10))

  1.这⾥,我们使⽤⼀个11×11的更⼤窗⼝来捕捉对象。同时,步幅为4,以减少输出的⾼度和宽度。另外,输出通道的数⽬远⼤于LeNet。有(96)。

  2.减⼩卷积窗⼝,使⽤填充为2来使得输⼊与输出的⾼和宽⼀致,且增⼤输出通道数。

  3.使⽤三个连续的卷积层和较⼩的卷积窗⼝。除了最后的卷积层,输出通道的数量进⼀步增加。在前两个卷积层之后,汇聚层不⽤于减少输⼊的⾼度和宽度。

  4.全连接层的输出数量是LeNet中的好⼏倍。使⽤dropout层来减轻过拟合。

  5.最后是输出层。由于这⾥使⽤Fashion-MNIST,所以⽤类别数为10,⽽⾮论⽂中的1000。

  6.为什么后面使用了两个相同的4096?是因为前面的卷积抽取特征不够好不够深,所以后面采用了两个大的dense来补,砍掉一个的话效果会变差。

1.3模型输出概览

X = torch.randn(1, 1, 224, 224)
for layer in net:
    X=layer(X)
    print(layer.__class__.__name__,'output shape:\t',X.shape)

'''
Conv2d output shape:	 torch.Size([1, 96, 54, 54])
ReLU output shape:	 torch.Size([1, 96, 54, 54])
MaxPool2d output shape:	 torch.Size([1, 96, 26, 26])
Conv2d output shape:	 torch.Size([1, 256, 26, 26])
ReLU output shape:	 torch.Size([1, 256, 26, 26])
MaxPool2d output shape:	 torch.Size([1, 256, 12, 12])
Conv2d output shape:	 torch.Size([1, 384, 12, 12])
ReLU output shape:	 torch.Size([1, 384, 12, 12])
Conv2d output shape:	 torch.Size([1, 384, 12, 12])
ReLU output shape:	 torch.Size([1, 384, 12, 12])
Conv2d output shape:	 torch.Size([1, 256, 12, 12])
ReLU output shape:	 torch.Size([1, 256, 12, 12])
MaxPool2d output shape:	 torch.Size([1, 256, 5, 5])
Flatten output shape:	 torch.Size([1, 6400])
Linear output shape:	 torch.Size([1, 4096])
ReLU output shape:	 torch.Size([1, 4096])
Dropout output shape:	 torch.Size([1, 4096])
Linear output shape:	 torch.Size([1, 4096])
ReLU output shape:	 torch.Size([1, 4096])
Dropout output shape:	 torch.Size([1, 4096])
Linear output shape:	 torch.Size([1, 10])
'''

1.4实现

  使用上一节讲过的改进纯数据版:我直接整了一大块,后续还想使用直接复制即可。

from torchvision import transforms
import torchvision
from torch.utils import data


def load_data_fashion_mnist_nw2(batch_size, resize=None):
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=True)
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=2),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=2))

def train_ch6_data(net, train_iter, test_iter, num_epochs, lr, device):
    """⽤GPU训练模型(在第六章定义)"""
    global train_l, train_acc, metric

    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)

    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    # animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
    #                         legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(num_epochs):
        # 训练损失之和,训练准确率之和,样本数
        metric = d2l.Accumulator(3)
        net.train()
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
        test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)
        print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
              f'test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')

  超参数与数据集加载:

batch_size = 128
train_iter, test_iter = load_data_fashion_mnist_nw2(batch_size, resize=224)

  训练命令行:

lr, num_epochs = 0.01, 10
train_ch6_data(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

'''
training on cuda:0
loss 1.336, train acc 0.500, test acc 0.744
loss 0.659, train acc 0.752, test acc 0.791
loss 0.542, train acc 0.798, test acc 0.808
loss 0.476, train acc 0.824, test acc 0.841
loss 0.436, train acc 0.840, test acc 0.851
loss 0.403, train acc 0.853, test acc 0.860
loss 0.381, train acc 0.861, test acc 0.862
loss 0.361, train acc 0.867, test acc 0.870
loss 0.344, train acc 0.875, test acc 0.876
loss 0.335, train acc 0.877, test acc 0.880
1200.0 examples/sec on cuda:0
'''

2.VGG

2.1模型概览

  为什么要有VGG?因为Alex没有提供一个通用的模板来指导后续的研究人员设计新的网络。

  其与Alex相比的特点标记在了图中:

 2.2网络实现

  首先,设置好每一个vgg块,是由n个卷积层与一个最大池化层组成:

def vgg_block(num_convs, in_channels, out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels, out_channels,
                                kernel_size=3, padding=1))
        layers.append(nn.ReLU())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
    return nn.Sequential(*layers)

  定义vgg块的个数与相应的输入输出通道:其中,224除2的话除到5次得7除不动了,所以对于输入224尺寸的图片,最多分5个块(再温习一下尺寸公式o=(i+2p-k+s)/s,带入i=224,即每一次经过一个vgg块尺寸减半(因为3×3p=1不会减尺寸)。

conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))

  进而得出vgg整体网络:

def vgg(conv_arch):
    conv_blks = []
    in_channels = 1
    # 卷积层部分
    for (num_convs, out_channels) in conv_arch:
        conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
        in_channels = out_channels
        
    return nn.Sequential(
        *conv_blks, nn.Flatten(),
        # 全连接层部分
        nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
        nn.Linear(4096, 10))

net = vgg(conv_arch)

2.2.1为什么会有两次in_c=out_c?

  这是应为在VGG块中in_c=out_c,但是到了vgg函数中in_c不是共享的,还是1!两个函数不互通,如下图:

 

2.3模型输出概览

  其中的规律可得每经过一个vgg块,空间减半通道翻倍,这个规律经常用到。

X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
    X = blk(X)
    print(blk.__class__.__name__,'output shape:\t',X.shape)

'''
Sequential output shape:	 torch.Size([1, 64, 112, 112])
Sequential output shape:	 torch.Size([1, 128, 56, 56])
Sequential output shape:	 torch.Size([1, 256, 28, 28])
Sequential output shape:	 torch.Size([1, 512, 14, 14])
Sequential output shape:	 torch.Size([1, 512, 7, 7])
Flatten output shape:	 torch.Size([1, 25088])
Linear output shape:	 torch.Size([1, 4096])
ReLU output shape:	 torch.Size([1, 4096])
Dropout output shape:	 torch.Size([1, 4096])
Linear output shape:	 torch.Size([1, 4096])
ReLU output shape:	 torch.Size([1, 4096])
Dropout output shape:	 torch.Size([1, 4096])
Linear output shape:	 torch.Size([1, 10])
'''

在此注意其中的Seq操作(具体也可见5_1节关于模型嵌套),在此再啰嗦一下。

  Seq(*[1,2,3])会变成Seq(1,2,3),这是一个块返回的,从而conv_blks.append到conv_blks后时,conv_blks为[Seq1, Seq2...]丢进最后的return的Seq后再解包,进而形成了最后的结果:在此看一下conv_blks没有解包时是什么:是各个vgg块的list

在vgg块中,也是打包操作 :进而再通过append将各个vgg块打包到一个list中,再通过最后的Seq

  总结:Seq里面套Seqi,则会顺序进行Seqi里面的layer, 直到Seqi所有的layers进行完之后则在进行Seq里面的第二项(顺序)

2.4实现

  降低计算量,可以将原始设置的通道数全部除4,从而降低参数量与运算量,在此也表明了vgg可以通过整体调整通道数。

ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)

  训练命令行:使用输出数据的改编版,别忘了加上上面的块。

lr, num_epochs, batch_size = 0.05, 10, 64
train_iter, test_iter = load_data_fashion_mnist_nw2(batch_size, resize=224)
train_ch6_data(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

3.NiN

3.1模型概览

  两个模型VGG与NiN之间的不同与改进。

  其中 ,为什么最后要摒弃全连接:卷积层需要的参数:Ci×Co×k^2 ,而全连接为out_c×h×w×L1out_c !!参数太大内存太大,也会占用更多的计算带宽,并且和容易过拟合。使用全局平均池化,本质是卷积操作,会降低参数与计算量,避免过拟合现象。

    其中,全局平均池化:也就是这个池化层的w,h与输入的特征图的wh相同,进而得出输出的卷积尺寸为1×1。如果想预测1k类,那就输入1k个通道,每个通道经过上述池化操作只会取1个(平均)值,再加个Flattern通过Softmax得出预测结果。

3.2代码实现

  首先定义NiN块:层中第一个卷积核尺寸、输出通道与stride均见概览图!

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    return nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
        nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())

  进而再构造NiN网络:注意!最后全局平均池化生成的是一个4维的,还需要Flatten来生成(bs,cls)

net = nn.Sequential(
    nin_block(1, 96, kernel_size=11, strides=4, padding=0),
    nn.MaxPool2d(3, stride=2),
    nin_block(96, 256, kernel_size=5, strides=1, padding=2),
    nn.MaxPool2d(3, stride=2),
    nin_block(256, 384, kernel_size=3, strides=1, padding=1),
    nn.MaxPool2d(3, stride=2),
    # 标签类别数是10
    nin_block(384, 10, kernel_size=3, strides=1, padding=1),
    nn.AdaptiveAvgPool2d((1, 1)),
    # 将四维的输出转成⼆维的输出,其形状为(批量⼤⼩,10)
    nn.Flatten())

   第一个输入通道为1,因为FMinist为灰度图,输出为10是种类为10

  注意最后这个AdaptiveAvgpool2d,里面写的(1,1)是说全局平均池化后得到的尺寸为(1,1),这就是torch的写法,没有为什么,详情见原文档:

 3.2.1模型层预览

X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)

'''
Sequential output shape:	 torch.Size([1, 96, 54, 54])
MaxPool2d output shape:	 torch.Size([1, 96, 26, 26])
Sequential output shape:	 torch.Size([1, 256, 26, 26])
MaxPool2d output shape:	 torch.Size([1, 256, 12, 12])
Sequential output shape:	 torch.Size([1, 384, 12, 12])
MaxPool2d output shape:	 torch.Size([1, 384, 5, 5])
Sequential output shape:	 torch.Size([1, 10, 5, 5])
AdaptiveAvgPool2d output shape:	 torch.Size([1, 10, 1, 1])
Flatten output shape:	 torch.Size([1, 10])
'''

3.2.2训练命令行

lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = load_data_fashion_mnist_nw2(batch_size, resize=224)
train_ch6_data(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

4.GoogLeNet

4.1模型概览

4.1.1Inception块

  如下图:需要强调的是,inception不会改变尺寸,指挥改变通道数,且在各个部分分配的通道数是超参数,人工训练出来的!没有道理。其他的见图注:

 4.1.2整体

  整体分为五部分:

4.2代码实现 

4.2.1首先定义inception块:

class Inception(nn.Module):
    # c1--c4是每条路径的输出通道数
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路1,单1x1卷积层
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # 线路2,1x1卷积层后接3x3卷积层
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3,1x1卷积层后接5x5卷积层
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 线路4,3x3最⼤汇聚层后接1x1卷积层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
        
    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 在通道维度上连结输出
        return torch.cat((p1, p2, p3, p4), dim=1)

  c1-c4表示每条分支的通道数,与Inception图结合着理解,其中线路2举例,1×1后接3×3,对后面的3×3的输入通道数对应的就是上面1×1的输出通道数,这里也是要提前设置好这个超参数。

  池化层的本质是算出的那个Yij是池化窗口与X对应元素相乘的最大值或平均值,只不过不设置stride的时候与池化窗口默认一致,输出尺寸还是按照公式来o=[(i+2p-k)/s]+1,所以分支4也不改变尺寸大小。

  最后cat的dim=1,是因为在四维张量里,通道数的维度位置是1(第二个)

4.2.2整体定义

  整体googlenet分为五块,每一块如整体概览图所示,其中关于每个inception中的通道数是超参数,试出来的,没有原因。。。

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                    nn.ReLU(),
                    nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                    nn.ReLU(),
                    nn.Conv2d(64, 192, kernel_size=3, padding=1),
                    nn.ReLU(),
                    nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
                    Inception(256, 128, (128, 192), (32, 96), 64),
                    nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
                    Inception(512, 160, (112, 224), (24, 64), 64),
                    Inception(512, 128, (128, 256), (24, 64), 64),
                    Inception(512, 112, (144, 288), (32, 64), 64),
                    Inception(528, 256, (160, 320), (32, 128), 128),
                    nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                    Inception(832, 384, (192, 384), (48, 128), 128),
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten())

net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

 4.2.3模型概览

X = torch.rand(size=(1, 1, 96, 96))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)

'''
Sequential output shape:	 torch.Size([1, 64, 24, 24])
Sequential output shape:	 torch.Size([1, 192, 12, 12])
Sequential output shape:	 torch.Size([1, 480, 6, 6])
Sequential output shape:	 torch.Size([1, 832, 3, 3])
Sequential output shape:	 torch.Size([1, 1024])
Linear output shape:	 torch.Size([1, 10])
'''

  这里会发现GoogLeNet对通道数比较敏感,不像VGG那样便于修改通道数。但是它对尺寸不敏感,因为最后都是用全局平均池化,输出尺寸都会变成(1,1),影响最后softmax的只有通道数,所以只要对应好b1的输入通道数即可。

4.2.4训练命令行

  别问为什么用原函数了,问就是笔者换服务器了!!再也不用憋屈的本地小显卡了!!!

lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu(1))

5.ResNet

5.1模型概览

首先先了解一下残差块的结构:

 由于本模型关联性较强,所以直接上代码部分

5.2代码实现

5.2.1残差块的实现:

class Residual(nn.Module): #@save
    def __init__(self, input_channels, num_channels,
                use_1x1conv=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels,
                                kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels,
                                kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,
                                    kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)
        
    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

可结合图7.6.3观看。首先第一个3×3的卷积层指定步长,并指定输入输出通道,一般尺寸减半的时候通道数要翻倍的。

第二个卷积层不再设步长,s默认等于1,通道数不变,尺寸也不变。

总结3×3这一层,如果strides=1,则尺寸不变,=2即尺寸减半,通道数按输入的来(卷积尺寸向下取整)。

再总结1×1这一层,如果strides=1,则尺寸不变,=2则尺寸减半,通道数按输入的来(卷积尺寸向下取整,1.5按1)。

上面的总结这里补充一下,卷积尺寸是向下取整的,见下图:

如果要使用1×1的卷积层,会在设置一个conv3,要保证经过1×1卷积层最终输出的尺寸和高宽与经过两个3×3的是一致的。

BN操作,在卷积后面指定上一层卷积输出通道数,在全连接后面指定上一层全连接的第一维度输出数。因为要构造gamma\beta\moving_mean\var,他们的形状分别为(1,n,1,1)、(1,n)。

要保证1×1与3×3最后输出的通道数与尺寸一致,这样才能相加。

5.2.2为什么要保持通道与尺寸一致?--广播机制与BN层回顾

这里要重温一下广播的定义,起源于复习BN层的计算,为什么BN层要传入输出通道数或Linear的输出数?

为了探寻bn里面各个变量之间的关系,造了一个网络与输入张量(2,2,5,5)

 带入BN块,得到如下结果:

首先来看gamma与beta的量,传入的num_features主要是构造这四个变量形状用的,全连接就是(1,n),卷积是(1,n,1,1)

 再看传入到bn计算层,X的尺寸为经过卷积后的尺寸,3=5-3+1;通道由2变为6,bs不变;mean与var计算是根据按维度加得来的,由于保持了keepdim=True,所以dim指定的维度变为1,故mean与var变成(1,6,1,1)正常。

最重要的对X_hat做矩阵运算,[(2,6,3,3)-(1,6,1,1)]/(sqrt(1,6,1,1)+eps,由于广播机制,最终得到的尺寸为(2,6,3,3)然后再送入Y,同理最终得到的尺寸与送进来的X一致,所以经过这一系列的的运算,前后一致,故不会改变输入输出的尺寸,只是对输入的数据做了归一化与缩放偏置。

在这里可以看到,这两个尺寸的张量能够利用广播机制做除的矩阵运算,

广播机制的根本

记住,广播机制满足简洁版:1.对比两个要广播的tensor,要么两个维度大小相等;2.要么一个有这个维度,另一个没有;3.要么一个有,另一个也有但是为1

那么为什么上面那个能够进行广播运算,而地下这个只有一个维度(通道)不一样就不行?

仔细观察发现,后面这个例子不满足一个张量有,另一个张量也有但是大小为1的条件(5)。

所以综上,想要实现Y+X的残差块,就必须保证通道与尺寸一致才行!!

5.2.3残差网络实现

 总模型分成了五块,第一块与GooLeNet一样,首先进入的第一模块:通道数变为64,经过7×7,s=2卷积,再接3×3,s=2的最大池化层

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
        nn.BatchNorm2d(64), nn.ReLU(),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

残差模块的实现:

见模型总图片:

残差模块:

def resnet_block(input_channels, num_channels, num_residuals,
                first_block=False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels,
                                use_1x1conv=True, strides=2))
        else:
            blk.append(Residual(num_channels, num_channels))
    return blk
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

注意:not False返回True;not True返回False

第一个block不执行stride=2(因为第一块的最大池化已经进行了尺寸减半操作了),所以两个残差块都要走else条件,则输入输出通道相同,尺寸也不减半。

这里面一共有4个模块组成,每个模块使用若干个残差块,在num_residuals里面设置。

在这里取每个模块有2个残差块,第一个模块中,i==0时,not True返回False,所以为else,i==1时,也返回else,所以第一个模块中的两个残差快均执行的通道不变,尺寸不变,没有1×1层的残差操作,结合模型总图与图一致,故思路正确!!

后面的3个模块,每个均包含两个残差快,其中第一个(i==0)包含了1×1,第二个不包含1×1,也都make sense。

模型的组装:

net = nn.Sequential(b1, b2, b3, b4, b5,
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), nn.Linear(512, 10))

可以看到,第一个b1的两个残差块s都为1,都没有1×1卷积;之后的残差块第一个s=2,有1×1卷积,与前面的描述一致。

检验与执行:看一下经过第二个模块确实没有再将尺寸减半。

X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)

'''
Sequential output shape:	 torch.Size([1, 64, 56, 56])
Sequential output shape:	 torch.Size([1, 64, 56, 56])
Sequential output shape:	 torch.Size([1, 128, 28, 28])
Sequential output shape:	 torch.Size([1, 256, 14, 14])
Sequential output shape:	 torch.Size([1, 512, 7, 7])
AdaptiveAvgPool2d output shape:	 torch.Size([1, 512, 1, 1])
Flatten output shape:	 torch.Size([1, 512])
Linear output shape:	 torch.Size([1, 10])
'''

5.2.4训练命令行

lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu(1))

6.DenseNet

这个网络沐神的课是没有的,只有书上有,所以可以说是读者自己复现debug理解的。

6.1模型概览与原理图

其主要由稠密块dense block与过渡层transition layer组成,前者定义如何连接输⼊和 输出,⽽后者则控制通道数量,使其不会太复杂。talk is cheap:

6.2代码实现

首先来看稠密块形成:首先定义一个稠密块里面的卷积操作层,实现了一次BN+Relu+(k=3p=1)的卷积操作:其尺寸是不会变的,只会变通道:

def conv_block(input_channels, num_channels):
    return nn.Sequential(
        nn.BatchNorm2d(input_channels), nn.ReLU(),
        nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))
class DenseBlock(nn.Module):
    def __init__(self, num_convs, input_channels, num_channels):
        super(DenseBlock, self).__init__()
        layer = []
        for i in range(num_convs):
            layer.append(conv_block(
                num_channels * i + input_channels, num_channels))
        self.net = nn.Sequential(*layer)
        
    def forward(self, X):
        for blk in self.net:
            Y = blk(X)
            # 连接通道维度上每个块的输⼊和输出
            X = torch.cat((X, Y), dim=1)
        return X

生成一个demo检测一下这个稠密块都干了啥:

blk = DenseBlock(2, 3, 10)
X = torch.randn(4, 3, 8, 8)
Y = blk(X)
Y.shape

'''
torch.Size([4, 23, 8, 8])
'''

可以根据此画出原理图:

 总结一下,原来的通道数为c,经过DenseBlock(a,c,d),输出的通道数为3+ad,结合稠密定义的图片,也可以更好的理解论文上的原图:(这里每个稠密块只采用了两个卷积层)

 接下来再看过度层:

def transition_block(input_channels, num_channels):
    return nn.Sequential(
        nn.BatchNorm2d(input_channels), nn.ReLU(),
        nn.Conv2d(input_channels, num_channels, kernel_size=1),
        nn.AvgPool2d(kernel_size=2, stride=2))

经过一个过渡层,使用1×1卷积,通道减为过渡层输入的num_channels,尺寸从in_c降到num_c;

尺寸通过Avg池化s=2减半,进一步降低模型复杂度。

再来用上一个用过的demo演示一下:

blk = transition_block(23, 10)
blk(Y).shape

'''
torch.Size([4, 10, 4, 4])
'''

6.3构造DenseNet

首先对第一个模块构造:

b1 = nn.Sequential(
    nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
    nn.BatchNorm2d(64), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

 没啥好讲的,就是对图片进行了类似resnet第一块操作。

紧接着重头戏:稠密层与过渡层:

# num_channels为当前的通道数
num_channels, growth_rate = 64, 32
num_convs_in_dense_blocks = [4, 4, 4, 4]
blks = []
for i, num_convs in enumerate(num_convs_in_dense_blocks):
    blks.append(DenseBlock(num_convs, num_channels, growth_rate))
    # 上⼀个稠密块的输出通道数
    num_channels += num_convs * growth_rate
    # 在稠密块之间添加⼀个转换层,使通道数量减半
    if i != len(num_convs_in_dense_blocks) - 1:
        blks.append(transition_block(num_channels, num_channels // 2))
        num_channels = num_channels // 2

使用4个稠密块,每个稠密块设置4个卷积层,卷积层通道数(即增长率)设为32,故每个稠密块将增加128个通道数。

每个模块之间,resnet通过s=2减小wh,Densenet通过过渡层减半wh并减少通道数。

net = nn.Sequential(
    b1, *blks,
    nn.BatchNorm2d(num_channels), nn.ReLU(),
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten(),
    nn.Linear(num_channels, 10))

然后训练即可:

lr, num_epochs, batch_size = 0.1, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu(1))

李沐《动手学深度学习》d2l——安装和使用 今天想要跟着沐神学习一下循环神经网络,在跑代码的时候,d2l出现了问题,这里记录一下解决的过程,方便以后查阅。 阅读详情

相关推荐

卷积神经网络(CNN)学习笔记3:Matlab代码理解

近期正在学习卷积神经网络,大致的原理在之前参考前辈们经验,转载的两个笔记中已经做了简要梳理,下边就Github中的DeepLearnToolbox中的CNN的代码进行理解和实现讲解 参考代码来源:[CNN卷积神经网络的Matlab实现](https://github.com/rasmusbergpalm/DeepLearnToolbox) 网络结构图: 打开路径\tests\test_exampl

冰凝凝的专栏 2万+

《动手学深度学习》中d2l库的安装教程(含细节,开盖即食)

​ 在按照《动手学深度学习》这本书时会有需要导入d2l库的使用。​ 首先先随便了解一下d2l库如下:​ d2I是一个与《动手学深度学习》(Dive into Deep Learning)一书配套的开源教学库,它包含了作者李沐设计的深度学习相关代码和示例。这个库旨在帮助读者通过实践经验来理解和掌握深度学习的基础知识和核心算法,如神经网络、卷积神经网络(CNN)、循环神经网络(RNN)等。除了提供理论知识外,d2Il库还通过实际代码应用来帮助读者加深对这些算法的理解。

m0_75243362的博客 5万+

卷积神经网络 CNN 系列总结(二)---数据预处理、激活函数、梯度、损失函数、优化方法等

进行预处理很重要的一点是:任何预处理策略(比如数据均值)都只能在训练集数据上进行计算,算法训练完毕后再应用到验证集或者测试集上。例如,如果先计算整个数据集图像的平均值然后每张图片都减去平均值,最后将整个数据集分成训练/验证/测试集,那么这个做法是错误的。应该怎么做呢?应该先分成训练/验证/测试集,只是从训练集中求图片平均值,然后各个集(训练/验证/测试集)中的图像再减去这个平均值。

yousuotu的博客 1354

d2l@动手学深度学习@pytorch名称和d2l包的搭配安装问题#@save修饰符@在线运行notebook和免费算力

summarytorchtorchaudio从上面的介绍来看,torch是必备的,但是后两者作为功能扩展的增强包License:安装 — 动手学深度学习 2.0.0 documentation (d2l.ai)d2l 第2版中,提示的方式安装PyTorch的CPU或GPU版本:下面提供的时cpu版本(不涉及CUDA的安装)

xuchaoxin1375的博客 7375

d2l-zh-1.0.zip_D2L 文件_d2lzh安装_deeplearning_mxnet_pig17v

深度学习教程实现代码,供初学者使用,使用mxnet

MAC M1用户必看:UTM虚拟机安装Win11全流程避坑指南(附驱动下载)

本文为MAC M1用户提供UTM虚拟机安装Win11的完整指南,涵盖镜像准备、驱动优化及性能调优等关键步骤。特别针对TPM 2.0验证、网络连接等常见问题提供解决方案,帮助用户高效运行Windows 11 ARM版。

weixin_29204205的博客 337

探索深度学习与实践:D2L-ZH——一个面向中文用户的开源教程

探索深度学习与实践:D2L-ZH——一个面向中文用户的开源教程 项目地址:https://gitcode.com/gh_mirrors/d2/d2l-zh 项目简介 是由 D2L 团队创建的一个深度学习开源教程,特别为中文用户设计。该项目不仅提供了一套完整的理论知识体系,还结合了实际操作案例,使读者能够在理解概念的同时,学会如何在实践中应用深度学习技术。 技术分析 D2L-ZH 基于 Python...

gitblog_00038的博客 922

ICESat-2数据下载的两种方法

ICESat-2数据下载

weixin_46341898的博客 2899

(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(1)(序言、pytorch的安装、神经网络涉及符号)

开源项目地址:d2l-ai/d2l-zh 书及代码下载链接:https://github.com/d2l-ai/d2l-zh/releases 本笔记基于2021年7月26日发布的版本,下载地址在github网页的最下面 ps. 本文只挑重点记录 文章目录序言 序言

Dontla的博客 2665

使用Android GPS api 实现一个轨迹记录的小功能

引入 市场上的跑步软件已经是非常多了,如咕咚和悦跑圈还有其他,很多大厂也在涉足这个方面。而最简单的运动跑步,最近几年也因简单,不限场地,运动效果出众备受大众喜欢。 我的这篇文章的内容很多也是从网上学习其他的博客内容,外加利用android自身的提供的api实现的,希望能对有这方面需求的朋友有所帮助。 介绍 先上两张图,界面比较丑,不过一些数据基本都有了,这个图片是临时要用截取的,所以轨迹线很短,之前

尝试着去了解代码 1万+

【毕设全程指导】基于YOLOv8的包装商品检测与可视化系统(SKU110K实战)

本文介绍了一个基于YOLOv8的包装商品检测与可视化系统毕业设计方案。项目采用SKU110K数据集,通过CSV转TXT格式转换适配YOLO模型训练,包含详细的环境搭建、模型配置和训练流程。系统实现了包装商品的自动化检测,并开发了Tkinter可视化界面进行交互式检测展示。该方案结合产业应用需求与技术深度,既具备实用价值,又能充分展示学生的技术能力,适合作为计算机视觉相关专业的毕业设计选题。整个项目从数据准备到界面开发完整覆盖,帮助学生系统掌握目标检测项目的完整实现流程。

LXY_LOVE36的博客 145

卷积神经网络CNN的学习路线入门(1)---深度学习

大家可以按照顺序参看这几篇博客,注意对第一篇博客中的卷积运算的Filter的维度进行一个深入理解,对第二篇博客中的相应资源进行认真阅读,相信大家对CNN会有一个清晰明朗的认识的,加油,喜欢学习深度学习的小伙伴们!!!首先推荐大家一本经典书籍,是《Neural Networks and Deep Learning》,英文版,你懂得,所以我就用中文版代替啦,非常简明易懂,大家可以去我的主页进行下载。1、

阿华Go,从现在开始的博客 6452

R语言机器学习算法实战系列(十三)随机森林生存分析构建预后模型 (Random Survival Forest)

本教程运用随机森林生存分析筛选与患者生存状态显著相关特征。该方法借随机森林这一强大机器学习算法,构建多棵决策树并集成结果,以提高生存分析准确性,识别关键生物标志物。接着,利用选定特征构建风险得分,将多个特征转化为单一数值,量化患者风险水平。之后,依据风险得分将患者分为高、低风险亚型,为临床治疗提供个性化指导。最后,通过分析时间依赖的 ROC 曲线评估模型长期预测能力。此曲线计算不同阈值下的真正例率与假正例率,在多个时间点评估,全面展现模型长期稳定性与准确性。

专注生信领域 2017

d2l-java 的安装和配置教程

d2l-java 的安装和配置教程 1. 项目基础介绍 d2l-java 是 Deep Learning for Java 的简称,该项目是基于 Java 语言实现的深度学习框架。它致力于为 Java 开发者提供一个易于使用、功能强大的深度学习库,使得 Java 开发者能够方便地在自己的应用程序中集成深度学习功能。 2. 关键技术和框架 该项目主要使用了以下技术和框架: Java:作为主要的编程...

gitblog_00408的博客 890

Open3d Qt的环境配置

目前统一使用qt6配置,open3d中可视化功能目前使用vtk代替,语言为c++。

阁楼 918

JAVA:Spring Boot3 集成 Spring AI 实现 Skill 开发

本文介绍了如何通过Spring Boot和Spring AI实现大模型技能(Skill)开发,让AI具备调用业务系统能力。文章首先解释了Skill的概念,即AI可调用的业务能力,如查询订单、天气等,并展示了执行流程和整体架构。然后详细介绍了实践样例,包括技术版本、Maven依赖配置、AI服务设置、ChatClient创建和Skill技能注册中心的实现。注册中心提供了技能的注册、查询、启用/禁用等功能,支持按类别管理技能,并能够获取所有可用技能列表。通过这种方式,开发者可以轻松为大模型扩展业务功能调用能力。

木头 620

卷积神经网络(CNN)的训练及代码实现

本文部分内容来自zouxy09的博客,谢谢!http://blog.youkuaiyun.com/zouxy09/article/details/9993371 以及斯坦福大学深度学习教程:http://ufldl.stanford.edu/wiki/index.php/UFLDL教程 CNN结构的连接比权值多很多,因为权值共享。CNN通过数据驱动的方式学习得到一些滤波器,作为提取输入的特征的一种方法

qustqustjay的专栏 1万+

SLAM 轨迹评估工具——EVO安装、使用介绍

evo是一款用于视觉里程计和SLAM问题的轨迹评估工具. 核心功能是能够绘制相机的轨迹, 或评估轨迹与真值之间的误差. 支持多种数据集的轨迹格式(TUM、KITTI、EuRoC MAV、ROS的bag), 同时支持这些数据格式之间的相互转换,本文轨迹展示以tum数据格式为例。

u010196944的博客 1万+

卷积神经网络CNN实战6

卷积神经网络过拟合 卷积神经网络正则化和Dropout 神经网络的学习能力受神经元数目以及神经网络层次的影响,神经元数目越大,神经网络层次越高,那么神经网络的学习能力越强,那么就有可能出现过拟合的问题;(通俗来讲:神经网络的空间表达能力变得更紧丰富了) Regularization:正则化,通过降低模型的复杂度,通过在cost函数上添加一个正则项的方式来降低overfitting,主要有L1和L2两种方式 Dropout:通过随机删除神经网络中的神经元来解决overfitting问题,在每次迭代的时候,只

leva的博客 500

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

探索D2L-ZH-PyTorch-Slides: 深度学习实践之路

探索D2L-ZH-PyTorch-Slides: 深度学习实践之路 去发现同类优质开源项目:https://gitcode.com/ 在深度学习的世界中,有一个特别的项目值得每一位初学者和进阶者关注——D2L-ZH-PyTorch-Slides。这是一个由中国知名的深度学习开源社区D2L.ai创建的、全面讲解PyTorch框架的中文教程,它不仅提供详尽的理论知识,还包含丰富的实践案例,旨在帮助读者...

gitblog_00048的博客 536

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

DL —— 卷积神经网络(convolutional neural networks,CNN)

卷积神经网络是深度学习最核心的部分; 最早用于美国银行系统识别手写支票; 卷积神经网络 一、训练数据 因为卷积神经网络是为了解决图像领域的应用而提出的网络结构,所以训练数据一般为视频图像数据(张量数据,tensor);可以根据图像的性质对CNN的结构进行设计,使得CNN相比一般的神经网络结构更加高效、性能更好。 其中: X(i)是第i张图像; H * W * D是每个图像的尺寸; D表示整个训练...

biubiusion的博客 1487

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

深度学习之卷积神经网络(Convolutional Neural Networks, CNN)(二)

前面我们说了CNN的一般层次结构, 每个层的作用及其参数的优缺点等内容.https://blog.youkuaiyun.com/fenglepeng/article/details/106077002 一 CNN参数初始化及参数学习方法 和机器学习很多算法一样, CNN在进行训练之前也要进行参数的初始化操作. 我们知道, 在机器学习中的参数值一般都是随机给定的. 但是, 这CNN的参数初始化又和机器学习中有所不同. 1.1 W的初始化 由于CNN 中的参数更新的过程一般是通过BP算法实现的,再由前面我们在深..

fenglepeng的博客 8074

L2-Net 论文详解

L2-Net 本文提供了相应的代码,是基于 matconvnet,论文的思路相对来说比较简单,可以使用 pytorch 或者 tersorflow 等框架复现。 主要思路 之前有 SIFT 等手工设计的 patch 特征,这里提出的 L2-Net 的方法,利用 CNN 方法在 Euclidean 空间学习 patch 特征。本文主要的工作包括: 提出递进的采样策略,可以保证网络在很少的 epoc...

honyniu的专栏 9584

L2-Net论文粗解

本篇文章来自于对L2-Net: Deep Learning of Discriminative Patch Descriptor in Euclidean Space文章的翻译和自己的一些理解,很多内容也参考了添加链接描述,然后做了一些修改,如果有不对的地方欢迎指出。 个人觉得最主要的还是理解它的网络结构,损失函数可能不是很好理解,不必过于强求 总述: 卷积神经网络在patch匹配中的应用,可根据有无度量学习层而分为两类: (1)有度量学习层的把patch的匹配视为一个二进制分类,因此也就不存在描述符的概念

weixin_49369396的博客 1934

深度学习入门终极指南:从零开始掌握D2L-PyTorch框架

D2L-PyTorch是《动手学深度学习》(Dive Into Deep Learning)的PyTorch实现版本,专为深度学习初学者和开发者设计。这个开源项目提供了丰富的教程、代码示例和实战项目,帮助你快速入门并掌握深度学习核心技能。无论你是学生、研究人员还是行业从业者,都能通过这个框架系统学习从基础到高级的深度学习知识。 ## 为什么选择D2L-PyTorch? 深度学习框架众多,为什么

gitblog_00097的博客 745
上一篇: torch中的张量处理
下一篇: d2l第13章的两个kaggle,以及该章的aug,finetune,cls的dataset处理
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值