1.2 pytorch深度学习--softmax回归

准备

由线性回归到softmax回归

本章的核心目标是对图片分类,和之前线性回归中求w和b的值不同,本章的目的是求出来所属类别,即一个是连续的,一个是离散的。在编程之前,可以想象该系统为一个黑箱,输入值是各个图片,输出值是图片所属类别的序号。

将问题细化思考,输入的是图片数字化后的值,输出是图片属于各个分类的概率,选择最大概率的类别作为输出。

在确定到这个程度之后就需要考虑黑箱内部的东西了,根据线性模型,我们首先想到的是,将数字化后的各个值赋予权重后相加,之后加上偏差b。得到的输出通过某种办法,转化成概率。将最大的概率的结果与实际结果做对比,如果不同,则通过损失函数对w和b进行迭代,减小误差。

首先考虑将输出值转化成概率。上述过程中我们明确了希望找到最大概率作为结果输出。根据概率的定义,我们知道每个值都应该是非负数,所有情况相加应该得0。因此,输出结果首先通过某个方程y将在实数范围内的结果映射到非负数范围内,同时不会改变相对大小。之后则可以通过用yi除以全部y求和,这样则可以满足相加得1。

softmax函数

前人提出了softmax函数,满足了上述的要求。

\hat{y}_{i}=\frac{exp(o_{i})}{\sum exp(o_{k})}

根据定义我们再次分析需求,

1. 对输出值求指数可以将在实数范围内的数映射到非负数范围内,符合概率要求;

2. 指数后的值本身除以所有指数后的值的和。显然,当全部输出相加时等于1,符合概率要求。

损失函数——交叉熵损失

在确定输出后就需要考虑通过损失函数对w和b进行迭代更新。

在线性回归时我们选择了均方损失(MSE),在此处我们将选择另一种损失函数,交叉熵损失函数。(由于本人学艺不精,此处根据最大似然函数推导得出交叉熵损失没有完全弄明白,为防止误人子弟,不具体说明。)

l(y,\hat{y})=-\sum y_{j}log\hat{y}_{j}

我们已知标签y只有1和0两个值,因此公式简化为当yj=1时的值。

此处附一篇对于交叉熵损失和均方损失对比的讲解。

损失函数|交叉熵损失函数https://zhuanlan.zhihu.com/p/35709485

图像分类数据集

我们将图像分类中应用最广泛的数据集之一Fashion-MNIST。可以通过torchvision调用。

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

d2l.use_svg_display() #上一章有对该函数的调用,本章不重复展示
trans = transforms.ToTensor()
"""
通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
并除以255使得所有像素的数值均在0到1之间
"""
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)
"""
root表示文件下载到本地的位置,其中..表示的是上级目录,你写代码地方的上级。
root="../data"表示在上级目录中创建一个data文件夹并进入下载,如果已有则直接进入
train=True表示的是下载后加载训练集到mnist_train上
transform=trans更改类型到指定类型
download=True表示下载,如果指定文件夹里有对应内容,可以为False
"""

softmax训练

首先考虑一下实现步骤:

  1. 确定学习率,batch size,输入输出空间;
  2. 读取训练集fashion-mnist;
  3. net函数,损失函数,更新函数;
  4. 对数据进行一次训练,更新w权重并记录损失和精度;
  5. 对数据进行多次训练;
  6. 通过折线图展示训练结果。

第一二步

if __name__ == "__main__":
    # 学习率 learning rate: lr
    lr = 0.1
    # 循环次数,刷数据的遍数
    num_epochs = 5
    # 一个批次包含的图片量
    batch_size = 256
    # 已知图片长宽是28 28,则拉长后为28*28
    num_inputs = 28 * 28
    # 输出图片类别
    num_outputs = 10
    # 读取一个批次的训练集和测试集
    train_iter, test_iter = load_data_fashion_mnist(batch_size)
    # 初始化权重和偏移,需要计算梯度
    w = torch.normal(0, 0.01, [num_inputs, num_outputs],requires_grad=True)
    b = torch.zeros(num_outputs, requires_grad=True)
    """第一步及第二步完成"""

第三步:函数编写

根据softmax定义首先写出softmax函数:

def softmax(X):
    return torch.exp(X) / torch.exp(X).sum(dim=1, keepdim=True)

和线性回归不同的是,线性回归的输出数为1,而softmax根据类别的种类有多个输出数。已知fashion-mnist数据集有10个种类,因此输出是10。下面是训练网络函数:

def net(X):
    """
    将每个图片reshape成一条数据(1, 28*28), reshape后的数据是第0
    维数量是图片数量,第一维数量是图片图片的像素数

    与W做叉乘并加上偏移b并将结果做softmax得到概率
    """
    out = torch.matmul(X.reshape(-1, W.shape[0]), W) + b
    return softmax(out)

损失函数。我们已确定损失函数是交叉熵函数。

def corss_entropy(y_hat, y):
    return -torch.log(y_hat[range(len(y)), y])

更新函数。SGD函数,随机梯度下降。在此之前我们应该已经计算过梯度。

def SGD(params, lr, batch_size):
"""params是[W, b]"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

第四步:单次训练

接下来需要编写单次训练的函数,我们希望对全部样本训练一次,得到训练后的损失率(即错误样本 / 全部样本)和训练精度(即训练正确样本/全部样本)。

为了记录每一次的结果,可以简单的直接定义变量对每个批量的结果累加,或者定义一个加法器类,记录结果。

此处考虑加法器类。首先考虑加法器实现的功能:累加,输出结果。

class Accumulator:
    def __init__(self, n) -> None:
        """n意味着需要记住几个值"""
        self.data = [0.0] * n
    
    def add(self, *args):
        """累加"""
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def __getitem__(self, item):
        """用于直接读数"""
        return self.data[item]

 此外,因为需要训练精度,因此需要求的正确判断样本类型的个数。定义accuracy函数

def accuracy(y_hat:torch.Tensor, y):
    """求每一行的最大值的位置:
       例如y_hat[0] = [0.1, 0.1, 0.2, 0.5, 0.1]
       最大值在第三位,输出3                 """
    y_hat = y_hat.argmax(dim=1)
    """将取等后的布尔结果赋值给cmp"""
    cmp = y_hat.ttype(y.dtype) == y
    return float(cmp.type(y.dtype).sum)

 单次训练

# 首先确定训练网络,训练数据集,随时函数和更新函数
def train_epoch_ch3(net, train_iter, loss, updater):
    metric = Accumulator(3)
    for X, y in train_iter:
        y_hat = net(X)
        l = loss(y_hat, y)
        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]

 

你可能感兴趣的:(跟李沐手撸深度学习,深度学习,线性代数,机器学习)