反向传播的一般情形

前提条件

  • 使用交叉熵损失函数
  • 使用sigmod激活函数
  • 输出层使用softmax函数

网络结构

  • 全连接,输出层没有sigmoid函数,使用softmax函数

目标

  • 推导前提条件下的四个反向传播方程
  • Python实现手写数字识别(两个隐藏层,第一隐藏层192个神经元,第二个隐藏层30个神经元)
    网络结构示意图
    其中,wijlw^{l}_{ij}wijl表示第lll层的第jjj个神经元到第l+1l+1l+1层第iii个神经元的权重,可以将wijlw^{l}_{ij}wijl理解为(Wl)T(W^{l})^{T}(Wl)T对应的元素;bilb^{l}_{i}bil表示第lll层到第l+1l+1l+1层第iii个神经元的偏置。

BP方程推导过程

  • 推导方式:从某个神经元jjj拓展到整个层的神经元
  • 引入中间变量:δjl=∂E∂zjl\delta^{l}_{j}=\frac{\partial\\E}{\partial\\z^{l}_{j}}δjl=zjlE,表示第𝑙层第𝑗个神经元的误差。其中EEE表示损失函数。
    在这里插入图片描述

BP1

定义网络的输出层为第LLL层,则由softmaxsoftmaxsoftmax函数可得,第LLL层第jjj个神经元的输出为:
hjL=ezjL∑j=1dLezjLh^{L}_{j}=\frac{e^{z^{L}_{j}}}{\sum_{j=1}^{d_{L}}e^{z^{L}_{j}}\qquad}hjL=j=1dLezjLezjL(1)
因为损失函数为交叉熵损失函数,则
E=∑j=1dL−yjlnhjLE=\sum_{j=1}^{d_{L}}-y_{j}lnh^{L}_{j}\qquadE=j=1dLyjlnhjL(2)
又因为标签yyy是one-hot(yi=1y_{i}=1yi=1,其余项均为0),所以
E=−lnhiLE=-lnh^{L}_{i}E=lnhiL(3)
进一步地,由式(1)和式(3),可得
E=−lnhiL=−lneziL∑j=1dLezjL=−ziL+ln∑j=1dLezjLE=-lnh^{L}_{i}=-ln\frac{e^{z^{L}_{i}}}{\sum_{j=1}^{d_{L}}e^{z^{L}_{j}}\qquad}=-z^{L}_{i}+ln{\sum_{j=1}^{d_{L}}e^{z^{L}_{j}}\qquad}E=lnhiL=lnj=1dLezjLeziL=ziL+lnj=1dLezjL(4)
下面对δjl=∂E∂zjl\delta^{l}_{j}=\frac{\partial\\E}{\partial\\z^{l}_{j}}δjl=zjlE进行分类讨论,当j=ij=ij=i时,有
δil=∂E∂zil=−1+hiL\delta^{l}_{i}=\frac{\partial\\E}{\partial\\z^{l}_{i}}=-1+h^{L}_{i}δil=zilE=1+hiL(5)
j≠ij{\neq}ij=i时,有
δjl=∂E∂zjl=0+hjL\delta^{l}_{j}=\frac{\partial\\E}{\partial\\z^{l}_{j}}=0+h^{L}_{j}δjl=zjlE=0+hjL(6)
联合式(5)和式(6)可得
δjl=∂E∂zjl=hjL−yj\delta^{l}_{j}=\frac{\partial\\E}{\partial\\z^{l}_{j}}=h^{L}_{j}-y_{j}δjl=zjlE=hjLyj(7)
扩展到矩阵运算,得到BP1
δL=∂E∂zL=hL−y\delta^{L}=\frac{\partial\\E}{\partial\\z^{L}}=h^{L}-yδL=zLE=hLyδL∈RdL×1\delta^{L}{\in}R^{d_{L}{\times}1}δLRdL×1(8)

BP2

BP2是整个反向传播方程最关键的一步。BP2寻找网络层与层之间的联系,即推导δl+1\delta^{l+1}δl+1δl\delta^{l}δl之间的关系,又由δl+1\delta^{l+1}δl+1δl\delta^{l}δl的定义是EEE关于zl+1z^{l+1}zl+1zlz^{l}zl的误差,最终转化为寻找zl+1z^{l+1}zl+1zlz^{l}zl的联系。
在这里插入图片描述
由上图可得,第l+1l+1l+1层的第jjj个神经元zjl+1z^{l+1}_{j}zjl+1接收由第lll层所有神经元发送的权重(多对一的关系),即
zjl+1=∑k=1dlwjklhkl+bjlz^{l+1}_{j}={\sum_{k=1}^{d_{l}}{w^{l}_{jk}}{h_{k}^{l}}\qquad}+b^{l}_{j}zjl+1=k=1dlwjklhkl+bjl(9)
在这里插入图片描述

而第lll层第jjj个神经元zjlz^{l}_{j}zjlhjlh^{l}_{j}hjl之间的差别仅仅是一个sigmoid函数
hjl=σ(zjl)h^{l}_{j}=\sigma(z^{l}_{j})hjl=σ(zjl)(10)
联合式(9)和式(10)可以得到zlz^{l}zlzl+1z^{l+1}zl+1的关系
zjl+1=∑k=1dlwjklσ(zkl)+bjlz^{l+1}_{j}={\sum_{k=1}^{d_{l}}{w^{l}_{jk}}\sigma(z^{l}_{k})\qquad}+b^{l}_{j}zjl+1=k=1dlwjklσ(zkl)+bjl(11)
这里需要注意的是,第lll层的第jjj个神经元向第l+1l+1l+1层所有的神经元都发送了权重(一对多的关系),所以
δjl=∂E∂zjl=∑k=1dl+1∂E∂zkl+1∂zl+1∂zjl=∑k=1dl+1δkl+1wkjlσ,(zjl)\delta^{l}_{j}=\frac{\partial\\E}{\partial\\z^{l}_{j}}={\sum_{k=1}^{d_{l+1}}\frac{\partial\\E}{\partial\\z^{l+1}_{k}}\frac{\partial\\z^{l+1}}{\partial\\z^{l}_{j}}\qquad}\\={\sum_{k=1}^{d_{l+1}}\delta^{l+1}_{k}w^{l}_{kj}\sigma^{,}(z^{l}_{j})\qquad}δjl=zjlE=k=1dl+1zkl+1Ezjlzl+1=k=1dl+1δkl+1wkjlσ(zjl)(12)
在这里插入图片描述

扩展到矩阵运算,其中δl+1∈Rdl+1×1\delta^{l+1}\in{R^{d_{l+1}\times1}}δl+1Rdl+1×1Wl∈Rdl×dl+1W^{l}\in{R^{d_{l}\times{d_{l+1}}}}WlRdl×dl+1zl∈Rdl×1z^{l}\in{R^{d_{l}\times1}}zlRdl×1,得到BP2
δl=∂E∂zl=σ,(zl)⊙(Wlδl+1)\delta^{l}=\frac{\partial\\E}{\partial\\z^{l}}=\sigma^{,}(z^{l})\odot(W^{l}\delta^{l+1})δl=zlE=σ(zl)(Wlδl+1)δl∈Rdl×1\delta^{l}\in{R^{d_{l}\times1}}δlRdl×1(13)
⊙\odot表示哈达玛乘积,即矩阵对应元素相乘)
BP2比较复杂,字母下标很容易搞混淆,关键在于理解层与层之间多对多的关系。

BP3

