Sigmoid函数简介及其Python实现

一、Sigmoid 函数简介

Sigmoid 函数(也称为 Logistic 函数)是一个在数学、机器学习(尤其是在逻辑回归和早期神经网络中)广泛使用的函数。它的主要特点是将任意实数输入映射到 (0, 1) 这个开区间内。

1. 数学公式

Sigmoid 函数通常用希腊字母 σ (sigma) 表示,其数学表达式为:

σ ( x ) = 1 1 + e − x σ(x) = \frac{1}{1 + e^{-x}} σ(x)=1+ex1

其中:

  • x 是函数的输入(一个实数)。
  • e 是自然对数的底(欧拉数,约等于 2.71828)。

2. 关键特性

(1) 输出范围 (0, 1): 这是 Sigmoid 最重要的特性之一。无论输入 x x x 是什么值(正无穷、负无穷或介于两者之间),输出值 σ ( x ) σ(x) σ(x) 总是严格大于 0 且严格小于 1。这使得它非常适合用来表示概率值,或者在二元分类问题中表示属于某个类别的可能性。
(2) S 形曲线: 函数的图形呈 “S” 形。当 x x x 趋近于负无穷时, e − x e^{-x} ex 趋近于正无穷,分母变得非常大, σ ( x ) σ(x) σ(x) 趋近于 0。当 x x x 趋近于正无穷时, e − x e^{-x} ex 趋近于 0,分母趋近于 1, σ ( x ) σ(x) σ(x) 趋近于 1。
(3) 单调递增: 函数在其整个定义域内是严格单调递增的,这意味着输入 x x x 越大,输出 σ ( x ) σ(x) σ(x) 也越大。
(4) 中心对称: 函数关于点 (0, 0.5) 中心对称。即 σ ( 0 ) = 1 / ( 1 + e 0 ) = 1 / ( 1 + 1 ) = 0.5 σ(0) = 1 / (1 + e^0) = 1 / (1 + 1) = 0.5 σ(0)=1/(1+e0)=1/(1+1)=0.5
(5) 导数易于计算: Sigmoid 函数的导数可以用其自身来表示:
σ ′ ( x ) = σ ( x ) ∗ ( 1 − σ ( x ) ) σ'(x) = σ(x) * (1 - σ(x)) σ(x)=σ(x)(1σ(x))
这个特性在神经网络的反向传播算法中非常有用,因为计算梯度时可以直接利用前向传播计算出的 Sigmoid 值。

3. 应用场景

  • 逻辑回归 (Logistic Regression): Sigmoid 函数是逻辑回归模型的核心,用于将线性模型的输出转换为概率。
  • 神经网络激活函数: 在早期的神经网络中,Sigmoid 曾被广泛用作隐藏层和输出层的激活函数。
    • 输出层: 对于二元分类问题,输出层使用 Sigmoid 可以直接输出概率。
    • 隐藏层: 现在在深度神经网络的隐藏层中,Sigmoid 的使用已大大减少,主要因为梯度消失 (Vanishing Gradient) 问题。当输入 x 的绝对值很大时,Sigmoid 的导数趋近于 0,这会导致在反向传播过程中梯度逐层乘以接近 0 的数,使得深层网络的权重更新非常缓慢甚至停滞。ReLU 及其变种(如 Leaky ReLU, ELU)已成为更常用的隐藏层激活函数。
  • 概率建模: 任何需要将实数值压缩到 (0, 1) 区间以表示概率或置信度的场景。

缺点 (尤其是在深度学习隐藏层中):

  1. 梯度消失: 如上所述,饱和区域(输入绝对值大时)梯度接近 0。
  2. 输出非零中心 (Not Zero-Centered): Sigmoid 的输出恒大于 0。这会导致后续层接收到的输入总是正数,可能在梯度下降过程中引起参数更新的“之”字形抖动(Zigzagging dynamics),降低收敛速度。
  3. 计算复杂度: 相对于 ReLU 函数 (max(0, x)),指数运算 e^x 的计算成本稍高。

二、Python 实现

我们可以使用 Python 的 math 库(处理单个数值)或 numpy 库(处理数值或数组/向量/矩阵)来实现 Sigmoid 函数。在机器学习中,通常使用 numpy 因为它能高效地处理向量化运算。

1. 使用 math 库 (适用于单个数值)

import math

def sigmoid_math(x):
  """
  计算单个数值的 Sigmoid 值。

  Args:
    x: 输入的实数。

  Returns:
    x 的 Sigmoid 值,范围在 (0, 1) 之间。
  """
  # 防止 e^(-x) 过大导致 OverflowError (当 x 是非常小的负数时)
  # Sigmoid(x) = 1 / (1 + exp(-x)) = exp(x) / (exp(x) + 1)
  # 当 x 非常小时,exp(x) 接近 0,Sigmoid(x) 接近 0
  # 当 x 非常大时,exp(-x) 接近 0,Sigmoid(x) 接近 1
  # 这里直接计算标准形式,对于极端值 math.exp 会处理或抛出异常
  try:
      result = 1 / (1 + math.exp(-x))
  except OverflowError:
      # 如果 exp(-x) 溢出,说明 -x 非常大,即 x 是非常小的负数
      # 此时 Sigmoid 值接近 0
      result = 0.0
  return result

# 示例
print(f"Sigmoid(0) = {sigmoid_math(0)}")
print(f"Sigmoid(10) = {sigmoid_math(10)}")
print(f"Sigmoid(-10) = {sigmoid_math(-10)}")
# 一个较大的正数,接近 1
print(f"Sigmoid(100) = {sigmoid_math(100)}")
# 一个较小的负数,接近 0
print(f"Sigmoid(-100) = {sigmoid_math(-100)}")

math实现

2. 使用 numpy 库 (适用于数值、列表、数组、矩阵)

这是在机器学习和数据科学中最常用的方式,因为 numpy 的函数可以对整个数组进行元素级 (element-wise) 操作,效率很高。

import numpy as np

def sigmoid_numpy(x):
  """
  计算输入 x (可以是数值、列表、numpy 数组等) 的 Sigmoid 值。

  Args:
    x: 输入,可以是单个数值、列表、元组、numpy 数组等。

  Returns:
    与 x 相同形状的 numpy 数组,包含每个元素的 Sigmoid 值。
  """
  # np.exp() 可以直接处理数组
  # 对于非常大的负数 x, -x 很大, np.exp(-x) 可能溢出或得到 inf
  # 对于非常大的正数 x, -x 很小, np.exp(-x) 接近 0
  # Numpy 通常能较好地处理这些边界情况,例如 exp(很大负数) -> 0, exp(很大正数) -> inf
  # 1 / (1 + inf) -> 0. 这不是我们想要的,当 x 很大时,结果应为 1
  # Sigmoid(x) = 1 / (1 + exp(-x))
  # 为了数值稳定性,可以做一些处理,但通常 numpy 的标准实现足够健壮
  # 或者使用 scipy.special.expit(x) 是一个数值上更稳定的实现

  # 标准实现:
  x = np.array(x)  # 确保 x 是 numpy 数组
  return 1 / (1 + np.exp(-x))

# 示例
# 单个值
print(f"Sigmoid(0) = {sigmoid_numpy(0)}")

# 列表
input_list = [-10, -1, 0, 1, 10]
print(f"Sigmoid({input_list}) = {sigmoid_numpy(input_list)}")

# Numpy 数组
input_array = np.array([[-2, -0.5], [0.5, 2]])
print(f"Sigmoid(\n{input_array}\n) =\n{sigmoid_numpy(input_array)}")

# 极端值
print(f"Sigmoid(710) approx = {sigmoid_numpy(710)}") # np.exp(-710) 接近 0, 结果接近 1
# print(f"Sigmoid(800) = {sigmoid_numpy(800)}") # np.exp(-800) 可能下溢为 0,结果为 1
print(f"Sigmoid(-710) approx = {sigmoid_numpy(-710)}") # np.exp(710) 接近 inf, 1/(1+inf) -> 0
# print(f"Sigmoid(-800) = {sigmoid_numpy(-800)}") # np.exp(800) 可能溢出为 inf, 结果为 0

numpy实现

3. 使用 scipy.special.expit (数值稳定)

scipy 库提供了一个专门为 Sigmoid 函数优化的实现,通常在数值上更稳定。

from scipy.special import expit # expit is the logistic sigmoid function

