当前位置: 首页 > news >正文

【深度学习-Day 21】框架入门:神经网络模型构建核心指南 (Keras PyTorch)

Langchain系列文章目录

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”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

深度学习系列文章目录

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)


文章目录

  • Langchain系列文章目录
  • Python系列文章目录
  • PyTorch系列文章目录
  • 机器学习系列文章目录
  • 深度学习系列文章目录
  • Java系列文章目录
  • JavaScript系列文章目录
  • 深度学习系列文章目录
  • 前言
  • 一、理解模型构建的基石:模型容器
    • 1.1 为什么需要模型容器?
      • 1.1.1 组织与管理网络层
      • 1.1.2 便于参数管理与训练
    • 1.2 TensorFlow (Keras) 中的模型容器
      • 1.2.1 Sequential API:线性堆叠的艺术
        • (1)概念与适用场景
        • (2)如何使用 Sequential API
      • 1.2.2 Functional API:构建复杂模型的利器
        • (1)概念与适用场景
        • (2)如何使用 Functional API
    • 1.3 PyTorch 中的模型容器:`nn.Module`
      • 1.3.1 `nn.Module`:万物皆模块
        • (1)核心理念
        • (2)如何定义模型
    • 1.4 模型容器选择小结
      • 1.4.1 Keras Sequential vs Functional
      • 1.4.2 PyTorch `nn.Module` 的灵活性
  • 二、神经网络的核心组件:常用层
    • 2.1 全连接层 (Dense Layer / Linear Layer)
      • 2.1.1 原理与作用
      • 2.1.2 Keras 中的 `layers.Dense`
      • 2.1.3 PyTorch 中的 `nn.Linear`
    • 2.2 激活层 (Activation Layer)
      • 2.2.1 为什么需要激活函数?
      • 2.2.2 Keras 中的激活
        • (1)作为层(如 `Dense`)的参数
        • (2)作为独立的层 `layers.Activation`
      • 2.2.3 PyTorch 中的激活
        • (1)`torch.nn` 模块中的激活函数
        • (2)`torch.nn.functional` 中的激活函数
    • 2.3 Dropout 层 (Dropout Layer)
      • 2.3.1 原理与作用 (简述)
      • 2.3.2 Keras 中的 `layers.Dropout`
      • 2.3.3 PyTorch 中的 `nn.Dropout`
    • 2.4 其他常用层 (简介)
      • 2.4.1 卷积层 (Convolutional Layers)
      • 2.4.2 池化层 (Pooling Layers)
      • 2.4.3 展平层 (Flatten Layer)
      • 2.4.4 批量归一化层 (Batch Normalization Layer)
  • 三、洞悉模型结构:如何查看你的网络
    • 3.1 为什么需要查看模型结构?
      • 3.1.1 验证模型定义是否正确
      • 3.1.2 理解参数数量与计算量
      • 3.1.3 便于沟通与文档撰写
    • 3.2 Keras 中的模型可视化
      • 3.2.1 `model.summary()`
      • 3.2.2 `keras.utils.plot_model()` (可选,依赖 `pydot` 和 `graphviz`)
    • 3.3 PyTorch 中的模型可视化
      • 3.3.1 打印模型实例 `print(model)`
      • 3.3.2 使用 `torchsummary` 库 (推荐)
      • 3.3.3 使用 TensorBoard (高级,简提)
  • 四、实践:用框架搭建一个简单的多层感知器 (MLP)
    • 4.1 任务定义
      • 4.1.1 问题描述
      • 4.1.2 MLP 结构设计
    • 4.2 Keras 实现
      • 4.2.1 使用 Sequential API
      • 4.2.2 (可选) 使用 Functional API
      • 4.2.3 查看模型结构
    • 4.3 PyTorch 实现
      • 4.3.1 定义 `nn.Module` 子类
      • 4.3.2 查看模型结构
    • 4.4 代码关键点解析
      • 4.4.1 输入形状的指定
      • 4.4.2 激活函数的应用位置
      • 4.4.3 框架间的细微差异
  • 五、常见问题与排查建议 (Q&A)
    • 5.1 输入维度不匹配错误 (Input Dimension Mismatch Error)
      • 5.1.1 原因分析
      • 5.1.2 解决方案
    • 5.2 如何选择 Sequential 还是 Functional API (Keras)?
      • 5.2.1 简单线性模型
      • 5.2.2 复杂模型
    • 5.3 PyTorch 模型中 `forward` 方法的 `x` 是什么?
      • 5.3.1 输入数据
      • 5.3.2 确保数据流正确
    • 5.4 我的模型参数数量为什么这么多/这么少?
      • 5.4.1 参数来源
      • 5.4.2 检查与调整
  • 六、总结