得到BP2之后,开始正式推导∂E∂bjl−1\frac{\partial\\E}{\partial\\b^{l-1}_{j}}bjl1E(写成∂E∂bjl−1\frac{\partial\\E}{\partial\\b^{l-1}_{j}}bjl1E而不是∂E∂bjl\frac{\partial\\E}{\partial\\b^{l}_{j}}bjlE的形式只是为了对应BP2),这是反向传播过程中真正需要更新的参数。
由上文中的式(9)
zjl+1=∑k=1dlwjklhkl+bjlz^{l+1}_{j}={\sum_{k=1}^{d_{l}}{w^{l}_{jk}}{h_{k}^{l}}\qquad}+b^{l}_{j}zjl+1=k=1dlwjklhkl+bjl(9)
进行简单地变换得到
zjl=∑k=1dl−1wjkl−1hkl−1+bjl−1z^{l}_{j}={\sum_{k=1}^{d_{l-1}}{w^{l-1}_{jk}}{h_{k}^{l-1}}\qquad}+b^{l-1}_{j}zjl=k=1dl1wjkl1hkl1+bjl1(14)
推导可得
∂E∂bjl−1=∂E∂zjl∂zjl∂bjl−1=δjl\frac{\partial\\E}{\partial\\b^{l-1}_{j}}=\frac{\partial\\E}{\partial\\z^{l}_{j}}\frac{\partial\\z^{l}_{j}}{\partial\\b^{l-1}_{j}}=\delta^{l}_{j}bjl1E=zjlEbjl1zjl=δjl
扩展到矩阵运算,得到BP3
∂E∂bl−1=∂E∂zl∂zl∂bl−1=δl\frac{\partial\\E}{\partial\\b^{l-1}}=\frac{\partial\\E}{\partial\\z^{l}}\frac{\partial\\z^{l}}{\partial\\b^{l-1}}=\delta^{l}bl1E=zlEbl1zl=δl

BP4

BP4由式(14)推导∂E∂wjkl−1\frac{\partial\\E}{\partial\\w^{l-1}_{jk}}wjkl1E
zjl=∑k=1dl−1wjkl−1hkl−1+bjl−1z^{l}_{j}={\sum_{k=1}^{d_{l-1}}{w^{l-1}_{jk}}{h_{k}^{l-1}}\qquad}+b^{l-1}_{j}zjl=k=1dl1wjkl1hkl1+bjl1(14)
对于具体的每一个参数wjkl−1{w^{l-1}_{jk}}wjkl1,有
∂E∂wjkl−1=∂E∂zjl∂zjl∂wjkl−1=δjlhkl−1\frac{\partial\\E}{\partial\\w^{l-1}_{jk}}=\frac{\partial\\E}{\partial\\z^{l}_{j}}\frac{\partial\\z^{l}_{j}}{\partial\\w^{l-1}_{jk}}=\delta^{l}_{j}h_{k}^{l-1}wjkl1E=zjlEwjkl1zjl=δjlhkl1
扩展到矩阵运算,δl∈Rdl×1\delta^{l}\in{R^{d_{l}\times1}}δlRdl×1hl−1∈Rdl−1×1h^{l-1}\in{R^{d_{l-1}\times1}}hl1Rdl1×1,得到BP4
∂E∂Wl−1=∂E∂zl∂zl∂Wl−1=hl−1(δl)T\frac{\partial\\E}{\partial\\W^{l-1}}=\frac{\partial\\E}{\partial\\z^{l}}\frac{\partial\\z^{l}}{\partial\\W^{l-1}}=h^{l-1}(\delta^{l})^{T}Wl1E=zlEWl1zl=hl1(δl)T∂E∂Wl−1∈Rdl−1×dl\frac{\partial\\E}{\partial\\W^{l-1}}\in{R^{d_{l-1}\times{d_{l}}}}Wl1ERdl1×dl

