从零实现Softmax回归 - d2l-zh项目详解

从零实现Softmax回归 - d2l-zh项目详解

【免费下载链接】d2l-zh 《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。 【免费下载链接】d2l-zh 项目地址: https://gitcode.com/GitHub_Trending/d2/d2l-zh

引言:为什么需要Softmax回归?

在机器学习实践中,我们经常面临分类问题:不是预测"多少",而是预测"哪一个"。比如判断邮件是否为垃圾邮件、识别图像中的物体类别、预测用户是否会订阅服务等。虽然线性回归擅长处理连续值预测,但对于分类任务,我们需要一种能够输出概率分布的模型——这就是Softmax回归的用武之地。

Softmax回归是多分类问题的基石,它能够将线性模型的输出转换为概率分布,为每个类别分配一个0到1之间的概率值,且所有类别的概率之和为1。本文将带你从零开始实现Softmax回归,深入理解其数学原理和实现细节。

Softmax回归的数学基础

核心公式

Softmax函数定义为:

$$ \mathrm{softmax}(\mathbf{o})j = \frac{\exp(o_j)}{\sum{k=1}^q \exp(o_k)} $$

其中$\mathbf{o}$是未规范化的预测(logits),$q$是类别数量。这个公式确保:

  1. 所有输出都是非负数
  2. 所有输出的和为1
  3. 保持原始logits的大小顺序

交叉熵损失函数

对于分类问题,我们使用交叉熵损失函数:

$$ l(\mathbf{y}, \hat{\mathbf{y}}) = - \sum_{j=1}^q y_j \log \hat{y}_j $$

其中$\mathbf{y}$是真实标签的独热编码,$\hat{\mathbf{y}}$是预测的概率分布。

环境设置与数据准备

多框架支持实现

d2l-zh项目支持多种深度学习框架,以下是各框架的初始化代码:

# MXNet
from d2l import mxnet as d2l
from mxnet import autograd, np, npx, gluon
npx.set_np()

# PyTorch  
from d2l import torch as d2l
import torch

# TensorFlow
from d2l import tensorflow as d2l
import tensorflow as tf

# PaddlePaddle
from d2l import paddle as d2l
import paddle

数据加载

使用Fashion-MNIST数据集,批量大小为256:

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

从零实现Softmax回归

1. 初始化模型参数

num_inputs = 784  # 28x28图像展平
num_outputs = 10   # 10个类别

# 权重和偏置初始化
W = np.random.normal(0, 0.01, (num_inputs, num_outputs))
b = np.zeros(num_outputs)
W.attach_grad()
b.attach_grad()

2. 实现Softmax函数

def softmax(X):
    X_exp = d2l.exp(X)
    partition = d2l.reduce_sum(X_exp, 1, keepdims=True)
    return X_exp / partition  # 广播机制

3. 定义模型

def net(X):
    return softmax(d2l.matmul(d2l.reshape(X, (-1, W.shape[0])), W) + b)

4. 实现交叉熵损失函数

def cross_entropy(y_hat, y):
    return - d2l.log(y_hat[range(len(y_hat)), y])

5. 计算分类精度

def accuracy(y_hat, y):
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        y_hat = d2l.argmax(y_hat, axis=1)
    cmp = d2l.astype(y_hat, y.dtype) == y
    return float(d2l.reduce_sum(d2l.astype(cmp, y.dtype)))

6. 训练过程

def train_epoch_ch3(net, train_iter, loss, updater):
    """训练模型一个迭代周期"""
    metric = Accumulator(3)  # 训练损失、训练精度、样本数
    for X, y in train_iter:
        with autograd.record():
            y_hat = net(X)
            l = loss(y_hat, y)
        l.backward()
        updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.size)
    return metric[0] / metric[2], metric[1] / metric[2]

数值稳定性考虑

数值溢出问题

原始Softmax实现可能存在数值稳定性问题:

mermaid

改进方案:Log-Softmax

为了解决数值稳定性问题,可以使用Log-Softmax技巧:

def log_softmax(X):
    X_max = d2l.reduce_max(X, axis=1, keepdims=True)
    X_shifted = X - X_max
    log_sum_exp = d2l.log(d2l.reduce_sum(d2l.exp(X_shifted), axis=1, keepdims=True))
    return X_shifted - log_sum_exp

性能优化技巧

1. 向量化计算

# 小批量矢量化计算
def softmax_vectorized(O):
    """按行执行softmax"""
    O_exp = d2l.exp(O)
    partition = d2l.reduce_sum(O_exp, axis=1, keepdims=True)
    return O_exp / partition

2. 内存优化

# 使用原地操作减少内存分配
def softmax_inplace(X):
    X_exp = d2l.exp(X)
    d2l.reduce_sum(X_exp, axis=1, keepdims=True, out=partition)
    d2l.divide(X_exp, partition, out=X_exp)
    return X_exp

实战:训练与评估

训练配置

lr = 0.1
num_epochs = 10

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

训练循环

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], 
                        ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    
    return train_metrics

性能评估

训练完成后,我们可以评估模型在测试集上的表现:

# 评估测试精度
test_accuracy = evaluate_accuracy(net, test_iter)
print(f'测试精度: {test_accuracy:.3f}')

# 可视化预测结果
def predict_ch3(net, test_iter, n=6):
    for X, y in test_iter:
        break
    trues = d2l.get_fashion_mnist_labels(y)
    preds = d2l.get_fashion_mnist_labels(d2l.argmax(net(X), axis=1))
    titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
    d2l.show_images(d2l.reshape(X[0:n], (n, 28, 28)), 1, n, titles=titles[0:n])

常见问题与解决方案

1. 梯度消失/爆炸

mermaid

2. 过拟合处理

技术描述适用场景
L2正则化添加权重惩罚项所有场景
Dropout随机丢弃神经元深层网络
早停监控验证集性能训练时间有限
数据增强增加训练数据多样性数据量不足

3. 类别不平衡

对于类别不平衡问题,可以采用加权交叉熵损失:

def weighted_cross_entropy(y_hat, y, class_weights):
    """加权交叉熵损失"""
    loss = -d2l.log(y_hat[range(len(y_hat)), y])
    weights = class_weights[y]
    return d2l.reduce_mean(loss * weights)

进阶主题

1. 自定义Softmax温度参数

def softmax_with_temperature(X, temperature=1.0):
    """带温度参数的Softmax"""
    X = X / temperature
    X_exp = d2l.exp(X)
    partition = d2l.reduce_sum(X_exp, axis=1, keepdims=True)
    return X_exp / partition

2. 混合精度训练

def mixed_precision_softmax(X):
    """混合精度Softmax"""
    X = X.astype('float16')
    X_max = d2l.reduce_max(X, axis=1, keepdims=True)
    X_shifted = X - X_max
    X_exp = d2l.exp(X_shifted)
    partition = d2l.reduce_sum(X_exp, axis=1, keepdims=True)
    return (X_exp / partition).astype('float32')

总结与最佳实践

通过本文的从零实现,我们深入理解了Softmax回归的:

  1. 数学原理:Softmax函数如何将logits转换为概率分布
  2. 损失函数:交叉熵损失的理论基础和实践实现
  3. 数值稳定性:处理数值溢出和下溢的技巧
  4. 性能优化:向量化计算和内存优化策略
  5. 实战应用:完整的训练、评估和预测流程

关键要点

  • 🎯 Softmax回归是多分类问题的基本模型
  • 🔢 注意数值稳定性,使用Log-Softmax技巧
  • ⚡ 利用向量化计算提升性能
  • 📊 监控训练过程,防止过拟合
  • 🧪 在不同框架下保持一致的实现

下一步学习

掌握了Softmax回归的基础后,建议继续学习:

  • 多层感知机(MLP)和深度神经网络
  • 卷积神经网络(CNN)用于图像分类
  • 循环神经网络(RNN)用于序列数据
  • 注意力机制和Transformer架构

Softmax回归作为深度学习的入门模型,为你后续学习更复杂的神经网络架构奠定了坚实的基础。

【免费下载链接】d2l-zh 《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。 【免费下载链接】d2l-zh 项目地址: https://gitcode.com/GitHub_Trending/d2/d2l-zh

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值