Python 深度学习第二版(GPT 重译)(四)(2)

简介: Python 深度学习第二版(GPT 重译)(四)

Python 深度学习第二版(GPT 重译)(四)(1)https://developer.aliyun.com/article/1485277

9.3.1 模块化、层次结构和重用

如果你想让一个复杂系统变得简单,你可以应用一个通用的方法:将你的复杂混乱的系统结构化为模块,将模块组织成层次结构,并开始在适当的地方重用相同的模块(“重用”在这个上下文中是抽象的另一个词)。这就是 MHR 公式(模块化-层次化-重用),它是几乎每个领域中使用“架构”这个术语的系统架构的核心。它是任何有意义的复杂系统的组织核心,无论是大教堂、你自己的身体、美国海军还是 Keras 代码库(见图 9.7)。


图 9.7 复杂系统遵循层次结构,并组织成不同的模块,这些模块被多次重复使用(比如你的四肢,它们都是同一个蓝图的变体,或者你的 20 个“手指”)。

如果你是一名软件工程师,你已经对这些原则非常熟悉:一个有效的代码库是模块化、层次化的,你不会重复实现相同的东西,而是依赖可重用的类和函数。如果你按照这些原则来因素化你的代码,你可以说你在做“软件架构”。

深度学习本身只是通过梯度下降对连续优化应用这一方法的结果:你采用了经典的优化技术(在连续函数空间上的梯度下降),并将搜索空间结构化为模块(层),组织成深层次的层级结构(通常只是一个堆栈,最简单的层次结构),在其中重复利用任何可以的东西(例如,卷积就是关于在不同空间位置重复使用相同信息)。

同样,深度学习模型架构主要是关于巧妙地利用模块化、层次化和重用。你会注意到所有流行的卷积神经网络架构不仅结构化为层,而且结构化为重复的层组(称为“块”或“模块”)。例如,在上一章中我们使用的流行的 VGG16 架构结构化为重复的“卷积、卷积、最大池化”块(见图 9.8)。

此外,大多数卷积神经网络通常具有金字塔结构(特征层次结构)。例如,回想一下我们在上一章中构建的第一个卷积神经网络中使用的卷积滤波器数量的增长:32、64、128。随着层次深度的增加,滤波器的数量也增加,而特征图的大小相应缩小。你会在 VGG16 模型的块中看到相同的模式(见图 9.8)。

图 9.8 VGG16 架构:请注意重复的层块和特征图的金字塔结构

更深的层次结构本质上是好的,因为它们鼓励特征的重复使用,从而实现抽象化。一般来说,一堆窄层次的深层比一堆大层次的浅层表现更好。然而,由于“梯度消失”问题,你可以堆叠的层次有限。这引出了我们的第一个基本模型架构模式:残差连接。

关于深度学习研究中消融研究的重要性

深度学习架构通常比设计更进化——它们是通过反复尝试和选择有效的方法开发出来的。就像生物系统一样,如果你拿任何复杂的实验性深度学习设置,很可能你可以删除一些模块(或用随机的特征替换一些训练过的特征),而不会损失性能。

深度学习研究人员面临的激励使情况变得更糟:通过使系统比必要复杂,他们可以使其看起来更有趣或更新颖,从而增加他们通过同行评审过程的机会。如果你阅读了很多深度学习论文,你会注意到它们通常在风格和内容上都被优化以满足同行评审,这些方式实际上会损害解释的清晰度和结果的可靠性。例如,深度学习论文中的数学很少用于清晰地形式化概念或推导非显而易见的结果——相反,它被利用作为严肃性的信号,就像推销员身上的昂贵西装一样。

研究的目标不应仅仅是发表论文,而是产生可靠的知识。至关重要的是,理解系统中的因果关系是生成可靠知识的最直接方式。而且有一种非常低成本的方法来研究因果关系:消融研究。消融研究包括系统地尝试去除系统的部分——使其更简单——以确定其性能实际来自何处。如果你发现 X + Y + Z 给你良好的结果,也尝试 X、Y、Z、X + Y、X + Z 和 Y + Z,看看会发生什么。