BP方程

  • BP1:δL=∂E∂zL=hL−y\delta^{L}=\frac{\partial\\E}{\partial\\z^{L}}=h^{L}-yδL=zLE=hLyδL∈RdL×1\delta^{L}{\in}R^{d_{L}{\times}1}δLRdL×1
  • BP2:δl=∂E∂zl=σ,(zl)⊙(Wlδl+1)\delta^{l}=\frac{\partial\\E}{\partial\\z^{l}}=\sigma^{,}(z^{l})\odot(W^{l}\delta^{l+1})δl=zlE=σ(zl)(Wlδl+1)δl∈Rdl×1\delta^{l}\in{R^{d_{l}\times1}}δlRdl×1(13)
  • BP3:∂E∂bl−1=∂E∂zl∂zl∂bl−1=δl\frac{\partial\\E}{\partial\\b^{l-1}}=\frac{\partial\\E}{\partial\\z^{l}}\frac{\partial\\z^{l}}{\partial\\b^{l-1}}=\delta^{l}bl1E=zlEbl1zl=δl
  • BP4:∂E∂Wl−1=∂E∂zl∂zl∂Wl−1=hl−1(δl)T\frac{\partial\\E}{\partial\\W^{l-1}}=\frac{\partial\\E}{\partial\\z^{l}}\frac{\partial\\z^{l}}{\partial\\W^{l-1}}=h^{l-1}(\delta^{l})^{T}Wl1E=zlEWl1zl=hl1(δl)T∂E∂Wl−1∈Rdl−1×dl\frac{\partial\\E}{\partial\\W^{l-1}}\in{R^{d_{l-1}\times{d_{l}}}}Wl1ERdl1×dl

问题

  • 如果输出层使用sigmoid函数,均方误差损失函数,BP方程会有什么变化?
    答:仅BP1发生变化
  • 如果改用Relu函数作为激活函数,BP方程会有什么变化?
    答:仅BP2发生变化

代码实现

  • Python实现手写数字识别(两个隐藏层,第一隐藏层192个神经元,第二个隐藏层30个神经元)
  • 输入图像像素为28×28=78428\times28=78428×28=784
import _pickle as cPickle
import gzip

import numpy as np


def softmax(x, axis=0):
    # 计算每行的最大值
    row_max = x.max(axis=axis)

    # 每行元素都需要减去对应的最大值,否则求exp(x)会溢出,导致inf情况
    row_max = row_max.reshape(-1, 1)
    x = x - row_max

    # 计算e的指数次幂
    x_exp = np.exp(x)
    x_sum = np.sum(x_exp, axis=axis, keepdims=True)
    s = x_exp / x_sum
    return s


def sigmoid(x):
    return 1.0 / (1 + np.exp(-x))


def sigmoid_derivation(x):
    result = sigmoid(x)
    return result * (1 - result)


class NeuralNetwork:
    """初始化网络"""

    def __init__(self, input_layer=784, hidden_layer1=192, hidden_layer2=30, output_layer=10,
                 learning_rate=0.1):
        """初始化网络结构"""
        # 输入层的节点数
        self.input_layer = input_layer
        # 第一个隐层的节点数
        self.hidden_layer1 = hidden_layer1
        # 第二个隐层的节点数
        self.hidden_layer2 = hidden_layer2
        # 输出层的节点数
        self.output_layer = output_layer
        # 损失函数
        self.activation = sigmoid
        self.activation_derivation = sigmoid_derivation
        # 学习率
        self.learning_rate = learning_rate

        """初始化权重和偏置矩阵"""
        # 输入层到第一个隐层
        self.W1 = np.random.randn(self.input_layer, self.hidden_layer1)
        self.b1 = np.random.randn(self.hidden_layer1, 1)
        # 第一个隐层到第二个隐层
        self.W2 = np.random.randn(self.hidden_layer1, self.hidden_layer2)
        self.b2 = np.random.randn(self.hidden_layer2, 1)
        # 第二个隐层到输出层
        self.W3 = np.random.randn(self.hidden_layer2, self.output_layer)
        self.b3 = np.random.randn(self.output_layer, 1)

    """前向传播"""

    def forward(self, x):
        z2 = np.dot(self.W1.T, x) + self.b1
        h2 = self.activation(z2)
        z3 = np.dot(self.W2.T, h2) + self.b2
        h3 = self.activation(z3)
        z4 = np.dot(self.W3.T, h3) + self.b3
        out = softmax(z4)
        return z2, h2, z3, h3, z4, out

    """训练网络"""

    def train(self, data, epochs=1):
        X, Y = data
        count = len(Y)
        # 将Y转换为one-hot
        new_Y = np.zeros((count, 10))
        # 训练
        for epoch in range(epochs):
            for i in range(count):
                new_Y[i][Y[i]] = 1.0
                # 将X,new_Y转化成二维矩阵
                x = np.array(X[i], ndmin=2).T
                target = np.array(new_Y[i], ndmin=2).T
                """前向传播"""
                z2, h2, z3, h3, z4, out = self.forward(x)
                """计算损失"""
                loss = (-np.log(out) * target).sum()
                print('loss:' + str(loss))
                # if loss < 1e-3:
                #     break
                """反向传播,根据BP方程更新W和b"""
                # BP1
                delta_z4 = out - target
                # 更新第二个隐层到输出层的W,b  z4 = W3.T @ h3 + b3
                delta_W3 = np.dot(h3, delta_z4.T)
                self.W3 -= self.learning_rate * delta_W3
                delta_b3 = delta_z4
                self.b3 -= self.learning_rate * delta_b3
                # 更新第一个隐层到第二个隐层的W,b z3 = W2.T @ h2 + b2, h3 = sigmoid(z3)
                delta_h3 = np.dot(self.W3, delta_z4)
                delta_z3 = np.multiply(self.activation_derivation(z3), delta_h3)
                delta_W2 = np.dot(h2, delta_z3.T)
                self.W2 -= self.learning_rate * delta_W2
                delta_b2 = delta_z3
                self.b2 -= self.learning_rate * delta_b2
                # 更新输入层到第一个隐层的W,b z2 = W1.T @ x + b1, h2 = sigmoid(z2)
                delta_h2 = np.dot(self.W2, delta_z3)
                delta_z2 = np.multiply(self.activation_derivation(z2), delta_h2)
                delta_W1 = np.dot(x, delta_z2.T)
                self.W1 -= self.learning_rate * delta_W1
                delta_b1 = delta_z2
                self.b1 -= self.learning_rate * delta_b1

    """测试"""

    def test(self, data):
        X, Y = data
        count = len(Y)
        # 预测
        res = 0
        for i in range(0, count):
            x = np.array(X[i], ndmin=2).T
            z2, h2, z3, h3, z4, out = self.forward(x)
            # 取out的最大值下标作为预测结果
            predict = np.argmax(out)
            # print(predict, Y[i])
            if int(predict) == int(Y[i]):
                res += 1
        rating = res / count * 100
        print("correct rating: %.2f" % rating + '%')


if __name__ == "__main__":
    # 实例化神经网络
    net = NeuralNetwork()
    # 读取数据
    f = gzip.open('../data/mnist.pkl.gz', 'rb')
    training_data, validation_data, test_data = cPickle.load(f, encoding='latin1')
    f.close()
    net.train(training_data)
    net.test(test_data)

逻辑回归

  • 10个分类器实现手写数字识别
import _pickle as cPickle
import gzip

import numpy as np
import torch
from torch import nn

nets = []


class LogisticRegression(nn.Module):
    def __init__(self, input_layer=784, hidden_layer1=192, hidden_layer2=30, output_layer=1):
        super(LogisticRegression, self).__init__()
        self.fc1 = nn.Linear(input_layer, hidden_layer1)
        self.fc2 = nn.Linear(hidden_layer1, hidden_layer2)
        self.fc3 = nn.Linear(hidden_layer2, output_layer)
        self.activation = nn.Sigmoid()

    def forward(self, x):
        z2 = self.fc1(x)
        h2 = self.activation(z2)
        z3 = self.fc2(h2)
        h3 = self.activation(z3)
        z4 = self.fc3(h3)
        out = self.activation(z4)
        return out


def train(data, epochs=1):
    X, Y = data
    count = len(Y)
    for i in range(10):
        net = LogisticRegression()
        # 定义损失函数和优化器
        criterion = nn.BCELoss()
        optimizer = torch.optim.SGD(net.parameters(), lr=1e-3, momentum=0.9)
        tmp_Y = np.where(Y == i, np.ones_like(Y), np.zeros_like(Y))
        for epoch in range(epochs):
            for j in range(count):
                x = torch.from_numpy(X[j])
                y = torch.from_numpy(tmp_Y[j:j + 1].astype(np.float32))
                out = net(x)
                loss = criterion(out, y)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
                print("loss:{:.4f}".format(loss))
        nets.append(net)


