【深度学习基础篇03】激活函数详解:从生物突触到非线性注入

文章目录

在上一章中,我们已经知道,如果没有激活函数,无论神经网络堆叠多少层,它最终都只是一个线性模型,无法拟合复杂的非线性规律。这一章,我们将从生物神经元的工作机制出发,揭开激活函数的神秘面纱,理解它如何为神经网络注入"灵魂",并通过代码直观感受它的作用。


一、为什么需要激活函数?

我们先来看一个直观的例子。假设我们有一个两层的神经网络:

  • 第一层: r 1 = b 1 + w 11 x 1 + w 12 x 2 r_1 = b_1 + w_{11}x_1 + w_{12}x_2 r1=b1+w11x1+w12x2, r 2 = b 2 + w 21 x 1 + w 22 x 2 r_2 = b_2 + w_{21}x_1 + w_{22}x_2 r2=b2+w21x1+w22x2
  • 第二层: z 1 = b ˉ 1 + w ˉ 11 r 1 + w ˉ 12 r 2 z_1 = \bar{b}1 + \bar{w}{11}r_1 + \bar{w}_{12}r_2 z1=bˉ1+wˉ11r1+wˉ12r2

如果我们把第一层的结果代入第二层,就会发现:

z 1 = b ˉ 1 + w ˉ 11 ( b 1 + w 11 x 1 + w 12 x 2 ) + w ˉ 12 ( b 2 + w 21 x 1 + w 22 x 2 ) = ( b ˉ 1 + w ˉ 11 b 1 + w ˉ 12 b 2 ) ⏟ b ? + ( w ˉ 11 w 11 + w ˉ 12 w 21 ) ⏟ α 1 x 1 + ( w ˉ 11 w 12 + w ˉ 12 w 22 ) ⏟ α 2 x 2 \begin{aligned} z_1 &= \bar{b}1 + \bar{w}{11}(b_1 + w_{11}x_1 + w_{12}x_2) + \bar{w}{12}(b_2 + w{21}x_1 + w_{22}x_2) \\ &= \underbrace{(\bar{b}1 + \bar{w}{11}b_1 + \bar{w}{12}b_2)}{b_?} + \underbrace{(\bar{w}{11}w{11} + \bar{w}{12}w{21})}{\alpha_1}x_1 + \underbrace{(\bar{w}{11}w_{12} + \bar{w}{12}w{22})}_{\alpha_2}x_2 \end{aligned} z1=bˉ1+wˉ11(b1+w11x1+w12x2)+wˉ12(b2+w21x1+w22x2)=b? (bˉ1+wˉ11b1+wˉ12b2)+α1 (wˉ11w11+wˉ12w21)x1+α2 (wˉ11w12+wˉ12w22)x2

最终的输出 z 1 z_1 z1 仍然是输入 x 1 , x 2 x_1, x_2 x1,x2 的线性组合。这意味着,没有激活函数的多层神经网络,本质上和一层神经网络没有区别 ,它永远无法拟合像 y = x 3 + 2 x 2 y = x^3 + 2x^2 y=x3+2x2 这样的非线性函数。


二、激活函数的生物学灵感

生物神经元给了我们破解"线性陷阱"的灵感。

  • 生物神经元并非简单地传递信号,它有自己的"决定权"。
  • 当它接收到的刺激(信号总和)低于某个阈值时,它会保持抑制,不产生任何神经冲动。
  • 当刺激高于这个阈值时,它才会被激活,产生一个强烈的神经冲动并传递出去。

这种"阈值判断"的机制,本质上就是一种非线性 行为。激活函数,就是在人工神经网络中模拟这种行为的数学工具。它对线性组合的结果 r r r 进行一次"筛选",决定这个神经元是否被激活,以及激活的强度。


三、激活函数的核心作用:注入非线性

激活函数的核心作用,就是为神经网络引入非线性因素,打破"线性陷阱"。

  1. 打破线性组合:如果没有激活函数,多层网络等价于单层网络。激活函数的存在,使得每一层的输出都成为上一层输出的非线性变换,从而让整个网络可以逼近任意复杂的非线性函数。
  2. 万能近似定理:一个包含足够多隐藏层和非线性激活函数的前馈神经网络,可以在紧集上以任意精度逼近任何连续函数。这是深度学习强大能力的理论基石。
  3. 可导性 :激活函数最重要的特性是可求导。在梯度下降优化过程中,我们需要计算损失函数对参数的梯度,这依赖于激活函数的导数。

四、常见激活函数详解

1. Sigmoid 函数

Sigmoid 是最经典的激活函数之一,它将输入映射到 (0, 1) 区间,非常适合表示概率。

  • 公式
    S ( x ) = 1 1 + e − x = e x e x + 1 S(x) = \frac{1}{1 + e^{-x}} = \frac{e^x}{e^x + 1} S(x)=1+e−x1=ex+1ex

  • 图像 :一条"S"形曲线,输入为0时输出为0.5,输入趋向正无穷时输出趋近1,输入趋向负无穷时输出趋近0。

  • 优点

    • 输出范围在 (0, 1) 之间,可作为概率使用。
    • 函数平滑,易于求导。
  • 缺点

    • 梯度消失 :当输入 x x x 很大或很小时,函数的梯度(斜率)会变得非常小,导致梯度下降更新缓慢,网络难以训练。
    • 输出不是以0为中心,会影响后续层的梯度方向。

2. ReLU 函数

ReLU(Rectified Linear Unit)是目前深度学习中最常用的激活函数,它解决了Sigmoid的梯度消失问题。

  • 公式
    f ( x ) = max ⁡ ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x)

  • 图像 :一条在x<0时为0,x≥0时为y=x的折线。

  • 优点

    • 缓解梯度消失:在正区间,梯度恒为1,避免了梯度消失。
    • 计算速度极快,只需要一个阈值判断。
  • 缺点

    • 神经元死亡:当学习率设置过大时,大量神经元的输入可能会恒为负,导致其梯度永远为0,参数永远无法更新,这些神经元就"死亡"了。

五、代码实战:激活函数如何改变网络能力?

代码部分本阶段可以不用看懂,看懂结果就好
想要代码逐行解释的,可以翻到文章最后。

我们通过一个简单的例子,直观地感受一下激活函数的作用。我们的目标是让神经网络拟合一个非线性函数 y = x 3 + 2 x 2 y = x^3 + 2x^2 y=x3+2x2。

1. 准备数据

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# 生成数据
x = np.linspace(-4, 10, 500)
y_true = x**3 + 2 * x**2

plt.figure(figsize=(12, 4))
plt.plot(x, y_true, label='True function: $y = x^3 + 2x^2$')
plt.title('Target Non-linear Function')
plt.legend()
plt.show()

2. 定义一个简单的神经网络

我们定义一个两层的全连接网络,通过控制是否使用激活函数,来观察拟合效果的差异。

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim

# 将数据转换为PyTorch张量
x_tensor = torch.tensor(x, dtype=torch.float32).unsqueeze(1)
y_tensor = torch.tensor(y_true, dtype=torch.float32).unsqueeze(1)

# 定义网络
class SimpleNet(nn.Module):
    def __init__(self, activation=None):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(1, 64)  # 第一层
        self.activation = activation  # 激活函数
        self.fc2 = nn.Linear(64, 1)  # 第二层

    def forward(self, x):
        x = self.fc1(x)
        if self.activation:
            x = self.activation(x)
        x = self.fc2(x)
        return x

# 训练函数
def train_model(activation, epochs=1500):
    model = SimpleNet(activation)
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr=0.01)
    
    for epoch in range(epochs):
        optimizer.zero_grad()
        y_pred = model(x_tensor)
        loss = criterion(y_pred, y_tensor)
        loss.backward()
        optimizer.step()
    
    return model(x_tensor).detach().numpy()

# 训练不同配置的网络
y_pred_no_act = train_model(None)  # 无激活函数
y_pred_1_sigmoid = train_model(nn.Sigmoid())  # 1个Sigmoid
y_pred_4_relu = train_model(nn.ReLU())  # 使用ReLU

3. 可视化结果

python 复制代码
plt.figure(figsize=(18, 5))

# 无激活函数
plt.subplot(1, 3, 1)
plt.plot(x, y_true, 'b-', label='True')
plt.plot(x, y_pred_no_act, 'r-', label='Predicted')
plt.title('No Activation Function')
plt.legend()

# 1个Sigmoid
plt.subplot(1, 3, 2)
plt.plot(x, y_true, 'b-', label='True')
plt.plot(x, y_pred_1_sigmoid, 'r-', label='Predicted')
plt.title('With 1 Sigmoid Layer')
plt.legend()

# 使用ReLU
plt.subplot(1, 3, 3)
plt.plot(x, y_true, 'b-', label='True')
plt.plot(x, y_pred_4_relu, 'r-', label='Predicted')
plt.title('With ReLU Activation')
plt.legend()

plt.tight_layout()
plt.show()

从可视化结果中,我们可以清晰地看到:

  • 无激活函数:网络只能拟合出一条直线,完全无法捕捉到目标函数的曲线形态。
  • 有激活函数(Sigmoid/ReLU):网络能够很好地拟合出非线性的曲线,尤其是ReLU,拟合效果非常出色。

这直观地证明了:激活函数是神经网络具备强大拟合能力的关键


六、激活函数在网络中的位置

在神经网络的计算流程中,激活函数通常位于线性层之后

  1. 线性组合: r i = b i + ∑ j w i j x j r_i = b_i + \sum_j w_{ij}x_j ri=bi+∑jwijxj
  2. 激活变换: a i = σ ( r i ) a_i = \sigma(r_i) ai=σ(ri)

这个过程可以用向量形式简洁地表示为:
a = σ ( r ) \boldsymbol{a} = \sigma(\boldsymbol{r}) a=σ(r)

在多层网络中,每一层的输出 a a a 都会作为下一层的输入 x x x,形成一个"线性组合 -> 非线性激活 -> 线性组合 -> ..."的迭代过程。


总结

激活函数是深度学习的"非线性魔法":

  • 它从生物神经元的"阈值判断"机制中获得灵感,为神经网络注入了非线性因素。
  • 它打破了"线性陷阱",使得多层神经网络真正具备了逼近任意复杂函数的能力。
  • Sigmoid 和 ReLU 是两种最具代表性的激活函数,ReLU 因其高效性和对梯度消失的缓解,成为了现代网络的首选。

彩蛋:代码逐行解释

整体功能先说明

这段代码的核心目标是:通过对比"无激活函数""Sigmoid激活""ReLU激活"三种神经网络的拟合效果,直观证明激活函数为神经网络注入非线性的核心作用 ,具体是拟合非线性函数 y = x 3 + 2 x 2 y = x^3 + 2x^2 y=x3+2x2。


逐行详细解释

python 复制代码
import torch
  • 功能:导入PyTorch核心库,PyTorch是深度学习主流框架,提供张量运算、神经网络构建、自动求导等核心功能。
  • 实战作用:后续所有的张量定义、网络搭建、梯度下降都依赖这个库。
python 复制代码
import torch.nn as nn
  • 功能:导入PyTorch的神经网络模块(nn = neural network)。
  • 实战作用nn.Module(所有网络的基类)、nn.Linear(全连接层)、nn.Sigmoid/ReLU(激活函数)、nn.MSELoss(损失函数)都来自这个模块。
python 复制代码
import torch.optim as optim
  • 功能:导入PyTorch的优化器模块。
  • 实战作用 :后续用optim.Adam(主流优化器)实现梯度下降,更新网络参数。
python 复制代码
import numpy as np
  • 功能:导入NumPy库,Python科学计算的核心库,用于生成数据、数值运算。
  • 实战作用 :生成拟合用的输入数据x和真实标签y_true
python 复制代码
import matplotlib.pyplot as plt
  • 功能:导入Matplotlib绘图库,用于可视化数据和拟合结果。
  • 实战作用:最后画出"真实函数曲线"和"不同网络的预测曲线",直观对比效果。
python 复制代码
x = np.linspace(-4, 10, 500)
  • 语法解析np.linspace(起始值, 终止值, 样本数) → 生成指定范围内的等间距数值。
  • 具体含义 :在[-4, 10]区间内生成500个均匀分布的数值,作为神经网络的输入特征。
  • 实战作用:500个样本足够密集,能画出平滑的函数曲线,避免样本太少导致的可视化失真。
python 复制代码
y_true = x**3 + 2 * x**2
  • 语法解析x**3表示x的三次方,2 * x**2表示2倍x的平方,NumPy数组支持逐元素运算。
  • 具体含义 :根据非线性函数 y = x 3 + 2 x 2 y = x^3 + 2x^2 y=x3+2x2,计算每个输入x对应的真实输出(标签)。
  • 实战作用 :这是我们要让神经网络拟合的"目标",后续用y_true计算预测误差(损失)。
python 复制代码
# 将数据转换为PyTorch张量
x_tensor = torch.tensor(x, dtype=torch.float32).unsqueeze(1)
  • 分步解析
    1. torch.tensor(x, dtype=torch.float32):将NumPy数组x转换为PyTorch张量,指定数据类型为32位浮点数(深度学习主流精度,平衡精度和速度)。
    2. .unsqueeze(1):给张量增加一个维度,维度位置为1。
      • x的形状:(500,)(一维数组,500个元素);
      • 转换后形状:(500, 1)(二维张量,500行1列,对应"500个样本,每个样本1个特征")。
  • 实战作用 :PyTorch的nn.Linear层要求输入必须是二维张量(样本数×特征数) ,不能是一维数组,所以必须用unsqueeze扩维。
python 复制代码
y_tensor = torch.tensor(y_true, dtype=torch.float32).unsqueeze(1)
  • 解析 :和x_tensor逻辑完全一致,将真实标签y_true转换为形状为(500, 1)的32位浮点张量。
  • 实战作用:后续计算损失函数时,要求预测值和真实值的形状、数据类型完全一致,否则会报错。
python 复制代码
# 定义网络
class SimpleNet(nn.Module):
  • 语法解析 :定义一个名为SimpleNet的类,继承自nn.Module(PyTorch所有神经网络的基类)。
  • 核心规则 :自定义网络必须继承nn.Module,且必须实现__init__(初始化层)和forward(前向传播)两个方法。
  • 实战作用:搭建一个两层的全连接网络,用于拟合目标函数。
python 复制代码
    def __init__(self, activation=None):
  • 语法解析 :定义类的初始化方法,参数activation用于指定激活函数(默认值None表示无激活函数)。
  • 实战作用 :通过传入不同的激活函数(None/nn.Sigmoid()/nn.ReLU()),构建三种不同的网络配置,方便对比。
python 复制代码
        super(SimpleNet, self).__init__()
  • 语法解析 :调用父类nn.Module的初始化方法,这是PyTorch自定义网络的"必写步骤"。
  • 实战作用 :初始化nn.Module的内部参数(如网络层的权重、偏置),如果不写,后续网络层无法正常工作。
python 复制代码
        self.fc1 = nn.Linear(1, 64)  # 第一层
  • 语法解析nn.Linear(in_features, out_features) → 定义全连接层(也叫线性层),参数含义:
    • in_features=1:输入特征数(对应x_tensor的列数,每个样本1个特征);
    • out_features=64:输出特征数(该层有64个神经元,输出64维特征)。
  • 实战作用:第一层将1维输入映射为64维特征,增加网络的拟合能力(64个神经元足够拟合简单的三次函数)。
  • 参数初始化nn.Linear会自动随机初始化权重(w)和偏置(b),无需手动设置。
python 复制代码
        self.activation = activation  # 激活函数
  • 解析 :将初始化方法传入的activation参数保存为类的属性,后续前向传播时使用。
  • 实战作用:灵活控制网络是否使用激活函数、使用哪种激活函数。
python 复制代码
        self.fc2 = nn.Linear(64, 1)  # 第二层
  • 语法解析in_features=64(输入为第一层的64维输出),out_features=1(输出1维,对应目标函数的单值输出)。
  • 实战作用:第二层将64维特征映射回1维输出,得到最终的预测值。