前言

大家好!在上一篇【深度学习-Day 20】我们初步认识了深度学习框架中的核心数据结构——张量(Tensors),并学习了如何在 TensorFlow 和 PyTorch 中进行张量操作及自动求导。掌握了“原材料”的处理,今天我们将更进一步,学习如何使用这些框架来“搭建房子”——构建神经网络模型。

构建模型是深度学习流程中的核心环节。一个设计良好的模型结构是决定项目成败的关键因素之一。本文将带你深入了解两大主流框架 TensorFlow (主要通过其高级 API Keras) 和 PyTorch 中模型构建的机制,包括模型容器(Model Containers)的选择与使用、常用神经网络层的定义,以及如何查看和理解你所搭建的模型结构。最终,我们将通过实践,亲手用框架搭建一个简单的多层感知器(MLP)模型。

无论你是初学者还是希望系统梳理知识的进阶者,本文都将为你提供清晰、易懂的指引,助你轻松迈出用框架构建模型的第一步。

一、理解模型构建的基石:模型容器

想象一下,你要用乐高积木搭建一个复杂的城堡。你需要一个“蓝图”或者一个“底座”来有条不紊地组织这些积木块。在深度学习中,“模型容器”就扮演了类似的角色。它帮助我们将一个个独立的神经网络层(如全连接层、激活层等)有序地组合起来,形成一个完整的模型。

1.1 为什么需要模型容器?

1.1.1 组织与管理网络层

神经网络,尤其是深度神经网络,往往包含许多层。模型容器提供了一种结构化的方式来定义这些层以及它们之间的连接关系。

  • 类比:就像一个项目经理,模型容器负责管理各个“施工队”(网络层),确保它们按照预定的“施工图”(网络结构)协同工作。
  • 简化复杂架构:对于复杂的模型结构(例如,有多个输入、多个输出,或者层之间有共享),模型容器能够清晰地表达这种拓扑关系。

1.1.2 便于参数管理与训练

模型容器不仅仅是层的简单堆叠,它还承担着重要的“后勤”工作。

  • 自动参数跟踪:一旦将层加入到模型容器中,容器会自动收集并管理这些层内部所有可训练的参数(权重和偏置)。
  • 流线化训练过程:在模型训练时,我们通常会直接对整个模型容器进行操作,如传递输入数据、计算损失、执行反向传播和参数更新,而无需手动管理每一层的这些过程。

1.2 TensorFlow (Keras) 中的模型容器

TensorFlow 的高级 API Keras 提供了两种主要的模型构建方式:Sequential API 和 Functional API。

1.2.1 Sequential API:线性堆叠的艺术

keras.Sequential 模型是最简单的一种模型,适用于层的线性堆叠。顾名思义,它允许你像搭积木一样,一层一层地顺序添加网络层。

(1)概念与适用场景
  • 概念:Sequential 模型是一个简单的层栈,其中每一层只有一个输入张量和一个输出张量。
  • 适用场景:非常适合初学者入门,以及构建结构相对简单、没有分支或共享层的模型,例如标准的多层感知器(MLP)或简单的卷积神经网络(CNN)。