如果你成为一名深度学习研究人员,请剔除研究过程中的噪声:为你的模型进行消融研究。始终问自己,“可能有一个更简单的解释吗?这种增加的复杂性真的有必要吗?为什么?”

9.3.2 残差连接

你可能知道电话游戏,也称为英国的中国耳语和法国的阿拉伯电话,其中一个初始消息被耳语给一个玩家,然后由下一个玩家耳语给下一个玩家,依此类推。最终的消息与其原始版本几乎没有任何相似之处。这是一个有趣的比喻,用于描述在嘈杂信道上的顺序传输中发生的累积错误。

实际上,在顺序深度学习模型中的反向传播与电话游戏非常相似。你有一系列函数,就像这样:

y = f4(f3(f2(f1(x))))

游戏的名字是根据记录在f4输出上的错误来调整链中每个函数的参数。要调整f1,你需要通过f2f3f4传播错误信息。然而,链中的每个连续函数都会引入一定量的噪声。如果你的函数链太深,这种噪声开始压倒梯度信息,反向传播停止工作。你的模型根本无法训练。这就是梯度消失问题。

修复很简单:只需强制链中的每个函数都是非破坏性的——保留前一个输入中包含的信息的无噪声版本。实现这一点的最简单方法是使用残差连接。这很简单:只需将层或层块的输入添加回其输出(见图 9.9)。残差连接充当信息捷径,绕过具有破坏性或嘈杂块(例如包含relu激活或 dropout 层的块)的错误梯度信息,使其能够无噪声地通过深度网络传播。这项技术是在 2015 年由微软的 He 等人开发的 ResNet 系列模型中引入的。¹

图 9.9 处理块周围的残差连接

在实践中,你可以这样实现一个残差连接。

列表 9.1 伪代码中的残差连接

x = ...                  # ❶
residual = x             # ❷
x = block(x)             # ❸
x = add([x, residual])   # ❹

❶ 一些输入张量

❷ 保存原始输入的指针。这被称为残差。

❸ 这个计算块可能会具有破坏性或嘈杂,这没关系。

❹ 将原始输入添加到层的输出中:最终输出将始终保留有关原始输入的完整信息。

请注意,将输入添加回块的输出意味着输出应当有与输入相同的形状。但是,如果您的块包括具有增加滤波器数量或最大池化层的卷积层,则情况并非如此。在这种情况下,使用没有激活的 1 × 1 Conv2D层线性地将残差投影到所需的输出形状(请参见列表 9.2)。您通常会在目标块中的卷积层中使用padding="same",以避免由于填充而导致空间下采样,并且您会在残差投影中使用步幅以匹配由最大池化层引起的任何下采样(请参见列表 9.3)。

列表 9.2 残差块,其中滤波器数量发生变化

from tensorflow import keras 
from tensorflow.keras import layers
inputs = keras.Input(shape=(32, 32, 3))
x = layers.Conv2D(32, 3, activation="relu")(inputs)
residual = x                                                     # ❶
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)   # ❷
residual = layers.Conv2D(64, 1)(residual)                        # ❸
x = layers.add([x, residual])                                     # ❹

❶ 将残差单独放在一边。

❷ 这是我们创建残差连接的层:它将输出滤波器的数量从 32 增加到 64。请注意,我们使用 padding="same"以避免由于填充而导致下采样。

❸ 残差只有 32 个滤波器,因此我们使用 1 × 1 Conv2D 将其投影到正确的形状。

❹ 现在块输出和残差具有相同的形状,可以相加。

列表 9.3 目标块包含最大池化层的情况

inputs = keras.Input(shape=(32, 32, 3))
x = layers.Conv2D(32, 3, activation="relu")(inputs)
residual = x                                                    # ❶
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)  # ❷
x = layers.MaxPooling2D(2, padding="same")(x)                   # ❷
residual = layers.Conv2D(64, 1, strides=2)(residual)            # ❸
x = layers.add([x, residual])                                   # ❹

