深度学习-多分类

发布于:2025-07-10 ⋅ 阅读:(20) ⋅ 点赞:(0)

在这里插入图片描述
​开头摘要​​:
本文将深入探讨如何使用PyTorch实现基于Softmax回归的MNIST手写数字识别系统。从多分类问题的核心概念出发,详细解析​​One-Hot编码​​技术如何将类别标签向量化,剖析​​交叉熵损失函数​​的数学原理及其在训练中的优化机制。通过完整代码实战,展示数据加载、网络构建、模型训练与评估的全流程,并重点介绍​​TensorBoard可视化​​工具的应用技巧。本教程涵盖模型保存加载、预测结果可视化等实用功能,为初学者提供从理论到实践的全面指导,帮助快速掌握分类任务的核心技术栈。

多分类

  • 输出是每个类别的概率,要求有多少个分类最终的输出层就有多少个神经元
  • 各分类输出概率的总和是1(使用softmax归一化)
    在这里插入图片描述

one_hot编码

One-Hot 编码是机器学习中处理​​分类数据​​的核心技术,它将离散类别转换为向量表示,使其能被神经网络处理。

一句话:在一个样本中:n个分类,结果是第k个,则 y one-hot = [ 0 , ⋯   , 1 ⏟ 第k位 , ⋯   , 0 ] y_{\text{one-hot}} = [0, \cdots, \underbrace{1}_{\text{第k位}}, \cdots, 0] yone-hot=[0,,k 1,,0]

编码原理

对于一个包含 C 个类别的特征:

  • 创建长度为 C 的零向量
  • 对第 k 类数据,将其向量中第 k 个位置设为 1
  • 其余位置保持为 0

y one-hot = [ 0 , ⋯   , 1 ⏟ 第k位 , ⋯   , 0 ] y_{\text{one-hot}} = [0, \cdots, \underbrace{1}_{\text{第k位}}, \cdots, 0] yone-hot=[0,,k 1,,0]

假设动物分类的类别为:[“狗”, “猫”, “鸟”]
类别 One-Hot 编码
狗 [1, 0, 0]
猫 [0, 1, 0]
鸟 [0, 0, 1]

交叉熵损失函数

一句话:在 H ( y , p i ) = − ∑ x y log ⁡ p i H(y,p_i)=-\sum_{x}y\log p_i H(y,pi)=xylogpi中,只有一条1*log(pi)为最终交叉熵损失函数值,其它都是0*log(pi)=0

交叉熵损失函数与逻辑回归中的损失函数效果相同,都是为如何调整参数指明方向,即通过求取梯度,调整参数使损失函数的值逼近0,只是交叉熵损失函数用在多分类中
L ( y ^ , y ) = − y log ⁡ ( y ^ ) − ( 1 − y ) log ⁡ ( 1 − y ^ ) L(\hat{y}, y) = - y \log(\hat{y}) - (1 - y) \log(1 - \hat{y}) L(y^,y)=ylog(y^)(1y)log(1y^)

交叉熵损失函数
H ( y , p i ) = − ∑ x y log ⁡ p i H(y,p_i)=-\sum_{x}y\log p_i H(y,pi)=xylogpi

