开发者指南 / 顺序模型

顺序模型

作者: fchollet
创建日期: 2020/04/12
最后修改日期: 2023/06/25
描述: 顺序模型的完整指南。

在 Colab 上查看 GitHub 源代码


设置

import keras
from keras import layers
from keras import ops

何时使用顺序模型

Sequential 模型适用于 一个简单的层堆叠 其中每个层都有 恰好一个输入张量和一个输出张量

示意图如下 Sequential 模型:

# 定义具有 3 个层的顺序模型
model = keras.Sequential(
    [
        layers.Dense(2, activation="relu", name="layer1"),
        layers.Dense(3, activation="relu", name="layer2"),
        layers.Dense(4, name="layer3"),
    ]
)
# 在测试输入上调用模型
x = ops.ones((3, 3))
y = model(x)

等价于以下函数:

# 创建 3 个层
layer1 = layers.Dense(2, activation="relu", name="layer1")
layer2 = layers.Dense(3, activation="relu", name="layer2")
layer3 = layers.Dense(4, name="layer3")

# 在测试输入上调用层
x = ops.ones((3, 3))
y = layer3(layer2(layer1(x)))

当以下情况不适合使用顺序模型时:

  • 你的模型有多个输入或多个输出
  • 你的任何层有多个输入或多个输出
  • 你需要共享层
  • 你希望有非线性拓扑(例如,残差连接,多分支模型)

创建顺序模型

你可以通过将层的列表传递给顺序构造函数来创建顺序模型:

model = keras.Sequential(
    [
        layers.Dense(2, activation="relu"),
        layers.Dense(3, activation="relu"),
        layers.Dense(4),
    ]
)

其层可以通过 layers 属性访问:

model.layers
[<Dense name=dense, built=False>,
 <Dense name=dense_1, built=False>,
 <Dense name=dense_2, built=False>]

你还可以通过 add() 方法逐步创建顺序模型:

model = keras.Sequential()
model.add(layers.Dense(2, activation="relu"))
model.add(layers.Dense(3, activation="relu"))
model.add(layers.Dense(4))

注意还有一个相应的 pop() 方法来移除层: 顺序模型的行为非常像层的列表。

model.pop()
print(len(model.layers))  # 2
2

还要注意,顺序构造函数接受 name 参数,就像 Keras 中的任何层或模型。这对于用语义上有意义的名称注释 TensorBoard 图形非常有用。

model = keras.Sequential(name="my_sequential")
model.add(layers.Dense(2, activation="relu", name="layer1"))
model.add(layers.Dense(3, activation="relu", name="layer2"))
model.add(layers.Dense(4, name="layer3"))

预先指定输入形状

通常,Keras 中所有层都需要知道其输入的形状 以便能够创建其权重。因此,当你像这样创建一个层时,最初它没有权重:

layer = layers.Dense(3)
layer.weights  # 空
[]

它在首次对输入调用时创建其权重,因为权重的形状取决于输入的形状:

# 对测试输入调用层
x = ops.ones((1, 4))
y = layer(x)
layer.weights  # 现在它有权重,形状为 (4, 3) 和 (3,)
[<KerasVariable shape=(4, 3), dtype=float32, path=dense_6/kernel>,
 <KerasVariable shape=(3,), dtype=float32, path=dense_6/bias>]

当然,这也适用于顺序模型。当你实例化一个没有输入形状的顺序模型时,它并没有“构建”:它没有权重 (调用model.weights 会导致错误,仅指出这一点)。权重是在模型第一次看到一些输入数据时创建的:

model = keras.Sequential(
    [
        layers.Dense(2, activation="relu"),
        layers.Dense(3, activation="relu"),
        layers.Dense(4),
    ]
)  # 此时没有权重!

# 此时,你不能这样做:
# model.weights

# 你也不能这样做:
# model.summary()

# 在测试输入上调用模型
x = ops.ones((1, 4))
y = model(x)
print("调用模型后的权重数量:", len(model.weights))  # 6
调用模型后的权重数量: 6

一旦模型“构建”完成,你可以调用其 summary() 方法来显示其

model.summary()  # 输出模型的总结信息
模型: "sequential_3"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层(类型)                      输出形状                    参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ dense_7 (Dense)                 │ (1, 2)                    │         10 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense_8 (Dense)                 │ (1, 3)                    │          9 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense_9 (Dense)                 │ (1, 4)                    │         16 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 35 (140.00 B)
 可训练参数: 35 (140.00 B)
 不可训练参数: 0 (0.00 B)

不过,在逐步构建顺序模型时,显示到目前为止模型的摘要信息,包括当前的输出形状是非常有用的。在这种情况下,您应该通过将 Input 对象传递给模型来开始模型,以便它从一开始就知道其输入形状:

model = keras.Sequential()  # 创建顺序模型
model.add(keras.Input(shape=(4,)))  # 添加输入层,定义输入形状为4
model.add(layers.Dense(2, activation="relu"))  # 添加密集层,激活函数为ReLU

model.summary()  # 输出模型的总结信息
模型: "sequential_4"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层(类型)                      输出形状                    参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ dense_10 (Dense)                │ (None, 2)                 │         10 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 10 (40.00 B)
 可训练参数: 10 (40.00 B)
 非可训练参数: 0 (0.00 B)

注意,Input对象不会作为model.layers的一部分显示,因为它不是一个层:

model.layers
[<Dense name=dense_10, built=True>]

使用预定义输入形状构建的模型总是有权重(即使在看到任何数据之前)并且总是有定义的输出形状。

一般来说,如果你知道输入形状,推荐的最佳实践是总是提前指定顺序模型的输入形状。


常见调试工作流程: add() + summary()

