Python Keras 和TensorFlow 入门(T神经网络剖析:了解核心Keras API)
神经网络剖析:了解核心Keras API
层:深度学习的基础模块
神经网络的基本数据结构是层,我们在第2 章中了解过。层是一个数据处理模块,它接
收一个或多个张量作为输入,并输出一个或多个张量。有些层是无状态的,但大多数层具有状
态,即层的权重。权重是利用随机梯度下降学到的一个或多个张量,其中包含神经网络的知识
(knowledge)。
不同类型的层适用于不同的张量格式和不同类型的数据处理。例如,简单的向量数据存储
在形状为(samples, features) 的2 阶张量中,通常用密集连接层[densely connected layer,
也叫全连接层(fully connected layer)或密集层(dense layer),对应于Keras 的Dense 类]来处理。序列数据存储在形状为(samples, timesteps, features) 的3 阶张量中,通常用循环
层(recurrent layer)来处理,比如LSTM 层或一维卷积层(Conv1D)。图像数据存储在4 阶张量
中,通常用二维卷积层(Conv2D)来处理。
你可以把层看作深度学习的乐高积木,Keras 将这个比喻具象化。在Keras 中构建深度学习
模型,就是将相互兼容的层拼接在一起,建立有用的数据变换流程。
1. Keras 的Layer 基类
简单的API 应该具有单一的核心抽象概念。在Keras 中,这个核心概念就是Layer 类。Keras
中的一切,要么是Layer,要么与Layer 密切交互。
Layer 是封装了状态(权重)和计算(一次前向传播)的对象。权重通常在build() 中定
义(不过也可以在构造函数__init__() 中创建),计算则在call() 方法中定义。
在第2 章中,我们实现了一个NaiveDense 类,它包含两个权重W 和b,并进行如下计算:
output = activation(dot(input, W) + b)。Keras 的层与之非常相似,如代码清单3-22
所示。
代码清单3-22 Dense 层的实现:作为Layer 的子类

稍后将详细介绍build() 方法和call() 方法的作用。如果你还不理解所有内容,请不必
担心。
一旦将这样的层实例化,它就可以像函数一样使用,接收一个TensorFlow 张量作为输入。

你可能想知道,既然最终对层的使用就是简单调用(通过层的__call__() 方法),那我
们为什么还要实现call() 和build() 呢?原因在于我们希望能够及时创建状态。我们来看看
它们是如何做到的。
2. 自动推断形状:动态构建层
就像玩乐高积木一样,你只能将兼容的层“拼接”在一起。层兼容性(layer compatibility)
的概念具体指的是,每一层只接收特定形状的输入张量,并返回特定形状的输出张量。看下面
这个例子。