(2)如何使用 Sequential API

你可以通过向 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激活函数输出概率分布。

1.2.2 Functional API:构建复杂模型的利器

当模型结构不是简单的线性堆叠时,例如模型有多个输入或多个输出、层之间存在共享、或者网络拓扑结构比较复杂(如有向无环图 DAG),keras.Model (Functional API) 提供了更大的灵活性。

(1)概念与适用场景
  • 概念:Functional API 允许你定义更复杂的模型,层可以像函数一样被调用,并处理张量。你通过连接这些“层函数”来构建模型的计算图。
  • 适用场景:多输入多输出模型、共享层模型(例如 Siamese 网络)、带有残差连接的模型(例如 ResNet)等。
(2)如何使用 Functional API

使用 Functional API 时,你需要:

  1. 定义一个输入节点(keras.Input)。
  2. 像调用函数一样调用层,并将前一层的输出作为当前层的输入。
  3. 使用 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 对象。

1.3 PyTorch 中的模型容器:nn.Module

在 PyTorch 中,所有的神经网络模块,无论是单个的层还是整个复杂的模型,都应该继承自 torch.nn.Module 类。这个类是 PyTorch 构建所有网络的基石。

1.3.1 nn.Module:万物皆模块

(1)核心理念
  • 统一基类nn.Module 是所有网络组件的父类。自定义模型时,你需要创建一个继承自 nn.Module 的类。
  • 层作为属性:通常,你会在模型的 __init__ 方法中将所需的层(如 nn.Linear, nn.ReLU 等)定义为类的属性。
  • 前向传播逻辑:你必须在模型类中实现一个名为 forward 的方法,该方法接收输入数据,并定义数据如何通过网络层进行传播,最终返回模型的输出。
(2)如何定义模型
# 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 依次通过定义的层。

1.4 模型容器选择小结

1.4.1 Keras Sequential vs Functional

  • Sequential API:简单、直接,适用于构建层按顺序线性排列的模型。是快速搭建原型和简单模型的理想选择。
  • Functional API:更为灵活和强大,能够构建具有复杂拓扑结构(如多输入/输出、层共享、分支等)的模型。当 Sequential API 无法满足需求时,Functional API 是你的不二之选。

1.4.2 PyTorch nn.Module 的灵活性

  • PyTorch 的 nn.Module 提供了一种统一且高度灵活的模型构建方式。无论是简单的线性模型还是极其复杂的架构,都通过继承 nn.Module 并实现 __init__forward 方法来完成。这种设计赋予了开发者极大的控制权和自由度。

选择哪种方式取决于你的具体需求和模型的复杂度。对于初学者,从 Keras Sequential API 或 PyTorch nn.Module 的简单实现开始会更容易上手。

二、神经网络的核心组件:常用层

模型是由各种不同功能的“层”(Layers)搭建起来的。下面我们介绍一些在构建神经网络时最常遇到的层。

2.1 全连接层 (Dense Layer / Linear Layer)

全连接层,在 Keras 中称为 Dense 层,在 PyTorch 中称为 Linear 层,是神经网络中最基础也最常见的层之一。

2.1.1 原理与作用

  • 原理:全连接层的每一个神经元都与前一层的所有神经元相连接。它对输入数据执行一个线性变换,即权重矩阵与输入向量相乘,再加上一个偏置向量。
  • 数学表示:如果输入是 x x x,权重是 W W W,偏置是 b b b,则输出 y y y 可以表示为:
    y = W x + b y = Wx + b y=Wx+b
    对于一个批次的输入 X X X,则为 Y = X W T + b Y = XW^T + b Y=XWT+b(Keras/TensorFlow中习惯的表示)或者 Y = X W + b Y = XW + b Y=XW+b(PyTorch中习惯的表示,取决于 W W W 的形状定义)。
  • 作用:全连接层能够学习输入特征之间的全局模式。在 MLP 中,它们是主要的计算单元;在 CNN 的末端,它们通常用于分类。

2.1.2 Keras 中的 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)

2.1.3 PyTorch 中的 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)

2.2 激活层 (Activation Layer)

激活函数是神经网络的“灵魂”之一,它们为模型引入非线性,使得网络能够学习和表示比线性模型复杂得多的函数。

2.2.1 为什么需要激活函数?

  • 引入非线性:如果没有激活函数(或者说激活函数是线性的),那么无论神经网络有多少层,整个网络本质上仍然是一个线性模型,其表达能力将非常有限,无法解决复杂的非线性问题。
  • (我们在 【深度学习-Day 13】激活函数大阅兵 中详细讨论过各种激活函数及其特性,这里不再赘述。)

2.2.2 Keras 中的激活

在 Keras 中,激活函数可以通过两种方式应用:

(1)作为层(如 Dense)的参数

这是最常见的方式,直接在定义层时通过 activation 参数指定。

# Keras: Activation as a parameter
hidden_layer = layers.Dense(64, activation='relu')
output_layer = layers.Dense(10, activation='softmax')
(2)作为独立的层 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 参数,或者你想在多个操作之间插入一个激活函数时,独立的激活层会很有用。

2.2.3 PyTorch 中的激活

在 PyTorch 中,激活函数通常也存在于 torch.nn 模块中(作为有状态的层)或 torch.nn.functional 模块中(作为无状态的函数)。

(1)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 表示对每行的数值进行softmaxdef forward(self, x):x = self.fc1(x)x = self.relu(x) # 调用 ReLU 实例x = self.fc2(x)x = self.softmax(x) # 调用 Softmax 实例return x
(2)torch.nn.functional 中的激活函数

torch.nn.functional (通常导入为 F) 提供了许多与 nn 模块中层对应的函数式版本。这些函数是无状态的,可以直接在 forward 方法中调用,有时可以使代码更简洁。

# PyTorch: Activations from nn.functional
import torch.nn.functional as Fclass 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) 时更为清晰,因为激活函数会作为独立的模块显示出来。

2.3 Dropout 层 (Dropout Layer)

Dropout 是一种非常有效的正则化技术,用于减少神经网络中的过拟合现象。(我们将在 【深度学习-Day 26】正则化技术(二):Dropout 中详细讨论其原理。)

2.3.1 原理与作用 (简述)

  • 原理:在训练过程的每次迭代中,Dropout 层会以一定的概率(dropout rate)随机地“丢弃”(即将其输出设置为零)一部分神经元的输出。
  • 作用:通过这种方式,它强迫网络学习更加鲁棒的特征,因为网络不能依赖于任何一个特定的神经元。它类似于训练多个不同的网络然后取平均,但开销小得多。

2.3.2 Keras 中的 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 通常放在激活函数的后面。

2.3.3 PyTorch 中的 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() 模式)。

2.4 其他常用层 (简介)

除了上述核心层,还有许多其他类型的层,我们将在后续专门的章节中详细介绍它们。这里先做个简要的提及,让你有个初步印象:

2.4.1 卷积层 (Convolutional Layers)

  • 用途:主要用于处理网格状数据,如图像(2D卷积)和序列(1D卷积)。它们通过卷积核(滤波器)在输入数据上滑动来提取局部特征。
  • Keras: layers.Conv1D, layers.Conv2D, layers.Conv3D
  • PyTorch: nn.Conv1d, nn.Conv2d, nn.Conv3d
  • 我们将在 【阶段三:卷积神经网络 (CNN)】 中深入学习。

2.4.2 池化层 (Pooling Layers)

  • 用途:通常用在卷积层之后,用于降低特征图的空间维度(下采样),从而减少参数数量、计算量,并有助于控制过拟合,同时提供一定程度的平移不变性。
  • 类型:最常见的是最大池化(Max Pooling)和平均池化(Average Pooling)。
  • Keras: layers.MaxPooling1D, layers.MaxPooling2D, layers.AveragePooling2D, etc.
  • PyTorch: nn.MaxPool1d, nn.MaxPool2d, nn.AvgPool2d, etc.
  • 同样会在 CNN 章节详细介绍。