❶ 将残差单独放在一边。

❷ 这是我们创建残差连接的两层块:它包括一个 2 × 2 最大池化层。请注意,我们在卷积层和最大池化层中都使用 padding="same"以避免由于填充而导致下采样。

❸ 我们在残差投影中使用 strides=2 以匹配由最大池化层创建的下采样。

❹ 现在块输出和残差具有相同的形状,可以相加。

为了使这些想法更具体,这里是一个简单卷积网络的示例,结构化为一系列块,每个块由两个卷积层和一个可选的最大池化层组成,并在每个块周围有一个残差连接:

inputs = keras.Input(shape=(32, 32, 3))
x = layers.Rescaling(1./255)(inputs)
def residual_block(x, filters, pooling=False):                            # ❶
    residual = x
    x = layers.Conv2D(filters, 3, activation="relu", padding="same")(x)
    x = layers.Conv2D(filters, 3, activation="relu", padding="same")(x)
    if pooling:
        x = layers.MaxPooling2D(2, padding="same")(x)
        residual = layers.Conv2D(filters, 1, strides=2)(residual)         # ❷
    elif filters != residual.shape[-1]:
        residual = layers.Conv2D(filters, 1)(residual)                    # ❸
    x = layers.add([x, residual])
    return x
x = residual_block(x, filters=32, pooling=True)                           # ❹
x = residual_block(x, filters=64, pooling=True)                           # ❺
x = residual_block(x, filters=128, pooling=False)                         # ❻
x = layers.GlobalAveragePooling2D()(x)
outputs = layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs=inputs, outputs=outputs)
model.summary()

❶ 应用具有残差连接的卷积块的实用函数,可以选择添加最大池化

❷ 如果我们使用最大池化,我们添加一个步幅卷积以将残差投影到预期形状。

❸ 如果我们不使用最大池化,只有在通道数量发生变化时才投影残差。

❹ 第一个块

❺ 第二个块;请注意每个块中滤波器数量的增加。

❻ 最后一个块不需要最大池化层,因为我们将在其后立即应用全局平均池化。

这是我们得到的模型摘要:

Model: "model" 
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to 
==================================================================================================
input_1 (InputLayer)            [(None, 32, 32, 3)]  0 
__________________________________________________________________________________________________
rescaling (Rescaling)           (None, 32, 32, 3)    0           input_1[0][0] 
__________________________________________________________________________________________________
conv2d (Conv2D)                 (None, 32, 32, 32)   896         rescaling[0][0] 
__________________________________________________________________________________________________
conv2d_1 (Conv2D)               (None, 32, 32, 32)   9248        conv2d[0][0]
__________________________________________________________________________________________________
max_pooling2d (MaxPooling2D)    (None, 16, 16, 32)   0           conv2d_1[0][0]
__________________________________________________________________________________________________
conv2d_2 (Conv2D)               (None, 16, 16, 32)   128         rescaling[0][0]
__________________________________________________________________________________________________
add (Add)                       (None, 16, 16, 32)   0           max_pooling2d[0][0]
                                                                 conv2d_2[0][0]
__________________________________________________________________________________________________
conv2d_3 (Conv2D)               (None, 16, 16, 64)   18496       add[0][0]
__________________________________________________________________________________________________
conv2d_4 (Conv2D)               (None, 16, 16, 64)   36928       conv2d_3[0][0]
__________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)  (None, 8, 8, 64)     0           conv2d_4[0][0]
__________________________________________________________________________________________________
conv2d_5 (Conv2D)               (None, 8, 8, 64)     2112        add[0][0]
__________________________________________________________________________________________________
add_1 (Add)                     (None, 8, 8, 64)     0           max_pooling2d_1[0][0]
                                                                 conv2d_5[0][0]
__________________________________________________________________________________________________
conv2d_6 (Conv2D)               (None, 8, 8, 128)    73856       add_1[0][0]
__________________________________________________________________________________________________
conv2d_7 (Conv2D)               (None, 8, 8, 128)    147584      conv2d_6[0][0]
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 8, 8, 128)    8320        add_1[0][0]
__________________________________________________________________________________________________
add_2 (Add)                     (None, 8, 8, 128)    0           conv2d_7[0][0]
                                                                 conv2d_8[0][0]
