SoftMax回归的从零开始
1.获取数据集
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
2.初始化模型参数
数据集中每个样本是28x28
的图片,我们将其展开变为784
的向量。
在softmax
回归中,我们的输出与类别一样多。(因为我们的数据集有10个类别,所以网络输出维度为10)。
因此,权重将构成一个784×10
的矩阵,偏置(b)将构成一个1×10
的行向量。
例如:W=[784,10],X=[10,784],WX=[10,10].
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.定义softmax操作
X_exp
中将X
中的所有数据取了指数,然后partition
按行求和,并且keepdims
(便于广播机制
这样下来,我们令return
出去的东西是t
t
的每一行之和都是partition
中这一行的值。那么t
的每一行之和显然是1
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
4.定义模型
类似线性回归模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
5.定义损失函数
交叉熵损失函数
我们先来看这个例子,实际上这个就是取出了y_hat[0,0]
和y_hat[2,1]
,可以拓展到高纬
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
那么我们的交叉熵损失函数为:
def cross_entropy(y_hat, y):
# 取出模型预估的该类的概率列表(一维数组),然后用targets取出
# 概率列表中对应的概率,因为targets中概率肯定为1,所以函数中可以省略
return - torch.log(y_hat[range(len(y_hat)), y])
6.分类精度
没啥好说的。看某行预测的最大概率的种类是不是和targets中一样。
由于True为1,False为0,sum一下即为预测正确的数量。
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)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
评估net的预测精度。
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
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]
Accumulator类
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):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
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]
updater为线性回归模型中的小批量随机梯度下降
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
tips
Q1:什么是广播机制?
A:如果两个张量的维度数不同,将较低维度的张量的形状通过在前面添加尺寸为1的维度进行扩展,直到两个张量的维度数相同。
对于每个维度,如果两个张量的大小相等,或者其中一个张量的大小为1,那么在该维度上可以进行广播。如果我们上面的partition没有keepdims,那么他只是一个张量,不满足广播的机制,但是我们可以这样
return X_exp / partition[:, None]
给他扩充一维,使他可以广播。
Q2:为什么要使用softmax函数?
A:假设O=WX+B,我们能否将O视为我们对应的输出呢?不能!因为O可能是负数,并且对我们的类的预测加和可能不为1
要将输出视为概率,我们必须保证在任何数据上的输出都是⾮负的且总和为1。(softmax是⼀个⾮线性函数,但softmax回归的输出仍然由输⼊特征的仿射变换决定。因此,softmax回仍然归是⼀个线性模型
数学好难