01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来
01-【深度学习-Day 1】为什么深度学习是未来?一探究竟AI、ML、DL关系与应用
02-【深度学习-Day 2】图解线性代数:从标量到张量,理解深度学习的数据表示与运算
03-【深度学习-Day 3】搞懂微积分关键:导数、偏导数、链式法则与梯度详解
04-【深度学习-Day 4】掌握深度学习的“概率”视角:基础概念与应用解析
05-【深度学习-Day 5】Python 快速入门:深度学习的“瑞士军刀”实战指南
06-【深度学习-Day 6】掌握 NumPy:ndarray 创建、索引、运算与性能优化指南
07-【深度学习-Day 7】精通Pandas:从Series、DataFrame入门到数据清洗实战
08-【深度学习-Day 8】让数据说话:Python 可视化双雄 Matplotlib 与 Seaborn 教程
09-【深度学习-Day 9】机器学习核心概念入门:监督、无监督与强化学习全解析
10-【深度学习-Day 10】机器学习基石:从零入门线性回归与逻辑回归
11-【深度学习-Day 11】Scikit-learn实战:手把手教你完成鸢尾花分类项目
12-【深度学习-Day 12】从零认识神经网络:感知器原理、实现与局限性深度剖析
13-【深度学习-Day 13】激活函数选型指南:一文搞懂Sigmoid、Tanh、ReLU、Softmax的核心原理与应用场景
14-【深度学习-Day 14】从零搭建你的第一个神经网络:多层感知器(MLP)详解
15-【深度学习-Day 15】告别“盲猜”:一文读懂深度学习损失函数
16-【深度学习-Day 16】梯度下降法 - 如何让模型自动变聪明?
17-【深度学习-Day 17】神经网络的心脏:反向传播算法全解析
18-【深度学习-Day 18】从SGD到Adam:深度学习优化器进阶指南与实战选择
19-【深度学习-Day 19】入门必读:全面解析 TensorFlow 与 PyTorch 的核心差异与选择指南
20-【深度学习-Day 20】PyTorch入门:核心数据结构张量(Tensor)详解与操作
21-【深度学习-Day 21】框架入门:神经网络模型构建核心指南 (Keras & PyTorch)
大家好!在上一篇【深度学习-Day 20】我们初步认识了深度学习框架中的核心数据结构——张量(Tensors),并学习了如何在 TensorFlow 和 PyTorch 中进行张量操作及自动求导。掌握了“原材料”的处理,今天我们将更进一步,学习如何使用这些框架来“搭建房子”——构建神经网络模型。
构建模型是深度学习流程中的核心环节。一个设计良好的模型结构是决定项目成败的关键因素之一。本文将带你深入了解两大主流框架 TensorFlow (主要通过其高级 API Keras) 和 PyTorch 中模型构建的机制,包括模型容器(Model Containers)的选择与使用、常用神经网络层的定义,以及如何查看和理解你所搭建的模型结构。最终,我们将通过实践,亲手用框架搭建一个简单的多层感知器(MLP)模型。
无论你是初学者还是希望系统梳理知识的进阶者,本文都将为你提供清晰、易懂的指引,助你轻松迈出用框架构建模型的第一步。
想象一下,你要用乐高积木搭建一个复杂的城堡。你需要一个“蓝图”或者一个“底座”来有条不紊地组织这些积木块。在深度学习中,“模型容器”就扮演了类似的角色。它帮助我们将一个个独立的神经网络层(如全连接层、激活层等)有序地组合起来,形成一个完整的模型。
神经网络,尤其是深度神经网络,往往包含许多层。模型容器提供了一种结构化的方式来定义这些层以及它们之间的连接关系。
模型容器不仅仅是层的简单堆叠,它还承担着重要的“后勤”工作。
TensorFlow 的高级 API Keras 提供了两种主要的模型构建方式:Sequential API 和 Functional API。
keras.Sequential
模型是最简单的一种模型,适用于层的线性堆叠。顾名思义,它允许你像搭积木一样,一层一层地顺序添加网络层。
你可以通过向 Sequential
构造函数传递一个层列表,或通过 .add()
方法逐个添加层来创建模型。
# TensorFlow Keras Sequential API Example
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# 假设输入数据是 784 维的向量 (例如 MNIST 图片展平后)
# 输出是 10 个类别的概率
# 方法一:通过列表传递层
model_sequential_list = keras.Sequential([
layers.Dense(128, activation='relu', input_shape=(784,)), # 输入层 + 第一个隐藏层
layers.Dropout(0.2), # 添加 Dropout 层防止过拟合
layers.Dense(64, activation='relu'), # 第二个隐藏层
layers.Dense(10, activation='softmax') # 输出层
], name="MySequentialModel_List")
# 方法二:通过 .add() 方法
model_sequential_add = keras.Sequential(name="MySequentialModel_Add")
model_sequential_add.add(layers.Dense(128, activation='relu', input_shape=(784,)))
model_sequential_add.add(layers.Dropout(0.2))
model_sequential_add.add(layers.Dense(64, activation='relu'))
model_sequential_add.add(layers.Dense(10, activation='softmax'))
# 查看模型摘要
model_sequential_list.summary()
关键行注释:
layers.Dense(128, activation='relu', input_shape=(784,))
: 定义一个全连接层,有128个神经元,使用ReLU激活函数,并指定输入数据的形状为(784,)。input_shape
只在第一层需要指定。layers.Dropout(0.2)
: 添加一个Dropout层,随机失活20%的神经元,用于正则化。layers.Dense(10, activation='softmax')
: 定义输出层,有10个神经元(对应10个类别),使用Softmax激活函数输出概率分布。当模型结构不是简单的线性堆叠时,例如模型有多个输入或多个输出、层之间存在共享、或者网络拓扑结构比较复杂(如有向无环图 DAG),keras.Model
(Functional API) 提供了更大的灵活性。
使用 Functional API 时,你需要:
keras.Input
)。keras.Model
类,通过指定模型的输入和输出来实例化模型。# TensorFlow Keras Functional API Example
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# 定义输入
inputs = keras.Input(shape=(784,), name="input_layer")
# 定义层并连接
x = layers.Dense(128, activation='relu', name="hidden_layer_1")(inputs)
x = layers.Dropout(0.2, name="dropout_layer")(x)
x = layers.Dense(64, activation='relu', name="hidden_layer_2")(x)
outputs = layers.Dense(10, activation='softmax', name="output_layer")(x)
# 创建模型
model_functional = keras.Model(inputs=inputs, outputs=outputs, name="MyFunctionalModel")
# 查看模型摘要
model_functional.summary()
关键行注释:
inputs = keras.Input(shape=(784,), name="input_layer")
: 定义模型的输入张量,指定其形状。x = layers.Dense(...)(inputs)
: 调用 Dense
层,并将 inputs
作为其输入,输出结果赋值给 x
。这种链式调用是 Functional API 的核心。model_functional = keras.Model(inputs=inputs, outputs=outputs, ...)
: 通过指定模型的输入和输出来实例化 Model
对象。nn.Module
在 PyTorch 中,所有的神经网络模块,无论是单个的层还是整个复杂的模型,都应该继承自 torch.nn.Module
类。这个类是 PyTorch 构建所有网络的基石。
nn.Module
:万物皆模块nn.Module
是所有网络组件的父类。自定义模型时,你需要创建一个继承自 nn.Module
的类。__init__
方法中将所需的层(如 nn.Linear
, nn.ReLU
等)定义为类的属性。forward
的方法,该方法接收输入数据,并定义数据如何通过网络层进行传播,最终返回模型的输出。# PyTorch nn.Module Example
import torch
import torch.nn as nn
import torch.nn.functional as F # F 通常包含无状态的操作,如激活函数
class SimpleMLP_PyTorch(nn.Module):
def __init__(self, input_size, hidden_size1, hidden_size2, num_classes):
super(SimpleMLP_PyTorch, self).__init__() # 必须调用父类的构造函数
# 定义层
self.fc1 = nn.Linear(input_size, hidden_size1) # 第一个全连接层
self.relu1 = nn.ReLU() # ReLU 激活
self.dropout = nn.Dropout(p=0.2) # Dropout 层
self.fc2 = nn.Linear(hidden_size1, hidden_size2) # 第二个全连接层
self.relu2 = nn.ReLU() # ReLU 激活
self.fc3 = nn.Linear(hidden_size2, num_classes) # 输出层
def forward(self, x):
# 定义前向传播逻辑
x = self.fc1(x)
x = self.relu1(x)
x = self.dropout(x)
x = self.fc2(x)
x = self.relu2(x)
x = self.fc3(x)
# 注意:在 PyTorch 中,对于多分类问题,Softmax 通常与损失函数 (如 nn.CrossEntropyLoss) 结合使用,
# nn.CrossEntropyLoss 内部会自动应用 log_softmax 和 NLLLoss。
# 如果确实需要直接输出 Softmax 概率,可以显式调用 F.softmax(x, dim=1)
return x
# 示例实例化
input_dim = 784
h1_dim = 128
h2_dim = 64
output_dim = 10
model_pytorch = SimpleMLP_PyTorch(input_dim, h1_dim, h2_dim, output_dim)
# 打印模型结构(一种查看方式)
print(model_pytorch)
关键行注释:
super(SimpleMLP_PyTorch, self).__init__()
: 这是Python中调用父类(即nn.Module
)构造函数的标准方式,必须执行。self.fc1 = nn.Linear(input_size, hidden_size1)
: 在 __init__
方法中实例化层,并将它们作为类的属性(例如 self.fc1
)。nn.Linear
是全连接层。def forward(self, x):
: 定义了数据 x
如何在网络中流动。这里,x
依次通过定义的层。nn.Module
的灵活性nn.Module
提供了一种统一且高度灵活的模型构建方式。无论是简单的线性模型还是极其复杂的架构,都通过继承 nn.Module
并实现 __init__
和 forward
方法来完成。这种设计赋予了开发者极大的控制权和自由度。选择哪种方式取决于你的具体需求和模型的复杂度。对于初学者,从 Keras Sequential API 或 PyTorch nn.Module
的简单实现开始会更容易上手。
模型是由各种不同功能的“层”(Layers)搭建起来的。下面我们介绍一些在构建神经网络时最常遇到的层。
全连接层,在 Keras 中称为 Dense
层,在 PyTorch 中称为 Linear
层,是神经网络中最基础也最常见的层之一。
layers.Dense
我们已经在前面的模型容器示例中看到了 layers.Dense
的用法。
units
: 整数,输出空间的维度(即该层神经元的数量)。activation
: 激活函数,可以是字符串形式的预定义激活函数名(如 ‘relu’, ‘sigmoid’, ‘softmax’),也可以是一个激活函数对象。默认为 None
(即线性激活 a ( x ) = x a(x)=x a(x)=x)。use_bias
: 布尔值,是否使用偏置向量。默认为 True
。kernel_initializer
, bias_initializer
: 权重和偏置的初始化器。input_shape
: 一个元组,用于指定输入数据的形状。只在模型的第一层需要提供(或者使用 keras.Input
)。# Keras Dense Layer Example
# (已在 Sequential 和 Functional API 示例中展示)
# model.add(layers.Dense(64, activation='relu', input_shape=(784,)))
# dense_output = layers.Dense(10, activation='softmax')(previous_layer_output)
nn.Linear
nn.Linear
实现了对输入数据的线性变换。
in_features
: 整数,每个输入样本的大小(即输入特征的数量)。out_features
: 整数,每个输出样本的大小(即该层神经元的数量)。bias
: 布尔值,如果设置为 False
,则该层将不学习加法偏置。默认为 True
。# PyTorch Linear Layer Example
# (已在 nn.Module 示例中展示)
# self.fc1 = nn.Linear(in_features=784, out_features=128)
# linear_output = self.fc1(input_tensor)
激活函数是神经网络的“灵魂”之一,它们为模型引入非线性,使得网络能够学习和表示比线性模型复杂得多的函数。
在 Keras 中,激活函数可以通过两种方式应用:
Dense
)的参数这是最常见的方式,直接在定义层时通过 activation
参数指定。
# Keras: Activation as a parameter
hidden_layer = layers.Dense(64, activation='relu')
output_layer = layers.Dense(10, activation='softmax')
layers.Activation
有时,你可能想在没有内置 activation
参数的层之后,或者想更明确地表示激活步骤时,可以使用 layers.Activation
层。
# Keras: Activation as a separate layer
model = keras.Sequential([
layers.Dense(64, input_shape=(784,)), # 线性输出
layers.Activation('relu'), # 应用 ReLU 激活
layers.Dense(10), # 线性输出
layers.Activation('softmax') # 应用 Softmax 激活
])
activation
参数,或者你想在多个操作之间插入一个激活函数时,独立的激活层会很有用。在 PyTorch 中,激活函数通常也存在于 torch.nn
模块中(作为有状态的层)或 torch.nn.functional
模块中(作为无状态的函数)。
torch.nn
模块中的激活函数这些激活函数本身也是 nn.Module
的子类,可以像普通层一样在 __init__
中实例化并在 forward
方法中调用。
# PyTorch: Activations from nn module
class MyModelWithNNActivations(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.relu = nn.ReLU() # 实例化 ReLU 层
self.fc2 = nn.Linear(hidden_dim, output_dim)
self.softmax = nn.Softmax(dim=1) # 实例化 Softmax 层, dim=1 表示对每行的数值进行softmax
def forward(self, x):
x = self.fc1(x)
x = self.relu(x) # 调用 ReLU 实例
x = self.fc2(x)
x = self.softmax(x) # 调用 Softmax 实例
return x
torch.nn.functional
中的激活函数torch.nn.functional
(通常导入为 F
) 提供了许多与 nn
模块中层对应的函数式版本。这些函数是无状态的,可以直接在 forward
方法中调用,有时可以使代码更简洁。
# PyTorch: Activations from nn.functional
import torch.nn.functional as F
class MyModelWithFunctionalActivations(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
x = self.fc1(x)
x = F.relu(x) # 直接调用 F.relu 函数
x = self.fc2(x)
x = F.softmax(x, dim=1) # 直接调用 F.softmax 函数
return x
nn.Module
vs nn.functional
:如果激活函数有可学习的参数(例如 PReLU),则必须使用 nn
模块中的版本。对于没有可学习参数的激活函数(如 ReLU, Sigmoid, Tanh, Softmax),两者皆可。使用 nn.functional
通常更简洁,但 nn.Module
的方式使得网络结构在 print(model)
时更为清晰,因为激活函数会作为独立的模块显示出来。Dropout 是一种非常有效的正则化技术,用于减少神经网络中的过拟合现象。(我们将在 【深度学习-Day 26】正则化技术(二):Dropout 中详细讨论其原理。)
layers.Dropout
rate
: 浮点数,介于0和1之间,表示要丢弃的输入单元的比例。例如,rate=0.2
表示随机丢弃20%的输入单元。# Keras Dropout Layer
# model.add(layers.Dense(128, activation='relu'))
# model.add(layers.Dropout(0.5)) # 在激活的全连接层之后添加 Dropout
Dropout 通常放在激活函数的后面。
nn.Dropout
p
: 浮点数,元素被归零的概率。默认为 0.5。注意与 Keras 中 rate
的含义相同。inplace
: 布尔值,如果设置为 True
,将就地执行此操作。默认为 False
。# PyTorch Dropout Layer
# self.dropout = nn.Dropout(p=0.5)
# x = self.dropout(activated_x) # 在激活值之后应用
重要提示:Dropout 层在训练和评估(或测试)时的行为是不同的。在训练时,它会随机丢弃神经元;在评估时,它会自动关闭,并对权重进行相应的缩放,以确保输出的期望值与训练时一致。框架会自动处理这种切换(通常通过 model.train()
和 model.eval()
模式)。
除了上述核心层,还有许多其他类型的层,我们将在后续专门的章节中详细介绍它们。这里先做个简要的提及,让你有个初步印象:
layers.Conv1D
, layers.Conv2D
, layers.Conv3D
nn.Conv1d
, nn.Conv2d
, nn.Conv3d
layers.MaxPooling1D
, layers.MaxPooling2D
, layers.AveragePooling2D
, etc.nn.MaxPool1d
, nn.MaxPool2d
, nn.AvgPool2d
, etc.layers.Flatten()
torch.flatten(input_tensor, start_dim=1)
(函数式) 或 nn.Flatten(start_dim=1, end_dim=-1)
(模块式)。start_dim=1
通常是为了保留 batch 维度。# Keras Flatten Example (通常在CNN后接Dense层前使用)
# model.add(layers.Conv2D(32, (3, 3), activation='relu'))
# model.add(layers.MaxPooling2D((2, 2)))
# model.add(layers.Flatten())
# model.add(layers.Dense(10, activation='softmax'))
# PyTorch Flatten Example
# class CNN(nn.Module):
# def __init__(self):
# super().__init__()
# self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
# self.relu = nn.ReLU()
# self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
# self.flatten = nn.Flatten() # 或者在 forward 中使用 torch.flatten
# self.fc = nn.Linear(16 * 14 * 14, 10) # 假设输入是28x28的MNIST,池化一次后是14x14
# def forward(self, x):
# x = self.pool(self.relu(self.conv1(x)))
# x = self.flatten(x) # x = x.view(x.size(0), -1) 另一种展平方式
# x = self.fc(x)
# return x
layers.BatchNormalization()
nn.BatchNorm1d
, nn.BatchNorm2d
, nn.BatchNorm3d
当你辛辛苦苦搭建好一个模型后,如何确认它的结构是否如你所愿?参数数量是否合理?这时,查看模型结构的功能就显得尤为重要。
nn.Module
构建的模型,可视化或摘要可以帮助你确认各层是否按照预期连接,数据流是否正确。model.summary()
这是最常用也最直接的方法,它会在控制台打印出模型的文本摘要,包括:
# Keras model.summary()
# (已在之前的 Keras 模型示例中展示)
# model_sequential_list.summary()
# model_functional.summary()
输出示例:
Model: "MySequentialModel_List"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 128) 100480
dropout (Dropout) (None, 128) 0
dense_1 (Dense) (None, 64) 8256
dense_2 (Dense) (None, 10) 650
=================================================================
Total params: 109386 (427.29 KB)
Trainable params: 109386 (427.29 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
(None, 128)
中的 None
表示该维度可以是任意大小,通常对应批处理大小 (batch size)。
keras.utils.plot_model()
(可选,依赖 pydot
和 graphviz
)如果你希望得到一个更直观的图形化模型结构,可以使用 keras.utils.plot_model
函数。它可以将模型结构保存为一张图片。
pydot
和 graphviz
。你可以通过 pip 安装 pydot
(pip install pydot
),而 graphviz
是一个系统级的软件包,需要根据你的操作系统进行安装(例如,在 Ubuntu 上 sudo apt-get install graphviz
)。# Keras keras.utils.plot_model()
# (确保已安装 pydot 和 graphviz)
try:
tf.keras.utils.plot_model(
model_functional, # 使用 Functional API 构建的模型效果更佳
to_file='functional_model_plot.png',
show_shapes=True, # 显示形状信息
show_dtype=False, # 不显示数据类型
show_layer_names=True, # 显示层名称
rankdir='TB', # 'TB' for top-to-bottom; 'LR' for left-to-right
expand_nested=False, # 是否展开嵌套模型
dpi=96 # 图像的DPI
)
print("模型结构图已保存为 functional_model_plot.png")
except ImportError:
print("无法生成模型结构图,请确保已安装 pydot 和 graphviz。")
这张图会清晰地展示层与层之间的连接关系,对于理解复杂模型非常有帮助。
print(model)
直接打印 nn.Module
的实例会输出一个模型的概览,显示每一层及其子模块的结构和一些参数信息。
# PyTorch print(model)
# (已在之前的 PyTorch 模型示例中展示)
# print(model_pytorch)
输出示例:
SimpleMLP_PyTorch(
(fc1): Linear(in_features=784, out_features=128, bias=True)
(relu1): ReLU()
(dropout): Dropout(p=0.2, inplace=False)
(fc2): Linear(in_features=128, out_features=64, bias=True)
(relu2): ReLU()
(fc3): Linear(in_features=64, out_features=10, bias=True)
)
这种方式简洁明了,对于理解模型层次结构很有帮助。
torchsummary
库 (推荐)如果你想要类似 Keras model.summary()
那样的详细输出,包括每层的输出形状和参数数量,可以使用第三方库 torchsummary
。
pip install torchsummary
# PyTorch torchsummary (推荐)
from torchsummary import summary
# 确保模型和输入数据在同一个设备上 (CPU 或 GPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_pytorch.to(device)
# 需要提供一个与模型输入匹配的示例输入尺寸
# (batch_size, channel, height, width) for CNNs
# (batch_size, input_features) for MLPs
try:
summary(model_pytorch, input_size=(input_dim,)) # 对于MLP,通常是 (input_features,)
except Exception as e:
print(f"使用 torchsummary 时发生错误: {e}")
print("请确保模型已移至正确设备,且 input_size 与模型 forward 方法的输入匹配。")
输出示例(类似于 Keras 的 summary()
):
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 128] 100,480
ReLU-2 [-1, 128] 0
Dropout-3 [-1, 128] 0
Linear-4 [-1, 64] 8,256
ReLU-5 [-1, 64] 0
Linear-6 [-1, 10] 650
================================================================
Total params: 109,386
Trainable params: 109,386
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.42
Estimated Total Size (MB): 0.42
----------------------------------------------------------------
torchsummary
提供了非常详细的信息,对于分析 PyTorch 模型非常有价值。
TensorBoard 是一个强大的可视化工具套件,最初为 TensorFlow 开发,但现在也可以与 PyTorch (通过 torch.utils.tensorboard.SummaryWriter
) 很好地集成。它可以用来可视化模型图、训练指标(如损失和准确率)、权重分布等。
在 PyTorch 中,你可以使用 SummaryWriter
将模型图写入 TensorBoard 日志文件:
from torch.utils.tensorboard import SummaryWriter
# writer = SummaryWriter('runs/my_experiment_name')
# # 假设 dummy_input 是一个符合模型输入形状的示例张量
# dummy_input = torch.randn(1, input_dim).to(device) # batch_size=1
# try:
# writer.add_graph(model_pytorch, dummy_input)
# writer.close()
# print("模型图已写入 TensorBoard 日志。启动 TensorBoard 查看:tensorboard --logdir=runs")
# except Exception as e:
# print(f"写入 TensorBoard 图时出错: {e}")
我们将在后续关于模型训练与监控的文章中更详细地介绍 TensorBoard 的使用。
理论学习之后,最好的巩固方式就是动手实践。现在,我们将使用 Keras 和 PyTorch 分别搭建一个结构相同的简单多层感知器 (MLP) 模型。
我们假设一个通用的分类任务。例如,输入是 784 784 784 维的特征向量(比如展平的 28 × 28 28 \times 28 28×28 像素的灰度图像,如 MNIST 数据集),我们的目标是将其分为 10 10 10 个不同的类别。
我们将设计一个具有以下结构的 MLP:
我们可以用 Mermaid 语法绘制这个结构图:
graph TD
A[输入层 (784 特征)] --> B(全连接层 1: 128 单元, ReLU)
B --> DRP1(Dropout: rate=0.2)
DRP1 --> C(全连接层 2: 64 单元, ReLU)
C --> D(输出层: 10 单元, Softmax)
我们将主要使用 Sequential API,因为它非常适合这种线性堆叠的结构。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# 定义 Keras MLP 模型
def create_keras_mlp(input_shape=(784,), num_classes=10):
model = keras.Sequential([
layers.Input(shape=input_shape, name="input"), # 使用 Input 层明确指定输入
layers.Dense(128, activation='relu', name="hidden_layer_1"),
layers.Dropout(0.2, name="dropout_1"),
layers.Dense(64, activation='relu', name="hidden_layer_2"),
layers.Dense(num_classes, activation='softmax', name="output_layer")
], name="Simple_MLP_Keras")
return model
keras_mlp_model = create_keras_mlp()
为了对比,我们也可以用 Functional API 实现相同的结构:
def create_keras_mlp_functional(input_shape=(784,), num_classes=10):
inputs = keras.Input(shape=input_shape, name="input")
x = layers.Dense(128, activation='relu', name="hidden_layer_1")(inputs)
x = layers.Dropout(0.2, name="dropout_1")(x)
x = layers.Dense(64, activation='relu', name="hidden_layer_2")(x)
outputs = layers.Dense(num_classes, activation='softmax', name="output_layer")(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="Simple_MLP_Keras_Functional")
return model
# keras_mlp_model_functional = create_keras_mlp_functional()
print("Keras MLP Model Summary:")
keras_mlp_model.summary()
# (可选) 生成模型图
# try:
# tf.keras.utils.plot_model(keras_mlp_model, to_file='keras_mlp_plot.png', show_shapes=True)
# print("Keras MLP 结构图已保存为 keras_mlp_plot.png")
# except ImportError:
# print("无法生成 Keras MLP 结构图,请确保 pydot 和 graphviz 已安装。")
我们将定义一个继承自 nn.Module
的类。
nn.Module
子类import torch
import torch.nn as nn
import torch.nn.functional as F
class PyTorchMLP(nn.Module):
def __init__(self, input_size=784, num_classes=10):
super(PyTorchMLP, self).__init__()
self.fc1 = nn.Linear(input_size, 128)
self.dropout1 = nn.Dropout(p=0.2)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, num_classes)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.dropout1(x)
x = F.relu(self.fc2(x))
# 输出层通常不直接应用 softmax,因为 nn.CrossEntropyLoss 会处理它
# 如果需要直接概率输出,可以在推理时应用: x = F.softmax(self.fc3(x), dim=1)
x = self.fc3(x)
return x
pytorch_mlp_model = PyTorchMLP()
print("\nPyTorch MLP Model Structure:")
print(pytorch_mlp_model)
from torchsummary import summary
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
pytorch_mlp_model.to(device)
print("\nPyTorch MLP Model Summary (using torchsummary):")
try:
summary(pytorch_mlp_model, input_size=(784,))
except Exception as e:
print(f"生成 torchsummary 时出错: {e}")
input_shape
参数指定(不含批次大小)。keras.Input
层作为第一层,明确定义输入。keras.Input
层开始。nn.Linear
等层在其 __init__
方法中通过 in_features
参数接收输入特征数。forward
方法第一次接收到实际数据时确定的。torchsummary
需要你提供一个 input_size
来帮助它推断。Dense
等层的 activation
参数,也可以作为独立的 layers.Activation
层。__init__
中实例化为 nn.ReLU
, nn.Softmax
等模块,然后在 forward
中调用;或者直接在 forward
中使用 torch.nn.functional
(如 F.relu
, F.softmax
) 中的函数。activation='softmax'
。nn.CrossEntropyLoss
作为损失函数时,推荐输出层不加 Softmax,因为 nn.CrossEntropyLoss
内部已经包含了 LogSoftmax
和 NLLLoss
,这样做可以提高数值稳定性。如果在推理阶段需要概率输出,可以再显式调用 F.softmax
。model.eval()
(PyTorch) 或推理时 (Keras) 自动关闭 Dropout 并调整权重。通过这些实践,你应该对如何在两大主流框架中定义和组织神经网络模型有了更清晰的认识。记住,模型构建只是第一步,接下来我们还需要学习如何加载数据、定义损失函数和优化器,并最终训练和评估我们的模型,这些内容将在后续文章中展开(【深度学习-Day 22】和【深度学习-Day 23】)。
在模型构建过程中,新手常常会遇到一些问题。这里列举几个常见的问题及其排查思路。
这是最常见的问题之一,通常表现为 “ValueError: Input 0 of layer … is incompatible with the layer: expected axis … to have value … but received input with shape …” (Keras) 或类似 “mat1 and mat2 shapes cannot be multiplied” (PyTorch) 的错误。
input_shape
或 in_features
设置不当:模型的第一层或特定层的输入维度参数设置错误。input_shape
(Keras) / in_features
(PyTorch):确保每一层的输入输出维度能够正确衔接。model.summary()
(Keras) 或 torchsummary.summary()
(PyTorch):打印出每一层的输出形状,逐层检查维度变化是否符合预期。forward
方法的 x
是什么?def forward(self, x):
中,参数 x
代表传递给模型的一批输入数据(a batch of input data)。(batch_size, ...)
,其中 ...
代表输入特征的维度。例如,对于 MLP 处理展平的 MNIST 图像,x
的形状可能是 (batch_size, 784)
。forward
方法内部,你需要确保 x
按照你设计的网络结构,依次通过定义的各个层进行转换。每一行代码 x = self.some_layer(x)
或 x = F.some_function(x)
都在更新 x
的状态,将其从上一层的输出转变为当前操作的输出,最终得到整个模型的输出。Dense
/Linear
):参数主要来自权重矩阵 W W W 和偏置向量 b b b。参数数量为 (input_features * output_features) + output_features
。Conv2D
):参数来自卷积核权重和偏置。数量为 (kernel_height * kernel_width * input_channels * output_channels) + output_channels
。model.summary()
或 torchsummary
查看各层参数。恭喜你,完成了本次关于使用深度学习框架构建模型的学习!通过本文,我们深入探讨了模型构建的核心概念和实践技巧。现在,让我们回顾一下关键知识点:
模型容器的重要性:
Sequential
API 用于快速搭建线性堆叠模型,以及更灵活的 Functional
API 用于构建复杂网络拓扑。nn.Module
,所有模型和层都继承自它,通过在 __init__
中定义层并在 forward
方法中指定数据流向来构建模型,提供了极高的灵活性。常用层的功能与实现:
Dense
/Linear
): 实现线性变换,学习全局特征,其参数为权重 W W W 和偏置 b b b。Activation
/ReLU
, Softmax
, etc.): 为模型引入非线性,使其能学习复杂模式。可以作为层参数或独立模块/函数使用。模型结构的可视化:
model.summary()
提供文本摘要;keras.utils.plot_model()
可生成模型结构图(需 pydot
和 graphviz
)。print(model)
显示模块层级;推荐使用 torchsummary
库的 summary()
函数获取详细的类 Keras 摘要;TensorBoard 可用于更高级的图可视化。实践构建MLP:
nn.Module
) 实现了相同的网络结构。框架选择与使用哲学: