有没有觉得神经网络无处不在?它们出现在新闻中,出现在你的手机里,甚至出现在你的社交媒体上。但老实说,我们大多数人都不知道它们究竟是如何工作的。那些花里胡哨的数学和奇怪的术语,比如 “反向传播”?
本文我们探索多层感知器(MLP)--最基本的神经网络类型——使用一个小型网络,仅使用少量数据点,对一个简单的二维数据集进行分类。
我们将过程进行拆解,并用图示的方式展示每一步,你将看到栩栩如生的数学知识,了解数字和方程是如何在网络中流动的,以及学习是如何真正发生的!
定义
多层感知器 (MLP) 是一种神经网络,它使用多层连接节点来学习模式。它因具有多个层而得名 - 通常是一个输入层、一个或多个中间(隐藏)层和一个输出层。
每个节点都连接到下一层的所有节点。当网络学习时,它会根据训练示例调整这些连接的强度。例如,如果某些连接导致正确的预测,它们就会变得更强。如果它们导致错误,它们就会变得更弱。
这种通过示例学习的方式有助于网络识别模式并对以前从未见过的新情况做出预测。
MLP 被认为是神经网络和深度学习领域的基础,因为它们可以处理简单方法难以解决的复杂问题。
使用的数据集
为了理解 MLP 的工作原理,从一个简单的示例开始:一个只有几个样本的mini 2D 数据集。我们将使用上一篇文章中的相同数据集,以使事情易于管理。
我们不必直接进行训练,而是尝试了解构成神经网络的关键部分以及它们如何协同工作。
步骤 0:网络结构
首先,看看网络的各个部分:
节点(神经元)
我们先从神经网络的基本结构开始。该结构由许多称为节点或神经元的单个单元组成。
这些节点被组织成称为层的组,以便协同工作。
输入层
输入层是我们的起点。它接收我们的原始数据,这里的节点数量与我们拥有的特征数量相匹配。
隐藏层
接下来是隐藏层。我们可以有一个或多个这样的层,并且可以选择每个层有多少个节点。通常,随着层数的增加,我们在每层中使用的节点数会减少。
输出层
最后一层给出了最终答案。输出层的节点数取决于我们的任务:对于二元分类或回归,我们可能只有一个输出节点,而对于多类问题,每个类都有一个节点。
权重
节点通过权重相互连接——权重是控制每条信息重要程度的数字。节点之间的每个连接都有自己的权重。这意味着我们需要很多权重:一层中的每个节点都连接到下一层中的每个节点。
该神经网络共有14个权重。
偏差
除了权重,每个节点还有一个偏差——一个额外的数字,可以帮助它做出更好的决策。权重控制节点之间的连接,而偏差则帮助每个节点调整其输出。
神经网络
总而言之,我们将使用并训练这个神经网络:
我们的网络由 4 层组成:1 个输入层(2 个节点)、2 个隐藏层(3 个节点和 2 个节点)和 1 个输出层(1 个节点)。这创建了一个 2–3–2–1 架构。
看一下这张从上到下展示我们网络的新图表。我对其进行了更新,使数学运算更容易理解:信息从顶部节点开始,流经各个层,直到到达底部的最终答案。
现在我们了解了网络是如何构建的,再看看信息是如何在网络中传播的。这称为前向传递。
步骤 1:前向传递
接下来我们一步一步看看我们的网络如何将输入转化为输出:
权重初始化
在我们的网络开始学习之前,我们需要为每个权重指定一个起始值。我们选择 -1 到 1 之间的小随机数。从随机数开始有助于我们的网络在没有任何早期偏好或模式的情况下进行学习。
加权求和
每个节点分两步处理传入数据。首先,它将每个输入乘以其权重,并将所有这些数字相加。然后,它再添加一个数字(偏差)以完成计算。偏差本质上是一个输入为常数 1 的权重。
激活函数
每个节点取其加权和并通过激活函数运行以产生其输出。激活函数通过引入非线性行为帮助我们的网络学习复杂的模式。
在隐藏层中,我们使用 ReLU 函数(整流线性单元)。ReLU 很简单:如果数字为正,则保持不变;如果数字为负,则变为零。
逐层计算
这两步过程(加权和激活)在每一层中依次发生。每一层的计算有助于将我们的输入数据逐步转化为最终的预测。
输出生成
最后一层创建了我们网络的最终答案。对于我们的是/否分类任务,我们在这一层中使用了一种称为sigmoid的特殊激活函数。
S 型函数将任何数字转换为 0 到 1 之间的值。这使其非常适合是/否决策,因为我们可以将输出视为概率:越接近 1 表示越可能是“是”,越接近 0 表示越可能是“否”。
这个前向传递过程将我们的输入转换为 0 到 1 之间的预测。但这些预测有多准确?接下来,我们将测量我们的预测与正确答案的接近程度。
第 2 步:损失计算
损失函数
为了检查我们的网络表现如何,我们会测量其预测与正确答案之间的差异。对于二元分类,我们使用一种称为二元交叉熵的方法,该方法可以显示我们的预测与真实值的偏差。
神经网络中的数学符号
为了提高网络的性能,我们需要使用一些数学符号。在继续之前,让我们先定义一下每个符号的含义:
权重和偏差权重表示为矩阵,偏差表示为向量(或一维矩阵)。括号[1]
表示层数。
输入、输出、加权和以及激活后的值节点内的值可以表示为向量,形成一致的数学框架。
这些数学符号帮助我们准确地写出我们的网络所做的事情:
我们看一张显示网络中发生的所有数学运算的图表。每层都有:
我们看看每一层到底发生了什么:
第一个隐藏层:
- 获取输入x,将其乘以权重W [1],加上偏差b[1] 得到z[1]
第二个隐藏层:
- 取a[1],乘以权重W [2],加上偏差b[2] 得到z[2]
输出层:
- 取a[2],乘以权重W [3],加上偏差b[3 ]得到 z[3]
- 对 z[3] 应用 sigmoid 函数得到最终预测 ŷ
现在我们看到了网络中的所有数学知识,那么我们如何改进这些数字以获得更好的预测呢?这就是反向传播的作用所在——它向我们展示了如何调整权重和偏差以减少错误。
步骤3:反向传播
在我们了解如何改进网络之前,让我们快速回顾一下我们需要的一些数学工具:
梯度
为了优化我们的神经网络,我们使用梯度——一个与导数密切相关的概念。让我们回顾一些基本的导数规则:
偏导数
正导数和偏导数之间的区别:
正导数:
偏导数:
- 显示当一个变量发生变化时函数的变化量,同时保持其他变量不变(作为常数)。
梯度计算与反向传播
回到我们的神经网络,我们需要确定如何调整每个权重和偏差以最小化误差。我们可以使用一种称为反向传播的方法来做到这一点,该方法向我们展示了改变每个值如何影响我们的网络误差。
由于反向传播在我们的网络中向后进行,将图表上下翻转来看看它是如何工作的。
网络矩阵规则
由于我们的网络使用矩阵(权重和偏差组),因此我们需要特殊规则来计算变化如何影响我们的结果。以下是两个关键矩阵规则。对于向量v、u(大小为 1 × n) 和矩阵W、X(大小为n × n):
- 求和规则:∂( W + X )/∂W = I (单位矩阵,大小为n × n)∂( u + v )/∂v = I (单位矩阵,大小为n × n)
- 矩阵向量积规则:∂( vW )/∂ W = v ᵀ∂( vW )/∂ v = W ᵀ
利用这些规则,我们得到:
激活函数导数
对于向量a和z(大小为 1 × n),其中 a = ReLU( z ):
∂a /∂z = diag( z > 0)
创建一个对角矩阵,显示:如果输入为正则为 1,如果输入为零或负则为 0。
对于a = σ( z ),其中 σ 是 S 型函数:
∂a / ∂z = a⊙(1 - a )
这将直接将元素相乘(⊙表示将每个位置相乘)。
二元交叉熵损失导数
对于损失为 L = -[ y log(ŷ) + (1- y ) log(1- ŷ )] 的单个示例:
∂ L /∂ ŷ = -( y - ŷ ) / [ ŷ (1- ŷ )]
到目前为止,我们可以将所有偏导数总结如下:
下图展示了我们迄今为止得到的所有偏导数:
链式法则
在我们的网络中,变化会经过多个步骤:权重会影响其所在层的输出,进而影响下一层,依此类推,直到最终误差。链式法则告诉我们将这些逐步变化相乘,以找出每个权重和偏差如何影响最终误差。
误差计算
我们不是直接计算权重和偏差导数,而是先计算层误差 ∂ L /∂ zˡ(相对于预激活输出的梯度)。这样可以更轻松地计算如何调整前面各层的权重和偏差。
权重梯度和偏差梯度
利用这些层误差和链式法则,我们可以将权重和偏差梯度表示为:
梯度向我们展示了网络中的每个值如何影响网络的误差。然后我们对这些值进行细微的更改,以帮助我们的网络做出更好的预测
步骤 4:权重更新
更新权重
一旦我们知道每个权重和偏差如何影响误差(梯度),我们就会通过沿梯度的反方向调整这些值来改进网络。这会逐步减少网络的误差。
学习率与优化
我们不会一下子做出大的改变,而是进行细微的、谨慎的调整。我们使用一个称为学习率 ( η ) 的数字来控制每个值的改变程度:
这种进行小的、可控的改变的方式称为随机梯度下降(SGD)。我们可以把它写成:
η(学习率)的值通常选择较小,通常在0.1到0.0001之间,以确保稳定学习。
我们刚刚看到了我们的网络如何从一个例子中学习。网络对数据集中的每个示例重复所有这些步骤,每轮练习都会变得更好
步骤摘要
以下是我们基于单个示例训练网络的所有步骤:
扩展到完整数据集
epoch
我们的网络对数据集中的每个示例重复这四个步骤——前向传递、损失计算、反向传播和权重更新。遍历所有示例一次称为一个 epoch。
网络通常需要多次查看所有示例才能很好地完成其任务,甚至需要 1000 次。每次查看都有助于它更好地学习模式。
Batch
我们的网络不是一次只从一个示例中学习,而是一次从小组示例中学习(称为批次)。这有几个好处:
处理批次时,网络会在进行更改之前查看组中的所有示例。这比在每个示例之后更改值能带来更好的结果。
测试步骤
准备经过全面训练的神经网络
训练完成后,我们的网络就可以对以前从未见过的新示例进行预测了。它使用与训练相同的步骤,但只需通过网络向前移动即可进行预测。
做出预测
处理新数据时:
- 输出层生成预测(例如,二元分类的 0 到 1 之间的概率)
神经网络的确定性
当我们的网络两次看到相同的输入时,它会两次给出相同的答案(只要我们没有改变它的权重和偏差)。网络处理新示例的能力来自其训练,而不是来自预测中的任何随机性。
写在最后
随着我们的网络反复练习这些示例,它的任务会越来越好。随着时间的推移,它犯的错误越来越少,预测也越来越准确。这就是神经网络学习的方式:查看示例,找出错误,做出小幅改进,然后重复!
多层感知器分类器代码摘要
现在看看神经网络是如何运作的。下面是一些 Python 代码,它使用我们刚刚学到的相同结构和规则构建了我们一直在讨论的网络。
import pandas as pd
import numpy as np
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score
# 创建简单的 2D 数据集
df = pd.DataFrame({
'🌞' : [ 0 , 1 , 1 , 2 , 3 , 3 , 2 , 3 , 0 , 0 , 1 , 2 , 3 ],
'💧' : [ 0 , 0 , 1 , 0 , 1 , 2 , 3 , 3 , 1 , 2 , 3 , 2 , 1 ],
'y' : [ 1 , - 1 , - 1 , - 1 , 1 , 1 , 1 , - 1 , - 1 , 1 , 1 , 1 ] },
index= range ( 1 , 14 ))
# 分成训练集和测试集
train_df, test_df = df.iloc[:8].copy(), df.iloc[8:].copy()
X_train, y_train = train_df[['🌞', '💧']], train_df['y']
X_test, y_test = test_df[['🌞', '💧']], test_df['y']
# 创建并配置我们的神经网络
mlp = MLPClassifier(hidden_layer_sizes=( 3 , 2 ), # 如上所述创建 2-3-2-1 架构
activity= 'relu' , # 隐藏层的 ReLU 激活
resolver= 'sgd' , # 随机梯度下降优化器
learning_rate_init= 0.1 , # 权重更新的步长
max_iter= 1000 , # 最大迭代次数
motion= 0 , # 如上所述,禁用纯 SGD 的动量
random_state= 42 # 为了获得可重复的结果
)
# 训练模型
mlp.fit(X_train, y_train)
# 进行预测并评估
y_pred = mlp.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2f}")