【深度学习4】softmax回归及其实现
·
# 硬性”类别:属于哪个类别; “软性”类别:属于每个类别的概率
# 表示分类数据的简单方法:独热编码(one-hot encoding)。
# 独热编码是一个向量,它的分量和类别一样多。
# 类别对应的分量设置为1,其他所有分量设置为0
# 与线性回归一样,softmax回归也是一个单层神经网络,softmax回归的输出层也是全连接层
# 问题:每次输入是一个2*2的灰度图像
# 我们可以用一个标量表示每个像素值,每个图像对应四个特征。 此外,假设每个图像属于类别“猫”“鸡”和“狗”中的一个。
# 我们有4个特征和3个可能的输出类别, 我们将需要12个标量来表示权重(带下标的), 3个标量来表示偏置(带下标的)
# 要将输出视为概率,我们必须保证在任何数据上的输出都是非负的且总和为1
# softmax函数能够将未规范化的预测变换为非负数并且总和为1,同时让模型保持可导的性质。
# 为了完成这一目标,我们首先对每个未规范化的预测求幂,这样可以确保输出非负。
# 为了确保最终输出的概率值总和为1,我们再让每个求幂后的结果除以它们的总和
# 我们需要一个训练的目标函数,来激励模型精准地估计概率。 例如, 在分类器输出0.5的所有样本中,我们希望这些样本是刚好有一半实际上属于预测的类别。 这个属性叫做校准(calibration)。
# 损失函数:最大似然估计
# softmax运算获取一个向量并将其映射为概率。
# softmax回归适用于分类问题,它使用了softmax运算中输出类别的概率分布。
# 交叉熵是一个衡量两个概率分布之间差异的很好的度量,它测量给定模型编码数据所需的比特数。
# 交叉熵:主观概率为Q的观察者在看到根据概率P生成的数据时的预期惊异
# 实现softmax由三个步骤组成:
# 对每个项求幂(使用exp);
# 对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
# 将每一行除以其规范化常数,确保结果的和为1。
# softmax_pytorch_fmnist.py -- 纯 PyTorch 的 softmax 回归示例(可在 PyCharm 运行)
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
# ----- 超参数 -----
batch_size = 256
lr = 0.1
num_epochs = 10
num_inputs = 784 # 28*28
num_outputs = 10 # 10 classes
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print("Device:", device)
# ----- 数据集 -----
transform = transforms.Compose([
transforms.ToTensor(), # [0,1], shape (1,28,28)
])
train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform)
train_iter = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True)
test_iter = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True)
# ----- 参数(手写模型) -----
W = torch.randn(num_inputs, num_outputs, device=device) * 0.01
W.requires_grad_(True)
b = torch.zeros(num_outputs, device=device, requires_grad=True)
# ----- 辅助函数 -----
def softmax(X):
# X: (batch_size, num_outputs)
X_exp = torch.exp(X - X.max(dim=1, keepdim=True)[0]) # 稳定数值
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition
def net(X):
X = X.reshape(-1, num_inputs)
return softmax(torch.matmul(X, W) + b)
def cross_entropy(y_hat, y):
# y_hat: (batch, num_outputs) ; y: (batch,)
# 避免 log(0) -> 数值稳定
return -torch.log(y_hat[torch.arange(len(y_hat)), y] + 1e-12)
def accuracy(y_hat, y):
if y_hat.dim() > 1 and y_hat.shape[1] > 1:
y_hat_labels = y_hat.argmax(dim=1)
else:
y_hat_labels = y_hat
cmp = (y_hat_labels.type(y.dtype) == y)
return float(cmp.type(torch.float32).sum())
def evaluate_accuracy(net_fn, data_iter):
net_fn_eval = net_fn # 在这里 net 是函数,用 torch.no_grad() 即可
acc_sum = 0.0
n = 0
with torch.no_grad():
for X, y in data_iter:
X = X.to(device)
y = y.to(device)
y_hat = net_fn_eval(X)
acc_sum += accuracy(y_hat, y)
n += y.numel()
return acc_sum / n
# ----- 优化器 -----
optimizer = torch.optim.SGD([W, b], lr=lr)
# ----- 训练函数 -----
def train_epoch(net_fn, train_iter, loss_fn, updater):
# 返回 (训练损失, 训练精度)
train_loss_sum = 0.0
train_acc_sum = 0.0
n = 0
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_hat = net_fn(X)
l = loss_fn(y_hat, y) # shape (batch,)
# 梯度
updater.zero_grad()
l.mean().backward()
updater.step()
train_loss_sum += float(l.sum().item())
train_acc_sum += accuracy(y_hat, y)
n += y.numel()
return train_loss_sum / n, train_acc_sum / n
def train(net_fn, train_iter, test_iter, loss_fn, num_epochs, updater):
for epoch in range(num_epochs):
train_loss, train_acc = train_epoch(net_fn, train_iter, loss_fn, updater)
test_acc = evaluate_accuracy(net_fn, test_iter)
print(f"Epoch {epoch+1}: train loss {train_loss:.4f}, train acc {train_acc:.4f}, test acc {test_acc:.4f}")
# ----- 训练 -----
# train(net, train_iter, test_iter, cross_entropy, num_epochs, optimizer)
# ----- 预测与显示 -----
labels_map = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
def get_fashion_mnist_labels(indices):
return [labels_map[i] for i in indices]
def show_images(imgs, titles=None, nrows=1, ncols=6, figsize=(10, 3)):
plt.figure(figsize=figsize)
for i in range(len(imgs)):
plt.subplot(nrows, ncols, i+1)
plt.imshow(imgs[i].squeeze(), cmap='gray')
if titles:
plt.title(titles[i])
plt.axis('off')
plt.tight_layout()
plt.show()
def predict_and_show(net_fn, test_iter, n=6):
for X, y in test_iter:
X = X.to(device)
y = y.to(device)
break
preds = net_fn(X).argmax(dim=1).cpu().numpy()
trues = y.cpu().numpy()
imgs = X.cpu().numpy()
titles = [f"{get_fashion_mnist_labels([t])[0]}\n{get_fashion_mnist_labels([p])[0]}" for t,p in zip(trues[:n], preds[:n])]
show_images(imgs[:n], titles=titles, nrows=1, ncols=n, figsize=(12,2))
# predict_and_show(net, test_iter, n=6)
if __name__ == '__main__':
# 把下面三行放进这个 if 块中
# 在 macOS 或 Windows 上运行 .py 文件时,Python 使用的是 spawn 模式(不是 Linux 的 fork 模式)
# 否则在启动 DataLoader 子进程时,Python 会“重新执行”整个文件,导致递归创建进程、直接崩溃。
# 训练
train(net, train_iter, test_iter, cross_entropy, num_epochs, optimizer)
# 预测
predict_and_show(net, test_iter, n=6)
# FashionMNIST 的每张图像是 1 × 28 × 28,即 单通道灰度图。
# 显示时,matplotlib 的 imshow() 默认用灰度色表(灰阶)。
softmax回归的简洁实现:
import matplotlib
matplotlib.use('TkAgg') # 或 'Qt5Agg' / 'MacOSX',取决于系统
import matplotlib.pyplot as plt
import torch
from torch import nn, optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from d2l import torch as d2l # d2l 的 PyTorch 版
# ===================== 超参数 =====================
batch_size = 256
num_epochs = 10
lr = 0.1
num_inputs = 28*28
num_outputs = 10
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")
# ===================== 数据加载 =====================
train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transforms.ToTensor())
test_dataset = datasets.FashionMNIST(root='./data', train=False, download=True, transform=transforms.ToTensor())
train_iter = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_iter = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# ===================== 模型 =====================
# 保持和 MXNet 版本一样风格:Sequential + Dense
net = nn.Sequential()
net.add_module('flatten', nn.Flatten())
net.add_module('linear', nn.Linear(num_inputs, num_outputs))
net.to(device)
# ===================== 损失函数和优化器 =====================
loss = nn.CrossEntropyLoss(reduction='none') # PyTorch 交叉熵
trainer = optim.SGD(net.parameters(), lr=lr)
# ===================== 训练 =====================
# 使用 d2l 的 PyTorch 版本 train_ch3
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
# 以上只有训练内容,无画图
更多推荐


所有评论(0)