该层将返回一个张量,其第一维的大小已被转换为32。它后面只能连接一个接收32 维向
量作为输入的层。
在使用Keras 时,往往不必担心尺寸兼容性问题,因为添加到模型中的层是动态构建的,
以匹配输入层的形状,例如下面这段代码。
from tensorflow.keras import models
from tensorflow.keras import layers
model = models.Sequential([
layers.Dense(32, activation="relu"),
layers.Dense(32)
])
这些层没有收到任何关于输入形状的信息;相反,它们可以自动推断,遇到第一个输入的
形状就是其输入形状。
在第2 章实现的简单Dense 层中(我们将其命名为NaiveDense),我们必须将该层的输
入大小明确传递给构造函数,以便能够创建其权重。这种方法并不理想,因为它会导致模型的
每个新层都需要知道前一层的形状。
model = NaiveSequential([
NaiveDense(input_size=784, output_size=32, activation="relu"),
NaiveDense(input_size=32, output_size=64, activation="relu"),
NaiveDense(input_size=64, output_size=32, activation="relu"),
NaiveDense(input_size=32, output_size=10, activation="softmax")
])
如果某一层生成输出形状的规则很复杂,那就更糟糕了。如果某一层返回输出的形状是
(batch, input_ size * 2 if input_size % 2 == 0 else input_size * 3)
,那该
怎么办?
如果我们把NaiveDense 层重新实现为能够自动推断形状的Keras 层,那么它看起来就像
前面的SimpleDense 层(见代码清单3-22),具有build() 方法和call() 方法。
在SimpleDense 中,我们不再像NaiveDense 示例那样在构造函数中创建权重;相反,
我们在一个专门的状态创建方法build() 中创建权重。这个方法接收该层遇到的第一个输入形状作为参数。第一次调用该层时(通过其__call__() 方法),build() 方法会自动调用。事实
上,这就是为什么我们将计算定义在一个单独的call() 方法中,而不是直接定义在__call__()
方法中。基层__call__() 方法的代码大致如下。
def __call__(self, inputs):
if not self.built:
self.build(inputs.shape)
self.built = True
return self.call(inputs)
有了自动形状推断,前面的示例就变得简洁了,如下所示。
model = keras.Sequential([
SimpleDense(32, activation="relu"),
SimpleDense(64, activation="relu"),
SimpleDense(32, activation="relu"),
SimpleDense(10, activation="softmax")
])
注意,自动形状推断并不是Layer 类的__call__() 方法的唯一功能。它还要处理更多的
事情,特别是急切执行和图执行之间的路由(这个概念将在第7 章中介绍),以及输入掩码(第
11 章将介绍)。现在你只需记住:在实现你自己的层时,将前向传播放在call() 方法中。
从层到模型
深度学习模型是由层构成的图,在Keras 中就是Model 类。到目前为止,你只见过Sequential
模型(Model 的一个子类),它是层的简单堆叠,将单一输入映射为单一输出。但随着深入学习,
你会接触到更多类型的网络拓扑结构。一些常见的结构包括:
- 双分支(two-branch)网络
- 多头(multihead)网络
- 残差连接
网络拓扑结构可能会非常复杂。例如,图3-9 是Transformer 各层的图拓扑结构,这是一个
用于处理文本数据的常见架构。
在Keras 中构建模型通常有两种方法:直接作为Model 类的子类,或者使用函数式API,
后者可以用更少的代码做更多的事情。第7 章将介绍这两种方法。
模型的拓扑结构定义了一个假设空间。你可能还记得,第1 章介绍过,机器学习就是在预
先定义的可能性空间内,利用反馈信号的指引,寻找特定输入数据的有用表示。通过选择网络
拓扑结构,你可以将可能性空间(假设空间)限定为一系列特定的张量运算,将输入数据映射
为输出数据。然后,你要为这些张量运算的权重张量寻找一组合适的值。
要从数据中学习,你必须对其进行假设。这些假设定义了可学习的内容。因此,假设空间
的结构(模型架构)是非常重要的。它编码了你对问题所做的假设,即模型的先验知识。如果
你正在处理一个二分类问题,使用的模型由一个没有激活的Dense 层组成(纯仿射变换),那
么你就是在假设这两个类别是线性可分的。

选择正确的网络架构,更像是一门艺术而不是科学。虽然有一些最佳实践和原则,但只有
实践才能帮助你成为合格的神经网络架构师。后面几章将教你构建神经网络的明确原则,并帮
助你训练直觉,判断哪些架构对特定问题有效、哪些无效。你将在这些问题上拥有可靠的直觉:
每种类型的模型架构适合解决哪类问题?在实践中如何构建这些网络?如何选择正确的学习配
置?如何调节模型,直到产生你想要的结果?
编译步骤:配置学习过程
一旦确定了模型架构,你还需要选定以下3 个参数。
- 损失函数(目标函数)——在训练过程中需要将其最小化。它衡量的是当前任务是否成功。
- 优化器——决定如何基于损失函数对神经网络进行更新。它执行的是随机梯度下降
(SGD)的某个变体。 - 指标——衡量成功的标准,在训练和验证过程中需要对其进行监控,如分类精度。与损
失不同,训练不会直接对这些指标进行优化。因此,指标不需要是可微的。
一旦选定了损失函数、优化器和指标,就可以使用内置方法compile() 和fit() 开始训
练模型。此外,也可以编写自定义的训练循环。第7 章将介绍如何做到这一点,它需要做更多
的工作!下面我们先来看一下compile() 和fit()。
compile() 方法的作用是配置训练过程,在第2 章第一个神经网络示例中已经介绍过。它
接收的参数是optimizer、loss 和metrics(一个列表)。