2.4.3 展平层 (Flatten Layer)

  • 用途:将多维的输入张量“压平”成一个一维张量。例如,在 CNN 中,经过一系列卷积和池化层后得到的特征图通常是多维的,在送入全连接层之前,需要先通过 Flatten 层将其转换为一维向量。
  • Keras: layers.Flatten()
  • PyTorch: 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

2.4.4 批量归一化层 (Batch Normalization Layer)

  • 用途:用于加速深度网络训练,提高模型的稳定性和性能。它通过对每个小批量(mini-batch)数据的激活值进行归一化处理,并学习相应的缩放和平移参数。
  • Keras: layers.BatchNormalization()
  • PyTorch: nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d
  • 我们将在 【深度学习-Day 27】其他正则化与模型改进技巧 中更详细地介绍它。

三、洞悉模型结构:如何查看你的网络

当你辛辛苦苦搭建好一个模型后,如何确认它的结构是否如你所愿?参数数量是否合理?这时,查看模型结构的功能就显得尤为重要。

3.1 为什么需要查看模型结构?

3.1.1 验证模型定义是否正确

  • 确保连接无误:特别是对于使用 Functional API 或复杂 nn.Module 构建的模型,可视化或摘要可以帮助你确认各层是否按照预期连接,数据流是否正确。
  • 检查层类型和参数:确认每一层的类型、神经元数量、激活函数等设置是否正确。

3.1.2 理解参数数量与计算量

  • 参数统计:模型摘要通常会列出每一层的参数数量以及总参数数量。这有助于评估模型的复杂度、潜在的内存消耗和计算需求。
  • 调试与优化:如果模型参数过多,可能容易过拟合或训练缓慢;参数过少,则可能表达能力不足。查看参数量是模型优化的第一步。

3.1.3 便于沟通与文档撰写

  • 清晰展示:一个清晰的模型结构图或摘要是与他人交流模型设计、撰写报告或论文时的重要辅助材料。

3.2 Keras 中的模型可视化

3.2.1 model.summary()

这是最常用也最直接的方法,它会在控制台打印出模型的文本摘要,包括:

  • 每一层的名称 (如果指定) 和类型。
  • 每一层的输出形状 (Output Shape)。
  • 每一层的参数数量 (Param #)。
  • 总参数数量、可训练参数数量和不可训练参数数量。
# Keras model.summary()
# (已在之前的 Keras 模型示例中展示)
# model_sequential_list.summary()
# model_functional.summary()

输出示例:

Model: "MySequentialModel_List"
_________________________________________________________________Layer (type)                Output Shape              Param #
=================================================================dense (Dense)               (None, 128)               100480dropout (Dropout)           (None, 128)               0dense_1 (Dense)             (None, 64)                8256dense_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)。

3.2.2 keras.utils.plot_model() (可选,依赖 pydotgraphviz)

如果你希望得到一个更直观的图形化模型结构,可以使用 keras.utils.plot_model 函数。它可以将模型结构保存为一张图片。

  • 依赖:这个功能需要安装 pydotgraphviz。你可以通过 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-rightexpand_nested=False, # 是否展开嵌套模型dpi=96 # 图像的DPI)print("模型结构图已保存为 functional_model_plot.png")
except ImportError:print("无法生成模型结构图,请确保已安装 pydot 和 graphviz。")

这张图会清晰地展示层与层之间的连接关系,对于理解复杂模型非常有帮助。

3.3 PyTorch 中的模型可视化

3.3.1 打印模型实例 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)
)

这种方式简洁明了,对于理解模型层次结构很有帮助。