__________________________________________________________________________________________________
global_average_pooling2d (Globa (None, 128)          0           add_2[0][0]
__________________________________________________________________________________________________
dense (Dense)                   (None, 1)            129         global_average_pooling2d[0][0]
==================================================================================================
Total params: 297,697 
Trainable params: 297,697 
Non-trainable params: 0 
__________________________________________________________________________________________________

使用残差连接,您可以构建任意深度的网络,而无需担心梯度消失。

现在让我们继续下一个重要的卷积神经网络架构模式:批量归一化

9.3.3 批量归一化

归一化是一类方法,旨在使机器学习模型看到的不同样本更相似,这有助于模型学习并很好地泛化到新数据。数据归一化的最常见形式是您在本书中已经多次看到的:通过从数据中减去均值使数据以零为中心,并通过将数据除以其标准差使数据具有单位标准差。实际上,这假设数据遵循正态(或高斯)分布,并确保该分布居中并缩放为单位方差:

normalized_data = (data - np.mean(data, axis=...)) / np.std(data, axis=...)

本书中的先前示例在将数据馈送到模型之前对数据进行了归一化。但是数据归一化可能在网络操作的每次转换之后感兴趣:即使进入DenseConv2D网络的数据具有 0 均值和单位方差,也没有理由预期这将是数据输出的情况。归一化中间激活是否有帮助?

批量归一化就是这样。这是一种层类型(Keras 中的BatchNormalization),由 Ioffe 和 Szegedy 于 2015 年引入;²它可以在训练过程中随着均值和方差随时间变化而自适应地归一化数据。在训练过程中,它使用当前数据批次的均值和方差来归一化样本,在推断过程中(当可能没有足够大的代表性数据批次可用时),它使用训练过程中看到的数据的批次均值和方差的指数移动平均值。

尽管原始论文指出批量归一化通过“减少内部协变量转移”来运作,但没有人确切知道为什么批量归一化有帮助。有各种假设,但没有确定性。你会发现这在深度学习中很常见——深度学习不是一门确切的科学,而是一组不断变化的、经验性的最佳工程实践,由不可靠的叙事编织在一起。有时你会觉得手中的书告诉你如何做某事,但并没有完全令人满意地解释为什么它有效:这是因为我们知道如何做但不知道为什么。每当有可靠的解释时,我会确保提到。批量归一化不是这种情况之一。

实际上,批量归一化的主要效果似乎是有助于梯度传播——就像残差连接一样——从而允许更深的网络。一些非常深的网络只有包含多个BatchNormalization层才能训练。例如,批量归一化在许多与 Keras 捆绑在一起的高级卷积网络架构中被广泛使用,如 ResNet50、EfficientNet 和 Xception。

BatchNormalization 层可以在任何层之后使用——DenseConv2D等:

x = ...
x = layers.Conv2D(32, 3, use_bias=False)(x)     # ❶
x = layers.BatchNormalization()(x)

❶ 因为 Conv2D 层的输出被归一化,所以该层不需要自己的偏置向量。

注意DenseConv2D都涉及偏置向量,这是一个学习的变量,其目的是使层仿射而不是纯线性。例如,Conv2D返回,概略地说,y = conv(x, kernel) + bias,而Dense返回y = dot(x, kernel) + bias。因为归一化步骤将使层的输出以零为中心,所以在使用BatchNormalization时不再需要偏置向量,可以通过选项use_bias=False创建该层。这使得该层稍微更加精简。

重要的是,我通常建议将前一层的激活放在批量归一化层之后(尽管这仍然是一个争论的话题)。所以,不要像列表 9.4 中所示那样做,而要像列表 9.5 中所示那样做。

列表 9.4 如何不使用批量归一化

x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.BatchNormalization()(x)

列表 9.5 如何使用批量归一化:激活放在最后

x = layers.Conv2D(32, 3, use_bias=False)(x)    # ❶
x = layers.BatchNormalization()(x)
x = layers.Activation("relu")(x)               # ❷

❶ 注意这里缺少激活。

❷ 我们将激活放在 BatchNormalization 层之后。

这种方法的直观原因是,批量归一化将使你的输入以零为中心,而你的relu激活使用零作为保留或丢弃激活通道的中心:在激活之前进行归一化最大化了relu的利用。也就是说,这种顺序最佳实践并不是绝对关键的,所以如果你进行卷积,然后激活,然后批量归一化,你的模型仍然会训练,并且不一定会看到更糟糕的结果。

关于批量归一化和微调

批量归一化有许多怪癖。其中一个主要的怪癖与微调有关:在微调包含BatchNormalization层的模型时,我建议将这些层保持冻结(将它们的trainable属性设置为False)。否则,它们将继续更新其内部均值和方差,这可能会干扰周围Conv2D层应用的非常小的更新。

现在让我们来看看我们系列中的最后一个架构模式:深度可分离卷积。

9.3.4 深度可分离卷积

如果我告诉你,有一种层可以作为Conv2D的即插即用替代品,可以使你的模型更小(可训练权重参数更少)、更精简(浮点操作更少),并使其在任务上表现更好几个百分点,你会怎么想?这正是深度可分离卷积层(Keras 中的SeparableConv2D)所做的。这个层在每个输入通道上执行空间卷积,然后通过点卷积(1×1 卷积)混合输出通道,如图 9.10 所示。

图 9.10 深度可分离卷积:深度卷积后跟点卷积

这相当于将空间特征的学习与通道特征的学习分开。就像卷积依赖于图像中的模式不与特定位置绑定一样,深度可分离卷积依赖于中间激活中的空间位置高度相关,但不同通道高度独立。因为这个假设通常对深度神经网络学习到的图像表示是正确的,它作为一个有用的先验,帮助模型更有效地利用其训练数据。一个对其将要处理的信息结构有更强先验的模型是一个更好的模型——只要这些先验是准确的。

深度可分离卷积相比常规卷积需要更少的参数,并涉及更少的计算,同时具有可比较的表征能力。它导致更小的模型收敛更快,更不容易过拟合。当你在有限数据上从头开始训练小模型时,这些优势变得尤为重要。

当涉及到大规模模型时,深度可分离卷积是 Xception 架构的基础,这是一个性能优异的卷积神经网络,与 Keras 捆绑在一起。你可以在论文“Xception: 使用深度可分离卷积进行深度学习”中了解更多关于深度可分离卷积和 Xception 的理论基础。³

硬件、软件和算法的共同演进

考虑一个具有 3×3 窗口、64 个输入通道和 64 个输出通道的常规卷积操作。它使用了 336464 = 36,864 个可训练参数,当你将其应用于图像时,它运行的浮点操作数量与这个参数数量成比例。同时,考虑一个等效的深度可分离卷积:它只涉及 3364 + 6464 = 4,672 个可训练参数,并且浮点操作数量比例更少。这种效率改进只会随着滤波器数量或卷积窗口大小的增加而增加。

因此,你会期望深度可分离卷积会明显更快,对吧?等一下。如果你正在编写这些算法的简单 CUDA 或 C 实现,这是正确的——事实上,在 CPU 上运行时,你确实会看到有意义的加速,其中底层实现是并行化的 C。但实际上,你可能正在使用 GPU,并且你在其上执行的远非“简单”的 CUDA 实现:它是一个cuDNN 内核,这是一段被极致优化的代码,直到每个机器指令。花费大量精力优化这段代码是有意义的,因为 NVIDIA 硬件上的 cuDNN 卷积每天负责许多 exaFLOPS 的计算。但这种极端微观优化的副作用是,其他方法几乎没有机会在性能上竞争——即使是具有显著内在优势的方法,比如深度可分离卷积。

尽管多次要求 NVIDIA 进行优化,深度可分离卷积并没有像常规卷积那样受益于几乎相同级别的软件和硬件优化,因此它们仍然只比常规卷积快,即使它们使用的参数和浮点运算量减少了平方倍。不过,需要注意的是,即使深度可分离卷积并没有加速,仍然是一个好主意:它们较低的参数数量意味着你不太容易过拟合,并且它们假设通道应该是不相关的导致模型收敛更快,表示更加稳健。

在这种情况下的轻微不便可能在其他情况下变成一道不可逾越的障碍:因为整个深度学习的硬件和软件生态系统都被微调为一组非常特定的算法(特别是通过反向传播训练的卷积网络),所以偏离传统路线的成本极高。如果你尝试使用替代算法,比如无梯度优化或脉冲神经网络,那么你设计的前几个并行 C++ 或 CUDA 实现将比一个老式的卷积网络慢几个数量级,无论你的想法多么聪明和高效。说服其他研究人员采纳你的方法将是一项艰巨的任务,即使它确实更好。

可以说,现代深度学习是硬件、软件和算法之间的共同演化过程的产物:NVIDIA GPU 和 CUDA 的可用性导致了反向传播训练的卷积网络的早期成功,这又促使 NVIDIA 优化其硬件和软件以适应这些算法,进而导致研究社区围绕这些方法形成共识。在这一点上,找到一条不同的道路将需要对整个生态系统进行多年的重新设计。

9.3.5 将其整合在一起:一个迷你 Xception 风格的模型

以下是迄今为止学到的卷积网络架构原则的提醒:

  • 你的模型应该组织成重复的层块,通常由多个卷积层和一个最大池化层组成。
  • 你的层中的滤波器数量应随着空间特征图的大小减小而增加。
  • 深而窄比宽而浅更好。
  • 在层块周围引入残差连接有助于训练更深的网络。
  • 在卷积层之后引入批量归一化层可能是有益的。
  • Conv2D 层替换为 SeparableConv2D 层可能是有益的,因为它们更节省参数。

让我们将这些想法整合到一个单一模型中。其架构将类似于 Xception 的较小版本,并且我们将应用它到上一章的狗与猫任务中。对于数据加载和模型训练,我们将简单地重用我们在第 8.2.5 节中使用的设置,但我们将用以下卷积网络替换模型定义:

inputs = keras.Input(shape=(180, 180, 3))
x = data_augmentation(inputs)                                      # ❶
x = layers.Rescaling(1./255)(x)                                    # ❷
x = layers.Conv2D(filters=32, kernel_size=5, use_bias=False)(x)    # ❸
for size in [32, 64, 128, 256, 512]:                               # ❹
    residual = x
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)
    x = layers.SeparableConv2D(size, 3, padding="same", use_bias=False)(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)
    x = layers.SeparableConv2D(size, 3, padding="same", use_bias=False)(x)
    x = layers.MaxPooling2D(3, strides=2, padding="same")(x)
    residual = layers.Conv2D(
        size, 1, strides=2, padding="same", use_bias=False)(residual)
    x = layers.add([x, residual])