y i :是真实标签(真实分布),通常采用 o n e − h o t 编码(真实类别为 1 ,其余为 0 ) y_i:是真实标签(真实分布),通常采用one-hot编码(真实类别为1,其余为0) yi:是真实标签(真实分布),通常采用onehot编码(真实类别为1,其余为0
(乘的时候按类,分开乘了,0*log or 1*log ,单个样本,最终结果取决于那个唯一的1*log的值)

p i :是预测概率(模型输出的概率分布) p_i:是预测概率(模型输出的概率分布) pi:是预测概率(模型输出的概率分布)

log ⁡ ( p i ) :是预测概率的对数值 \log(p_i):是预测概率的对数值 log(pi):是预测概率的对数值

整体计算:是真实标签 y i 与预测概率对数 log ⁡ ( p i ) 的乘积再求和取负 整体计算:是真实标签y_i与预测概率对数\log(p_i)的乘积再求和取负 整体计算:是真实标签yi与预测概率对数log(pi)的乘积再求和取负

示例

在这里插入图片描述

  • L=0.357 表示当前预测不够准确(理想值应接近0)
  • Log的底数无所谓,经过训练,任何底数的结果都是相同的

log(x)
在这里插入图片描述

实战案例基于mnnist的手写识别

TensorBoard的使用

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(comment='test_tensorboard')  # 用于记录要可视化的数据
#writer = SummaryWriter(存放的地址)

如果不指定绝对路径,PyTorch 默认创建runs在当前文件夹下
在这里插入图片描述

在你安装TensorBoard的虚拟py环境中运行以下代码即可

tensorboard --logdir="这个event文件所在目录的绝对地址"

在这里插入图片描述
在这里插入图片描述

部分代码解释

import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from torchvision.datasets import MNIST
import matplotlib.pyplot as plt
import os
import numpy as np
from datetime import datetime
import os
log_dir = "runs"
os.makedirs(log_dir, exist_ok=True)

创建一个名为runs的文件夹

import matplotlib.pyplot as plt
writer = SummaryWriter(log_dir=log_subdir)

记录数据便于后续画图

3.定义网络基本框架

class SoftmaxRegression(torch.nn.Module):
    #括号中的 torch.nn.Module 表示你的 SoftmaxRegression 类 继承自 PyTorch 的 Module 基类
    def __init__(self):  #self 指代 当前类的实例对象(即正在创建的具体模型)
        super().__init__()
        # 单层网络结构:784输入 -> 10输出
        self.linear = torch.nn.Linear(28 * 28, 10)#定义了这个网络的基本结构,有784个输入特征,10个输出
        
    def forward(self, x):
        # 应用log_softmax到线性层输出
        return torch.nn.functional.log_softmax(self.linear(x), dim=1)
        

log_softmax(self.linear(x), dim=1)相当于两步
softmax_output = exp(z_i) / sum(exp(z_j)) # 转换为概率分布
log_softmax = log(softmax_output) # 取自然对数

torch.nn.functional.log_softmax(self.linear(x), dim=1),dim=1
im=0:跨样本操作(通常不需要)(每个样本的第n类概率加起来为1)
dim=1:跨类别操作(分类任务的标准做法)(每个样本的n个类的各个概率加起来为1)

在这个模型中没有隐藏层,这是一个单层神经网络(也称为 Softmax 回归或多元逻辑回归),是直接从784个输出特征到,输出层的十个输出神经元

线性变换-神经元a0
线性变换-神经元a1
1......1
线性变换-神经元a9
输入的784个特征x
激活函数log_softmax(z)

4.加载训练数据

def get_data_loader(is_train, batch_size=128):
    transform = transforms.Compose([
        transforms.ToTensor(),#张量化图像
        transforms.Normalize((0.1307,), (0.3081,))  # MNIST的均值和标准差
    ])
    dataset = MNIST(root='./data', train=is_train, download=True, transform=transform)
    return DataLoader(dataset, batch_size=batch_size, shuffle=is_train, pin_memory=True)

dataset = MNIST( # 创建MNIST数据集对象
root=‘./data’, # 指定数据存储路径为当前目录下的data文件夹
train=is_train, # 确定加载训练集(True)还是测试集(False)
download=True, # 如果本地没有数据集则自动下载
transform=transform # 应用指定的数据预处理转换
)
return DataLoader( # 返回数据加载器
dataset, # 使用的数据集对象
batch_size=batch_size, # 指定每次加载的数据批大小
shuffle=is_train, # 决定是否打乱数据顺序
pin_memory=True # 内存优化选项(GPU训练时使用)
)

5.返回正确率

#test_data是测试数据
#net 是一个 ​​已经实例化并训练好的神经网络对象​​
    """评估模型准确率"""
def evaluate(test_data, net): 
    net.eval()
    correct = 0
    total = 0

    with torch.no_grad():
        for images, labels in test_data:
            images, labels = images.to(device), labels.to(device)
            outputs = net(images.view(-1, 28 * 28))
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    return correct / total

6.保存训练结果

def save_model(net, filename='softmax_model.pth'):#当前目录下
    """保存模型"""
    torch.save(net.state_dict(), filename)#提取模型的所有可学习参数
    print(f"模型已保存至 {filename}")n

#net 是一个 ​​已经实例化并训练好的神经网络对象​​

7.加载之前的训练结果

def load_model(net, filename='softmax_model.pth'):
    """加载模型"""
    if os.path.exists(filename):
        net.load_state_dict(torch.load(filename, map_location=device))
        #map_location=device是为了指定模型加载到哪个设备上(CPU或GPU)
        print(f"模型已从 {filename} 加载")
    else:
        print(f"警告: 未找到模型文件 {filename}")
    return net

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)

8.可视化

def visualize_predictions(model, test_loader, num_images=12):
    """可视化模型预测结果"""
    model.eval()
    images, labels = next(iter(test_loader))
    images, labels = images.to(device), labels.to(device)

    with torch.no_grad():
        outputs = model(images.view(-1, 28 * 28))
        _, predictions = torch.max(outputs, 1)

    plt.figure(figsize=(12, 8))
    for i in range(num_images):
        plt.subplot(3, 4, i + 1)
        img = images[i].cpu().numpy().squeeze()
        plt.imshow(img, cmap='gray')
        plt.title(f"预测: {predictions[i].item()} (真实: {labels[i].item()})")
        plt.axis('off')

    plt.tight_layout()
    plt.savefig('softmax_predictions.png', dpi=150)
    plt.show()

    # 将图像添加到TensorBoard(使用PIL图像替代)
    from PIL import Image
    from torchvision.utils import save_image

    # 创建临时图像文件
    temp_img_path = "temp_grid.png"
    save_image(images[:num_images], temp_img_path, nrow=4)

    # 读取并添加到TensorBoard
    img = Image.open(temp_img_path)
    img_array = np.array(img)
    writer.add_image('predictions', img_array, dataformats='HWC')

总体实现

import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from torchvision.datasets import MNIST
import matplotlib.pyplot as plt
import os
import numpy as np
from datetime import datetime

# 解决Matplotlib中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号

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

# 确保日志目录存在
log_dir = "runs"
os.makedirs(log_dir, exist_ok=True)

# 创建TensorBoard记录器
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_subdir = os.path.join(log_dir, f"softmax_mnist_{timestamp}")
writer = SummaryWriter(log_dir=log_subdir) #用来画图的数据就放在log_dir=log_subdir里

class SoftmaxRegression(torch.nn.Module):
    #括号中的 torch.nn.Module 表示你的 SoftmaxRegression 类 继承自 PyTorch 的 Module 基类
    """简单的Softmax回归模型"""
    def __init__(self):  #self 指代 当前类的实例对象(即正在创建的具体模型)
        super().__init__()
        # 单层网络结构:784输入 -> 10输出
        self.linear = torch.nn.Linear(28 * 28, 10)

    def forward(self, x):
        # 应用log_softmax到线性层输出
        return torch.nn.functional.log_softmax(self.linear(x), dim=1)

def get_data_loader(is_train, batch_size=128):
    """获取数据加载器"""
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))  # MNIST的均值和标准差
    ])
    dataset = MNIST(root='./data', train=is_train, download=True, transform=transform)
    return DataLoader(dataset, batch_size=batch_size, shuffle=is_train, pin_memory=True)

def evaluate(test_data, net):
    """评估模型准确率"""
    net.eval()
    correct = 0
    total = 0

    with torch.no_grad():
        for images, labels in test_data:
            images, labels = images.to(device), labels.to(device)
            outputs = net(images.view(-1, 28 * 28))
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    return correct / total

def save_model(net, filename='softmax_model.pth'):
    """保存模型"""
    torch.save(net.state_dict(), filename)
    print(f"模型已保存至 {filename}")

def load_model(net, filename='softmax_model.pth'):
    """加载模型"""
    if os.path.exists(filename):
        net.load_state_dict(torch.load(filename, map_location=device))
        print(f"模型已从 {filename} 加载")
    else:
        print(f"警告: 未找到模型文件 {filename}")
    return net