在构建新的顺序架构时,使用add()逐步堆叠层并频繁打印模型摘要是很有用的。例如,这使您能够监视一堆Conv2DMaxPooling2D层如何下采样图像特征图:

model = keras.Sequential()
model.add(keras.Input(shape=(250, 250, 3)))  # 250x250 RGB图像
model.add(layers.Conv2D(32, 5, strides=2, activation="relu"))
model.add(layers.Conv2D(32, 3, activation="relu"))
model.add(layers.MaxPooling2D(3))

# 你能猜出此时的当前输出形状吗?可能不行。
# 我们只需打印出来:
model.summary()

# 答案是: (40, 40, 32),所以我们可以继续下采样...

model.add(layers.Conv2D(32, 3, activation="relu"))
model.add(layers.Conv2D(32, 3, activation="relu"))
model.add(layers.MaxPooling2D(3))
model.add(layers.Conv2D(32, 3, activation="relu"))
model.add(layers.Conv2D(32, 3, activation="relu"))
model.add(layers.MaxPooling2D(2))

# 那现在呢?
model.summary()

# 现在我们有4x4的特征图,是时候应用全局最大池化了。
model.add(layers.GlobalMaxPooling2D())

# 最后,我们添加一个分类层。
model.add(layers.Dense(10))
模型: "sequential_5"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层(类型)                      输出形状                  参数数量 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 123, 123, 32)      │      2,432 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_1 (Conv2D)               │ (None, 121, 121, 32)      │      9,248 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 40, 40, 32)        │          0 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 11,680 (45.62 KB)
 可训练参数: 11,680 (45.62 KB)
不可训练参数: 0 (0.00 B)
模型: "sequential_5"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━┓
┃ 层(类型)                       输出形状                   参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━┩
│ conv2d (Conv2D)                 │ (None, 123, 123, 32)      │      2,432 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_1 (Conv2D)               │ (None, 121, 121, 32)      │      9,248 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 40, 40, 32)        │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_2 (Conv2D)               │ (None, 38, 38, 32)        │      9,248 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_3 (Conv2D)               │ (None, 36, 36, 32)        │      9,248 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 12, 12, 32)        │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_4 (Conv2D)               │ (None, 10, 10, 32)        │      9,248 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_5 (Conv2D)               │ (, 8, 8, 32)          │      9,248 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (, 4, 4, 32)          │          0 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 48,672 (190.12 KB)
 可训练参数: 48,672 (190.12 KB)
 不可训练参数: 0 (0.00 B)

非常实用,对吗?


拥有模型后该做什么

一旦您的模型架构准备好了,您可能希望:


使用顺序模型进行特征提取

一旦构建了顺序模型,它就像一个 功能API模型。 这意味着每一层都有一个 inputoutput 属性。这些属性可以用于做一些很棒的事情,比如 快速创建一个模型,从顺序模型中提取所有中间层的输出:

initial_model = keras.Sequential(
    [
        keras.Input(shape=(250, 250, 3)),
        layers.Conv2D(32, 5, strides=2, activation="relu"),
        layers.Conv2D(32, 3, activation="relu"),
        layers.Conv2D(32, 3, activation="relu"),
    ]
)
feature_extractor = keras.Model(
    inputs=initial_model.inputs,
    outputs=[layer.output for layer in initial_model.layers],
)

# 对测试输入调用特征提取器。
x = ops.ones((1, 250, 250, 3))
features = feature_extractor(x)

这是一个类似的示例,只从一层提取特征:

initial_model = keras.Sequential(
    [
        keras.Input(shape=(250, 250, 3)),
        layers.Conv2D(32, 5, strides=2, activation="relu"),
        layers.Conv2D(32, 3, activation="relu", name="my_intermediate_layer"),
        layers.Conv2D(32, 3, activation="relu"),
    ]
)
feature_extractor = keras.Model(
    inputs=initial_model.inputs,
    outputs=initial_model.get_layer(name="my_intermediate_layer").output,
)
# 对测试输入调用特征提取器。
x = ops.ones((1, 250, 250, 3))
features = feature_extractor(x)

使用顺序模型进行迁移学习

迁移学习是在模型中冻结底层并仅训练 顶层。如果您不熟悉它,请确保阅读我们的 迁移学习指南

以下是涉及顺序模型的两个常见迁移学习蓝图。

首先,假设您有一个顺序模型,您想冻结除最后一层外的所有 层。在这种情况下,您只需遍历 model.layers 并在每一层上设置 layer.trainable = False,除最后一层外。如下所示:

model = keras.Sequential([
    keras.Input(shape=(784)),
    layers.Dense(32, activation='relu'),
    layers.Dense(32, activation='relu'),
    layers.Dense(32, activation='relu'),
    layers.Dense(10),
])

# 您可能会想首先加载预训练权重。
model.load_weights(...)

# 冻结除最后一层外的所有层。
for layer in model.layers[:-1]:
  layer.trainable = False

# 重新编译并训练(这只会更新最后一层的权重)。
model.compile(...)
model.fit(...)

另一个常见的蓝图是使用顺序模型堆叠一个预训练模型和一些新初始化的分类层。像这样:

# 加载带有预训练权重的卷积基础模型
base_model = keras.applications.Xception(
    weights='imagenet',
    include_top=False,
    pooling='avg')

# 冻结基础模型
base_model.trainable = False

# 使用顺序模型在顶部添加一个可训练的分类器
model = keras.Sequential([
    base_model,
    layers.Dense(1000),
])

# 编译和训练
model.compile(...)
model.fit(...)

如果你进行迁移学习,你可能会发现自己经常使用这两种模式。

关于顺序模型,你需要知道的就是这些!

要了解有关在Keras中构建模型的更多信息,请参见: