【lzy学习笔记-dive into deep learning】3.4 3.6 3.7 softmax原理与实现

d2l存在的意义其实也就是把之前声明过的函数都存起来了,通过第一次@save标记自动记录了下来,以后就能直接调用了。d2l完全可取代。

3.4 softmax回归

回归 vs 分类

回归:“多少”
分类:“哪一个”
一种情况,只关心硬类别(样本属于哪个类别),但是仍然使用软类别的模型(属于每个类别的概率)

3.4.1 分类问题

表示分类数据的方法:独热编码one-hot encoding
独热编码是一个向量,分量和类别一样多;
类别对应的分量设置为1,其它所有分量设置为0.
例如:y∈{(1,0,0),(0,1,0),(0,0,1)}

3.4.2 网络架构

为了估计所有可能类别的条件概率,需要一个有多个输出的模型,每个类别对应一个输出。
为了解决线性模型的分类问题,需要和输出⼀样多的仿射函数(affine function)。
每个输出对应于它自己的仿射函数。
在这里插入图片描述
向量形式表达为o = Wx + b

3.4.3 全连接层的参数开销

在这里插入图片描述

3.4.4 softmax运算

需要:
①希望模型的输出yˆj可以视为属于类j的概率,然后选择具有最大输出值的类别argmaxj yj作为我们的预测
②**不能将未规范化的预测o直接视作感兴趣的输出。**因为将线性层的输出直接
视为概率时存在⼀些问题:没有限制这些输出数字的总和为1;输入不同,可以为负值。这些违反了概率基本公理。
③需要⼀个训练目标来鼓励模型精准地估计概率。在分类器输出0.5的所有样本中,希望这些样本有⼀半实际上属于预测的类。这个属性叫做校准(calibration)
符合条件的模型产生:
在这里插入图片描述

3.4.5 小批量样本的矢量化

在这里插入图片描述

3.4.6 损失函数:最大似然估计

对数似然
在这里插入图片描述
softmax及其导数
在这里插入图片描述
交叉熵损失
使⽤ (3.4.8)来定义损失l,它是所有标签分布的预期损失值。此损失称为交叉熵损失(crossentropy loss),它是分类问题最常⽤的损失之⼀。

3.4.7 信息论基础


在这里插入图片描述
惊异
压缩与预测的关系:当数据易于预测,也就易于压缩
在这里插入图片描述
交叉熵
在这里插入图片描述

3.4.8 模型预测和评估

在训练softmax回归模型后,给出任何样本特征,我们可以预测每个输出类别的概率。通常我们使⽤预测概率最⾼的类别作为输出类别。如果预测与实际类别(标签)⼀致,则预测是正确的。
在接下来的实验中,我们将使⽤精度(accuracy)来评估模型的性能。精度等于正确预测数与预测总数之间的⽐率。
小结
• softmax运算获取⼀个向量并将其映射为概率。
• softmax回归适⽤于分类问题,它使⽤了softmax运算中输出类别的概率分布。
• 交叉熵是⼀个衡量两个概率分布之间差异的很好的度量,它测量给定模型编码数据所需的⽐特数。

3.6 softmax回归的从零开始实现

在这里插入图片描述
交叉熵损失函数

import torch
import commfuncs
import time
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
import matplotlib.pyplot as plt

def get_dataloader_workers():
    return 0

def load_data_fashion_mnist(batch_size, resize=None):
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    # print(trans)

    mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)

    return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False, num_workers=get_dataloader_workers()))

# 0: 载入数据集
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size)

# 1: 初始化模型参数
num_inputs = 784 # 28*28 将图像展平成一个向量 每个像素位置看成一个特征
num_outputs = 10 # 数据集有10个类别

# 每个输出对应一个仿射函数
# o1 = w11 x1 + w12 x2 + ... + w1784 x784 + b1
# 02 = w21 x1 + w22 x2 + ... + w2784 x784 + b2
# ...
# o10 = w101 x1 + w102 x2 + ... + w10784 x784 + b10
# W: 10 * 784 b: 10 * 1  ->  W: 784*10    b:1*10

W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
# print(W.shape)

# X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
# print(X.sum(0, keepdim=True)) # 保持行 按列算
# tensor([[5., 7., 9.]])
# print(X.sum(1, keepdim=True)) # 保持列 按行算
# tensor([[ 6.],
#         [15.]])


# 2: 定义softmax操作 符合概率定理
def softmax(X):
    X_exp = torch.exp(X)
    # print(X_exp)
    partition = X_exp.sum(1, keepdim=True) # 保持列 按行算
    # print(partition)
    return X_exp / partition

# X = torch.normal(0, 1, (2, 5))
# print(X)
# X_prob = softmax(X)
# print(X_prob)
# print(X_prob.sum(1))

# 3: 定义模型
# 输⼊如何通过网络映射到输出
# reshape函数将每张原始图像展平为向量
# O = XW + b
# ^Y = softmax(O)
def net(X):
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

# 4: 定义损失函数
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.3, 0.5]])
# print(y_hat[[0, 1], y])
# x y 顺次对应 array[[x1,x2,x3],[y1,y2,y3]] ->(x1,y1)(x2,y2)(x3,y3)

def cross_entropy(y_hat, y):
    # print(len(y_hat))
    # print(range(len(y_hat)))
    # print(y_hat[range(len(y_hat)), y])
    return - torch.log(y_hat[range(len(y_hat)), y]) # https://zhuanlan.zhihu.com/p/35709485
# print(cross_entropy(y_hat, y)) # 顺次求log

# 5: 分类精度
def accuracy(y_hat, y): #y_hat 预测概率分布
    # print(y_hat, y_hat.shape, y_hat.dtype)
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        # y_hat是矩阵 假定第二个维度存储每个类的预测分数
        # argmax获得每行中最大元素的索引来获得预测类别
        y_hat = y_hat.argmax(axis=1)
        # print(y_hat, y_hat.shape, y_hat.dtype)
    cmp = y_hat.type(y.dtype) == y # 结果是包含0 1 的张量
    return float(cmp.type(y.dtype).sum())

# print(accuracy(y_hat, y) / len(y))

# tensor([[0.1000, 0.3000, 0.6000],
#         [0.3000, 0.3000, 0.5000]]) torch.Size([2, 3]) torch.float32
# tensor([2, 2]) torch.Size([2]) torch.int64
# 0.5

# 对于任意数据迭代器data_iter可访问的数据集,可以评估在任意模型net的精度
# 计算在指定数据集上模型的精度
def evaluate_accuracy(net, data_iter):
    if isinstance(net, torch.nn.Module):      # in this example, False
        net.eval()
    metric = Accumulator(2) # 正确预测数 预测总数; 遍历数据集时两者都将随着时间的推移而累加
    with torch.no_grad():
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

# 对多个变量进行累加
class Accumulator:
    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]

# print(evaluate_accuracy(net, test_iter))      # the accuracy is approximately 1/10 as the network has not been trained

# 训练模型一个迭代周期
def train_epoch_ch3(net, train_iter, loss, updater):
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):      # in this example, False
        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):      # in this example, False
            # 使用pytorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:
            # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度
    return metric[0] / metric[2], metric[1] / metric[2]

def sgd(params, lr, batch_size):
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        train_loss, train_acc = train_metrics
        test_acc = evaluate_accuracy(net, test_iter)
        print(f'epoch {epoch + 1}, train_loss {float(train_loss): f}, train_acc {float(train_acc): f}, '
              f'test_acc {float(test_acc): f}')

lr = 0.1
# 定制的优化器 批量SGD
def updater(batch_size):
    return sgd([W, b], lr, batch_size)

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

def get_fashion_mnist_labels(labels):
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels]

def show_images(imgs, num_rows, num_cols, titles=None):
    _, axes = plt.subplots(num_rows, num_cols)
    axes = axes.flatten()
    for i, (ax, img) in enumerate(zip(axes, imgs)):
        if torch.is_tensor(img):
            ax.imshow(img.numpy())
        else:
            ax.imshow(img)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
        if titles:
            ax.set_title(titles[i])
    plt.show()

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

predict_ch3(net, test_iter)

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

小结
• 借助softmax回归,我们可以训练多分类的模型。
• 训练softmax回归循环模型与训练线性回归模型⾮常相似:先读取数据,再定义模型和损失函数,然后
使用优化算法训练模型。⼤多数常⻅的深度学习模型都有类似的训练过程。

3.7 softmax回归的简洁实现

import torch
from torch import nn
from torchvision import transforms
import time
import torchvision
from torch.utils import data
from torchvision import transforms

def get_dataloader_workers():
    return 0

def load_data_fashion_mnist(batch_size, resize=None):
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    # print(trans)

    mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)

    return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False, num_workers=get_dataloader_workers()))

# 以均值0和标准差0.01随机初始化权重
def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

class Accumulator:
    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]

def accuracy(y_hat, y): #y_hat 预测概率分布
    # print(y_hat, y_hat.shape, y_hat.dtype)
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        # y_hat是矩阵 假定第二个维度存储每个类的预测分数
        # argmax获得每行中最大元素的索引来获得预测类别
        y_hat = y_hat.argmax(axis=1)
        # print(y_hat, y_hat.shape, y_hat.dtype)
    cmp = y_hat.type(y.dtype) == y # 结果是包含0 1 的张量
    return float(cmp.type(y.dtype).sum())

# 对于任意数据迭代器data_iter可访问的数据集,可以评估在任意模型net的精度
# 计算在指定数据集上模型的精度
def evaluate_accuracy(net, data_iter):
    if isinstance(net, torch.nn.Module):      # in this example, False
        net.eval()
    metric = Accumulator(2) # 正确预测数 预测总数; 遍历数据集时两者都将随着时间的推移而累加
    with torch.no_grad():
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

# 训练模型一个迭代周期
def train_epoch_ch3(net, train_iter, loss, updater):
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):      # in this example, False
        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):      # in this example, False
            # 使用pytorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:
            # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度
    return metric[0] / metric[2], metric[1] / metric[2]

def sgd(params, lr, batch_size):
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        train_loss, train_acc = train_metrics
        test_acc = evaluate_accuracy(net, test_iter)
        print(f'epoch {epoch + 1}, train_loss {float(train_loss): f}, train_acc {float(train_acc): f}, '
              f'test_acc {float(test_acc): f}')

batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size)

# step 1 初始化模型参数
# PyTorch不会隐式地调整输⼊的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整⽹络输⼊的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

net.apply(init_weights)
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

num_epochs = 10
train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

loss = nn.CrossEntropyLoss(reduction=‘none’)
对softmax的重新审视与实现(解决数值不稳定:上溢、下溢、指数计算)

解决方案:交叉熵和softmax相结合
上溢:
在这里插入图片描述
下溢、指数计算:
在这里插入图片描述
希望保留传统的softmax函数,以备我们需要评估通过模型输出的概率。但是,我们没有将softmax概率传递到损失函数中,而是在交叉熵损失函数中传递未规范化的预测,并同时计算softmax及其对数,这是⼀种类似“LogSumExp技巧”的聪明⽅式。
个人理解:要的效果也达到,但是复杂运算的时候中间步骤化简,转而计算不容易出问题的内容,具有等效性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值