在上面对compile() 的调用中,我们把优化器、损失函数和指标作为字符串(如"rmsprop")
来传递。这些字符串实际上是访问Python 对象的快捷方式。例如,“rmsprop” 会变成keras.
optimizers.RMSprop()。重要的是,也可以把这些参数指定为对象实例,如下所示。
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=keras.losses.MeanSquaredError(),
metrics=[keras.metrics.BinaryAccuracy()])
如果你想传递自定义的损失函数或指标,或者想进一步配置正在使用的对象,比如向优化
器传入参数learning_rate,那么这种方法很有用。
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate=1e-4),
loss=my_custom_loss,
metrics=[my_custom_metric_1, my_custom_metric_2])
第7 章将介绍如何创建自定义的损失函数和指标。一般来说,你无须从头开始创建自己的
损失函数、指标或优化器,因为Keras 提供了下列多种内置选项,很可能满足你的需求。
优化器:
- SGD(带动量或不带动量)
- RMSprop
- Adam
- Adagrad
- 等等
损失函数: - CategoricalCrossentropy
- SparseCategoricalCrossentropy
- BinaryCrossentropy
- MeanSquaredError
- KLDivergence
- CosineSimilarity
- 等等
指标: - CategoricalAccuracy
- SparseCategoricalAccuracy
- BinaryAccuracy
- AUC
- Precision
- Recall
- 等等
读完本书,你将看到以上许多选项的具体应用。
选择损失函数
为问题选择合适的损失函数,这是极其重要的。神经网络会采取各种方法使损失最小化,
如果损失函数与成功完成当前任务不完全相关,那么神经网络最终的结果可能会不符合你的预
期。想象一下,利用SGD 训练一个愚蠢而又无所不能的人工智能体,损失函数选择得非常糟糕:
“让所有活人的平均幸福感最大化。”为了简化工作,这个人工智能体可能会选择消灭绝大多数
人类,只留下几个人并专注于这几个人的幸福,因为平均幸福感并不受人数的影响。但这可能
并不是你想要的结果。请记住,你构建的所有神经网络在减小损失函数时都和上述人工智能体
一样无情。因此,一定要明智地选择损失函数,否则你将得到意想不到的副作用。
幸运的是,对于分类、回归和序列预测等常见问题,可以遵循一些简单的指导原则来选择
合适的损失函数。例如,对于二分类问题,可以使用二元交叉熵损失函数;对于多分类问题,
可以使用分类交叉熵损失函数。只有在面对全新的研究问题时,你才需要自己开发损失函数。
接下来的几章将详细说明对各种常见任务应选择哪种损失函数。
理解fit() 方法
compile() 之后将是fit()。fit() 方法执行训练循环,它有以下关键参数。
- 要训练的数据(输入和目标):这些数据通常以 NumPy 数组或 TensorFlow Dataset 对
象的形式传入。你将在后续章节中学到更多关于Dataset API 的内容。 - 训练轮数:训练循环应该在传入的数据上迭代多少次。
- 在每轮小批量梯度下降中使用的批量大小:在一次权重更新中,计算梯度所要考虑的训
练样本的数量。
代码清单3-23 展示了如何对NumPy 数据调用fit()。
代码清单3-23 对NumPy 数据调用fit()

调用fit() 将返回一个History 对象。这个对象包含history 字段,它是一个字典,字
典的键是"loss" 或特定指标名称,字典的值是这些指标每轮的值组成的列表。
>>> history.history
{"binary_accuracy": [0.855, 0.9565, 0.9555, 0.95, 0.951],
"loss": [0.6573270302042366,
0.07434618508815766,
0.07687718723714351,
0.07412414988875389,
0.07617757616937161]}
监控验证数据上的损失和指标
机器学习的目标不是得到一个在训练数据上表现良好的模型——做到这一点很容易,你只
需跟随梯度下降即可。机器学习的目标是得到总体上表现良好的模型,特别是在模型前所未见
的数据上。一个模型在训练数据上表现良好,并不意味着它在前所未见的数据上也会表现良好。
举例来说,模型有可能只是记住了训练样本和目标值之间的映射关系,但这对在前所未见的数
据上进行预测毫无用处。我们将在第5 章更详细地讨论这一点。
要想查看模型在新数据上的性能,标准做法是保留训练数据的一个子集作为验证数据
(validation data)。你不会在这部分数据上训练模型,但会用它来计算损失值和指标值。实现方
法是在fit() 中使用validation_data 参数,如代码清单3-24 所示。和训练数据一样,验
证数据也可以作为NumPy 数组或TensorFlow Dataset 对象传入。
代码清单3-24 使用validation_data 参数


在验证数据上的损失值叫作“验证损失”,以区别于“训练损失”。请注意,必须将训练数
据和验证数据严格分开:验证的目的是监控模型所学到的知识在新数据上是否真的有用。如果
验证数据在训练期间被模型看到过,那么验证损失和指标就会不准确。
注意,如果想在训练完成后计算验证损失和指标,可以调用evaluate() 方法。
loss_and_metrics = model.evaluate(val_inputs, val_targets, batch_size=128)
evaluate() 将对传入的数据进行批量迭代(批量大小为batch_size),并返回一个标量
列表,其中第一个元素是验证损失,后面的元素是验证指标。如果模型没有指标,则只返回验
证损失(不再是列表)。
推断:在训练后使用模型
一旦训练好了模型,就可以用它来对新的数据进行预测。这叫作推断(inference)。要做到
这一点,一个简单的方法就是调用该模型(call())。

