推荐课程:Softmax 回归_哔哩哔哩_bilibili
目录
一、softmax回归
1.1 网络架构
1.2 softmax运算
1.3 交叉熵损失函数
二、图像分类数据集
2.1 导包
2.2 创建数据集
2.3 可视化数据集函数
2.4 读取小批量
2.5 整合所有组件
三、softmax回归的从零开始实现
3.1 导包和读取数据集
3.2 定义初始化参数
3.3 实现softmax操作
3.4 定义模型
3.5 实现交叉熵损失函数
3.6 分类精度
3.7 优化器
3.8 训练
3.9 预测
四、softmax回归的简介实现
4.1 初始化模型参数
4.2 一步实现Softmax回归和损失函数的定义
4.3 定义优化算法
4.4 训练
分类任务是对离散变量预测,通过比较分类的概率来判断预测的结果。
softmax回归和线性回归一样也是将输入特征与权重做线性叠加,但是softmax回归的输出值个数等于标签中的类别数,这样就可以用于预测分类问题。
分类问题和线性回归的区别:分类任务通常有多个输出,作为不同类别的置信度。
一、softmax回归
1.1 网络架构
为了解决线性模型的分类问题,我们需要和输出一样多的仿射函数,每个输出对应它自己的仿射函数。
与线性回归一样,softmax回归也是一个单层神经网络。
在softmax回归中,输出层的输出值大小就代表其所属类别的置信度大小,置信度最大的那个类别我们将其作为预测。
1.2 softmax运算
首先,分类任务的目标是通过比较每个类别的置信度大小来判断预测的结果。但是,我们不能选择未规范化的最大输出值的 的类别作为我们的预测,原因有两点:
1. 输出值 的总和不一定为1
2. 输出值 有可能为负数。
这违反了概率论基本公理,很难判断所预测的类别是否真符合真实值。
softmax函数通过如下公式,解决了以上问题:
softmax函数确保了输出值的非负,和为1,这一种规范手段。
1.3 交叉熵损失函数
交叉熵损失常用来衡量两个概率之间的差别。
根据公式推断, 交叉熵损失函数的偏导数是我们softmax函数分配的概率与实际发生的情况之间的差距,换句话来说,其梯度是真实概率 和预测概率 之间的差距。
二、图像分类数据集
MNIST数据集是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单。我们将使用类似但更复杂的Fashion-MNIST数据集。
2.1 导包
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.2 创建数据集
通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中。
# 通过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)
查看Fashion-MNIST训练集和测试集大小,分别包含60000,10000张图片。
print(len(mnist_train), len(mnist_test))
查看图片分辨率,图片分辨率大小为[1, 28, 28]。
print(mnist_train[0][0].shape)
补充:
torchvision.datasets
是Torchvision提供的标准数据集。
torchvision.transforms
是包含一系列常用图像变换方法的包,可用于图像预处理、数据增强等工作。
torchvision.transforms.ToTensor()
把一个取值范围是[0,255]
的PIL.Image
或者shape
为(H,W,C)
的numpy.ndarray
,转换成形状为[C,H,W]
,取值范围是[0,1.0]
的torch.FloadTensor(浮点型的tensor)。
2.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) # 创建绘制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:
# PIL图片
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False) #不显示y轴
ax.axes.get_yaxis().set_visible(False) #不显示x轴
if titles:
ax.set_title(titles[i])
return axes
可视化展示训练集中前18个图片。
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y))
d2l.plt.show() # 将plt.imshow()处理后的数据显示出来
plt.subplots(num_rows, num_cols, figsize)
:创建绘制num_rows*num_cols个子图的位置区域,其中子图大小为figsize。
enumerate()
:获取可迭代对象的每个元素的索引值及该元素值。
zip()
:用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
imshow()
:负责对图像进行处理,并存入内存,并不显示。
plt.show()
:将plt.imshow()处理后的数据显示出来。
2.4 读取小批量
使用4个进程,以批量大小为256,来读取数据集。
# 读取小批量
batch_size = 256
def get_dataloader_workers():
"""使用4个进程来读取数据"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
2.5 整合所有组件
这个函数包含了以上所有工作。
def load_data_fashion_mnist(batch_size, resize=None):
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(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=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()))
三、softmax回归的从零开始实现
3.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) # 读取数据集
d2l.load_data_fashion_mnist()与 “ 2.5 整合所有组件 ”完全一致。
3.2 定义初始化参数
将展平每个图像,将它们视为长度为784的向量。因为我们的数据集有10个类别,所以网络输出维度为10。因此,权重w将构成一个784x10的矩阵,偏置将构成一个1x10的行向量。与线性回归一样我们将使用正态分布初始化权重w,偏置初始化为0。
# 初始化模型参数
num_inputs = 784
num_outputs = 10
W = torch.normal(0,0.01,size=(num_inputs, num_outputs),requires_grad=True) # 从正态分布中随机选择w
b = torch.zeros(1,requires_grad=True)
3.3 实现softmax操作
# 定义softmax函数
def softmax(X):
X_exp = torch.exp(X) # 对每个元素做指数运算
partition = X_exp.sum(1, keepdim=True) # 对每行进行求和,保持原有张量维数
return X_exp / partition # 这里应用了广播机制
观察softmax函数是否实现了:1. 所有输出值的总和为1; 2. 所有输出值都不为负数。
X_prob = softmax(X)
print(X)
print(X_prob, X_prob.sum(1))
3.4 定义模型
X整形为[10,784]与W进行矩阵乘法,输出大小为[10,10]。
# 定义模型
def net(X):
# 模型简单看来为:softmax(wx' + b)
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b) # X展平
reshape的参数为-1,表示根据其他维度自动计算该维度的大小 。
3.5 实现交叉熵损失函数
# 定义交叉熵损失函数
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
案例:创建一个数据y_hat,其中包含2个样本在3个类别的预测概率,再创建它们对应的标签y。如,y=[0,2],y_hat=[[0.1, 0.3, 0.6],[0.3, 0.2, 0.5]],根据y我们可以看出在y_hat中,第一个样本的预测概率为0.1,第二个样本的预测概率为0.5。因此,我们直接使用y作为y _hat中概率的索引。
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6],[0.3, 0.2, 0.5]])
print(y_hat[[0, 1], y]) # 查找真实标签对应的预测概率
print(cross_entropy(y_hat, y)) # 计算预测概率的交叉熵损失值
3.6 分类精度
将预测类别与真实y元素进行比较,计算评估指标accuracy。
1. 计算一个批量内正确预测的总量。
# 分类精度
def accuracy(y_hat, y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1) # y_hat第2个维度为预测标签,取最大元素
cmp = y_hat.type(y.dtype) == y # 将y_hat转换为y的数据类型然后作比较,cmp函数存储bool类型
return float(cmp.type(y.dtype).sum()) # 将正确预测的数量相加
准确率=符合条件的测定值个数/总测定值个数
print(accuracy(y_hat, y) / len(y))
2. 评估整个模型net的精确率。
def evaluate_accuracy(net, data_iter): # @save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module): # 判断模型是否为深度学习模型
net.eval() # 将模型设置为评估模式,阻断梯段传播,只计算精度
metric = Accumulator(2) # 累加器,创建2个空间,存储预测正确数量和总预测数量
with torch.no_grad(): # 以下不生成计算图
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel()) #累加
return metric[0] / metric[1] # 预测正确数量和总预测数量相除
numel()函数:返回数组中元素的个数。
累加器:Accumulator实例中创建了2个变量,用于分别存储正确预测的数量和预测的总数量。
class Accumulator:
"""在n个变量上累加"""
# 初始化根据传进来n的大小来创建n个空间,全部初始化为0.0
def __init__(self, n):
self.data = [0.0] * n
# 把原来类中对应位置的data和新传入的args做a + float(b)加法操作然后重新赋给该位置的data,从而达到累加器的累加效果
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))
3.7 优化器
小批量随机梯度下降来优化损失函数。
# 小批量梯度下降
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
3.8 训练
训练一轮:
# 训练一轮
def train_epoch_ch3(net, train_iter, loss, updater): # @save
"""训练模型一个迭代周期(定义见第3章)"""
if isinstance(net, torch.nn.Module): # 判断net模型是否为torch.nn模组,将模型设置为训练模式
net.train() # 计算梯度
metric = Accumulator(3) # Accumulator(3)创建3个变量:训练损失总和、训练准确度总和、样本数
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer): # 判断优化器是否为torch.optim.Optimizer模组
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad() # 把梯度设置为0
l.mean().backward() # 计算梯度
updater.step() # 自更新,优化参数
else:
# 使用定制的优化器和损失函数
l.sum().backward() # backward只支持标量,先sum为一个标量,再反向求导
updater(X.shape[0]) # 优化
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel()) # 累加一个batch的损失、正确预测数和batch大小
return metric[0] / metric[2], metric[1] / metric[2] # 返回训练损失和训练精度
训练函数:
# 训练函数
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型(定义见第3章)"""
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater) # 返回训练损失和训练精度
test_acc = evaluate_accuracy(net, test_iter) # 在测试数据集上评估精度
train_loss, train_acc = train_metrics
print("train_loss,train_acc,test_acc:", train_loss, train_acc, test_acc)
训练模型10个迭代周期:
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
3.9 预测
对图像进行分类预测。
# 预测
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签(定义见第3章)"""
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])
plt.show()
predict_ch3(net, test_iter)
四、softmax回归的简介实现
通过深度学习框架的高级API能够使实现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) # 读取数据集
4.1 初始化模型参数
# PyTorch不会隐式地调整输入的形状
# 因此,我们定义了展平层(flatten)在线性层前调整网络输入的形状
# Flatten()将任何维度的tensor转化为2D的tensor
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
# 初始化权重参数
def init_weights(m): # m就是当前的层layer
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01) # w从均值为0(默认),标准差为0.01的正态分布随机选取
m.bias.data.fill_(0) # b设为0
net.apply(init_weights) # 把init_weights函数apply到net上面
torch.nn.Flatten(start_dim=1, end_dim=- 1)
用于设置网络中的展平层,常用于将输入张量展平为二维张量,也可以指定展平维度的范围[start_dim, end_dim
]。
torch.nn.Linear(in_features, out_features)
用于设置网络中的全连接层,需要注意的是全连接层的输入与输出都是二维张量。
in_features
指的是输入的二维张量的第二个维度的大小。out_features
指的是输出的二维张量的第二个维度的大小。
torch.nn.Sequential()
是PyTorch中的一个类,它允许用户将多个计算层按照顺序组合成一个模型。
4.2 一步实现Softmax回归和损失函数的定义
在使用nn.CrossEntropyLoss()其内部会自动加上Softmax层。在交叉嫡损失函数中传递未归一化的预测,并同时计算softmax及其对数。
loss = nn.CrossEntropyLoss()
4.3 定义优化算法
使用学习率为0.1的小批量随机梯度下降作为优化算法。
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
4.4 训练
调用之前定义的训练函数来训练模型,这里d2l模组里直接给出。
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
如果观察不到训练结果图像,在d2l.train_ch3()函数的最后一行加上plt.show()即可。