【深度学习-Day 14】从零搭建你的第一个神经网络:多层感知器(MLP)详解
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)详解
文章目录
- Langchain系列文章目录
- Python系列文章目录
- PyTorch系列文章目录
- 机器学习系列文章目录
- 深度学习系列文章目录
- Java系列文章目录
- JavaScript系列文章目录
- 深度学习系列文章目录
- 前言
- 一、告别单层:为什么需要多层感知器?
- 1.1 单层感知器的局限性回顾
- 1.1.1 线性可分性问题 (XOR回顾)
- 1.1.2 表达能力的不足
- 1.2 多层感知器 (MLP) 的引入
- 1.2.1 克服线性限制
- 1.2.2 更强大的特征学习能力
- 二、多层感知器 (MLP) 结构详解
- 2.1 前馈神经网络 (Feedforward Neural Network) 概述
- 2.1.1 信息的单向流动
- 2.1.2 与循环神经网络 (RNN) 的区别 (简要提及)
- 2.2 MLP 的核心组成部分
- 2.2.1 输入层 (Input Layer)
- (1) 作用:接收原始数据
- (2) 神经元数量:通常等于特征数量
- 2.2.2 隐藏层 (Hidden Layer)
- (1) 作用:特征提取与转换
- (2) 神经元数量:超参数,影响模型复杂度
- (3) 激活函数的应用 (回顾上一章)
- 2.2.3 输出层 (Output Layer)
- (1) 作用:输出预测结果
- (2) 神经元数量:取决于任务类型 (回归 vs 分类)
- (3) 激活函数的选择 (Sigmoid, Softmax, Linear)
- 2.3 全连接层 (Dense Layer)
- 2.3.1 定义与特性
- (1) 每个神经元与前一层所有神经元连接
- (2) 权重矩阵与偏置向量
- 2.3.2 计算过程
- 2.4 网络的深度与宽度
- 2.4.1 深度 (Depth) 的概念
- (1) 定义:隐藏层的数量
- (2) 影响:学习更复杂的特征层次
- 2.4.2 宽度 (Width) 的概念
- (1) 定义:隐藏层中神经元的数量
- (2) 影响:学习特征的丰富程度
- 2.4.3 深度与宽度的权衡
- 三、构建第一个MLP:概念到实践
- 3.1 数据准备 (假设)
- 3.1.1 示例数据集描述 (例如,一个简单的二分类问题)
- 3.1.2 输入特征和输出标签
- 3.2 MLP模型设计
- 3.2.1 确定层数和神经元数量 (示例)
- 3.2.2 选择激活函数
- 3.3 前向传播过程详解
- 3.3.1 从输入层到第一个隐藏层
- 3.3.2 从隐藏层到输出层
- 3.4 简单代码示例 (Python + NumPy)
- 四、MLP 的优势与局限性
- 4.1 MLP 的主要优势
- 4.1.1 通用逼近定理 (简单提及)
- 4.1.2 处理非线性问题的能力
- 4.2 MLP 的一些局限性
- 4.2.1 参数数量可能较多
- 4.2.2 对于特定类型数据 (如图像、序列) 可能不是最优选择 (引出CNN, RNN)
- 4.2.3 容易过拟合 (后续文章会讲如何解决)
- 五、常见问题与进一步学习
- 5.1 如何确定隐藏层的数量和神经元个数?
- 5.1.1 经验法则
- 5.1.2 交叉验证与实验
- 5.2 为什么全连接层也叫稠密层?
- 5.3 MLP 和深度学习的关系?
- 六、总结
前言
在前面的文章中,我们已经了解了神经网络的基本单元——感知器(Day 12),以及赋予神经网络非线性能力的功臣——激活函数(Day 13)。然而,单个感知器由于其线性特性,在处理复杂问题时(例如经典的XOR问题)会显得力不从心。为了克服这一局限性,科学家们提出了将多个感知器以特定方式组织起来的想法,从而诞生了功能更为强大的多层感知器(Multilayer Perceptron, MLP)。MLP是深度学习领域最基础也是最重要的模型之一,理解它将为我们后续学习更复杂的网络结构(如CNN、RNN)打下坚实的基础。本文的目标就是带领大家深入理解多层感知器的结构、原理,并为后续的实践做好准备。
一、告别单层:为什么需要多层感知器?
在正式进入MLP的世界之前,让我们先回顾一下为什么单层感知器不够用,以及MLP是如何应运而生的。
1.1 单层感知器的局限性回顾
单层感知器,顾名思义,只包含一个输入层和一个输出层(或者说,只有一个计算层)。
1.1.1 线性可分性问题 (XOR回顾)
我们在讨论感知器时提到过,它本质上是一个线性分类器。这意味着它只能完美解决线性可分的问题。对于像XOR这样的线性不可分问题,单层感知器无法找到一条直线(或高维超平面)将不同类别的数据点正确分开。
- XOR问题示例:
- 输入 (0,0) -> 输出 0
- 输入 (0,1) -> 输出 1
- 输入 (1,0) -> 输出 1
- 输入 (1,1) -> 输出 0
如果我们将这四个点绘制在二维平面上,会发现无法用一条直线将 (0,1), (1,0) 与 (0,0), (1,1) 分开。
1.1.2 表达能力的不足
由于线性限制,单层感知器的模型表达能力非常有限。它无法学习到数据中复杂的非线性关系,这在许多现实世界的应用场景中是远远不够的。
1.2 多层感知器 (MLP) 的引入
为了突破单层感知器的这些限制,研究者们引入了隐藏层(Hidden Layer),构建了多层感知器。
1.2.1 克服线性限制
通过在输入层和输出层之间加入一个或多个隐藏层,并配合非线性激活函数,MLP能够学习和表示非线性决策边界。简单来说,隐藏层可以将原始输入特征映射到一个新的特征空间,在这个新的空间中,数据可能变得线性可分。
例如,对于XOR问题,一个包含单个隐藏层的MLP就可以完美解决。
1.2.2 更强大的特征学习能力
隐藏层的神经元可以被看作是特征提取器。每一层隐藏层都可以从前一层输出的特征中学习到更抽象、更复杂的特征表示。层数越多(即网络越深),模型学习到的特征层次就越丰富,从而具备更强大的模式识别和数据拟合能力。
二、多层感知器 (MLP) 结构详解
现在,让我们深入剖析MLP的“五脏六腑”,看看它究竟是如何构成的。
2.1 前馈神经网络 (Feedforward Neural Network) 概述
多层感知器是一种典型的前馈神经网络(Feedforward Neural Network, FNN)。
2.1.1 信息的单向流动
“前馈”这个词描述了信息在网络中传播的方向。在MLP中,数据从输入层开始,依次流经一个或多个隐藏层,最终到达输出层。这个过程中,信息是单向传播的,不会形成环路。也就是说,当前层的输出仅依赖于前一层的输出,而不会反过来影响前一层的计算(在训练过程中的反向传播算法是另一回事,这里指的是数据流动的方向)。
2.1.2 与循环神经网络 (RNN) 的区别 (简要提及)
与前馈神经网络相对的是循环神经网络(Recurrent Neural Network, RNN)。RNN中存在环路,允许信息在网络内部循环,使得网络具有记忆功能,特别适合处理序列数据(如文本、时间序列)。我们将在后续的文章中详细介绍RNN。目前,我们只需记住MLP是信息单向流动的前馈网络。
2.2 MLP 的核心组成部分
一个典型的MLP由以下三个核心部分组成:
2.2.1 输入层 (Input Layer)
(1) 作用:接收原始数据
输入层是网络的入口,负责接收外部输入的数据。这些数据通常是原始的特征向量。例如,在图像分类任务中,输入可以是展平的像素值;在预测房价的任务中,输入可以是房屋的面积、房间数量等特征。
(2) 神经元数量:通常等于特征数量
输入层神经元的数量(也称为节点数或单元数)通常由输入数据的维度决定。如果你的数据有 N N N 个特征,那么输入层一般就会有 N N N 个神经元,每个神经元对应一个特征。输入层本身通常不进行计算,只是将数据传递给第一个隐藏层。
2.2.2 隐藏层 (Hidden Layer)
(1) 作用:特征提取与转换
隐藏层是MLP的“大脑”,位于输入层和输出层之间。它们对输入数据进行一系列非线性变换,提取有用的特征,并将这些特征传递给下一层。正是由于隐藏层的存在,MLP才能够学习复杂的模式。一个MLP可以有一个或多个隐藏层。
(2) 神经元数量:超参数,影响模型复杂度
每个隐藏层中神经元的数量是一个重要的超参数(Hyperparameter),需要我们根据具体任务和数据进行调整。神经元数量过少,模型可能无法学习到足够的复杂性(欠拟合);神经元数量过多,模型可能过度拟合训练数据,泛化能力下降,并且会增加计算成本。如何选择合适的隐藏层神经元数量,我们会在后续文章中讨论。
(3) 激活函数的应用 (回顾上一章)
隐藏层的每个神经元通常都会应用一个非线性激活函数(如ReLU, Sigmoid, Tanh等)。正如我们在Day 13所学,激活函数引入非线性,使得网络能够学习和表示非线性关系,这是MLP能够解决复杂问题的关键。如果没有非线性激活函数,即使有多层网络,其整体效果也等同于一个单层线性网络。
2.2.3 输出层 (Output Layer)
(1) 作用:输出预测结果
输出层是网络的最后一层,负责输出最终的预测结果。
(2) 神经元数量:取决于任务类型 (回归 vs 分类)
输出层神经元的数量取决于具体的任务类型:
- 回归任务(Regression):通常只有一个输出神经元,输出一个连续值(例如预测房价)。
- 二分类任务(Binary Classification):通常也只有一个输出神经元,输出一个介于0和1之间的概率值(例如判断邮件是否为垃圾邮件)。
- 多分类任务(Multi-class Classification):输出层神经元的数量等于类别的数量,每个神经元输出对应类别的概率(例如手写数字识别,0-9共10个类别,则输出层有10个神经元)。
(3) 激活函数的选择 (Sigmoid, Softmax, Linear)
输出层激活函数的选择也与任务类型密切相关:
- 回归任务:通常不使用激活函数(或者说使用线性激活函数,即 f ( x ) = x f(x)=x f(x)=x),因为输出需要是任意连续值。
- 二分类任务:通常使用Sigmoid激活函数,将输出压缩到 ( 0 , 1 ) (0, 1) (0,1) 区间,表示属于正类的概率。
- 多分类任务:通常使用Softmax激活函数,它将所有输出神经元的值转换为一个概率分布,确保所有输出值之和为1,每个值代表属于对应类别的概率。
2.3 全连接层 (Dense Layer)
在MLP中,相邻两层之间的神经元通常是全连接(Fully Connected)的,这种连接方式构成的层也称为全连接层或稠密层(Dense Layer)。
2.3.1 定义与特性
(1) 每个神经元与前一层所有神经元连接
“全连接”意味着前一层中的每一个神经元都与当前层中的每一个神经元有连接。每个连接都有一个对应的权重(Weight)。
(2) 权重矩阵与偏置向量
对于一个全连接层,如果其前一层有 N N N 个神经元,当前层有 M M M 个神经元,那么连接这两个层就需要 N t i m e s M N \\times M NtimesM 个权重。这些权重可以组织成一个 M t i m e s N M \\times N MtimesN 的权重矩阵 W W W。此外,当前层的每个神经元通常还会有一个偏置(Bias) b b b,所以会有 M M M 个偏置项,可以组织成一个大小为 M M M 的偏置向量 b b b。
2.3.2 计算过程
对于全连接层中的任意一个神经元,其计算过程可以分为两步:
(1) 线性变换: Z = W X + b Z = WX + b Z=WX+b
首先,将前一层所有神经元的输出(表示为向量 X X X)与对应的权重进行加权求和,然后加上偏置项。如果用矩阵表示,对于整个层来说,其线性输出 Z Z Z 可以通过以下公式计算:
Z = W ⋅ X + b Z = W \cdot X + b Z=W⋅X+b
其中:
- X X X 是前一层的输出向量(维度为 N t i m e s 1 N \\times 1 Ntimes1)。
- W W W 是当前层的权重矩阵(维度为 M t i m e s N M \\times N MtimesN)。
- b b b 是当前层的偏置向量(维度为 M t i m e s 1 M \\times 1 Mtimes1)。
- Z Z Z 是当前层神经元激活前的线性输出向量(维度为 M t i m e s 1 M \\times 1 Mtimes1)。
(2) 激活函数: A = g ( Z ) A = g(Z) A=g(Z)
然后,将线性输出 Z Z Z 通过一个激活函数 g ( c d o t ) g(\\cdot) g(cdot) 得到当前层神经元的最终输出 A A A:
A = g ( Z ) A = g(Z) A=g(Z)
这个输出 A A A 将作为下一层的输入。
2.4 网络的深度与宽度
在设计MLP时,有两个重要的结构性概念:深度和宽度。
2.4.1 深度 (Depth) 的概念
(1) 定义:隐藏层的数量
网络的深度通常指的是网络中隐藏层的数量(有时也包括输出层,但更常见的是指隐藏层的层数)。一个“深”的神经网络意味着它有很多隐藏层。
(2) 影响:学习更复杂的特征层次
增加网络的深度可以让模型学习到数据中更复杂、更抽象的特征层次。浅层网络可能只能学习到一些简单的局部特征,而深层网络则能将这些局部特征组合起来,形成更全局、更高级的特征表示。例如,在图像识别中,浅层可能识别边缘和角点,中间层可能识别物体的部件,深层则可能识别整个物体。
2.4.2 宽度 (Width) 的概念
(1) 定义:隐藏层中神经元的数量
网络的宽度指的是某个隐藏层中神经元的数量。不同的隐藏层可以有不同的宽度。
(2) 影响:学习特征的丰富程度
增加隐藏层的宽度可以让模型在该层学习到更丰富的特征信息。每一层神经元越多,它能捕捉到的模式就可能越多。
2.4.3 深度与宽度的权衡
网络深度和宽度的选择对模型性能至关重要,但并没有固定的规则。
- 更深的网络:通常能学习更复杂的函数,但可能更难训练(例如梯度消失/爆炸问题,尽管有很多技术可以缓解),并且需要更多数据。
- 更宽的网络:能够学习更多的特征,但参数量也会相应增加,可能导致过拟合和计算资源的消耗。
通常需要通过实验和经验来找到适合特定任务的深度和宽度组合。一般来说,先尝试增加深度,再考虑增加宽度,通常比构建一个非常宽但很浅的网络效果更好。
三、构建第一个MLP:概念到实践
理解了MLP的结构后,让我们通过一个简单的例子来梳理如何构建一个MLP并理解其前向传播的过程。
3.1 数据准备 (假设)
假设我们有一个简单的二分类任务,目标是根据两个输入特征 x _ 1 x\_1 x_1 和 x _ 2 x\_2 x_2 来预测样本属于类别0还是类别1。
3.1.1 示例数据集描述 (例如,一个简单的二分类问题)
我们的数据点如下:
x _ 1 x\_1 x_1 | x _ 2 x\_2 x_2 | 类别 (y) |
---|---|---|
0.1 | 0.2 | 0 |
0.8 | 0.9 | 1 |
0.2 | 0.1 | 0 |
0.9 | 0.7 | 1 |
… | … | … |
3.1.2 输入特征和输出标签
- 输入特征 (Input Features): X = [ x _ 1 , x _ 2 ] X = [x\_1, x\_2] X=[x_1,x_2]。这是一个二维向量。
- 输出标签 (Output Label): y i n 0 , 1 y \\in {0, 1} yin0,1。
3.2 MLP模型设计
我们来设计一个简单的MLP:
- 输入层: 2个神经元 (对应 x _ 1 , x _ 2 x\_1, x\_2 x_1,x_2)
- 隐藏层: 1个隐藏层,包含3个神经元,激活函数使用ReLU。
- 输出层: 1个神经元 (因为是二分类),激活函数使用Sigmoid。
3.2.1 确定层数和神经元数量 (示例)
如上所述,我们选择了1个隐藏层,包含3个神经元。这是一个简单的起点。
3.2.2 选择激活函数
- 隐藏层:ReLU ( g _ h ( z ) = m a x ( 0 , z ) g\_h(z) = \\max(0, z) g_h(z)=max(0,z))
- 输出层:Sigmoid ( g _ o ( z ) = f r a c 1 1 + e − z g\_o(z) = \\frac{1}{1 + e^{-z}} g_o(z)=frac11+e−z)
3.3 前向传播过程详解
前向传播(Forward Propagation)是指数据从输入层经过隐藏层,最终到达输出层,产生预测值的过程。
3.3.1 从输入层到第一个隐藏层
假设输入样本为 X = b e g i n b m a t r i x x _ 1 x _ 2 e n d b m a t r i x X = \\begin{bmatrix} x\_1 \\ x\_2 \\end{bmatrix} X=beginbmatrixx_1x_2endbmatrix。
隐藏层的计算如下:
-
线性变换:
设隐藏层的权重矩阵为 W _ h W\_h W_h (维度 3 t i m e s 2 3 \\times 2 3times2),偏置向量为 b _ h b\_h b_h (维度 3 t i m e s 1 3 \\times 1 3times1)。
Z h = W h ⋅ X + b h Z_h = W_h \cdot X + b_h Zh=Wh⋅X+bh
[ z h 1 z h 2 z h 3 ] = [ w 11 w 12 w 21 w 22 w 31 w 32 ] [ x 1 x 2 ] + [ b h 1 b h 2 b h 3 ] \begin{bmatrix} z_{h1} \\ z_{h2} \\ z_{h3} \end{bmatrix} = \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \\ w_{31} & w_{32} \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} + \begin{bmatrix} b_{h1} \\ b_{h2} \\ b_{h3} \end{bmatrix} zh1zh2zh3 = w11w21w31w12w22w32 [x1x2]+ bh1bh2bh3
其中 z _ h 1 , z _ h 2 , z _ h 3 z\_{h1}, z\_{h2}, z\_{h3} z_h1,z_h2,z_h3 是隐藏层三个神经元激活前的线性输出。 -
应用激活函数 (ReLU):
隐藏层的输出 A _ h A\_h A_h (维度 3 t i m e s 1 3 \\times 1 3times1) 为:
A h = ReLU ( Z h ) = [ max ( 0 , z h 1 ) max ( 0 , z h 2 ) max ( 0 , z h 3 ) ] = [ a h 1 a h 2 a h 3 ] A_h = \text{ReLU}(Z_h) = \begin{bmatrix} \max(0, z_{h1}) \\ \max(0, z_{h2}) \\ \max(0, z_{h3}) \end{bmatrix} = \begin{bmatrix} a_{h1} \\ a_{h2} \\ a_{h3} \end{bmatrix} Ah=ReLU(Zh)= max(0,zh1)max(0,zh2)max(0,zh3) = ah1ah2ah3
3.3.2 从隐藏层到输出层
隐藏层的输出 A _ h A\_h A_h 作为输出层的输入。
输出层的计算如下:
-
线性变换:
设输出层的权重矩阵为 W _ o W\_o W_o (维度 1 t i m e s 3 1 \\times 3 1times3),偏置向量为 b _ o b\_o b_o (维度 1 t i m e s 1 1 \\times 1 1times1)。
Z o = W o ⋅ A h + b o Z_o = W_o \cdot A_h + b_o Zo=Wo⋅Ah+bo
z o = [ w o 1 w o 2 w o 3 ] [ a h 1 a h 2 a h 3 ] + b o z_o = \begin{bmatrix} w_{o1} & w_{o2} & w_{o3} \end{bmatrix} \begin{bmatrix} a_{h1} \\ a_{h2} \\ a_{h3} \end{bmatrix} + b_o zo=[wo1wo2wo3] ah1ah2ah3 +bo
其中 z _ o z\_o z_o 是输出层神经元激活前的线性输出。 -
应用激活函数 (Sigmoid):
输出层的最终预测值 h a t y \\hat{y} haty (维度 1 t i m e s 1 1 \\times 1 1times1) 为:
y ^ = Sigmoid ( Z o ) = 1 1 + e − z o \hat{y} = \text{Sigmoid}(Z_o) = \frac{1}{1 + e^{-z_o}} y^=Sigmoid(Zo)=1+e−zo1
这个 h a t y \\hat{y} haty 就是模型对于输入 X X X 预测其属于类别1的概率。
3.4 简单代码示例 (Python + NumPy)
下面是一个使用Python和NumPy实现上述简单MLP前向传播的示例。请注意,这只是为了演示概念,实际的训练过程(反向传播和参数更新)将在后续文章中介绍。
import numpy as npdef sigmoid(x):"""Sigmoid激活函数"""return 1 / (1 + np.exp(-x))def relu(x):"""ReLU激活函数"""return np.maximum(0, x)class SimpleMLP:def __init__(self, input_size, hidden_size, output_size):# 初始化权重和偏置 (通常使用随机值)# 为了简化,这里使用固定的示例值或随机初始化# W_h: hidden_size x input_size# b_h: hidden_size x 1# W_o: output_size x hidden_size# b_o: output_size x 1# np.random.seed(42) # 为了可复现性self.W_h = np.random.randn(hidden_size, input_size) * 0.1 self.b_h = np.zeros((hidden_size, 1))self.W_o = np.random.randn(output_size, hidden_size) * 0.1self.b_o = np.zeros((output_size, 1))print("Initialized W_h:\n", self.W_h)print("Initialized b_h:\n", self.b_h)print("Initialized W_o:\n", self.W_o)print("Initialized b_o:\n", self.b_o)def forward(self, X):"""MLP的前向传播X: 输入数据,形状为 (input_size, 1) 或 (input_size, num_samples)"""# 确保 X 是一个列向量 (input_size, 1) 如果只是单个样本if X.ndim == 1:X = X.reshape(-1, 1)# 输入层到隐藏层# Z_h = W_h * X + b_hZ_h = np.dot(self.W_h, X) + self.b_h # 线性变换A_h = relu(Z_h) # 应用ReLU激活函数print("\n--- Hidden Layer ---")print("Z_h (Linear Output):\n", Z_h)print("A_h (After ReLU):\n", A_h)# 隐藏层到输出层# Z_o = W_o * A_h + b_oZ_o = np.dot(self.W_o, A_h) + self.b_o # 线性变换A_o = sigmoid(Z_o) # 应用Sigmoid激活函数 (预测概率)print("\n--- Output Layer ---")print("Z_o (Linear Output):\n", Z_o)print("A_o (Prediction, After Sigmoid):\n", A_o)return A_o# 示例使用
if __name__ == '__main__':# 定义MLP结构: 输入层2个节点, 隐藏层3个节点, 输出层1个节点input_features = 2hidden_nodes = 3output_nodes = 1mlp = SimpleMLP(input_size=input_features, hidden_size=hidden_nodes, output_size=output_nodes)# 假设一个输入样本sample_X = np.array([0.1, 0.2]) print("\nInput X:\n", sample_X.reshape(-1,1))# 执行前向传播prediction = mlp.forward(sample_X)print(f"\nFinal Prediction for input {sample_X}: {prediction[0,0]:.4f}")# 另一个输入样本sample_X_2 = np.array([0.8, 0.9])print("\nInput X:\n", sample_X_2.reshape(-1,1))prediction_2 = mlp.forward(sample_X_2)print(f"\nFinal Prediction for input {sample_X_2}: {prediction_2[0,0]:.4f}")
关键行注释说明:
np.random.randn(...) * 0.1
: 初始化权重时,通常使用较小范围的随机数,有助于训练的稳定性。np.zeros(...)
: 偏置通常初始化为0。np.dot(self.W_h, X)
: 执行矩阵乘法,计算加权和。relu(Z_h)
和sigmoid(Z_o)
: 调用之前定义的激活函数。X.reshape(-1, 1)
: 确保单个输入样本是列向量,以匹配权重矩阵的维度进行点积运算。
运行上述代码,你会看到权重和偏置的初始化值,以及对于给定输入样本,在隐藏层和输出层的中间计算结果和最终预测值。这个预测值(0到1之间)代表了模型认为输入样本属于类别1的概率。
四、MLP 的优势与局限性
多层感知器作为深度学习的基石,拥有其独特的优势,但也存在一些固有的局限性。
4.1 MLP 的主要优势
4.1.1 通用逼近定理 (简单提及)
**通用逼近定理(Universal Approximation Theorem)**指出,一个包含足够多神经元的单隐藏层前馈网络(使用非线性激活函数),能够以任意精度逼近任意连续函数。这意味着MLP具有拟合各种复杂数据模式的理论能力。当然,这只是理论上的可能性,实际中找到合适的网络结构和参数可能非常困难。
4.1.2 处理非线性问题的能力
通过引入隐藏层和非线性激活函数,MLP能够有效地学习和表示数据中的非线性关系,这是其相比于线性模型(如单层感知器、线性回归、逻辑回归)最显著的优势。
4.2 MLP 的一些局限性
4.2.1 参数数量可能较多
在全连接的MLP中,如果网络层数较多或每层的神经元数量较大,权重和偏置参数的数量会迅速增加。这不仅会导致计算成本上升,还可能使得模型更容易在数据量不足时发生过拟合(Overfitting)。
4.2.2 对于特定类型数据 (如图像、序列) 可能不是最优选择 (引出CNN, RNN)
MLP将输入数据视为一个扁平的向量,忽略了数据本身可能存在的空间结构(如图像中的像素邻近关系)或时间序列结构(如文本中的词语顺序)。对于这类具有特定结构的数据:
- 图像数据:使用MLP处理高分辨率图像会导致参数量爆炸,并且无法有效利用图像的局部相关性。**卷积神经网络(CNN)**通过权值共享和局部连接等机制,更适合处理图像数据。
- 序列数据:MLP难以捕捉序列中的长期依赖关系。**循环神经网络(RNN)**及其变体(如LSTM、GRU)更擅长处理这类任务。
我们将在后续的系列文章中深入探讨CNN和RNN。
4.2.3 容易过拟合 (后续文章会讲如何解决)
由于其强大的拟合能力和可能较多的参数,MLP在训练数据量不足或模型复杂度过高时,很容易学习到训练数据中的噪声和特例,导致在未见过的新数据上表现不佳,即过拟合。后续我们会学习多种正则化技术(如L1/L2正则化、Dropout)、数据增强、早停等方法来缓解过拟合问题。
五、常见问题与进一步学习
在学习MLP的过程中,初学者可能会遇到一些疑问。
5.1 如何确定隐藏层的数量和神经元个数?
这可以说是神经网络设计中最具挑战性的问题之一,并没有一劳永逸的答案。
5.1.1 经验法则
有一些基于经验的启发式规则,但它们并非普适:
- 隐藏层数量:对于大多数问题,一个或两个隐藏层通常就足够了。更深的网络可以学习更复杂的表示,但也更难训练。可以从一个隐藏层开始尝试。
- 神经元数量:
- 隐藏层神经元的数量可以在输入层和输出层神经元数量之间。
- 常见的做法是使其为输入层大小的某个比例 (e.g., 2/3 输入层大小 + 输出层大小),或者输入层大小的一半到两倍。
- 有时会设计成金字塔形(逐层减少神经元)或反金字塔形。
5.1.2 交叉验证与实验
最可靠的方法是通过实验和交叉验证(Cross-validation)。尝试不同的网络结构(不同的层数和神经元组合),在验证集上评估它们的性能,选择表现最好的那个。这通常是一个迭代和试错的过程。自动化超参数调优工具(如Grid Search, Random Search, Bayesian Optimization)也可以帮助我们探索这些可能性。
5.2 为什么全连接层也叫稠密层?
“全连接层”(Fully Connected Layer)这个名字强调了其连接方式——前一层的所有神经元都连接到当前层的每一个神经元。
“稠密层”(Dense Layer)这个名字则更多地是从权重矩阵的角度来描述。因为几乎所有的连接都存在权重(不像稀疏连接那样大部分权重为0),所以其权重矩阵是“稠密”的。在很多深度学习框架(如Keras, PyTorch)中,实现全连接层的类通常就命名为Dense
。
5.3 MLP 和深度学习的关系?
多层感知器是**深度学习(Deep Learning)**的入门级模型,也是其重要组成部分。
- 当一个MLP拥有多个隐藏层时,它就可以被称为一个深度神经网络(Deep Neural Network, DNN)。
- “深度学习”这个术语本身就强调了模型结构的“深度”,即通过多层非线性变换来学习数据的分层特征表示。
- MLP的基本原理,如前向传播、激活函数、权重和偏置等概念,是理解更高级深度学习模型(如CNN, RNN, Transformer等)的基础。
可以说,掌握MLP是踏入广阔深度学习世界的第一步。
六、总结
本文详细介绍了多层感知器(MLP)的核心概念、结构及其工作原理。通过从单层感知器的局限性出发,我们理解了引入隐藏层的必要性,并深入探讨了MLP的各个组成部分。
以下是本文的核心内容回顾:
- MLP的动机:克服单层感知器无法解决线性不可分问题和表达能力不足的局限,通过引入隐藏层和非线性激活函数来学习复杂模式。
- MLP的结构:
- 是一种前馈神经网络,信息单向流动。
- 主要由输入层(接收数据)、隐藏层(特征提取与转换,核心计算单元,使用非线性激活函数)和输出层(输出预测结果,激活函数根据任务类型选择)构成。
- 相邻层之间通常是全连接的,也称为稠密层。
- 核心计算:在全连接层中,每个神经元的计算包括线性变换( Z = W X + b Z = WX + b Z=WX+b)和激活函数( A = g ( Z ) A = g(Z) A=g(Z))。
- 网络深度与宽度:
- 深度(隐藏层数量)影响模型学习特征的层次性。
- 宽度(隐藏层神经元数量)影响模型学习特征的丰富度。
- 两者需要权衡选择。
- 前向传播:演示了数据从输入层经过隐藏层到输出层,一步步计算得到最终预测值的过程,并提供了简单的Python+NumPy代码示例。
- MLP的优势与局限性:
- 优势:具有通用逼近能力,能处理非线性问题。
- 局限性:参数可能较多,对特定结构数据(图像、序列)并非最优,易过拟合。
- 关键概念理解:解释了如何选择隐藏层和神经元数量的思路,以及MLP与深度学习的关系。
通过本文的学习,您应该对如何构建一个简单的神经网络有了清晰的认识。在接下来的文章中,我们将继续深入学习神经网络的核心——如何通过损失函数来衡量模型的好坏(Day 15),以及如何利用梯度下降和反向传播算法来“训练”网络,使其自动学习和优化参数。敬请期待!