Skip to content

神经网络


神经网络(NN)是一种模拟人脑工作方式的数学模型或算法结构,它被广泛应用于人工智能、深度学习、语音识别、图像识别等领域。

它的目标是:模拟人脑中神经元如何接收、处理和传递信息,让计算机也能“像人脑一样学习”。


组成部分


人工神经网络通常由以下几个部分组成:

  1. 输入层(Input Layer)

    • 就是接收原始数据的地方,比如图像的像素值、文本的编码等。
  2. 隐藏层(Hidden Layers)

    • 类似“思考”的过程。输入经过加权处理 + 非线性变换,再传给下一层。

    • 隐藏层的多寡、结构决定了模型的“智能程度”。

  3. 输出层(Output Layer)

    • 最终给出预测结果,比如“是狗还是猫”、“分数是多少”等。

其中,每一层都有若干个神经元:

多个神经元来构建神经网络,相邻层之间的神经元相互连接:

  • 同一层的神经元之间没有连接。
  • \(N\) 层的每个神经元和第 \(N-1\) 层的所有神经元相连(这就是full connectedi的含义),这就是全连接神经网络。
  • \(N-1\) 层神经元的输出就是第 \(N\) 层神经元的输入。
  • 每个连接都有一个权重值 (\(w\) 系数和 \(b\) 系数)。

激活函数


激活函数用于对每层的输出数据进行变换,进而为整个网络注入了非线性因素。此时,神经网络就可以拟合各种曲线。

  • 没有引入非线性因素的网络等价于使用一个线性模型来拟合。
  • 通过给网络输出增加激活函数,实现引入非线性因素,使得网络模型可以逼近任意函数,提升网络对复杂问题的拟合能力。

导入需要的库

python
import matplotlib.pyplot as plt
# 首先导入所有必要的库
import numpy as np

# 设置样式
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

Sigmod 函数


函数定义:

\[\sigma(x) = \frac{1}{1 + e^{-x}} \]

导数:

\[\sigma'(x) = \sigma(x)(1 - \sigma(x)) \]

图像:

python
# Sigmoid函数及其导数实现
def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def sigmoid_derivative(x):
    s = sigmoid(x)
    return s * (1 - s)


# 绘制图像
x = np.linspace(-5, 5, 500)
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(x, sigmoid(x), label='Sigmoid', color='blue')
plt.title('Sigmoid函数')
plt.xlabel('x')
plt.ylabel('σ(x)')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(x, sigmoid_derivative(x), label='Sigmoid导数', color='red')
plt.title('Sigmoid导数')
plt.xlabel('x')
plt.ylabel(\'(x)')
plt.legend()

plt.tight_layout()
plt.show()

png

特点:

  • 将输入压缩到(0,1)区间,当输入的值大致在 <-6 或者 >6 时,意味着输入任何值得到的激活值都是差不多的,这样会丢失部分的信息
  • 比如:输入100和输出10000经过sigmoid的激活值几乎都是等于1的,但是输入的数据之间相差100倍的信息就丢失了

分析原因:

python
# 标记信息丢失区间
plt.figure(figsize=(8, 5))
plt.plot(x, sigmoid_derivative(x), label='Sigmoid导数', color='red')

# 标记导数接近0的区域
plt.fill_between(x, sigmoid_derivative(x), where=(x>2.5)|(x<-2.5),
                 color='red', alpha=0.3, label='梯度消失区域')
plt.title('Sigmoid导数及信息丢失区间')
plt.xlabel('x')
plt.ylabel(\'(x)')
plt.legend()
plt.show()

png

由图像可知:

  • 当|x| > 2.5 时,导数接近 0,梯度几乎消失
  • 此时网络参数将更新极其缓慢,意味着在这些区域输入的变化几乎不会影响输出,网络难以学习
  • 在深度网络中,多层小梯度相乘会导致梯度指数级减小

一般来说,sigmoid网络在5层之内就会产生梯度消失现象。而且,该激活函数并不是以0为中心的,所以在实践中这种激活函数使用的很少。sigmoid函数一般只用于二分类的输出层。


Tanh 函数


函数定义:

\[\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \]

导数:

\[\tanh'(x) = 1 - \tanh^2(x) \]

图像:

python
# Tanh函数及其导数实现
def tanh(x):
    return np.tanh(x)

def tanh_derivative(x):
    return 1 - np.tanh(x)**2

# 绘制图像
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(x, tanh(x), label='Tanh', color='blue')
plt.title('Tanh函数')
plt.xlabel('x')
plt.ylabel('tanh(x)')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(x, tanh_derivative(x), label='Tanh导数', color='red')
plt.title('Tanh导数')
plt.xlabel('x')
plt.ylabel('tanh\'(x)')
plt.legend()

plt.tight_layout()
plt.show()

png

特点:

  • 将输入压缩到(-1,1)区间,当输入在 <-3 或者 >3 时将被映射为 -1 或者 1。
  • 与Sigmoid相比,它是以0为中心的,且梯度相对于sigmoid大,使得其收敛速度要比Sigmoid快,减少迭代次数。

分析原因:

python
plt.figure(figsize=(8, 5))
plt.plot(x, tanh_derivative(x), label='Tanh导数', color='red')

# 标记导数接近0的区域
plt.fill_between(x, tanh_derivative(x), where=(x>2.5)|(x<-2.5),
                color='red', alpha=0.3, label='梯度消失区域')
plt.title('Tanh导数及信息丢失区间')
plt.xlabel('x')
plt.ylabel('tanh\'(x)')
plt.legend()
plt.show()

png

由图像可知:

  • 当|x|>2.5时,导数接近0,存在梯度消失问题,但相比Sigmoid,梯度消失的程度较轻
  • 其导数值范围是(0,1],而Sigmoid是(0,0.25]

若使用时可在隐藏层使用tanh函数,如RNN和LSTM网络中的隐藏层,在输出层使用sigmoid函数。


ReLU 函数


函数定义:

\[\text{ReLU}(x) = \max(0, x) \]

导数:

\[\text{ReLU}'(x) = \begin{cases} 1 & \text{if } x > 0 \\ 0 & \text{if } x \leq 0 \end{cases} \]

图像:

python
# ReLU函数及其导数实现
def relu(x):
    return np.maximum(0, x)

def relu_derivative(x):
    return np.where(x > 0, 1, 0)

# 绘制图像
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(x, relu(x), label='ReLU', color='blue')
plt.title('ReLU函数')
plt.xlabel('x')
plt.ylabel('ReLU(x)')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(x, relu_derivative(x), label='ReLU导数', color='red')
plt.title('ReLU导数')
plt.xlabel('x')
plt.ylabel('ReLU\'(x)')
plt.legend()

plt.tight_layout()
plt.show()

png

特点:

  • ReLU 激活函数将小于 0 的值映射为 0,而大于 0 的值则保持不变,它更加重视正信号,而忽略负信号,这种激活函数运算更为简单,能够提高模型的训练效率。
  • 当 x<0 时,ReLU 导数为 0,而当 x>0 时,则不存在饱和问题。所以,ReLU 能够在 x>0 时保持梯度不衰减,从而缓解梯度消失问题。
  • 然而,随着训练的推进,部分输入会落入小于 0 区域,导致对应权重无法更新。这种现象被称为“神经元死亡”

采用 Sigmoid 函数,计算量大(指数运算),反向传播求误差梯度时,计算量相对大,而采用ReLU激活函数,整个过程的计算量节省很多。

Sigmoid 函数反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。ReLU 会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生,适用于CNN等计算密集型网络和需要快速收敛的场景。


Softmax 函数


Softmax函数将任意实值向量转换为概率分布

函数定义:

\[\text{Softmax}(\mathbf{x})_i = \frac{e^{x_i}}{\sum_{j=1}^n e^{x_j}} \quad \text{对于} \ i = 1, ..., n \]

其中:

  • \(\mathbf{x} = (x_1, x_2, ..., x_n)\) 是输入向量
  • 输出向量的每个元素 \(\text{Softmax}(\mathbf{x})_i \in (0,1)\)
  • 所有输出元素之和为 \(1\)\(\sum_{i=1}^n \text{Softmax}(\mathbf{x})_i = 1\)

Softmax的导数是一个Jacobian矩阵:

\[\frac{\partial \text{Softmax}(\mathbf{x})_i}{\partial x_j} = \begin{cases} \text{Softmax}(\mathbf{x})_i (1 - \text{Softmax}(\mathbf{x})_i) & \text{如果 } i = j \\ -\text{Softmax}(\mathbf{x})_i \text{Softmax}(\mathbf{x})_j & \text{如果 } i \neq j \end{cases} \]

图像:

python
# Softmax函数实现
def softmax(x):
    e_x = np.exp(x - np.max(x))  # 数值稳定处理
    return e_x / e_x.sum()

# 1D情况下的Softmax及其导数
x_softmax = np.linspace(-5, 5, 500)
y_softmax = softmax(x_softmax)
softmax_derivative_1d = y_softmax * (1 - y_softmax)  # 1D特殊情况

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(x_softmax, y_softmax, label='Softmax', color='blue')
plt.title('Softmax函数(1D输入)')
plt.xlabel('x')
plt.ylabel('Softmax(x)')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(x_softmax, softmax_derivative_1d, label='Softmax导数(1D)', color='red')
plt.title('Softmax导数(1D情况)')
plt.xlabel('x')
plt.ylabel('Softmax\'(x)')
plt.legend()

plt.tight_layout()
plt.show()

png

特点:

  • Softmax 就是将网络输出的 logits 通过 softmax函数,就映射成为(O,1)的值
  • 这些值的累和为 1(满足概率的性质),那么我们将它理解成概率,选取概率最大(也就是值对应最大的)节点,作为我们的预测目标类别。

分析原因:

python
plt.figure(figsize=(8, 5))
plt.plot(x_softmax, softmax_derivative_1d, label='Softmax导数', color='red')

# 标记导数接近0的区域
plt.fill_between(x_softmax, softmax_derivative_1d,
                 where=(x_softmax>2)|(x_softmax<-2),
                 color='red', alpha=0.3, label='梯度消失区域')
plt.title('Softmax导数及信息丢失区间(1D情况)')
plt.xlabel('x')
plt.ylabel('Softmax\'(x)')
plt.legend()
plt.show()

png

由图像可知:

  • 当输入值差异很大时,输出接近one-hot编码,导数接近0
  • 数值不稳定问题需要特殊处理(减去最大值)
  • 多维情况下导数形成 Jacobian 矩阵,非对角线元素不为零

Softmax 不是严格意义上的信息“丢失”,而是信息“压缩”甚至“掩盖”,在数值差异过大的情况下可能带来梯度消失、训练不稳定等问题。尤其在深层网络或大类分类中更明显。


选择推荐


隐藏层

激活函数适用场景注意事项
Sigmoid1. 二分类输出层
2. 需要概率输出的场景
3. 早期神经网络(现较少用于隐藏层)
1. 梯度消失问题严重
2. 输出非零中心(可能影响梯度下降效率)
3. 计算量较大(含指数运算)
Tanh1. 隐藏层激活(优于Sigmoid)
2. RNN/LSTM网络
3. 需要输出范围在[-1,1]的场景
1. 梯度消失问题仍存在(但比Sigmoid轻)
2. 零中心输出(梯度下降更稳定)
ReLU1. CNN和DNN的隐藏层(最常用)
2. 需要稀疏激活的场景
3. 深层网络
1. 死亡ReLU问题(负值区梯度为0)
2. 输出非零中心
3. 无上界(可能梯度爆炸)
  • 优先选择 ReLU 激活函数,如果 ReLu效果不好,那么尝试 Leaky ReLu
  • 如果你使用了 ReLU,需要注意一下 Dead ReLU 问题,避免出现大的梯度从而导致过多的神经元死亡
  • 少用使用 Sigmoid 激活函数,可以尝试使用 Tanh 激活函数

输出层

激活函数适用场景注意事项
Sigmoid1. 二分类任务(输出概率)
2. 多标签分类(每个标签独立概率)
1. 搭配 Binary Cross-Entropy 损失函数
2. 梯度消失严重,避免用于隐藏层
3. 输出非零中心化(0,1),可能影响训练收敛
Tanh1. 有界回归任务(输出需归一化到 \([-1, 1]\)
2. 生成模型(如GAN生成器输出)
1. 输出零中心化,梯度比Sigmoid稳定
2. 仍存在梯度消失问题,不适合极深网络
ReLU1. 计数型回归(如预测商品销量,输出非负)
2. 轻量级输出层(需快速计算)
1. 输出无上界,可能不稳定
2. 需谨慎初始化(如He初始化)
3. 死亡神经元问题较少(因输出层无多次反向传播)
Softmax1. 单标签多分类任务(如分类10个类别)
2. 输出互斥概率分布(概率和为1)
1. 搭配 Categorical Cross-Entropy 损失函数
2. 计算时需数值稳定化(如 logits - max(logits)
3. 类别过多时计算成本高(如超1000类)
  • 二分类问题选择 Sigmod激活函数
  • 多分类问题选择 Softmax 激活函数

参数初始化


参数初始化的目的:

  • 保证激活值在合理范围内分布(不过大/不过小)
  • 保证反向传播时梯度不会消失或爆炸

零初始化


方法

\[W = 0, \quad b = 0 \]
python
import torch.nn as nn

model = nn.Linear(10, 5)
nn.init.constant_(model.weight, 0.0)
nn.init.constant_(model.bias, 0.0)

问题:每个神经元计算完全相同的结果(对称性),导致网络无法学习有用特征。

结论:不推荐


随机初始化


方法:高斯分布或均匀分布

\[W \sim \mathcal{N}(0, 1) \quad \text{or} \quad \text{Uniform}(-1, 1) \]
python
nn.init.normal_(model.weight, mean=0.0, std=1.0)
nn.init.constant_(model.bias, 0.0)

nn.init.uniform(model.weight)  # 均匀分布初始化

问题:可能会导致不同层之间的数值范围差异过大,造成梯度爆炸或消失。

结论:不够稳定,需改进


Xavier 初始化


适用:Sigmoid / Tanh 激活函数

公式

  • 均匀分布版本:
    \[W \sim \mathcal{U}\left[-\frac{\sqrt{6}}{\sqrt{n_{\text{in}} + n_{\text{out}}}}, \frac{\sqrt{6}}{\sqrt{n_{\text{in}} + n_{\text{out}}}}\right] \]
python
nn.init.xavier_uniform_(model.weight)
  • 正态分布版本:
    \[W \sim \mathcal{N}\left(0, \frac{2}{n_{\text{in}} + n_{\text{out}}}\right) \]
python
nn.init.xavier_normal_(model.weight)

优点:保持前向传播/反向传播中方差一致,避免梯度消失


He 初始化(Kaiming Initialization)


适用:ReLU / Leaky ReLU 激活函数

公式

  • 正态分布版本:
    \[W \sim \mathcal{N}\left(0, \frac{2}{n_{\text{in}}}\right) \]
python
nn.init.kaiming_uniform_(model.weight, mode='fan_in', nonlinearity='relu')
  • 均匀分布版本:
    \[W \sim \mathcal{U}\left[-\sqrt{\frac{6}{n_{\text{in}}}}, \sqrt{\frac{6}{n_{\text{in}}}}\right] \]
python
nn.init.kaiming_normal_(model.weight, mode='fan_in', nonlinearity='relu')

优点:显著缓解ReLU网络的死亡神经元问题,稳定训练。


案例实战


一个简单的二分类任务前馈神经网络:

  • 第1个隐藏层:权重初始化采用标准化的 Xavier 初始化激活函数使用 Sigmod
  • 第2个隐藏层:权重初始化采用标准化的He初始化激活函数采用 ReLU
  • 输出层:线性层,二分类,采用 Softmax 做数据归一化
python
import torch
import torch.nn as nn
from torchsummary import summary  # 计算模型参数,查看模型结构


# 创建神经网络模型类
class Model(nn.Module):
    # 初始化属性值
    def __init__(self):
        super(Model, self).__init__()  # 调用父类的初始化属性值
        # 创建第一个隐藏层模型,3个输入特征,3个输出特征
        self.linear1 = nn.Linear(3, 3)
        # 初始化权重 - 标准化的Xavier初始化
        nn.init.xavier_normal_(self.linear1.weight)

        # 创建第二个隐藏层模型,3个输入特征(上一层的输出特征),2个输出特征
        self.linear2 = nn.Linear(3, 2)
        # 初始化权重 - He初始化
        nn.init.kaiming_normal_(self.linear2.weight)

        # 创建输出层模型
        self.out = nn.Linear(2, 2)

    # 创建前向传播方法,自动执行forward()方法
    def forward(self, x):
        # 数据经过第一个线性层
        x = self.linear1(x)
        # 使用sigmoid激活函数
        x = torch.sigmoid(x)

        # 数据经过第二个线性层
        x = self.linear2(x)
        # 使用relu激活函数
        x = torch.relu(x)  # 注意:这里应该是torch.relu(x),原代码有拼写错误

        # 数据经过输出层
        x = self.out(x)
        # 使用softmax激活函数
        # dim=-1: 每一维度行数据相加为1
        x = torch.softmax(x, dim=-1)

        return x


if __name__ == '__main__':

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print('current device:', device)

    # 实例化model对象
    my_model = Model().to(device)

    # 随机产生数据
    my_data = torch.randn(5, 3).to(device)  # 5个样本,每个样本3个特征
    print("my_data shape:", my_data.shape)

    # 数据经过神经网络模型训练
    output = my_model(my_data)
    print("output shape->", output.shape)

    # 计算模型参数
    # 计算每层每个神经元的w和b个数总和
    summary(my_model, input_size=(3,), batch_size=5, device=str(device))

    # 查看模型参数
    print("查看模型参数w和b:")
    for name, parameter in my_model.named_parameters():
        print(name, parameter)

模型设计:

层级输入维度输出维度激活函数初始化方法
Linear133SigmoidXavier(适用于Sigmoid)
Linear232ReLUHe(适用于ReLU)
Output Layer22Softmax默认初始化

为什么 x = torch.softmax(x, dim=-1)

  • dim 决定了 在哪个维度上做归一化,也就是在这个维度上让所有值的和为 1。
  • dim=-1 是一种写法,表示 最后一个维度,无论输入是 2D、3D 还是更多维。
  • dim=1dim=-1 在 2D 情况下是一样的,都是对“列”归一化。
  • 但在更高维(比如 3D、4D)中,dim=-1 可以自动适配最后一维,更通用和稳健。

模型的参数个数 = 权重数 + 偏置数 = (in_features × out_features) + out_features

计算该模型的参数量:

  • 第一个隐藏层:nn.Linear(3, 3),共 3 * 3 + 3 = 12 个参数
  • 第二个隐藏层:nn.Linear(3, 2),共 3 * 2 + 2 = 8 个参数
  • 输出层:nn.Linear(2, 2),共 2 * 2 + 2 = 6 个参数

故一共有 26 个可训练参数


损失函数


在机器学习和深度学习中,损失函数是用来衡量模型参数的质量的函数,衡量的方式是比较网络输出和真实输出的差异:


多分类损失函数


公式定义


在多分类任务通常使用 Softmax 将 logits 转换为概率的形式,所以多分类的交叉熵损失也叫做 Softmax 损失,计算方式如下:

首先,将模型的原始输出(logits)通过 Softmax 函数转换为概率分布:

\[p_i = \frac{e^{z_i}}{\sum_{j=1}^C e^{z_j}} \]

其中:

  • \(z_i\) 是第 \(i\) 类的 logit 值,
  • \(C\) 是类别总数,
  • \(p_i\) 是第 \(i\) 类的预测概率。

然后,计算真实标签与预测概率之间的交叉熵损失:

\[L = -\sum_{i=1}^C y_i \log(p_i) \]

其中:

  • \(y_i\) 是第 \(i\) 类的真实标签(one-hot 编码形式,即真实类别为 1,其余为 0),
  • \(p_i\) 是第 \(i\) 类的预测概率,
  • \(C\) 是类别总数。

通常将 Softmax 和交叉熵合并表示为:

\[L = -\log\left(\frac{e^{z_k}}{\sum_{j=1}^C e^{z_j}}\right) \]

其中 \(z_k\) 是真实类别对应的 logit 值。


计算示例


假设:

  • 模型输出的 logits(未归一化的分数):

    \[\mathbf{z} = [2.0, 1.0, 0.1] \]
  • 真实标签(one-hot 编码):

    \[\mathbf{y} = [0, 1, 0] \]

首先,将 logits 通过 Softmax 函数转换为概率分布:

\[p_i = \frac{e^{z_i}}{\sum_{j=1}^C e^{z_j}} \]

得到:

\[\begin{align*} e^{z_1} &= e^{2.0} \approx 7.389 \\ e^{z_2} &= e^{1.0} \approx 2.718 \\ e^{z_3} &= e^{0.1} \approx 1.105 \\ \sum e^{z_j} &= 7.389 + 2.718 + 1.105 \approx 11.212 \\ \end{align*} \]

所以 Softmax 概率为:

\[\begin{align*} p_1 &= \frac{7.389}{11.212} \approx 0.659 \\ p_2 &= \frac{2.718}{11.212} \approx 0.242 \\ p_3 &= \frac{1.105}{11.212} \approx 0.099 \\ \end{align*} \]

即:

\[\mathbf{p} = [0.659, 0.242, 0.099] \]

由交叉熵损失公式:

\[L = -\sum_{i=1}^C y_i \log(p_i) \]

由于真实标签是 one-hot 编码(\(y_2 = 1\),其余为 0),所以只需计算真实类别对应的 \(-\log(p_2)\)

\[L = -(0\log(0.659) + 1\log(0.242) + 0\log(0.099)) \approx -(-1.418) \approx 1.418 \]

代码实现:

python
import torch
import torch.nn as nn

# 定义 logits 和真实标签(注意:这里 y 是类别索引,不是 one-hot)
z = torch.tensor([[2.0, 1.0, 0.1]], dtype=torch.float32)  # 必须是 2D (batch_size, num_classes)
y = torch.tensor([1], dtype=torch.long)  # 类别索引(1 表示第2类)

# 使用 nn.CrossEntropyLoss(自动包含 Softmax + 交叉熵)
criterion = nn.CrossEntropyLoss()
loss = criterion(z, y)

print("Logits:", z)
print("真实类别:", y.item())  # 1
print("交叉熵损失:", loss.item())  # ≈1.418

二分类损失函数


公式定义


在二分类任务中,通常使用 Sigmoid 函数将模型的原始输出(logit)转换为概率值,然后计算 二元交叉熵损失,具体步骤如下:

首先适用 Sigmoid 函数将 logit 转换为概率:

\[p = \sigma(z) = \frac{1}{1 + e^{-z}} \]

其中:

  • \(z\) 是模型的原始输出(logit),
  • \(p\) 是预测为正类(类别 1)的概率。

再计算元交叉熵损失:

\[L = -\left[ y \log(p) + (1 - y) \log(1 - p) \right] \]

其中:

  • \(y\) 是真实标签(0 或 1),
  • \(p\) 是预测为正类的概率。

特点:

  • \(y = 1\) 时,损失为 \(-\log(p)\)(希望 \(p\) 越大越好)。
  • \(y = 0\) 时,损失为 \(-\log(1 - p)\)(希望 \(p\) 越小越好)。

计算示例


假设:

  • 模型输出的 logit:
    \[z = 0.8 \]
  • 真实标签:
    \[y = 1 \]
  1. 计算 Sigmoid 概率:
    \[p = \frac{1}{1 + e^{-0.8}} \approx \frac{1}{1 + 0.449} \approx 0.690 \]
  2. 计算交叉熵损失:
    \[L = -\left[ 1 \cdot \log(0.690) + 0 \cdot \log(1 - 0.690) \right] \approx -\log(0.690) \approx 0.371 \]

代码实现:

python
import torch
import torch.nn as nn

# 定义 logit 和真实标签
z = torch.tensor([0.8], dtype=torch.float32)  # 注意是 1D 或 2D
y = torch.tensor([1.0], dtype=torch.float32)  # 二分类标签必须是浮点数

# 使用 nn.BCEWithLogitsLoss(内置 Sigmoid + 交叉熵)
criterion = nn.BCEWithLogitsLoss()
loss = criterion(z, y)

print("Logit:", z.item())  # 0.8
print("真实标签:", y.item())  # 1
print("Sigmoid 概率:", torch.sigmoid(z).item())  # ≈0.690
print("二元交叉熵损失:", loss.item())  # ≈0.371

回归MAE损失函数


公式定义


平均绝对误差(Mean Absolute Error, MAE)是一种用于回归任务的损失函数,MAE也被称为 L1 Loss,计算预测值与真实值之间绝对差值的平均值。

公式如下:

\[L = \frac{1}{N} \sum_{i=1}^N |y_i - \hat{y}_i| \]

其中:

  • \(y_i\) 是第 \(i\) 个样本的真实值,
  • \(\hat{y}_i\) 是第 \(i\) 个样本的预测值,
  • \(N\) 是样本数量。

特点:

  • 鲁棒性:MAE 对异常值不敏感,因为它是绝对误差的平均值,不像均方误差(MSE)会放大异常值的影响。
  • 梯度:MAE 的梯度是常数(±1),在优化时不会因为误差大小而改变步长,但在接近最小值时可能收敛较慢。
  • 输出解释:MAE 的值与实际目标值的单位一致,直观易解释(例如,MAE=2 表示平均误差为 2 个单位)。

计算示例


假设:

  • 真实值:\([3.0, -0.5, 2.0, 7.0]\)
  • 预测值:\([2.5, 0.0, 2.0, 8.0]\)

计算绝对误差:

\[|3.0 - 2.5| = 0.5 \\ |-0.5 - 0.0| = 0.5 \\ |2.0 - 2.0| = 0.0 \\ |7.0 - 8.0| = 1.0 \\ \]

MAE 损失:

\[L = \frac{0.5 + 0.5 + 0.0 + 1.0}{4} = \frac{2.0}{4} = 0.5 \]

代码实现:

python
import torch
import torch.nn as nn

# 真实值和预测值(假设 batch_size=4)
y_true = torch.tensor([3.0, -0.5, 2.0, 7.0])
y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0])

# 使用 PyTorch 的 L1Loss(即 MAE)
criterion = nn.L1Loss()
loss = criterion(y_pred, y_true)

print("MAE 损失:", loss.item())  # 输出 0.5

回归MSE损失函数


公式定义


均方误差(Mean Squared Error, MSE)是回归任务中最常用的损失函数之一,MSE也被称为L2 Loss,计算预测值与真实值之间平方差值的平均值。

公式如下:

\[L = \frac{1}{N} \sum_{i=1}^N (y_i - \hat{y}_i)^2 \]

其中:

  • \(y_i\) 是第 \(i\) 个样本的真实值,
  • \(\hat{y}_i\) 是第 \(i\) 个样本的预测值,
  • \(N\) 是样本数量。

特点:

  • 对异常值敏感:MSE会放大较大误差的影响(因为平方操作),因此对异常值比MAE更敏感。
  • 梯度特性:MSE的梯度与误差大小成正比(梯度为\(2(y_i - \hat{y}_i)\)),在误差较大时梯度也较大,有助于快速收敛,但在接近最小值时可能不稳定。
  • 输出解释:MSE的单位是目标值的平方(例如,若目标单位是"米",MSE单位是"平方米"),因此需开平方(RMSE)才能与实际单位一致。

计算示例


假设:

  • 真实值:\([3.0, -0.5, 2.0, 7.0]\)
  • 预测值:\([2.5, 0.0, 2.0, 8.0]\)

计算平方误差:

\[(3.0 - 2.5)^2 = 0.25 \\ (-0.5 - 0.0)^2 = 0.25 \\ (2.0 - 2.0)^2 = 0.0 \\ (7.0 - 8.0)^2 = 1.0 \\ \]

MSE 损失:

\[L = \frac{0.25 + 0.25 + 0.0 + 1.0}{4} = \frac{1.5}{4} = 0.375 \]

代码实现:

python
import torch
import torch.nn as nn

# 真实值和预测值(假设 batch_size=4)
y_true = torch.tensor([3.0, -0.5, 2.0, 7.0])
y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0])

# 使用 PyTorch 的 MSELoss
criterion = nn.MSELoss()
loss = criterion(y_pred, y_true)

print("MSE 损失:", loss.item())  # 输出 0.375

平滑L1损失函数


公式定义


平滑L1损失(Smooth L1 Loss)是L1 Loss(MAE)和L2 Loss(MSE)的结合体,由Ross Girshick在Fast R-CNN中提出。它在误差较小时使用平方项(类似MSE),在误差较大时使用线性项(类似MAE),兼具两者的优点。

公式如下:

\[L = \frac{1}{N} \sum_{i=1}^N z_i \]

其中:

\[z_i = \begin{cases} 0.5(y_i - \hat{y}_i)^2 & \text{if} |y_i - \hat{y}_i| < 1 \\ |y_i - \hat{y}_i| - 0.5 & \text{otherwise} \end{cases} \]

特点:

  • 鲁棒性:对异常值比MSE更鲁棒(大误差时退化为线性)
  • 梯度平滑:在误差接近0时梯度逐渐减小(避免MSE在极小误差时的不稳定)
  • 收敛性:比MAE在接近最优解时收敛更快(因小误差时使用二次项)

对比图像:


计算示例


假设:

  • 真实值:\([1.2, -0.8, 3.0, -2.5]\)
  • 预测值:\([0.9, -0.5, 2.6, -1.8]\)
  • 阈值 \(\beta=1\)(默认值)

逐项计算:

\(|1.2-0.9|=0.3<1\)\(0.5 \times 0.3^2 = 0.045\)

\(|-0.8-(-0.5)|=0.3<1\)\(0.5 \times 0.3^2 = 0.045\)

\(|3.0-2.6|=0.4<1\)\(0.5 \times 0.4^2 = 0.08\)

\(|-2.5-(-1.8)|=0.7<1\)\(0.5 \times 0.7^2 = 0.245\)

总损失:

\[L = \frac{0.045 + 0.045 + 0.08 + 0.245}{4} = 0.10375 \]

代码实现:

python
import torch
import torch.nn as nn

# 真实值和预测值
y_true = torch.tensor([1.2, -0.8, 3.0, -2.5])
y_pred = torch.tensor([0.9, -0.5, 2.6, -1.8])

# PyTorch的SmoothL1Loss(默认beta=1.0)
criterion = nn.SmoothL1Loss()
loss = criterion(y_pred, y_true)

print("Smooth L1 Loss:", loss.item())  # 输出 0.10375

PyTorch中的 beta参数 控制二次项与线性项的分界点:

  • |y - ŷ| < beta 时使用二次项
  • |y - ŷ| >= beta 时使用线性项

不同beta值的影响:

python
# 示例:调整beta值
criterion_beta05 = nn.SmoothL1Loss(beta=0.5)  # 更早切换到线性区域
criterion_beta2 = nn.SmoothL1Loss(beta=2.0)  # 更晚切换到线性区域