x = layers.GlobalAveragePooling2D()(x)                             # ❺
x = layers.Dropout(0.5)(x)                                         # ❻
outputs = layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs=inputs, outputs=outputs)

❶ 我们使用与之前相同的数据增强配置。

❷ 不要忘记输入重新缩放!

❸ 需要注意的是,支持可分离卷积的假设“特征通道在很大程度上是独立的”在 RGB 图像中并不成立!红色、绿色和蓝色通道在自然图像中实际上高度相关。因此,我们模型中的第一层是一个常规的 Conv2D 层。之后我们将开始使用 SeparableConv2D。

❹ 我们使用一系列具有增加特征深度的卷积块。每个块由两个经过批量归一化的深度可分离卷积层和一个最大池化层组成,并在整个块周围有一个残差连接。

❺ 在原始模型中,我们在密集层之前使用了一个 Flatten 层。在这里,我们使用了一个 GlobalAveragePooling2D 层。

❻ 像原始模型一样,我们为了正则化添加了一个 dropout 层。

这个卷积神经网络的可训练参数数量为 721,857,略低于原始模型的 991,041 个可训练参数,但仍在同一数量级。图 9.11 显示了其训练和验证曲线。

图 9.11 具有类似 Xception 架构的训练和验证指标

您会发现我们的新模型的测试准确率为 90.8%,而上一章中的朴素模型为 83.5%。正如您所看到的,遵循架构最佳实践确实对模型性能产生了即时且显著的影响!

