softmax回归
全连接层的参数开销
对于任何具有个d输入和q个输出的全连接层, 参数开销为o(dq)。
softmax运算
softmax函数将未规范化的预测变换为非负并且总和为1,同时要求模型保持可导。 我们首先对每个未规范化的预测求幂,这样可以确保输出非负。 为了确保最终输出的总和为1,我们再对每个求幂后的结果除以它们的总和。
在预测过程中,我们可以用下式来选择最有可能的类别。
损失函数交叉熵损失(cross-entropy loss)
图像分类数据集
#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()
#2.读取数据集
# 通过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)
#从数据库下载数据在上一级目录文件data中,train=bool,用于训练或测试, transform=trans把图片转化成矩阵的形式, download=bool是否从网上下载
len(mnist_train), len(mnist_test) #(60000, 10000)
#训练集和测试集分别包含60000和10000张图像。
mnist_train[0][0].shape #torch.Size([1, 28, 28])
#训练集第一个样本第一张图片的形状 mnist_train[0][1]训练集第一个样本第一张图片的标签
#以下函数用于在数字标签索引及其文本名称之间进行转换。
def get_fashion_mnist_labels(labels): #@save
"""返回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]
#get_fashion_mnist_labels([1,3]) ['trouser', 'dress']
batch_size = 256
def get_dataloader_workers(): #@save
"""使用4个进程来读取数据"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
#读取小批量。 num_workers使用多少个进程
整合所有组件
def load_data_fashion_mnist(batch_size, resize=None): #@save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
#如果传入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回归的从零开始实现
原始数据集中的每个样本都是28×28的图像, 我们将展平每个图像,把它们看作长度为784的向量。因为我们的数据集有10个类别,所以网络输出维度为10。 因此,权重将构成一个784×10的矩阵, 偏置将构成一个1×10的行向量。 与线性回归一样,我们将使用正态分布初始化我们的权重W,偏置初始化为0。
import torch
from IPython import display
from d2l import torch as d2l
#1.初始化模型参数
batch_size = 256 #设置数据迭代器的批量大小为256。
train_iter, test_iter = load_data_fashion_mnist(batch_size)
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)
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
#(tensor([[5., 7., 9.]]), tensor([[ 6.], [15.]]))
#只对同一个轴上的元素求和,即同一列(轴0)或同一行(轴1)
#keepdim=False,对同一列(轴0)求和,维度减1维,由原矩阵[2,3]变成列的维度[3],对同一行(轴1)求和,维度减1维,由原矩阵[2,3]变成行的维度[2],
#keepdim=True,对同一列(轴0)求和,行维度变为1维,由原矩阵[2,3]变成列的维度[1,3],对同一行(轴1)求和,列维度变为1维,由原矩阵[2,3]变成行的维度[2,1]
回想一下,实现softmax由三个步骤组成:
对每个项求幂(使用exp);
对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
将每一行除以其规范化常数,确保结果的和为1。
#2.定义softmax操作
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
y = torch.tensor([0, 2]) #假设现实中,这两张图片的标签分别是0,和1
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]]) ##假设实验中,第一张图片预测的标签分别是0,1和2的概率是[0.1, 0.3, 0.6],第二张图片预测的标签分别是0,1和2的概率是[0.3, 0.2, 0.5]
y_hat[[0, 1], y] #tensor([0.1000, 0.5000])
#y_hat中其中包含2个样本在3个类别的预测概率, 以及它们对应的标签y。使用y作为y_hat中概率的索引,我们知道在第一个样本y_hat[0]中,第一类标签y[0]是正确的预测; 而在第二个样本y_hat[1]中,第三类标签y[2]是正确的预测。
#3.定义模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
#交叉熵损失函数。
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
#分类精度
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1) #找到它的最大值,并且用它的Index来表示
cmp = y_hat.type(y.dtype) == y #就是先把y_hat换成和y一样的数据类型,然后比较y_hat和y是否在每一个位置上的值相等。而在Python里,False是0,True是1,所以可以用[0,1]表示,所以最后的求和
return float(cmp.type(y.dtype).sum())
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数 返回两成函数 metric[0] metric[1]
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: #@save
"""在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): # reset函数即重新设置空间大小并初始化。
self.data = [0.0] * len(self.data)
def __getitem__(self, idx): # __getitem__实现类似数组的取操作。
return self.data[idx]
训练
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
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):
# 使用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 train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第3章)"""
print(f'epoch {epoch + 1}, loss {l:f}')
for epoch in range(num_epochs):
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
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
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)
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])
predict_ch3(net, test_iter)
softmax回归的简洁实现
import torch
from torch import nn
from d2l import torch as d2l
#1.使用Fashion-MNIST数据集
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
#2.初始化模型参数
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
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);
#3.Softmax的实现
loss = nn.CrossEntropyLoss(reduction='none')
#4.优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
#5.训练
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)