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

深度学习之-“全连接网络的反向传播”

基于书中第十章,本节中,我们将深入学习反向传播的原理,并通过MNIST手写数字识别任务,结合PyTorch代码实现,手动编写反向传播逻辑,从而加深对于反向传播内部机制的理解。

神经网络与反向传播的基本概念

神经网络是一种由多层神经元组成的计算模型,每一层神经元通过权重和偏置连接起来。神经网络的学习过程可以分为两个阶段:前向传播和反向传播。

  • 前向传播:输入数据从输入层经过隐藏层,最终到达输出层,计算网络的预测结果。
  • 反向传播:根据预测结果与真实标签之间的误差,从输出层逐层向前计算梯度,并更新网络的权重和偏置。

反向传播的核心是链式法则,它通过将误差从输出层传递回输入层,计算每一层参数的梯度,从而指导参数的更新。

MNIST手写数字识别任务

MNIST是一个经典的手写数字识别数据集,包含60,000张训练图像和10,000张测试图像,每张图像是一个28x28的灰度图,标签为0到9的数字。我们的目标是构建一个神经网络,能够正确识别这些手写数字。

反向传播的数学原理

为了更好地理解反向传播,我们需要从数学角度分析它的工作原理。假设我们有一个简单的两层神经网络,输入层大小为784(28x28),隐藏层大小为128,输出层大小为10(对应10个数字类别)。

前向传播

前向传播的计算过程如下:

  1. 输入层到隐藏层:
    在这里插入图片描述
    其中,X是输入数据,W1是输入层到隐藏层的权重矩阵,b1是偏置,σ是激活函数(如ReLU)。
  2. 隐藏层到输出层:
    在这里插入图片描述
    其中,W2是隐藏层到输出层的权重矩阵,b2是偏置,softmax函数用于将输出转换为概率分布。

损失函数

我们使用交叉熵损失函数来衡量预测结果与真实标签之间的差异:
在这里插入图片描述
其中,yi是真实标签的one-hot编码,a2i是输出层的预测概率。

反向传播

反向传播的目标是计算损失函数对每一层参数的梯度,并更新参数。具体步骤如下:

  1. 计算输出层的误差:
    在这里插入图片描述

  2. 计算隐藏层的误差:
    在这里插入图片描述
    其中,σ′是激活函数的导数。

  3. 计算梯度并更新参数:
    在这里插入图片描述

  4. 使用梯度下降法更新参数:
    在这里插入图片描述
    其中,η是学习率。

代码实现

下面我们通过PyTorch实现一个简单的两层神经网络,并手动编写反向传播逻辑,同时利用CUDA加速训练过程。

import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# 检查CUDA是否可用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Using device: {device}')

# 定义数据预处理
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

# 加载MNIST数据集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

# 定义神经网络模型
class SimpleNN:
    def __init__(self, input_size, hidden_size, output_size):
        # 初始化参数
        self.W1 = torch.randn(input_size, hidden_size, device=device) * 0.01
        self.b1 = torch.zeros(1, hidden_size, device=device)
        self.W2 = torch.randn(hidden_size, output_size, device=device) * 0.01
        self.b2 = torch.zeros(1, output_size, device=device)
    
    def forward(self, X):
        # 前向传播
        self.z1 = torch.matmul(X, self.W1) + self.b1
        self.a1 = torch.relu(self.z1)
        self.z2 = torch.matmul(self.a1, self.W2) + self.b2
        self.a2 = torch.softmax(self.z2, dim=1)
        return self.a2
    
    def backward(self, X, y, output, learning_rate):
        # 反向传播
        m = X.shape[0]
        
        # 输出层误差
        dz2 = output - y
        dW2 = torch.matmul(self.a1.T, dz2) / m
        db2 = torch.sum(dz2, dim=0, keepdim=True) / m
        
        # 隐藏层误差
        dz1 = torch.matmul(dz2, self.W2.T) * (self.a1 > 0).float()
        dW1 = torch.matmul(X.T, dz1) / m
        db1 = torch.sum(dz1, dim=0, keepdim=True) / m
        
        # 更新参数
        self.W2 -= learning_rate * dW2
        self.b2 -= learning_rate * db2
        self.W1 -= learning_rate * dW1
        self.b1 -= learning_rate * db1
    
    def train(self, train_loader, epochs, learning_rate):
        for epoch in range(epochs):
            for images, labels in train_loader:
                # 将图像展平并移动到GPU
                images = images.view(-1, 28*28).to(device)
                
                # 将标签转换为one-hot编码并移动到GPU
                y = torch.zeros(labels.size(0), 10, device=device)
                y[torch.arange(labels.size(0)), labels] = 1
                
                # 前向传播
                output = self.forward(images)
                
                # 反向传播
                self.backward(images, y, output, learning_rate)
            
            if (epoch+1) % 5 == 0:
                print(f'Epoch [{epoch+1}/{epochs}]')

    def evaluate(self, test_loader):
        correct = 0
        total = 0
        for images, labels in test_loader:
            images = images.view(-1, 28*28).to(device)
            outputs = self.forward(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted.cpu() == labels).sum().item()
        
        print(f'Test Accuracy: {100 * correct / total:.2f}%')

# 初始化模型
input_size = 28 * 28
hidden_size = 128
output_size = 10
model = SimpleNN(input_size, hidden_size, output_size)

# 训练模型
model.train(train_loader, epochs=20, learning_rate=0.1)

# 测试模型
model.evaluate(test_loader)

在SimpleNN的init部分,W1 和 W2 分别是输入层到隐藏层和隐藏层到输出层的权重矩阵。我们使用 torch.randn 生成服从标准正态分布的随机数,并乘以 0.01 来缩小初始值的范围。b1 和 b2 是偏置,初始化为零。所有参数都被放置在指定的设备(如GPU)上。

在forward()中,实现了前向传播的过程。首先,输入数据 X 与权重矩阵 W1 进行矩阵乘法,再加上偏置 b1,得到隐藏层的加权输入 z1。然后,通过ReLU激活函数对 z1 进行非线性变换,得到隐藏层的激活值 a1。之后,隐藏层的激活值 a1 与权重矩阵 W2 进行矩阵乘法,再加上偏置 b2,得到输出层的加权输入 z2。最后,通过softmax函数将 z2 转换为概率分布 a2,表示每个类别的预测概率。

在反向传播中,通过链式法则,误差从输出层逐层向前传递,计算每一层的梯度。首先,计算输出层的误差 dz2,即预测值 output 与真实标签 y 的差值。然后,计算权重 W2 的梯度 dW2,通过将隐藏层的激活值 a1 的转置与误差 dz2 相乘,并除以样本数 m。偏置 b2 的梯度 db2 是误差 dz2 的均值。之后,计算隐藏层的误差 dz1,通过将输出层的误差 dz2 与权重矩阵 W2 的转置相乘,再乘以ReLU激活函数的导数(即 a1 > 0 的布尔值转换为浮点数)。然后,计算权重 W1 的梯度 dW1,通过将输入数据 X 的转置与误差 dz1 相乘,并除以样本数 m。偏置 b1 的梯度 db1 是误差 dz1 的均值。最后,使用梯度下降法更新参数。权重和偏置分别减去学习率与对应梯度的乘积。

运行结果如下,可以看到通过上述代码,我们能够得到较高的准确率。
在这里插入图片描述

深入思考

反向传播是神经网络训练的核心算法,但它并非完美无缺。在实际应用中,我们可能会遇到以下问题:

  1. 梯度消失:在深层网络中,梯度可能会逐渐变小,导致靠近输入层的参数几乎无法更新。使用ReLU激活函数和批量归一化可以有效缓解这一问题。
  2. 过拟合:神经网络容易过拟合训练数据。可以通过正则化(如L2正则化)和Dropout来减少过拟合。
  3. 计算效率:反向传播的计算复杂度较高,尤其是在大规模数据集和深层网络中。使用GPU加速和分布式计算可以显著提高训练速度。

总结

通过MNIST手写数字识别任务,我们从理论和代码两个层面深入探讨了反向传播的原理和实现。不仅学习了反向传播的数学原理,还通过手动编写反向传播逻辑,更好地理解了其内部机制。


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

相关文章:

  • TikTok隐私保护措施:确保用户安全
  • 利用 AWS API Gateway 和 Lambda 节省成本的指南
  • 数据库基础一(初步了解数据库)
  • 分布式锁—1.原理算法和使用建议二
  • 基于Matlab的多目标粒子群优化
  • 从“Switch-case“到“智能模式“:C#模式匹配的终极进化指南
  • 基于大数据的空气质量数据可视化分析系统
  • OpenCV给图像添加噪声
  • Express + MongoDB 实现用户登出
  • DDD 架构之领域驱动设计【通俗易懂】
  • 从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(二)
  • 【洛谷贪心算法题】P1094纪念品分组
  • 10分钟熟练掌握宝兰德中间件部署 iServer
  • Starrocks入门(二)
  • 深度剖析 Video-RAG:厦门大学和罗切斯特大学联合推出的一种用于长视频理解的检索增强生成技术
  • 基于大数据的音乐网站数据分析与可视化推荐系统
  • HTML邮件的制作以及遇到的问题
  • Qt常用控件之多行输入框QTextEdit
  • RabbitMQ系列(四)基本概念之Exchange
  • 行为型模式 - 职责链模式 (Chain of Responsibility Pattern)