当前位置: 首页 > article >正文

深度学习基础02_损失函数BP算法(上)

目录

一、损失函数

1、线性回归损失函数

1.MAE损失

2.MSE损失

3.SmoothL1Loss

2、多分类损失函数--CrossEntropyLoss

3、二分类损失函数--BCELoss

4、总结

二、BP算法

1、前向传播

1.输入层(Input Layer)到隐藏层(Hidden Layer)

2.隐藏层(Hidden Layer)到输出层(Output Layer)

2、反向传播

1.原理

2.链式法则 

3.案例

神经元计算

损失计算

参数更新

反向传播写法01

反向传播写法02(更常用)


一、损失函数

1、线性回归损失函数

1.MAE损失

MAE(Mean Absolute Error,平均绝对误差)通常也被称为 L1-Loss,通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。

公式

\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} \left| y_i - \hat{y}_i \right|

特点

  1. 鲁棒性:与均方误差(MSE)相比,MAE对异常值(outliers)更为鲁棒,因为它不会像MSE那样对较大误差平方敏感。

  2. 物理意义直观:MAE以与原始数据相同的单位度量误差,使其易于解释。

应用场景

MAE通常用于需要对误差进行线性度量的情况,尤其是当数据中可能存在异常值时,MAE可以避免对异常值的过度惩罚。

import torch
import torch.nn as nn

def test01():
    """
    MAE 平均绝对误差 通常也被称为 L1-Loss
    预测值和真实值之间的绝对差取平均值
    """
    l1_loss_fn = nn.L1Loss()
    y_pred = torch.tensor([1,2,3],dtype=torch.float32)
    y_true = torch.tensor([1.5,2.5,3.5],dtype=torch.float32)
    loss = l1_loss_fn(y_pred,y_true)
    print(loss) # tensor(0.5000)
2.MSE损失

MSE(Mean Squared Error,均方误差),平均评分误差通过对预测值和真实值之间的误差平方取平均值,来衡量预测值与真实值之间的差异。

公式

\text{MSE} = \frac{1}{n} \sum_{i=1}^{n} \left( y_i - \hat{y}_i \right)^2

特点

  1. 平方惩罚:因为误差平方,MSE 对较大误差施加更大惩罚,所以 MSE 对异常值更为敏感。

  2. 凸性:MSE 是一个凸函数,这意味着它具有一个唯一的全局最小值,有助于优化问题的求解。

应用场景

MSE被广泛应用在神经网络中。

def test02():
    """
    MSE 平均平方误差 通常也被称为 L2-Loss
    测值和真实值之间的误差平方取平均值
    """
    l2_loss_fn = nn.MSELoss()
    y_pred = torch.tensor([1,2,3],dtype=torch.float32)
    y_true = torch.tensor([1.5,2.5,3.5],dtype=torch.float32)
    loss = l2_loss_fn(y_pred,y_true)
    print(loss) # tensor(0.2500)
3.SmoothL1Loss

SmoothL1Loss可以做到在损失较小时表现为 L2 损失,而在损失较大时表现为 L1 损失。

公式

\text{SmoothL1Loss}(x) = \begin{cases} 0.5 \cdot x^2, & \text{if } |x| < 1 \\ |x| - 0.5, & \text{otherwise} \end{cases}

所有样本的平均损失:\\L=\frac{1}{n} \sum_{i=1}^{n} L_{i} 

特点

  1. 平滑过渡:当误差较小时,损失函数表现为 L2 Loss(平方惩罚);当误差较大时,损失函数逐渐向 L1 Loss过渡。这种平滑过渡既能对大误差有所控制,又不会对异常值过度敏感。

  2. 稳健性:对于异常值更加稳健,同时在小误差范围内提供了较好的优化效果。

应用场景

SmoothL1Loss常用于需要对大误差进行一定控制但又不希望完全忽略小误差的回归任务。特别适用于目标检测任务中的边界框回归,如 Faster R-CNN 等算法中。