3.3.2 使用 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,480ReLU-2                  [-1, 128]               0Dropout-3                  [-1, 128]               0Linear-4                   [-1, 64]           8,256ReLU-5                   [-1, 64]               0Linear-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 模型非常有价值。

3.3.3 使用 TensorBoard (高级,简提)

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 的使用。

四、实践:用框架搭建一个简单的多层感知器 (MLP)

理论学习之后,最好的巩固方式就是动手实践。现在,我们将使用 Keras 和 PyTorch 分别搭建一个结构相同的简单多层感知器 (MLP) 模型。

4.1 任务定义

4.1.1 问题描述

我们假设一个通用的分类任务。例如,输入是 784 784 784 维的特征向量(比如展平的 28 × 28 28 \times 28 28×28 像素的灰度图像,如 MNIST 数据集),我们的目标是将其分为 10 10 10 个不同的类别。

4.1.2 MLP 结构设计

我们将设计一个具有以下结构的 MLP:

  1. 输入层:接收 784 784 784 维的输入。
  2. 第一个隐藏层 128 128 128 个神经元,使用 ReLU 激活函数。
  3. Dropout 层:丢弃率为 0.2 0.2 0.2,防止过拟合。
  4. 第二个隐藏层 64 64 64 个神经元,使用 ReLU 激活函数。
  5. 输出层 10 10 10 个神经元(对应 10 10 10 个类别),使用 Softmax 激活函数输出每个类别的概率。

我们可以用 Mermaid 语法绘制这个结构图:

graph TDA[输入层 (784 特征)] --> B(全连接层 1: 128 单元, ReLU)B --> DRP1(Dropout: rate=0.2)DRP1 --> C(全连接层 2: 64 单元, ReLU)C --> D(输出层: 10 单元, Softmax)

4.2 Keras 实现

我们将主要使用 Sequential API,因为它非常适合这种线性堆叠的结构。

4.2.1 使用 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 modelkeras_mlp_model = create_keras_mlp()

4.2.2 (可选) 使用 Functional API

为了对比,我们也可以用 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()

4.2.3 查看模型结构

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 已安装。")

4.3 PyTorch 实现

我们将定义一个继承自 nn.Module 的类。

4.3.1 定义 nn.Module 子类

import torch
import torch.nn as nn
import torch.nn.functional as Fclass 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 xpytorch_mlp_model = PyTorchMLP()

4.3.2 查看模型结构

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}")

4.4 代码关键点解析

4.4.1 输入形状的指定

  • Keras:
    • 在 Sequential 模型中,通常在第一层通过 input_shape 参数指定(不含批次大小)。
    • 或者,更推荐的方式是使用 keras.Input 层作为第一层,明确定义输入。
    • 在 Functional API 中,必须由 keras.Input 层开始。
  • PyTorch:
    • nn.Linear 等层在其 __init__ 方法中通过 in_features 参数接收输入特征数。
    • 模型在实例化时并不立即知道完整的输入张量形状(尤其是批次大小),这是在 forward 方法第一次接收到实际数据时确定的。torchsummary 需要你提供一个 input_size 来帮助它推断。

4.4.2 激活函数的应用位置

  • Keras: 可以作为 Dense 等层的 activation 参数,也可以作为独立的 layers.Activation 层。
  • PyTorch: 可以在 __init__ 中实例化为 nn.ReLU, nn.Softmax 等模块,然后在 forward 中调用;或者直接在 forward 中使用 torch.nn.functional (如 F.relu, F.softmax) 中的函数。

4.4.3 框架间的细微差异

  • Softmax 和损失函数
    • 在 Keras 中,用于分类的输出层通常会显式地使用 activation='softmax'
    • 在 PyTorch 中,当使用 nn.CrossEntropyLoss 作为损失函数时,推荐输出层不加 Softmax,因为 nn.CrossEntropyLoss 内部已经包含了 LogSoftmaxNLLLoss,这样做可以提高数值稳定性。如果在推理阶段需要概率输出,可以再显式调用 F.softmax
  • Dropout 的行为:两个框架都会在 model.eval() (PyTorch) 或推理时 (Keras) 自动关闭 Dropout 并调整权重。

