【动手学深度学习】(三)Softmax回归


softmax回归

一、理论知识

  • 回归估计一个连续值
  • 分类预测一个离散类别
    回归
  • 单连续数值输出
  • 自然区间R
  • 跟真实值的区别作为损失
    在这里插入图片描述
    分类
  • 通常多个输出
  • 输出i是预测为第i类的置信度
    在这里插入图片描述
    一般我们使用交叉熵用来衡量两个概率的区别
    在这里插入图片描述
    将它作为损失
    在这里插入图片描述
    其梯度是真实概率和预测概率的区别:
    在这里插入图片描述
    其梯度是真实概率和预测概率的区别
    损失函数
    (1)L2 Loss
    在这里插入图片描述
    (2)L1 Loss
    在这里插入图片描述
    (3)Huber’s Robust Loss
    在这里插入图片描述
    优点:当预测值与真实值相差较远时候,梯度还是按照均匀的变化,在比较靠近的时候,梯度绝对值会变小,保证优化比较平滑。

1.图像分类数据集

MNIST数据集是图像分类中广泛使用的数据集之一,但其较为简单,我们将使用Fashion-MNIST数据集
1.导入需要的包

%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l

d2l.use_svg_display() #通过svg显示图片,清晰度更高

2.通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中
Fashion-MNIST由10个类别的图像组成

# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式
# 并处以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
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)


len(mnist_train), len(mnist_test)

(60000, 10000)

mnist_train[0][0].shape  #第0个example

torch.Size([1, 28, 28])
3.两个可视化数据集的函数

def get_fashion_mnist_labels(labels): 
    """返回Fashion-MNIST数据集的文本标签"""
    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, scale=1.5): 
    """绘制图像列表"""
    figsize = (num_cols * scale, num_rows * scale)
#     创建一个包含多个子图的图形
#     下划线表示我们对图形本身不感兴趣,只关心返回的子图像
    _,axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
#     print(_)
#     print(axes)
#     print(type(axes)) #numpy
#     使用NumPy中的flatten()函数,将axes数组从多维数组变成一维数组
# axes:原本是一个包含多个子图对象的二维数组
    axes = axes.flatten()

    for i, (ax, img) in enumerate(zip(axes, imgs)):
        if torch.is_tensor(img):
            # 图片张量
            ax.imshow(img.numpy())
        else:
            # PIL图片
            ax.imshow(img)
#       设置子图的X轴和Y轴刻度标签不可见
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
        if titles:
#             print(titles)
#           给每个子图设置相应的标题
            ax.set_title(titles[i])
    return axes

# iter():函数生成迭代器
# next():返回迭代器的下一个项目,一般与iter()结合使用
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))# 拿到第一个小批量的数据X和y标签y
# print(y)
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));
# 绘制两行图片,每一行有9张图片
# titles是每张图片的标号

在这里插入图片描述

4.读取一小批数据,大小为batch_size

batch_size = 256

def get_dataloader_workers():
    """使用4个进程来读取数据"""
    return 4

train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
                             num_workers=4)
# num_workers = 0代表单进程而不是没有进程

timer = d2l.Timer()  # Timer函数用于测试速度
# print(train_iter)

batch = next(iter(train_iter))
print(batch)

# for X,y in train_iter:
# #     print(train_iter)

# # for X, y in train_iter:
#     continue
f'{timer.stop():.2f} sec'  # 输出读取数据所用的秒数,精度为2位小数

‘3.51 sec’

❗❗❗

如果运行出现此种报错,一般是由于taLoader的多进程造成的,我们可以通过设置num_workers=0代表单进程加载.这里是引用

5.定义load_data_fashion_mnist函数
为了方便后续使用,我们可以把上面的各个实现数据读取的操作,写在函数中

def load_data_fashion_mnist(batch_size, resize=None):
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.inserts(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(root="../data",
                                                   train=True,
                                                   transform=trans,
                                                   download=True)
    mnist_test = torchvision.datasets.FashionMNIST(root="../data",
                                                  train=True,
                                                  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=True,
                          num_workers=get_dataloader_workers()))

2.softmax回归的从零开始实现

1.导入相关包

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

batch_size = 256
# 返回训练数据和测试数据的迭代器
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

2.将展平每个图像,将它们视为长度为784的向量。因为数据有10个类别,所以网格输出维度为

# 28*28 = 784
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)

3.给定一个矩阵X,对所有元素求和

X = torch.tensor([[1.0, 2.0, 3.0], [4.0,5.0,6.0]])
# 按照维度为0进行,将行数变为1,按照维度为1进行求和,把列数变为1,keepdim表示保持维度
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
(tensor([[5., 7., 9.]]),
 tensor([[ 6.],
         [15.]]))

4.实现softmax
在这里插入图片描述

def softmax(X):
    X_exp = torch.exp(X)
#     按行求和
    print('----------------X_exp------------')
    print(X_exp)
    partition = X_exp.sum(1, keepdim=True)
    print('-------------partition------------')
    print(partition)
#     运用广播机制
    print('----------X_exp / partition--------')
    print(X_exp / partition)
    return X_exp / partition

5.将每个元素变成一个非负数,依据概率原理,每行总和为1