def test03():
    """
    SmoothL1Loss 平滑损失函数
    在损失较小时表现为 L2 损失,而在损失较大时表现为 L1 损失
    """
    # 方式一
    loss_fn = nn.SmoothL1Loss()
    # 方式二
    loss_fn = nn.funtional.smooth_l1_loss
    y_pred = torch.tensor([1,2,3,6],dtype=torch.float32)
    y_true = torch.tensor([1.5,2.5,3.5,4.5],dtype=torch.float32)
    loss = loss_fn(y_pred,y_true)
    print(loss)

2、多分类损失函数--CrossEntropyLoss

交叉熵损失函数,使用在输出层使用softmax激活函数进行多分类时,一般都采用交叉熵损失函数。

公式

\text{CrossEntropyLoss}(y, \hat{y}) = - \sum_{i=1}^{C} y_i \log(\hat{y}_i)

其中,样本的实际标签 y 和模型预测的概率分布\hat{y}_i​, y 是one-hot编码形式,表示真实类别 ,\hat{y}_i 表示第 i 类的概率估计值(经过 softmax 后的概率分布) 。

*one-hot 编码

假设我们有一个三分类问题,三个可能的类别分别是0、1和2。如果某个样本的真实标签是1,那么它的one-hot编码就是 [0,1,0]。这意味着:

  • 第一个位置(索引0)是0,表示该样本不属于类别0。
  • 第二个位置(索引1)是1,表示该样本属于类别1。
  • 第三个位置(索引2)是0,表示该样本不属于类别2。

特点

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

def test04():
    """
    CrossEntropyLoss 交叉熵损失函数
    使用在输出层使用softmax激活函数进行多分类
    """
    # 初始化
    criterion = nn.CrossEntropyLoss()
    # 设有三个类别,模型输出是未经softmax的logits
    one_hot = torch.tensor([[1.5, 2.0, 0.5], [0.5, 1.0, 1.5]]) # 两个输出
    loss = criterion(one_hot, labels) # (L1+L2)/2
    print(loss.item()) # 0.6422001123428345

3、二分类损失函数--BCELoss

二分类交叉熵损失函数,使用在输出层使用sigmoid激活函数进行二分类时。对于二分类问题,CrossEntropyLoss 的简化版本称为二元交叉熵(Binary Cross-Entropy Loss)

公式

\text{BinaryCrossEntropy}(y, \hat{y}) = - \left[ y \log(\hat{y}) + (1 - y) \log(1 - \hat{y}) \right]

 log的底数一般默认为e,y是真实类别目标,根据公式可知L是一个分段函数 :

  • y=1时,L = -log(sigmoid 激活值)
  • y=0时,L = -log(1-sigmoid 激活值)
def test05():
    """
    BCELoss 二分类交叉熵损失函数
    使用在输出层使用sigmoid激活函数进行二分类时
    """
    # y 是模型的输出,已经被sigmoid处理过,确保其值域在(0,1)
    y = torch.tensor([[0.7], [0.2], [0.9], [0.7]])
    # targets 是真实的标签,0或1
    t = torch.tensor([[1], [0], [1], [0]], dtype=torch.float)
    
    # 方式一
    bce_loss_fn = nn.BCELoss()
    loss = bce_loss_fn(y,t)
    # 方式二
    loss = nn.functional.binary_cross_entropy(y,t)
    print(loss)

4、总结

以上损失函数是一个样本的损失值,总样本的损失值是求损失均值即可。

  • 当输出层使用softmax多分类时,使用交叉熵损失函数;

  • 当输出层使用sigmoid二分类时,使用二分类交叉熵损失函数, 比如在逻辑回归中使用;

  • 当功能为线性回归时,使用smooth L1损失函数或均方差损失-L2 loss;

二、BP算法

多层神经网络的学习能力比单层网络强得多。想要训练多层网络,需要更强大的学习算法。误差反向传播算法(Back Propagation)是其中最杰出的代表,它是目前最成功的神经网络学习算法。