但是,这种方法会一次性处理new_inputs 中的所有输入,如果其中包含大量数据,那么
这种方法可能是不可行的(尤其是,它可能需要比你的GPU 更大的内存)。
要想进行推断,一种更好的方法是使用predict() 方法。它将小批量地迭代数据,并返回
预测值组成的NumPy 数组。与__call__() 不同,它还可以处理TensorFlow Dataset 对象。

例如,对于前面训练的线性模型,如果对一些验证数据使用predict(),那么我们会得到
一些标量值,对应于模型对每个输入样本的预测结果。
>>> predictions = model.predict(val_inputs, batch_size=128)
>>> print(predictions[:10])
[[0.3590725 ]
[0.82706255]
[0.74428225]
[0.682058 ]
[0.7312616 ]
[0.6059811 ]
[0.78046083]
[0.025846 ]
[0.16594526]
[0.72068727]]
目前,这就是你需要了解的关于Keras 模型的全部内容。第4 章将继续用Keras 解决现实世
界中的机器学习问题。
完整代码
import tensorflow as tf
from tensorflow import keras
import numpy as np
# 1. 自定义SimpleDense层的实现
class SimpleDense(keras.layers.Layer):
def __init__(self, units, activation=None):
super().__init__()
self.units = units
self.activation = activation
def build(self, input_shape):
input_dim = input_shape[-1]
self.W = self.add_weight(
shape=(input_dim, self.units),
initializer="random_normal",
trainable=True
)
self.b = self.add_weight(
shape=(self.units,),
initializer="zeros",
trainable=True
)
def call(self, inputs):
y = tf.matmul(inputs, self.W) + self.b
if self.activation is not None:
y = tf.keras.activations.get(self.activation)(y)
return y
# 2. 使用自定义层
print("=== 使用自定义层 ===")
layer = SimpleDense(32, activation="relu")
input_tensor = tf.ones(shape=(2, 784))
output_tensor = layer(input_tensor)
print(f"输出形状: {output_tensor.shape}")
# 3. 构建模型
print("\n=== 构建模型 ===")
model = keras.Sequential([
SimpleDense(32, activation="relu"),
SimpleDense(64, activation="relu"),
SimpleDense(32, activation="relu"),
SimpleDense(10, activation="softmax")
])
# 4. 生成模拟数据
print("\n=== 生成模拟数据 ===")
num_samples = 1000
num_features = 784
num_classes = 10
# 训练数据
train_inputs = np.random.random((num_samples, num_features))
train_targets = np.random.randint(0, 2, size=(num_samples, num_classes))
# 验证数据
val_inputs = np.random.random((200, num_features))
val_targets = np.random.randint(0, 2, size=(200, num_classes))
print(f"训练数据形状: {train_inputs.shape}")
print(f"训练目标形状: {train_targets.shape}")
# 5. 编译模型
print("\n=== 编译模型 ===")
model.compile(
optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss=keras.losses.CategoricalCrossentropy(),
metrics=[keras.metrics.CategoricalAccuracy()]
)
# 6. 训练模型
print("\n=== 训练模型 ===")
history = model.fit(
train_inputs,
train_targets,
epochs=5,
batch_size=32,
validation_data=(val_inputs, val_targets)
)
# 7. 查看训练历史
print("\n=== 训练历史 ===")
print("验证准确率:", history.history["val_categorical_accuracy"])
print("训练损失:", history.history["loss"])
# 8. 评估模型
print("\n=== 评估模型 ===")
loss_and_metrics = model.evaluate(val_inputs, val_targets, batch_size=128)
print(f"验证损失: {loss_and_metrics[0]:.4f}")
print(f"验证准确率: {loss_and_metrics[1]:.4f}")
# 9. 使用模型进行预测
print("\n=== 模型预测 ===")
# 创建新数据进行预测
new_data = np.random.random((5, num_features))
predictions = model.predict(new_data, batch_size=128)
print("预测结果形状:", predictions.shape)
print("前5个样本的预测概率:")
for i, pred in enumerate(predictions):
print(f"样本 {i + 1}: {pred}")
print(f" 预测类别: {np.argmax(pred)}")
print(f" 最大概率: {np.max(pred):.4f}")
# 10. 使用内置Dense层的对比示例
print("\n=== 使用内置Dense层的对比 ===")
builtin_model = keras.Sequential([
keras.layers.Dense(32, activation="relu", input_shape=(784,)),
keras.layers.Dense(64, activation="relu"),
keras.layers.Dense(32, activation="relu"),
keras.layers.Dense(10, activation="softmax")
])
builtin_model.compile(
optimizer="rmsprop",
loss="categorical_crossentropy",
metrics=["accuracy"]
)
# 快速训练一下内置模型进行比较
builtin_history = builtin_model.fit(
train_inputs,
train_targets,
epochs=3,
batch_size=32,
validation_data=(val_inputs, val_targets),
verbose=0
)
print("内置Dense层模型验证准确率:", builtin_history.history["val_accuracy"][-1])
运行效果
=== 使用自定义层 ===
2025-10-28 14:18:49.301189: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: SSE3 SSE4.1 SSE4.2 AVX AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
输出形状: (2, 32)
=== 构建模型 ===
=== 生成模拟数据 ===
训练数据形状: (1000, 784)
训练目标形状: (1000, 10)
=== 编译模型 ===
=== 训练模型 ===
Epoch 1/5
WARNING:tensorflow:From D:\ProgramData\anaconda3\envs\PythonforDeepLearning\Lib\site-packages\keras\src\backend\tensorflow\core.py:232: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.
32/32 ━━━━━━━━━━━━━━━━━━━━ 6s 12ms/step - categorical_accuracy: 0.2770 - loss: 21.8293 - val_categorical_accuracy: 0.4850 - val_loss: 50.4253
Epoch 2/5
32/32 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - categorical_accuracy: 0.2150 - loss: 93.4785 - val_categorical_accuracy: 0.1400 - val_loss: 164.3410
Epoch 3/5
32/32 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - categorical_accuracy: 0.1420 - loss: 170.8436 - val_categorical_accuracy: 0.4850 - val_loss: 214.0001
Epoch 4/5
32/32 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - categorical_accuracy: 0.1160 - loss: 231.1929 - val_categorical_accuracy: 0.2550 - val_loss: 301.9287
Epoch 5/5
32/32 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - categorical_accuracy: 0.0940 - loss: 305.6853 - val_categorical_accuracy: 0.2550 - val_loss: 334.0901
=== 训练历史 ===
验证准确率: [0.48500001430511475, 0.14000000059604645, 0.48500001430511475, 0.2549999952316284, 0.2549999952316284]
训练损失: [21.829265594482422, 93.47850036621094, 170.8436279296875, 231.19293212890625, 305.6852722167969]
=== 评估模型 ===
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - categorical_accuracy: 0.2550 - loss: 334.0900
验证损失: 334.0900
验证准确率: 0.2550
=== 模型预测 ===
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 54ms/step
预测结果形状: (5, 10)
前5个样本的预测概率:
样本 1: [1.8663183e-20 1.0000000e+00 1.3050739e-35 3.8918473e-36 0.0000000e+00
3.2892922e-18 0.0000000e+00 9.5781046e-15 9.1326704e-20 0.0000000e+00]
预测类别: 1
最大概率: 1.0000
样本 2: [2.00226725e-20 1.00000000e+00 1.49044117e-35 4.44029108e-36
0.00000000e+00 3.54616837e-18 0.00000000e+00 1.01560885e-14
9.81708097e-20 0.00000000e+00]
预测类别: 1
最大概率: 1.0000
样本 3: [1.7950884e-19 1.0000000e+00 7.2306076e-34 2.2908179e-34 0.0000000e+00
2.4735772e-17 0.0000000e+00 4.8310240e-14 8.1717199e-19 0.0000000e+00]
预测类别: 1
最大概率: 1.0000
样本 4: [4.0837733e-21 1.0000000e+00 8.9158994e-37 2.5469754e-37 0.0000000e+00
8.6563795e-19 0.0000000e+00 3.2779360e-15 2.1024603e-20 0.0000000e+00]
预测类别: 1
最大概率: 1.0000
样本 5: [2.6786039e-20 1.0000000e+00 2.4984581e-35 7.4797859e-36 0.0000000e+00
4.5711924e-18 0.0000000e+00 1.2553348e-14 1.2980143e-19 0.0000000e+00]
预测类别: 1
最大概率: 1.0000
=== 使用内置Dense层的对比 ===
D:\ProgramData\anaconda3\envs\PythonforDeepLearning\Lib\site-packages\keras\src\layers\core\dense.py:92: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
super().__init__(activity_regularizer=activity_regularizer, **kwargs)
内置Dense层模型验证准确率: 0.009999999776482582
更多推荐
所有评论(0)