通过这些实践,你应该对如何在两大主流框架中定义和组织神经网络模型有了更清晰的认识。记住,模型构建只是第一步,接下来我们还需要学习如何加载数据、定义损失函数和优化器,并最终训练和评估我们的模型,这些内容将在后续文章中展开(【深度学习-Day 22】和【深度学习-Day 23】)。

五、常见问题与排查建议 (Q&A)

在模型构建过程中,新手常常会遇到一些问题。这里列举几个常见的问题及其排查思路。

5.1 输入维度不匹配错误 (Input Dimension Mismatch Error)

这是最常见的问题之一,通常表现为 “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) 的错误。

5.1.1 原因分析

  • 层间连接错误:当前层的期望输入维度与前一层实际输出的维度不匹配。
  • input_shapein_features 设置不当:模型的第一层或特定层的输入维度参数设置错误。
  • 数据预处理问题:实际喂给模型的数据形状与模型期望的输入形状不符(这更多是训练时的问题,但设计时也需考虑)。

5.1.2 解决方案

  • 仔细检查 input_shape (Keras) / in_features (PyTorch):确保每一层的输入输出维度能够正确衔接。
  • 使用 model.summary() (Keras) 或 torchsummary.summary() (PyTorch):打印出每一层的输出形状,逐层检查维度变化是否符合预期。
  • 对于 CNN 后的 Flatten 层:特别注意 Flatten 层之前的卷积/池化层的输出特征图尺寸,确保 Flatten 后的向量维度能被后续全连接层正确接收。

5.2 如何选择 Sequential 还是 Functional API (Keras)?

5.2.1 简单线性模型

  • 如果你的模型是一条直线的层序列,没有分支、共享层或多个输入/输出,Sequential API 更简洁、更易读。

5.2.2 复杂模型

  • 如果你的模型有以下任一特征:
    • 多输入或多输出。
    • 层之间有共享权重。
    • 非线性的拓扑结构(例如,残差连接)。
      则必须使用 Functional API,它提供了构建这类复杂模型的灵活性。

5.3 PyTorch 模型中 forward 方法的 x 是什么?

5.3.1 输入数据

  • def forward(self, x): 中,参数 x 代表传递给模型的一批输入数据(a batch of input data)。
  • 它的形状通常是 (batch_size, ...),其中 ... 代表输入特征的维度。例如,对于 MLP 处理展平的 MNIST 图像,x 的形状可能是 (batch_size, 784)

5.3.2 确保数据流正确

  • forward 方法内部,你需要确保 x 按照你设计的网络结构,依次通过定义的各个层进行转换。每一行代码 x = self.some_layer(x)x = F.some_function(x) 都在更新 x 的状态,将其从上一层的输出转变为当前操作的输出,最终得到整个模型的输出。

5.4 我的模型参数数量为什么这么多/这么少?