# 示例
print(f"Scipy Sigmoid(0) = {expit(0)}")
print(f"Scipy Sigmoid({input_list}) = {expit(input_list)}")
print(f"Scipy Sigmoid(\n{input_array}\n) =\n{expit(input_array)}")

# 处理极端值更稳健
print(f"Scipy Sigmoid(800) = {expit(800)}")
print(f"Scipy Sigmoid(-800) = {expit(-800)}")

sklearn
图像绘制代码如下:

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
  """计算 Sigmoid 函数值"""
  return expit(x) # 使用 scipy 的 expit 函数

# 1. 生成 x 值范围
# 我们需要一系列 x 值来绘制平滑的曲线
# np.linspace(start, stop, num) 在指定的间隔内返回均匀间隔的数字。
x = np.linspace(-10, 10, 200) # 生成从 -10 到 10 的 200 个点

# 2. 计算对应的 y 值 (Sigmoid 输出)
y = sigmoid(x)

# 3. 使用 matplotlib 绘图
plt.figure(figsize=(8, 5)) # 创建一个图形窗口,可以指定大小

plt.plot(x, y, label='Sigmoid Function', color='blue', linewidth=2) # 绘制曲线

# 4. 添加图形元素,使其更清晰
plt.title('Sigmoid Function: σ(x) = 1 / (1 + e^(-x))') # 图形标题
plt.xlabel('x') # x 轴标签
plt.ylabel('σ(x)') # y 轴标签

# 添加网格线
plt.grid(True, linestyle='--', alpha=0.6)

# 添加关键水平线和垂直线
plt.axhline(0.5, color='red', linestyle=':', linewidth=1, label='y = 0.5') # y=0.5 水平线
plt.axhline(1.0, color='gray', linestyle=':', linewidth=1, label='y = 1.0') # y=1.0 水平线
plt.axhline(0.0, color='gray', linestyle=':', linewidth=1, label='y = 0.0') # y=0.0 水平线
plt.axvline(0, color='gray', linestyle=':', linewidth=1) # x=0 垂直线

# 设置 y 轴范围,更清晰地显示 0 到 1 的区间
plt.ylim(-0.1, 1.1)

# 显示图例 (需要 plot 时指定了 label)
plt.legend()

# 5. 显示图形
plt.show()

Sigmoid函数图像

三、小结

在实际的机器学习项目中,如果你使用了像 TensorFlowPyTorch 这样的深度学习框架,它们内部都已经内置了高效且数值稳定的 Sigmoid 函数实现,你通常会直接调用框架提供的函数。但理解其原理和基本的 Python 实现仍然很重要。

### 实现Sigmoid函数及其在U2NET中的应用 对于U2NET模型,在特定情况下可能需要手动实现并理解`Sigmoid`函数的作用。通常,PyTorch库已经提供了内置的`torch.nn.Sigmoid()`模块来执行这一操作。然而,如果希望深入了解其工作原理或出于特殊需求自定义此功能,则可以按照如下方式编写: ```python import torch import numpy as np def sigmoid(x): """Compute the Sigmoid function for a tensor.""" return 1 / (1 + torch.exp(-x)) ``` 当应用于U2NET时,默认设置下,模型会在最后几层调用`F.sigmoid()`来进行激活处理[^1]。但是根据描述,为了适应多分类任务的需求,并考虑到交叉熵损失函数内部会自动完成Softmax转换过程,因此建议去掉原有的Sigmoid激活步骤。 实际上,这意味着不需要显式地将上述`sigmoid`函数集成到网络架构之中;相反,应该让模型直接输出未经变换的结果给后续的损失计算环节处理。具体来说就是修改源代码中提到的部分,使得返回值不再经过任何额外的非线性映射而保持原样传递下去。 ```python # 原始版本 return F.sigmoid(d0), F.sigmoid(d1), F.sigmoid(d2), F.sigmoid(d3), F.sigmoid(d4), F.sigmoid(d5), F.sigmoid(d6) # 修改后的版本 return d0, d1, d2, d3, d4, d5, d6 ``` 尽管如此,了解如何构建自己的`Sigmoid`函数仍然是有价值的,尤其是在调试过程中想要验证某些假设或是探索不同的设计思路之时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Humbunklung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值