python 复制代码
    def forward(self, x):
  • 语法解析 :定义网络的前向传播方法(forward是PyTorch的固定命名,不能改),参数x是网络的输入张量。
  • 核心规则 :PyTorch会自动根据forward方法构建反向传播的计算图,无需手动写反向传播。
python 复制代码
        x = self.fc1(x)
  • 解析 :将输入x传入第一层全连接层,计算线性组合: x f c 1 = x ⋅ w f c 1 + b f c 1 x_{fc1} = x \cdot w_{fc1} + b_{fc1} xfc1=x⋅wfc1+bfc1。
  • 形状变化 :输入(500, 1) → 输出(500, 64)
python 复制代码
        if self.activation:
  • 解析 :判断是否传入了激活函数(activation不为None时执行后续代码)。
  • 实战作用:实现"有无激活函数"的分支逻辑。
python 复制代码
            x = self.activation(x)
  • 解析 :将第一层的输出传入激活函数,进行非线性变换。
    • activation=nn.Sigmoid(): x = σ ( x f c 1 ) x = \sigma(x_{fc1}) x=σ(xfc1);
    • activation=nn.ReLU(): x = max ⁡ ( 0 , x f c 1 ) x = \max(0, x_{fc1}) x=max(0,xfc1);
    • activation=None:跳过这一步,直接进入第二层。
  • 形状变化 :激活函数是逐元素运算,形状保持(500, 64)不变。
python 复制代码
        x = self.fc2(x)
  • 解析 :将激活后的特征(或第一层直接输出)传入第二层全连接层,计算最终预测值: y p r e d = x ⋅ w f c 2 + b f c 2 y_{pred} = x \cdot w_{fc2} + b_{fc2} ypred=x⋅wfc2+bfc2。
  • 形状变化 :输入(500, 64) → 输出(500, 1)(和真实标签y_tensor形状一致)。
python 复制代码
        return x
  • 解析:返回网络的最终预测值。
python 复制代码
# 训练函数
def train_model(activation, epochs=1500):
  • 语法解析 :定义训练函数,参数:
    • activation:传入激活函数(控制网络配置);
    • epochs=1500:训练轮数(默认1500轮,足够网络收敛)。
  • 实战作用:封装训练逻辑,避免重复代码,方便调用不同激活函数的网络。
python 复制代码
    model = SimpleNet(activation)
  • 解析 :实例化SimpleNet类,创建一个指定激活函数的网络模型。
  • 实战作用 :每次调用train_model都会创建一个全新的网络(权重随机初始化),保证不同配置的网络训练公平。
python 复制代码
    criterion = nn.MSELoss()
  • 语法解析 :实例化均方误差损失函数(MSE = Mean Squared Error),公式: L o s s = 1 N ∑ ( y p r e d − y t r u e ) 2 Loss = \frac{1}{N}\sum (y_{pred} - y_{true})^2 Loss=N1∑(ypred−ytrue)2。
  • 实战作用:衡量预测值和真实值的差距,是回归任务(预测连续值)的首选损失函数。
python 复制代码
    optimizer = optim.Adam(model.parameters(), lr=0.01)
  • 语法解析
    1. optim.Adam:选择Adam优化器(比传统梯度下降更高效、稳定);
    2. model.parameters():获取网络中所有可训练的参数(fc1fc2wb);
    3. lr=0.01:学习率(超参数),控制每次参数更新的步长。
  • 实战作用:构建优化器,用于后续反向传播更新参数。
python 复制代码
    for epoch in range(epochs):
  • 解析 :循环训练epochs轮,每一轮都会完成"前向传播→计算损失→反向传播→更新参数"的完整流程。
python 复制代码
        optimizer.zero_grad()
  • 核心作用:清空优化器中所有参数的梯度。
  • 关键原因:PyTorch的梯度会累加(如果不清空,本轮梯度会加上上一轮的梯度),导致梯度计算错误,必须在每轮训练前清空。