def visualize_predictions(model, test_loader, num_images=12):
    """可视化模型预测结果"""
    model.eval()
    images, labels = next(iter(test_loader))
    images, labels = images.to(device), labels.to(device)

    with torch.no_grad():
        outputs = model(images.view(-1, 28 * 28))
        _, predictions = torch.max(outputs, 1)

    plt.figure(figsize=(12, 8))
    for i in range(num_images):
        plt.subplot(3, 4, i + 1)
        img = images[i].cpu().numpy().squeeze()
        plt.imshow(img, cmap='gray')
        plt.title(f"预测: {predictions[i].item()} (真实: {labels[i].item()})")
        plt.axis('off')

    plt.tight_layout()
    plt.savefig('softmax_predictions.png', dpi=150)
    plt.show()

    # 将图像添加到TensorBoard(使用PIL图像替代)
    from PIL import Image
    from torchvision.utils import save_image

    # 创建临时图像文件
    temp_img_path = "temp_grid.png"
    save_image(images[:num_images], temp_img_path, nrow=4)

    # 读取并添加到TensorBoard
    img = Image.open(temp_img_path)
    img_array = np.array(img)
    writer.add_image('predictions', img_array, dataformats='HWC')

def main():
    # 获取数据加载器
    train_loader = get_data_loader(is_train=True, batch_size=128)
    test_loader = get_data_loader(is_train=False, batch_size=512)

    # 创建模型
    model = SoftmaxRegression().to(device)
    print(f"模型参数量: {sum(p.numel() for p in model.parameters()):,}")

    # 尝试加载现有模型
    model = load_model(model)

    # 评估初始准确率
    init_acc = evaluate(test_loader, model)
    print(f"初始准确率: {init_acc:.4f}")
    writer.add_scalar('Accuracy/test', init_acc, 0)

    # 使用Adam优化器
    optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

    # 训练循环
    total_step = 0
    for epoch in range(10):
        model.train()
        total_loss = 0
        correct = 0
        total = 0

        for i, (images, labels) in enumerate(train_loader):
            images, labels = images.to(device), labels.to(device)

            # 前向传播
            outputs = model(images.view(-1, 28 * 28))
            loss = torch.nn.functional.nll_loss(outputs, labels)

            # 反向传播和优化
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            # 统计信息
            total_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

            # 每100个batch记录一次
            if (i + 1) % 100 == 0:
                train_acc = correct / total
                avg_loss = total_loss / (i + 1)
                print(f"Epoch [{epoch + 1}/10], Step [{i + 1}/{len(train_loader)}], "
                      f"Loss: {avg_loss:.4f}, Accuracy: {train_acc:.4f}")

                # 记录到TensorBoard
                writer.add_scalar('Loss/train', avg_loss, total_step)
                writer.add_scalar('Accuracy/train', train_acc, total_step)
                total_step += 1

        # 每个epoch结束后评估测试集
        test_acc = evaluate(test_loader, model)
        print(f"Epoch [{epoch + 1}/10], 测试准确率: {test_acc:.4f}")
        writer.add_scalar('Accuracy/test', test_acc, epoch)

    # 训练后保存模型
    save_model(model)

    # 最终评估
    final_acc = evaluate(test_loader, model)
    print(f"最终测试准确率: {final_acc:.4f}")

    # 可视化预测结果
    visualize_predictions(model, test_loader)

    # 在TensorBoard中添加模型图
    dummy_input = torch.randn(1, 784).to(device)
    writer.add_graph(model, dummy_input)

    # 关闭TensorBoard写入器
    writer.close()

    print(f"TensorBoard日志保存在: {log_subdir}")
    print("使用命令查看TensorBoard: tensorboard --logdir=runs")

if __name__ == '__main__':
    main()

核心收获

1. 多分类问题本质
○ 输出层神经元数=分类数
○ 使用Softmax确保概率归一化: σ ( z ) j = e z j ∑ k = 1 K e z k \sigma(z)_j = \frac{e^{z_j}}{\sum_{k=1}^K e^{z_k}} σ(z)j=k=1Kezkezj
2. 数据编码技术
○ One-Hot编码: y 猫 = [ 0 , 1 , 0 ] y_{\text{猫}} = [0,1,0] y=[0,1,0]
○ 数据标准化: N o r m a l i z e ( ( 0.1307 , ) , ( 0.3081 , ) ) Normalize((0.1307,), (0.3081,)) Normalize((0.1307,),(0.3081,))
3. 损失函数优化
○ 交叉熵损失: L = − ∑ y i log ⁡ ( p i ) L = -\sum y_i \log(p_i) L=yilog(pi)
○ Adam优化器自适应调整学习率

代码

数据加载
网络构建
训练循环
评估验证
结果可视化
模型部署

网站公告

今日签到

点亮在社区的每一天
去签到