【动手学深度学习Pytorch】2. Softmax回归代码

时间:2024-11-18 07:42:15

零实现

        导入所需要的包:

import torch
from IPython import display
from d2l import torch as d2l

        定义数据集参数、模型参数:

batch_size = 256 # 每次随机读取256张图片
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 将展平每个图片将其视为长度为784的向量,数据集存在10个类别
num_inputs = 784
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

        实现Softmax操作:

# 实现Softmax
def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True) #列数为特征数,行数为样本数
    return X_exp / partition #广播机制

# 尝试进行Softmax操作
X = torch.normal(0, 1, (2,5))
X_prob = softmax(X)
X_prob, X_prob.sum(1)

# 实现Softmax回归模型
def net(X):
    return softmax(torch.matmul(X.reshape(-1,W.shape[0]),W)+b)

        定义交叉熵函数:

# 创建一个数据y_hat,其中包含2个样本在3个类别的预测概率,使用y作为y_hat中概率的索引
y = torch.tensor([0,2])
y_hat = torch.tensor([[0.1, 0.3, 0.6],[0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
# 交叉熵函数
def cross_entropy(y_hat, y):
    return -torch.log(y_hat[range(len(y_hat)),y])
cross_entropy(y_hat, y)

        将预测类别于真实元素进行比较:

torch.argmax(input, dim=None, keepdim=False):用于返回指定维度中最大值的索引。通常用于分类任务中从预测输出中找到概率最大的类别

.dtype:.dtype 是张量的属性,用于返回该张量的 数据类型 (data type)。每个张量都有一个数据类型,用于定义其中存储元素的类型,例如浮点数、整数或布尔值。

tensor.type(dtype=None):不传入参数时,返回一个字符串,表示张量的类型;传入参数时,返回一个新的张量,该张量的类型与指定类型匹配。

x = torch.tensor([1.0, 2.0, 3.0])  # 默认 float32 类型
print(x.type())  # 输出: torch.FloatTensor

x_int = x.type(torch.int64)
print(x_int)         # 输出: tensor([1, 2, 3])
print(x_int.type())  # 输出: torch.LongTensor (int64 的别名)

net.eval():设置为评估模式。

def accuracy(y_hat, y):#计算预测争取的数量
    # 判断 y_hat 是否为多维张量(例如二维)
    if len(y_hat.shape)>1 and y_hat.shape[1] > 1:
        # 如果是多类别分类(第二维大于 1),通过argmax获取每行中概率或分数最大的类别索引
        y_hat = y_hat.argmax(axis=1)
    cmp = y_hat.type(y.dtype)==y  # 比较预测结果和真实标签是否相等
    return float(cmp.type(y.dtype).sum()) # 返回预测正确的总数量

accuracy(y_hat, y) / len(y)

def evaluate_accuracy(net, data_iter):#计算在指定数据集上的模型精度
    # 如果是 PyTorch 模型,设置为评估模式
    if isinstance(net, torch.nn.Module):
        net.eval() 
    metric = Accumulator(2)  # 初始化累加器,存储 [正确预测数, 总样本数]
    for X, y in data_iter:
        metric.add(accuracy(net(X), y), y.numel()) # 累加每批数据的预测结果
    return metric[0] / metric[1]  # 返回精度:正确预测数 / 总样本数

        Accumulator实例:

class Accumulator: #在n个变量上累加
    def __init__(self, n):
        self.data = [0.0] * n
    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]
    def reset(self):
        self.data = [0.0] * len(self.data)
    def __getitem__(self, idx):
        return self.data[idx]

evaluate_accuracy(net, test_iter)

        定义训练过程: 

net.train():设置为训练模式。

torch.optim.Optimizer.step():用于执行模型参数更新基于之前计算好的梯度(通过反向传播获得),按照优化算法的规则调整模型参数的值,以最小化损失函数。

def train_epoch_ch3(net, train_iter, loss, updater):
    if isinstance(net, torch.nn.Module):
        net.train()
    metric = Accumulator(3)
    for X, y in train_iter:
        y_hat = net(X)
        l = loss(y_hat, y) #计算损失
        if isinstance(updater, torch.optim.Optimizer):
            updater.zero_grad() # 清除梯度
            l.backward() # 反向传播计算梯度
            updater.step() # 根据梯度更新模型参数
            metric.add(
                float(l) * len(y),  # 累加当前批次的损失
                accuracy(y_hat, y),  # 累加当前批次的正确预测数
                y.size().numel())  # 累加当前批次的样本数
        else: # 如果是自定义优化器
            l.sum().backward()
            updater(X.shape[0]) # 自定义的更新函数,可能需要批次大小作为参数
            metric.add(float(l.sum()), 
                       accuracy(y_hat),
                       y.numel())
    return metric[0] / metric[2], metric[1] / metric[2]

        定义一个在动画中绘制数据的实用程序类:

class Animator: #实时观看在训练过程中的变化
    # 初始化绘图环境,包括图表的设置、标签、坐标轴范围、曲线样式等。
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-','m--','g-','r:'),nrows=1,ncols=1,
                 figsize=(3.5, 2,5)):
        if legend is None:
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols ==1:
            self.axes = [self.axes,]
        self.config_axes = lambda:d2l.set_axes(self.axes[0],
                                              xlabel, ylabel,
                                              xlim, ylim,
                                              xscale, yscale,
                                              legend)
        self.X, self.Y, self.fmt = None, None, fmts

    def add(self, x, y):
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)

        训练函数: 

# 训练函数
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
        # 进行可视化
        animator = Aminator(xlabel='epoch', xlim=[1, num_epochs], 
                            ylim=[0.3,],
                           legend=['train loss','train acc','test acc'])
        for epoch in range(num_epochs):
            train_metrics = train_epoch_ch2(net, train_iter, loss, updater)
            test_acc = evaluate_accuracy(net, test_iter)
            animator.add(epoch+1, train_metrics+(test_acc,))
        train_loss, train_acc = train_metrics

# 小批量随机梯度下降来优化训练算法
lr = 0.1
def updater(batch_size):
    return d2l.sgd([W,b],lr,batch_size)

num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater(10))

 简洁实现

        导入所需要的包:

import torch
from IPython import display
from d2l import torch as d2l

        初始化数据集、模型参数、损失函数以及训练优化算法:网络加入高斯噪声,增强泛化性。

torch.nn.init.normal_(tensor, mean=0.0, std=1.0):正态分布(高斯分布)随机初始化张量的值

nn.Sequential(*modules):用于将多个模块(如线性层、激活函数等)按顺序组合成一个模型。适合简单的前向计算场景。

nn.Flatten(start_dim=1, end_dim=-1):将输入张量展平成二维张量,适用于线性层输入。

nn.Linear(in_features, out_features, bias=True):实现一个线性层(全连接层)

nn.CrossEntropyLoss(weight=None, ignore_index=-100, reduction='mean'):计算分类任务中的交叉熵损失(适用于多分类问题)。
torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False):实现随机梯度下降(SGD)优化算法,用于更新模型参数。

net.parameters():返回模型的可训练参数的迭代器。

batch_size = 256 # 每次随机读取256张图片
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

net = nn.Sequential(nn.Flatten(),nn.Linear(784, 100))
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

loss = nn.CrossEntropyLoss()

trainer = torch.optim.SGD(net.parameters(),lr=0.1)

        用之前定义的训练函数训练模型:

num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater(10))