X = torch.normal(0, 1, (2, 5))
print(X)
X_prob = softmax(X)
X_prob, X_prob.sum(1)
tensor([[-0.0477, -0.8353,  0.6251, -0.0551, -1.3545],
        [ 1.9616, -0.2592, -1.7372,  1.6476, -0.1598]])
----------------X_exp------------
tensor([[0.9534, 0.4337, 1.8684, 0.9464, 0.2581],
        [7.1107, 0.7717, 0.1760, 5.1943, 0.8523]])
-------------partition------------
tensor([[ 4.4600],
        [14.1051]])
----------X_exp / partition--------
tensor([[0.2138, 0.0972, 0.4189, 0.2122, 0.0579],
        [0.5041, 0.0547, 0.0125, 0.3683, 0.0604]])
(tensor([[0.2138, 0.0972, 0.4189, 0.2122, 0.0579],
         [0.5041, 0.0547, 0.0125, 0.3683, 0.0604]]),
 tensor([1., 1.]))
1

6.实现softmax回归模型

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

7.创建一个数据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]])
# y0 = 0 = 0.1   y1 = 2 = 0.5
y_hat[[0, 1], y]
tensor([0.1000, 0.5000])

8.实现交叉熵损失函数

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

cross_entropy(y_hat, y)
tensor([2.3026, 0.6931])

9.将预测类别与真实y元素进行比较

def accuracy(y_hat, y):
    """计算预测正确的数量""" 
#     print(y_hat.shape)         列数大于1
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
#     将每一行元素值最大的下标存下来
        y_hat = y_hat.argmax(axis=1)
    cmp = y_hat.type(y.dtype) == y
#     转成和y同样的形状并求和
    return float(cmp.type(y.dtype).sum())

# 预测正确的样本数/y的长度
accuracy(y_hat, y) / len(y)
0.5

10.评估在任意模型net的准确率

def evaluate_accuracy(net, data_iter):
    """计算在指定数据集上模型的精度"""
    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]

11.Accumulator实例中创建了2个变量,用于分别存储正确预测的数量和预测的总数量

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)
0.1285

12.Softmax回归的训练

def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    """训练模型一个迭代周期(定义见第3章)"""
    if isinstance(net, torch.nn.Module):
        net.train() 
        
    # Accumulator(3)创建3个变量:训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)
    for X, y in train_iter:
        # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y)

		# 判断updater是否为优化器
        if isinstance(updater, torch.optim.Optimizer):
            # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad() #把梯度设置为0
            l.mean().backward() #计算梯度
            updater.step()		#自更新
        else:
            # 使用定制的优化器和损失函数
            # 自我实现的话,l出来是向量,先求和再求梯度
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度,metric的值由Accumulator得到
    return metric[0] / metric[2], metric[1] / metric[2]

13.定义一个在动画中绘制数据的使用程序类

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 = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ]
        # 使用lambda函数捕获参数
        self.config_axes = lambda: d2l.set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)

14.训练函数

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  
    """训练模型(定义见第3章)"""
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
                     
    # num_epochs:训练次数
    for epoch in range(num_epochs):
    	# train_epoch_ch3:训练模型,返回准确率和错误度
        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,))
    train_loss, train_acc = train_metrics

15.小批量随机梯度下降来优化模型的损失函数

lr = 0.1

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

16.模型训练10个迭代周期

num_epochs = 10

if __name__ == "__main__":
    train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

在这里插入图片描述

17.预测

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(net(X).argmax(axis=1)) #预测标签,取最大化概率
    titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
    d2l.show_images(
        X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

# predict_ch3(net, test_iter)

if __name__ == "__main__":
    predict_ch3(net, test_iter)
    

在这里插入图片描述

3.Softmax简洁实现

import torch
from torch import nn
from d2l import torch as d2l

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

1.softmax回归的输出层是一个全连接层

# PyTorch不会隐式地调整输入的形状
# 因此,我们定义了展平层 在线形层前调整网络输入的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)
        
net.apply(init_weights);

2.在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数

loss = nn.CrossEntropyLoss(reduction='none')

3.使用学习率为0.1的小批量随机梯度下降作为优化算法

trainer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epoches = 10
train_ch3(net, train_iter, test_iter, loss, num_epoches, trainer)

在这里插入图片描述

【相关总结】

torch.sum()

(1)torch.sum(input, *, dtype=None):返回输入张量input所有元素的和。
(2)torch.sum(input,dim,keepdim=False, *,dtype=None):返回指定维度进行求和

import torch
x = torch.ones((2, 3))

x_sum =  torch.sum(a)
x_0 =  torch.sum(a, dim=0)
x_1 =  torch.sum(a, dim=1)

print(x)
print(x_0)
print(x_1)

tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([2., 2., 2.])
tensor([3., 3.])

torch.argmax()

import torch
x = torch.rand(3, 2)
print(x)
y0 = torch.argmax(x, dim=0)  #dim=0,返回每一列最大值的索引
print(y0)
y1 = torch.argmax(x, dim=1)  #dim=1,返回每一行最大值的索引
print(y1)

tensor([[0.9407, 0.8543],
[0.4057, 0.6790],
[0.0154, 0.3698]])
tensor([0, 0])
tensor([0, 1, 1])

isinstance():[python]

函数isinstance()可以判断一个变量的类型。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

释怀°Believe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

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

抵扣说明:

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

余额充值