5.4.1 参数来源

  • 全连接层 (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
  • 其他层:如激活层、池化层、Dropout 层通常没有可训练参数或参数很少。批量归一化层有少量可训练参数(gamma 和 beta)。

5.4.2 检查与调整

  • 使用 model.summary()torchsummary 查看各层参数。
  • 如果参数过多,考虑:
    • 减少全连接层的神经元数量。
    • 在 CNN 中使用更小的卷积核、更少的输出通道或增加池化层。
    • 采用参数共享技术或更高效的模型结构(如 MobileNet, SqueezeNet 中的深度可分离卷积等,这些是进阶主题)。
  • 如果参数过少,模型可能表达能力不足(欠拟合),可以考虑适当增加层数或神经元数量。

六、总结

恭喜你,完成了本次关于使用深度学习框架构建模型的学习!通过本文,我们深入探讨了模型构建的核心概念和实践技巧。现在,让我们回顾一下关键知识点:

  1. 模型容器的重要性

    • Keras 提供了 Sequential API 用于快速搭建线性堆叠模型,以及更灵活的 Functional API 用于构建复杂网络拓扑。
    • PyTorch 的核心是 nn.Module,所有模型和层都继承自它,通过在 __init__ 中定义层并在 forward 方法中指定数据流向来构建模型,提供了极高的灵活性。
    • 模型容器负责组织网络层、管理参数,并简化训练流程。
  2. 常用层的功能与实现

    • 全连接层 (Dense/Linear): 实现线性变换,学习全局特征,其参数为权重 W W W 和偏置 b b b
    • 激活层/函数 (Activation/ReLU, Softmax, etc.): 为模型引入非线性,使其能学习复杂模式。可以作为层参数或独立模块/函数使用。
    • Dropout 层: 一种正则化技术,在训练时随机丢弃神经元输出,防止过拟合。
    • 其他层简介: 初步了解了卷积层、池化层、展平层和批量归一化层等在特定场景下的作用。
  3. 模型结构的可视化

    • Keras: model.summary() 提供文本摘要;keras.utils.plot_model() 可生成模型结构图(需 pydotgraphviz)。
    • PyTorch: print(model) 显示模块层级;推荐使用 torchsummary 库的 summary() 函数获取详细的类 Keras 摘要;TensorBoard 可用于更高级的图可视化。
    • 查看模型结构有助于验证定义、理解参数量并辅助调试。
  4. 实践构建MLP

    • 我们通过一个具体的多层感知器(MLP)示例,分别用 Keras (Sequential API) 和 PyTorch (nn.Module) 实现了相同的网络结构。
    • 比较了两者在输入指定、激活函数应用和输出层处理(如 Softmax 与损失函数的配合)等方面的异同。
  5. 框架选择与使用哲学

    • Keras 以用户友好和快速原型开发著称。
    • PyTorch 以其动态计算图和 Pythonic 的编程风格受到研究者和需要高度自定义的开发者的青睐。
    • 理解不同框架构建模型的核心思想,有助于你根据项目需求和个人偏好做出选择。

相关文章:

  • 告别printf!嵌入式系统高效日志记录方案
  • 第四十天打卡
  • Java 2D 图形类总结与分类
  • 放弃 tsc+nodemon 使用 tsx 构建Node 环境下 TypeScript + ESM 开发环境搭建指南
  • QT入门学习(二)---继承关系、访问控制和变量定义
  • 【环境搭建】Java、Python、Nodejs等开发环境搭建
  • Java垃圾回收机制详解:从原理到实践
  • ubuntu24.04 查看时区并设置Asia/Shanghai时区
  • 【Python序列化】TypeError: Object of type xxx is not JSON serializable问题的解决方案
  • Golang学习之旅
  • 单调栈(打卡)
  • 37、响应处理-【源码分析】-ReturnValueHandler原理
  • 使用API网关Kong配置反向代理和负载均衡
  • Ubuntu20.04 LTS 升级Ubuntu22.04LTS 依赖错误 系统崩溃重装 Ubuntu22.04 LTS
  • CMake指令:string(字符串操作)
  • 渊龙靶场-sql注入(数字型注入)
  • Redis部署架构详解:原理、场景与最佳实践
  • docker使用sh脚本创建容器,保持容器正常运行,异常关闭后马上重启
  • C++哈希表:冲突解决与高效查找
  • 总结:线程安全问题的原因和解决方案
  • 泰州模板自助建站/网站关键词seo排名
  • 做网站一定需要服务器吗/市场营销互联网营销
  • 网站备案掉了/百度旗下的所有产品
  • 设计师图片素材网站有哪些/百度搜索风云榜官网
  • 四川省住房建设厅网站进不去/seo智能优化软件
  • 做平面设计兼职的网站有哪些/微信crm系统软件