此时,如果您想进一步提高性能,您应该开始系统地调整架构的超参数 — 这是我们将在第十三章中详细讨论的一个主题。我们在这里没有经历这一步骤,因此前述模型的配置纯粹基于我们讨论的最佳实践,再加上在评估模型大小时的一点直觉。

请注意,这些架构最佳实践适用于计算机视觉的一般情况,不仅仅是图像分类。例如,Xception 被用作 DeepLabV3 中的标准卷积基础,这是一种流行的最先进的图像分割解决方案。

这就结束了我们对基本卷积神经网络架构最佳实践的介绍。有了这些原则,您将能够在各种计算机视觉任务中开发性能更高的模型。您现在已经在成为熟练的计算机视觉从业者的道路上走得很顺利。为了进一步加深您的专业知识,我们需要讨论最后一个重要主题:解释模型如何得出预测。


Python 深度学习第二版(GPT 重译)(四)(3)https://developer.aliyun.com/article/1485279

相关文章
|
11天前
|
机器学习/深度学习 自然语言处理 异构计算
Python深度学习面试:CNN、RNN与Transformer详解
【4月更文挑战第16天】本文介绍了深度学习面试中关于CNN、RNN和Transformer的常见问题和易错点,并提供了Python代码示例。理解这三种模型的基本组成、工作原理及其在图像识别、文本处理等任务中的应用是评估技术实力的关键。注意点包括:模型结构的混淆、过拟合的防治、输入序列长度处理、并行化训练以及模型解释性。掌握这些知识和技巧,将有助于在面试中展现优秀的深度学习能力。
35 11
|
4天前
|
机器学习/深度学习 PyTorch TensorFlow
Python数据科学之旅从基础到深度学习
【4月更文挑战第10天】在这系列文章中,我们探讨了数据科学中重要的Python库,如NumPy和Pandas,以及深度学习框架TensorFlow和PyTorch。NumPy提供高性能的多维数组操作,Pandas则提供了灵活的数据处理和分析。通过Matplotlib和Seaborn进行数据可视化
14 2
|
7天前
|
数据采集 存储 人工智能
【Python+微信】【企业微信开发入坑指北】4. 企业微信接入GPT,只需一个URL,自动获取文章总结
【Python+微信】【企业微信开发入坑指北】4. 企业微信接入GPT,只需一个URL,自动获取文章总结
21 0
|
12天前
|
机器学习/深度学习 人工智能 自然语言处理
总结几个GPT的超实用之处【附带Python案例】
总结几个GPT的超实用之处【附带Python案例】
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(3)
JavaScript 权威指南第七版(GPT 重译)(七)
32 0
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(1)
JavaScript 权威指南第七版(GPT 重译)(七)
60 0
|
12天前
|
存储 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(六)(4)
JavaScript 权威指南第七版(GPT 重译)(六)
90 2
JavaScript 权威指南第七版(GPT 重译)(六)(4)
|
12天前
|
前端开发 JavaScript API
JavaScript 权威指南第七版(GPT 重译)(六)(3)
JavaScript 权威指南第七版(GPT 重译)(六)
55 4
|
12天前
|
JSON 前端开发 JavaScript
JavaScript 权威指南第七版(GPT 重译)(五)(2)
JavaScript 权威指南第七版(GPT 重译)(五)
36 5
|
12天前
|
JSON JavaScript 前端开发
JavaScript 权威指南第七版(GPT 重译)(四)(4)
JavaScript 权威指南第七版(GPT 重译)(四)
67 6