python 复制代码
        y_pred = model(x_tensor)
  • 解析 :将输入x_tensor传入模型,执行前向传播,得到预测值y_pred
  • 形状(500, 1),和y_tensor一致。
python 复制代码
        loss = criterion(y_pred, y_tensor)
  • 解析 :计算预测值y_pred和真实值y_tensor的均方误差损失。
  • 数值含义:损失值越小,说明预测值越接近真实值,网络拟合效果越好。
python 复制代码
        loss.backward()
  • 核心作用 :自动反向传播,计算损失函数对所有可训练参数的梯度(∂Loss/∂w∂Loss/∂b)。
  • 底层逻辑:PyTorch通过计算图自动求导,无需手动推导梯度公式,这是框架的核心优势。
python 复制代码
        optimizer.step()
  • 核心作用 :根据反向传播得到的梯度,更新网络的参数(wb),公式: w = w − l r ⋅ ∂ L o s s / ∂ w w = w - lr \cdot ∂Loss/∂w w=w−lr⋅∂Loss/∂w, b = b − l r ⋅ ∂ L o s s / ∂ b b = b - lr \cdot ∂Loss/∂b b=b−lr⋅∂Loss/∂b。
  • 实战作用 :每执行一次step(),网络参数就会向"损失减小"的方向更新一步,逐步拟合目标函数。
python 复制代码
    return model(x_tensor).detach().numpy()
  • 分步解析
    1. model(x_tensor):训练完成后,用最终的网络参数重新预测输入x_tensor的输出;
    2. .detach():将预测张量从计算图中分离(切断梯度关联),避免后续转换NumPy时报错;
    3. .numpy():将PyTorch张量转换为NumPy数组,方便后续Matplotlib绘图(Matplotlib不支持张量绘图)。
  • 实战作用:返回训练后的网络预测结果,用于可视化对比。
python 复制代码
# 训练不同配置的网络
y_pred_no_act = train_model(None)  # 无激活函数
  • 解析 :调用train_model,传入activation=None,训练"无激活函数"的网络,返回预测结果。
python 复制代码
y_pred_1_sigmoid = train_model(nn.Sigmoid())  # 1个Sigmoid
  • 解析 :传入nn.Sigmoid(),训练"第一层后加Sigmoid激活"的网络。
python 复制代码
y_pred_4_relu = train_model(nn.ReLU())  # 使用ReLU
  • 解析 :传入nn.ReLU(),训练"第一层后加ReLU激活"的网络。

核心关键点回顾

  1. 张量维度unsqueeze(1)是关键,保证输入符合nn.Linear的维度要求(样本数×特征数);
  2. 激活函数逻辑 :通过if self.activation实现分支,对比有无激活的差异;
  3. 训练流程zero_grad() → 前向传播 → 计算损失 → backward() → step()是PyTorch训练的固定流程;
  4. 数据转换detach().numpy()是张量转NumPy的标准操作,避免梯度关联导致的报错。

通过这些代码,能清晰看到:无激活函数的网络只能拟合直线,而加了Sigmoid/ReLU的网络能拟合非线性曲线,这正是激活函数的核心价值。

相关推荐
Figo_Cheung2 小时前
Figo《量子几何学:从希尔伯特空间到全息时空的统一理论体系》(三)
人工智能·深度学习·几何学
人工智能AI技术2 小时前
Qwen3.5-Plus Agent开发实战:从0到1做自动执行AI助手
人工智能
liliangcsdn2 小时前
基于Saliency Map对LLM进行可解释性分析
人工智能·计算机视觉·目标跟踪
盟接之桥2 小时前
盟接之桥说制造:从客供的外在共生到内在的身心合一
运维·服务器·网络·人工智能·制造
RoyLin2 小时前
你的 nginx 在扼杀 AI 服务——为什么需要重新设计流量层
人工智能·devops
yunhuibin3 小时前
NIN网络学习
人工智能·python·深度学习·神经网络·学习
王解3 小时前
第八篇:内外兼修 —— 配置系统与日志监控
人工智能·ai agent·nanobot
zhangshuang-peta3 小时前
人工智能代理的上下文管理突破与长期任务执行
人工智能·ai agent·mcp·peta