现实任务使用神经网络时,大多是在使用 BP 算法进行训练,值得指出的是 BP 算法不仅可用于多层前馈神经网络,还可以用于其他类型的神经网络。

通常说 BP 网络时,一般是指用 BP 算法训练的多层前馈神经网络。

误差反向传播算法(BP)的基本步骤 

  1. 前向传播:正向计算得到预测值。

  2. 计算损失:通过损失函数 L(y{\text{pred}}, y{\text{true}})  计算预测值和真实值的差距。

  3. 梯度计算:反向传播的核心是计算损失函数 L 对每个权重和偏置的梯度。

  4. 更新参数:一旦得到每层梯度,就可以使用梯度下降算法来更新每层的权重和偏置,使得损失逐渐减小。

  5. 迭代训练:将前向传播、梯度计算、参数更新的步骤重复多次,直到损失函数收敛或达到预定的停止条件。

1、前向传播

前向传播(Forward Propagation)把输入数据经过各层神经元的运算并逐层向前传输,一直到输出层为止。

 一个简单的三层神经网络(输入层、隐藏层、输出层)前向传播的基本步骤分析:

1.输入层(Input Layer)到隐藏层(Hidden Layer)

给定输入 x 和权重矩阵 W 及偏置向量 b,隐藏层的输出(激活值)计算如下:

z^{(1)} = W_1 \cdot x + b_1

将z通过激活函数激活:

a^{(1)} = \sigma(z^{(1)})

2.隐藏层(Hidden Layer)到输出层(Output Layer)

隐藏层的输出a^{(1)}经过输出层的权重矩阵 W 和偏置 b 生成最终输出:

z^{(2)} = W_2 \cdot a^{(1)} + b_2

z通过输出层的激活函数激活后就是最终预测结果:

y_{\text{pred}} = a^{(2)} = \sigma(z^{(2)})

前向传播的主要作用

  1. 计算神经网络的输出结果,用于预测或计算损失。

  2. 在反向传播中使用,通过计算损失函数相对于每个参数的梯度来优化网络。

2、反向传播

反向传播(Back Propagation,简称BP)通过计算损失函数相对于每个参数的梯度来调整权重,使模型在训练数据上的表现逐渐优化。

反向传播结合了链式求导法则和梯度下降算法,是神经网络模型训练过程中更新参数的关键步骤。

1.原理

利用链式求导法则对每一层进行求导,直到求出输入层x的导数,然后利用导数值进行梯度更新。

2.链式法则 

链式求导法则(Chain Rule)是微积分中的一个重要法则,用于求复合函数的导数。在深度学习中,链式法则是反向传播算法的基础,这样就可以通过分层的计算求得损失函数相对于每个参数的梯度,以下面函数为例:

\mathrm{f(x)=\frac{1}{1+e^{-(wx+b)}}}

\begin{aligned} \frac{\partial f(x;w,b)}{\partial w}|_{x=1,w=0,b=0}& =\frac{\partial f(x;w,b)}{\partial h_6}\frac{\partial h_6}{\partial h_5}\frac{\partial h_5}{\partial h_4}\frac{\partial h_4}{\partial h_3}\frac{\partial h_3}{\partial h_2}\frac{\partial h_2}{\partial h_1}\frac{\partial h_1}{\partial w} \\ &=1\times-0.25\times1\times1\times-1\times1\times1 \\ &=0.25 \end{aligned}

import torch

def test01():
    """
    链式求导
    """
    x = torch.tensor(1.0)
    w = torch.tensor(0.0,required_grad=True)
    b = torch.tensor(0.0,required_grad=True)

    fx = torch.sigmoid(w*x + b)
    fx.backward() # fx对w求导
    print(w.grad) # tensor(0.2500)
    print(b.grad) # tensor(0.2500)

if __name__ == '__main__':
    test01()
3.案例

神经元计算

输入层到隐藏层:

\mathrm{h}_{1}=\mathrm{w}_{1}*\mathrm{i}_{1}+\mathrm{w}_{2}*\mathrm{i}_{2}+\mathrm{b}_{1}\quad =0.15 * 0.05 + 0.20 * 0.10 + 0.35 =0.3775 \\ k_{1}=sigmoid(h1)=sigmoid(0.3775)=0.5933

同理 h2=0.3925, k2=0.5969

隐藏层到输出层:

\mathrm{o}_{1}=\mathrm{w}_{5}*\mathrm{k}_{1}+\mathrm{w}_{6}*\mathrm{k}_{2}+\mathrm{b}_{2}\quad =0.40 * 0.5933 + 0.45 * 0.5969 + 0.60 =1.1059 \\ m_{1}=sigmoid(o1)=sigmoid(1.1059)=0.7514

同理 o2=1.2249, m2 =0.7729

所以,最终的预测结果分别为: 0.7514、0.7729

损失计算

预测值和真实值(target)进行比较计算损失:

MSELoss = \frac{1}{2}((\mathrm{m}_{1}\mathrm{-target}_{1})^{2}+((\mathrm{m}_{2}\mathrm{-target}_{2})^{2}) \\ = \frac{1}{2}((0.7514-0.01)^{2}+((0.7729-0.99)^{2}) =0.2984

import torch


"""手动计算代码过程"""
i1 = 0.05
i2 = 0.10

b1 = 0.35
b2 = 0.60

def h1():
    w1 = 0.15
    w2 = 0.20
    l1 = i1 * w1 + i2 * w2 + b1
    l1 = torch.tensor(l1)
    return torch.sigmoid(l1) # 1/(1+torch.e**(-l1))
print(f'h1:{h1()}') # 0.5933

def h2():
    w3 = 0.25
    w4 = 0.30
    l2 = i1 * w3 + i2 * w4 + b1
    l2 = torch.tensor(l2)
    return torch.sigmoid(l2) # 1/(1+torch.e**(-l2))
print(f'h2:{h2()}') # 0.5969

def o1():
    w5 = 0.40
    w6 = 0.45
    l3 = h1() * w5 + h2() * w6 + b2
    l3 = torch.tensor(l3)
    m1 = torch.sigmoid(l3) # 1/(1+torch.e**(-l3))
    return m1
print(f'o1:{o1()}') # 0.7514


def o2():
    w7 = 0.50
    w8 = 0.55
    l4 = h1() * w7 + h2() * w8 + b2
    l4 = torch.tensor(l4)
    m2 = torch.sigmoid(l4) # 1/(1+torch.e**(-l4))
    return m2
print(f'o2:{o2()}') # 0.7729


# 手动反向传播
def mse():
    o1_target = 0.01
    o2_target = 0.99
    return 0.5*((o1() - 0.01)**2 + (o2() - 0.99)**2)

loss = mse()

 梯度计算

使用链式法则计算w1,w5,w7梯度:

\begin{aligned} \frac{\partial\mathrm{L}}{\partial\mathrm{w}_{5}}& =\frac{\partial\mathrm{L}}{\partial\mathrm{m}_{1}}*\frac{\partial\mathrm{m}_{1}}{\partial\mathrm{o}_{1}}*\frac{\partial\mathrm{o}_{1}}{\partial\mathrm{w}_{5}} \\ &=(\mathrm{m}_{1}-\mathrm{target}_{1})*\mathrm{sigmoid}(\mathrm{o}_{1})*\left(1-\mathrm{sigmoid}(\mathrm{o}_{1})\right)*\mathrm{k}_{1} \\ &=(0.7514-0.01)*sigmoid(1.1059)*\left(1-sigmoid(1.1059)\right)*0.5933 \\ &=0.0822 \end{aligned}

 \begin{aligned} \frac{\partial\mathrm{L}}{\partial\mathrm{w}_7}& =\frac{\partial\mathrm{L}}{\partial\mathrm{m}_2}*\frac{\partial\mathrm{m}_2}{\partial\mathrm{o}_2}*\frac{\partial\mathrm{o}_2}{\partial\mathrm{w}_7} \\ &=(\mathrm{m}_{2}-\mathrm{target}_{2})*\mathrm{sigmoid}(\mathrm{o}_{2})*\left(1-\mathrm{sigmoid}(\mathrm{o}_{2})\right)*\mathrm{k}_{1} \\ &=(0.7729-0.99)*sigmoid(1.2249)*\left(1-sigmoid(1.2249)\right)*0.5933 \\ &=-0.0226 \end{aligned}

 

import torch


"""反向传播api手动实现"""
def train():
    # 前向传播
    # x1 x2
    i=torch.tensor([[0.05,0.1]])
    model1=torch.nn.Linear(2,2)
    # w1 w2 w3 w4
    model1.weight.data=torch.tensor([[0.15,0.20],
                                     [0.25,0.30]])
    # b1
    model1.bias.data=torch.tensor([0.35,0.35])

    l1_l2=model1(i)
    h1_h2=torch.sigmoid(l1_l2)

    model2=torch.nn.Linear(2,2)
    # w5 w6 w7 w8
    model2.weight.data=torch.tensor([[0.40,0.45],
                                     [0.50,0.55]])
    # b2
    model2.bias.data=torch.tensor([0.60,0.60])
    l3_l4=model2(h1_h2)
    o1_o2=torch.sigmoid(l3_l4)

    # 误差计算
    o1_o2_true=torch.tensor([[0.01,0.99]])
    loss=torch.sum((o1_o2 - o1_o2_true) ** 2) / 2
    # mse=torch.nn.MSELoss()
    # loss=mse(o1_o2,o1_o2_true)

    # 反向传播
    loss.backward()
    print(model1.weight.grad)
    print(model2.weight.grad)


train()
参数更新

现在就可以进行权重更新了:假设学习率是0.5:

w_5=0.40-0.5*0.0822=0.3589 \\ w_7=0.50+0.5*0.0226=0.5113 \\ w_1=0.15-0.5*0.0004=0.1498

整合一下,完整的反向传播写法:

反向传播代码写法01
import torch
import torch.nn as nn
import torch.optim as optim


class MyNet(nn.Module):

    def __init__(self):
        super(MyNet, self).__init__()
        # 定义网络结构
        self.linear1 = nn.Linear(2, 2)
        self.linear2 = nn.Linear(2, 2)
        self.activation = torch.sigmoid

        # 网络参数初始化
        # 隐藏层权重
        self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
        # 输出层权重
        self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
        # 隐藏层偏置
        self.linear1.bias.data = torch.tensor([0.35, 0.35])
        # 输出层偏置
        self.linear2.bias.data = torch.tensor([0.60, 0.60])

    def forward(self, x):
        x = self.linear1(x)
        x = self.activation(x)
        x = self.linear2(x)
        x = self.activation(x)

        return x


def train():
    net = MyNet()
    # 优化方法
    optimizer = optim.SGD(net.parameters(), lr=0.1)
    inputs = torch.tensor([[0.05, 0.10]])
    target = torch.tensor([[0.01, 0.99]])
    # 获得网络输出值
    pred = net(inputs) # module父类已实现前向传播
    mes = torch.nn.MSELoss()
    # 计算误差
    loss = mes(pred, target)
    # 梯度清零
    optimizer.zero_grad()
    # 反向传播
    loss.backward()

    # 打印(w1-w8)观察w5、w7、w1 的梯度值是否与手动计算一致
    print(net.linear1.weight.grad.data)
    print(net.linear2.weight.grad.data)
    #更新梯度
    optimizer.step()
    # 打印更新后的网络参数
    # print(net.state_dict())

train()
反向传播代码写法02(更常用)
import torch
import torch.nn as nn
import torch.optim as optim


class MyNet(nn.Module):

    def __init__(self):
        super(MyNet, self).__init__()
        # 定义网络结构
        # self.linear1 = nn.Linear(2, 2)
        # self.linear2 = nn.Linear(2, 2)
        # self.activation = torch.sigmoid
        self.hide1 = torch.nn.Sequential(nn.Linear(2, 2),torch.nn.Sigmoid())
        self.out = torch.nn.Sequential(nn.Linear(2, 2),torch.nn.Sigmoid())

        # 网络参数初始化 线性层
        # # 隐藏层权重
        # self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
        # # 输出层权重
        # self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
        # # 隐藏层偏置
        # self.linear1.bias.data = torch.tensor([0.35, 0.35])
        # # 输出层偏置
        # self.linear2.bias.data = torch.tensor([0.60, 0.60])
        self.hide1[0].weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
        self.out[0].weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
        self.hide1[0].bias.data = torch.tensor([0.35, 0.35])
        self.out[0].bias.data = torch.tensor([0.60, 0.60])

    def forward(self, x):
        # x = self.linear1(x)
        # x = self.activation(x)
        # x = self.linear2(x)
        # x = self.activation(x)
        x = self.hide1(x)
        x = self.out(x)
        return x


def train():
    net = MyNet()
    # 优化方法
    optimizer = optim.SGD(net.parameters(), lr=0.1)
    inputs = torch.tensor([[0.05, 0.10]])
    target = torch.tensor([[0.01, 0.99]])
    # 获得网络输出值
    pred = net(inputs) # module父类已实现前向传播
    mes = torch.nn.MSELoss()
    # 计算误差
    loss = mes(pred, target)
    # 梯度清零
    optimizer.zero_grad()
    # 反向传播
    loss.backward()

    # 打印(w1-w8)观察w5、w7、w1 的梯度值是否与手动计算一致
    print(net.hide1[0].weight.grad.data) # 0:线性层 1:激活函数
    print(net.out[0].weight.grad.data) # 0:线性层 1:激活函数
    #更新梯度
    optimizer.step()
    # 打印更新后的网络参数
    # print(net.state_dict())

train()


http://www.kler.cn/a/412790.html

相关文章:

  • 【系统架构设计师】高分论文:论软件架构的生命周期
  • Oracle JDK(通常简称为 JDK)和 OpenJDK区别
  • Android 中文文件名排序实现案例教程
  • 【ComfyUI】前景分割ComfyUI-BiRefNet-Hugo (无法选定分割的主体,背景鉴别由模型数据,也叫二分分割,显著性分割)
  • 快速理解微服务中Gateway的概念
  • visionpro实践项目(一)
  • 分布式项目使用Redis实现数据库对象自增主键ID
  • 音视频入门基础:MPEG2-TS专题(8)——TS Header中的适配域
  • 了解UIUX设计
  • Linux 服务器使用指南:诞生与演进以及版本(一)
  • 【软考速通笔记】系统架构设计师⑤——软件工程基础知识
  • 转录组数据挖掘(生物技能树)(第11节)下游分析
  • 【设计模式】1. 构建器模式(Builder Pattern)是一种创建型设计模式
  • 林业产品推荐系统:Spring Boot设计模式
  • 【MySQL系列】使用正则表达式确保`card_secret`字段格式正确
  • 【python】面试宝典(五)
  • 如何将低危的 SSRF 盲注升级为严重漏洞(AWS、S3)
  • SpringBoot开发——详细讲解 Spring Boot 项目中的 POM 配置
  • 利用Prompt工程为LLM提升推理能力
  • 自媒体图文视频自动生成软件|03| 页面和结构介绍
  • <<WTF-Solidity>>学习笔记(part 5-8)
  • 模糊逻辑学习 | 模糊推理 | 模糊逻辑控制
  • 通信综合—8.通信网络安全
  • OpenCV相机标定与3D重建(7)鱼眼镜头立体校正的函数stereoRectify()的使用
  • 设计模式:10、外观模式
  • CTF之密码学(DES)