def test(data):
    X, Y = data
    count = len(Y)
    res = 0
    with torch.no_grad():
        for n in range(count):
            out = 0
            index = 0
            print(Y[n])
            for i in range(len(nets)):
                x = torch.from_numpy(X[n])
                x_out = nets[i](x)
                print('{} {}'.format(i, x_out.item()))
                if x_out > out:
                    out = x_out
                    index = i
            if index == Y[n]:
                res += 1
    print(res / count)


if __name__ == "__main__":
    # 实例化神经网络
    # 读取数据
    f = gzip.open('../data/mnist.pkl.gz', 'rb')
    training_data, validation_data, test_data = cPickle.load(f, encoding='latin1')
    f.close()
    train(training_data)
    test(test_data)

<think>我们首先需要总结二分类和多分类逻辑回归的训练流程,然后针对每个步骤(前向传播、损失函数选择、反向传播、优化器使用、训练循环)进行问题检查。 参考引用[1]:二分类逻辑回归模型的基本思路是:线性映射函数$f(x)=\omega^T x$得到得分,通过sigmoid函数映射为概率,最后计算交叉熵损失。 对于多分类,逻辑回归的推广通常是使用softmax函数(引用[3])将线性层的输出映射为多个类别的概率分布,同样使用交叉熵损失。 训练流程总结: 1. 前向传播:计算线性函数$z = \omega^T x + b$(二分类)或$z = W x + b$(多分类,W为每一类对应的权重矩阵),然后通过sigmoid(二分类)或softmax(多分类)得到概率输出。 2. 损失函数:二分类使用二元交叉熵(BCE),多分类使用多元交叉熵(CE)。 3. 反向传播:计算损失函数对参数的梯度。 4. 优化器:使用梯度下降算法(如SGD、Adam等)更新参数。 5. 训练循环:重复上述过程直到收敛。 潜在问题检查: 1. 前向传播: - 二分类:sigmoid函数将输出映射到(0,1),表示正类的概率。 - 多分类:softmax函数将每个类的得分转换为概率,且所有类概率和为1(引用[3])。 - 问题:输入特征是否需要标准化?如果特征量纲差异大,可能导致训练困难。另外,对于多分类,注意softmax函数在数值计算上的稳定性(如指数溢出,引用[3]中示例有处理大数的情形,实际中可考虑减去最大值)。 2. 损失函数选择: - 二分类:二元交叉熵损失函数:$L = -[y \log(p) + (1-y) \log(1-p)]$,其中$p$是sigmoid输出的概率。 - 多分类:交叉熵损失函数:$L = -\sum_{c=1}^C y_c \log(p_c)$,其中$y_c$是类别c的指示变量(0或1),$p_c$是softmax输出的类别c的概率。 - 问题:类别不平衡问题。如果某个类别的样本数量很少,模型可能会偏向多数类。可以引入类别权重或采用其他策略(如重采样)。 3. 反向传播: - 根据链式法则计算梯度(引用[5])。 - 二分类:sigmoid的梯度计算存在饱和问题(当输入很大或很小时,梯度接近0,导致梯度消失)。多分类:softmax的梯度计算相对稳定,但同样需要注意数值问题。 - 问题:梯度消失(尤其是深层网络,但逻辑回归是浅层模型,所以问题不大)。另外,梯度爆炸问题(在逻辑回归中较少见,因为模型简单)。 4. 优化器使用: - 常用优化器:SGD、Adam等。 - 学习率设置:过大导致震荡,过小导致收敛慢。 - 问题:学习率未调整,或者没有使用合适的优化器。对于逻辑回归,通常SGD就足够,但Adam可以自适应调整学习率。 5. 训练循环: - 迭代次数(epoch)和批大小(batch size)的选择。 - 过拟合:训练集上表现好,测试集上表现差。解决:正则化(L1、L2)、早停(early stopping)。 - 问题:没有使用验证集来监控模型性能,导致过拟合。另外,没有进行特征工程(如特征选择、特征变换)可能影响模型性能。 其他问题: - 初始化:权重初始化不当可能导致训练困难(如全零初始化)。通常使用随机初始化(如正态分布或均匀分布)。 - 收敛性:逻辑回归是凸优化问题(二分类在参数空间是凸的,多分类也是凸的?),理论上可以收敛到全局最优,但实际中可能因为学习率等问题无法达到。 - 多分类任务中,确保分类是互斥的(引用[3]),否则不能使用softmax分类器(应改为多标签分类问题,使用多个二分类器)。 优化建议: - 特征标准化:将特征缩放至均值为0,方差为1,有助于优化过程。 - 正则化:在损失函数中加入L1或L2正则项,防止过拟合。 - 学习率调整:使用学习率衰减策略。 - 早停:根据验证集上的性能停止训练。 - 处理类别不平衡:在损失函数中为不同类别设置权重,或者采用过采样/欠采样。 代码实现示例(二分类): 注意:以下代码为简化示例,实际中需考虑数据加载、预处理等步骤。 ```python import torch import torch.nn as nn import torch.optim as optim # 定义模型 class LogisticRegression(nn.Module): def __init__(self, input_dim): super(LogisticRegression, self).__init__() self.linear = nn.Linear(input_dim, 1) # 二分类输出一个值 def forward(self, x): z = self.linear(x) p = torch.sigmoid(z) return p # 准备数据(示例) # 假设X_train, y_train已经准备好,且y_train是0/1的标签 # 初始化模型、损失函数、优化器 model = LogisticRegression(input_dim=10) criterion = nn.BCELoss() # 二元交叉熵 optimizer = optim.Adam(model.parameters(), lr=0.01) # 训练循环 for epoch in range(100): # 前向传播 outputs = model(X_train) loss = criterion(outputs, y_train.unsqueeze(1).float()) # 注意y_train的形状和类型 # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() # 打印训练信息 if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/100], Loss: {loss.item():.4f}') ``` 多分类示例: ```python class MultinomialLogisticRegression(nn.Module): def __init__(self, input_dim, output_dim): super(MultinomialLogisticRegression, self).__init__() self.linear = nn.Linear(input_dim, output_dim) # 输出维度为类别数 def forward(self, x): z = self.linear(x) # 注意:这里不需要显式调用softmax,因为交叉熵损失函数(nn.CrossEntropyLoss)内部会做softmax return z # 返回的是logits # 准备数据,y_train是类别索引(0到C-1) model = MultinomialLogisticRegression(input_dim=10, output_dim=5) criterion = nn.CrossEntropyLoss() # 内部包含softmax optimizer = optim.Adam(model.parameters(), lr=0.01) # 训练循环类似,但注意标签y_train是长整型,且不需要one-hot ``` 注意:在PyTorch中,多分类问题的交叉熵损失函数(nn.CrossEntropyLoss)要求输入是每个类别的得分(logits,即未经过softmax的线性输出),而标签是类别的索引(不是one-hot)。因此,在模型的前向传播中,我们直接返回线性层的输出。 总结:优化训练流程的关键点包括数据预处理、合适的损失函数、正则化、优化器设置以及防止过拟合的策略。同时,注意数值稳定性(如softmax计算时减去最大值)和类别不平衡问题。 根据以上分析,我们可以生成相关问题:</think>### 优化二分类与多分类逻辑回归模型的训练流程总结 #### 1. **前向传播优化与问题检查** - **二分类**:计算线性函数 $z = \omega^T x + b$,通过 **sigmoid** 输出概率:$p = \frac{1}{1+e^{-z}}$ - **多分类**:计算线性函数 $z = Wx + b$($W$ 为权重矩阵),通过 **softmax** 输出概率分布:$p_c = \frac{e^{z_c}}{\sum_{k} e^{z_k}}$ **问题**: - 数值稳定性:softmax 指数计算可能导致溢出(如 $z_c$ 过大),需对 $z$ 减去最大值:$z_c' = z_c - \max(z)$ [^3] - 特征未标准化:输入特征量纲差异大会影响收敛速度,需进行归一化 #### 2. **损失函数选择与问题检查** - **二分类**:二元交叉熵(BCE) $L = -\frac{1}{N} \sum_{i} [y_i \log(p_i) + (1-y_i) \log(1-p_i)]$ - **多分类**:多元交叉熵(CE) $L = -\frac{1}{N} \sum_{i} \sum_{c} y_{i,c} \log(p_{i,c})$ **问题**: - 类别不平衡:少数类样本损失被淹没,需添加类别权重(如 `nn.CrossEntropyLoss(weight=class_weights)`) - 错误使用 softmax:多标签分类(非互斥类别)不可用 softmax,应改用 sigmoid + BCE [^3] #### 3. **反向传播优化与问题检查** - **梯度计算**: - 二分类:$\frac{\partial L}{\partial \omega} = \frac{1}{N} \sum_i (p_i - y_i) x_i$ - 多分类:$\frac{\partial L}{\partial W} = \frac{1}{N} (P - Y)^T X$ ($P$ 为预测概率矩阵)[^5] **问题**: - 梯度消失:sigmoid 在 $|z|$ 较大时梯度接近 0(饱和区),可改用 **LogSoftmax + NLLLoss** 组合提升数值稳定性 - 未剪裁梯度:梯度爆炸时需添加 `torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)` #### 4. **优化器使用与问题检查** - **推荐优化器**: - 小数据集:**SGD**(动量=0.9)或 **L-BFGS**(二阶优化) - 大数据集:**Adam**(自适应学习率) **问题**: - 学习率未调整:固定学习率易震荡或收敛慢,需添加调度器(如 `ReduceLROnPlateau`) - 参数未初始化:权重全零初始化导致对称性破坏,应使用 `nn.init.xavier_uniform_()` #### 5. **训练循环潜在问题** - **设备管理**:未显式指定设备(CPU/GPU),需统一数据与模型位置: ```python device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) # 模型迁移 inputs = inputs.to(device) # 数据迁移 [^4] ``` - **过拟合风险**: - 缺失正则化:未添加 L2 惩罚项(`weight_decay=1e-4`)或 Dropout - 未使用早停(Early Stopping):验证集损失上升时未终止训练 - **批处理问题**: - Batch Size 过大:内存溢出(OOM),需梯度累积 - 未打乱数据:`DataLoader(shuffle=True)` 缺失导致顺序偏差 --- ### 优化后训练流程示例(PyTorch) ```python # 多分类模型(带数值稳定处理) class StableLogisticRegression(nn.Module): def __init__(self, input_dim, num_classes): super().__init__() self.linear = nn.Linear(input_dim, num_classes) def forward(self, x): z = self.linear(x) return torch.log_softmax(z, dim=1) # 避免数值溢出 [^3] # 训练流程优化 model = StableLogisticRegression(input_dim=10, num_classes=5).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=1e-4) # L2正则化 criterion = nn.NLLLoss(weight=class_weights) # 带类别权重的负对数似然 for epoch in range(epochs): for inputs, labels in dataloader: inputs, labels = inputs.to(device), labels.to(device) log_probs = model(inputs) loss = criterion(log_probs, labels) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) # 梯度剪裁 optimizer.step() # 早停检查 if val_loss > prev_val_loss: break ``` --- ### 关键问题修正清单 | 模块 | 常见问题 | 优化方案 | |--------------|--------------------------|-----------------------------------| | **前向传播** | softmax 数值溢出 | 对 logits 减去最大值 [^3] | | **损失函数** | 类别不平衡 | 添加类别权重 `class_weights` | | **反向传播** | sigmoid 梯度消失 | 改用 LogSoftmax + NLLLoss | | **优化器** | 学习率固定 | 添加 `ReduceLROnPlateau` 调度器 | | **训练循环** | 设备不一致 | 显式调用 `.to(device)` [^4] | | **正则化** | 过拟合 | 添加 `weight_decay` 或 Dropout |
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值