前言
Softmax回归与常规的线性回归不同的一点是其是针对分类问题的回归,在线性回归的基础上增加了一个softmax函数层,可以将神经网络的输出压缩到0-1之间,具体的公式为:
其中 j 为分类问题的第 j 类, 是指该网络的第 j 个输出经过Softmax后的值,也就是对于这个神经网络的输入,其为第 j 类的概率为;k 为分类问题的类别总数。对应的的神经网络模型如下:
图片来自:李沐老师《动手学深度学习》
代码书写流程
参考了第8节线性回归代码书写的内容,老师教程中的有些函数方法对于小白来说有点困难,这里的实现是非常简单的,相当于在线性回归里加了一个softmax层,然后修改了一下损失函数,代码如下:
第一步:下载训练数据集
# 下载数据
# 通过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)
第二步:构建softmax函数
根据前言中的公式来写的softmax函数,输入的labels对应的是神经网络的输出:
# 构建soft_max函数
def softmax(labels):
X_exp = torch.exp(labels)
partition = X_exp.sum(-1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
第三步:定义交叉熵损失函数
交叉熵损失函数的公式如下,其中指的是标签真实的概率分布,而指的是神经网络输出的预测概率分布。在我们的例子中只有其对应的真是标签位置 t 中的值为1,即,其余值都为0,比如我们的数据集有10个类别,输入的图片是第5类,那么的分布为[0,0,0,0,1,0,0,0,0,0] ,与此同时,对应的神经网络的输出可能为[0.01, 0.09, 0.12, 0, 0.75, 0.01, 0.01, 0.01, 0.0, 0.0],这时除了两个分布中的第5个数字,其他9个乘积全为0,因此计算的时候只用计算即可,其损失函数就可以写成如下图代码所示的形式,非常简单
def cross_entropy(y_pred, y):
return -torch.log(y_pred[range(len(y_pred)), y])
代码中y_pred时预测分布,y 为真实标签,y_pred[range(len(y_pred)), y]这个操作就是在求取,的值为1,可以省略。
第四步:定义权重更新函数(优化算法)
这个和线性回归的优化算法是一样的,在老师的教程中,将该方法封装成了一个函数保存在了d2l包中。
def update_params(w, b, lr, batch_size):
""""出错啦啦啦啦啦啦"""
# 不加 with torch.no_grad():的时候会报下面的错误,不知道为什么
# a leaf Variable that requires grad is being used in an in-place operation.
with torch.no_grad():
w += - lr * w.grad / batch_size
b += - lr * b.grad / batch_size
w.grad.zero_()
# w并不报错b'报错:'Tensor' object has no attribute 'grad_zero_'
# 啊,把.写成了_,服了,脑子不好使了
b.grad.zero_()
第五步:构建神经网络+初始化模型参数
# 构建神经网络
def linear_net(x, w, b):
"""
其中我们的权重矩阵的形状一定是确定的,因此在这里注意要对x矩阵进行 reshape,
(在网络中进行reshape可以提高网络的适配性)然后再进行线性操作
"""
# return softmax(torch.matmul(x.reshape((-1, w.shape[0])), w) + b)
# x.reshape((-1, w.shape[0])) 最里面那个,注意不是小括号是中括号
A = x.shape[0]
return softmax(torch.matmul(x.reshape([A, w.shape[0]]), w) + b)
# 初始化模型参数
w = torch.normal(0, 0.1, size=(784, 10), requires_grad=True)
b = torch.zeros(10, requires_grad=True)
第六步:划分数据集+训练
这里的自己加了一个loss计算的结果输出,因为感觉老师教程上面的写的有点复杂,没有很理解,想着自己先试试写的,还是老师那种计算精确度的方法更合适一些,并且将训练和测试封装为函数更合理,慢慢学啦,先写成这样,感觉自己学的好慢~~~
# 读取小批量数据
num_epochs = 10
batch_size = 32
lr = 0.001
net = linear_net
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
A = len(mnist_train) / 32
for epoch in range(num_epochs):
loss_all = 0
for x, y in train_iter:
a = x.sum(dim=1)
y_pred = net(x.sum(dim=1), w, b)
loss = cross_entropy(y_pred, y)
loss.sum().backward()
update_params(w, b, lr, batch_size)
with torch.no_grad():
loss_all = loss + loss_all
loss_all = loss_all / A
print(f'epoch {epoch + 1}, loss {float(loss_all.mean()):f}')
总结
到这里可以说实现了softmax回归,就是略显粗糙
完整代码
import d2l.torch
import torch
import torchvision
# from d2l.mxnet import Accumulator
from torch.utils import data
import matplotlib.pylab as plt
from torchvision import transforms
# 下载数据
# 通过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)
# print(len(mnist_train))
# print(len(mnist_test))
def get_dataloader_workers(): # @save
"""使用4个进程来读取数据"""
return 0
# 构建soft_max函数
def softmax(labels):
X_exp = torch.exp(labels)
partition = X_exp.sum(-1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
# 定义损失函数,交叉熵损失
def cross_entropy(y_pred, y):
return -torch.log(y_pred[range(len(y_pred)), y])
# 构建神经网络
def linear_net(x, w, b):
"""
其中我们的权重矩阵的形状一定是确定的,因此在这里注意要对x矩阵进行 reshape,
(在网络中进行reshape可以提高网络的适配性)然后再进行线性操作
"""
# return softmax(torch.matmul(x.reshape((-1, w.shape[0])), w) + b)
# x.reshape((-1, w.shape[0])) 最里面那个,注意不是小括号是中括号
A = x.shape[0]
return softmax(torch.matmul(x.reshape([A, w.shape[0]]), w) + b)
# 定义权重更新函数
def update_params(w, b, lr, batch_size):
""""出错啦啦啦啦啦啦"""
# 不加 with torch.no_grad():的时候会报下面的错误,不知道为什么
# a leaf Variable that requires grad is being used in an in-place operation.
with torch.no_grad():
w += - lr * w.grad / batch_size
b += - lr * b.grad / batch_size
w.grad.zero_()
# w并不报错b'报错:'Tensor' object has no attribute 'grad_zero_'
# 啊,把.写成了_,服了,脑子不好使了
b.grad.zero_()
# 初始化模型参数
w = torch.normal(0, 0.1, size=(784, 10), requires_grad=True)
b = torch.zeros(10, requires_grad=True)
# 读取小批量数据
num_epochs = 10
batch_size = 32
lr = 0.001
net = linear_net
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
A = len(mnist_train) / 32
for epoch in range(num_epochs):
loss_all = 0
for x, y in train_iter:
a = x.sum(dim=1)
y_pred = net(x.sum(dim=1), w, b)
loss = cross_entropy(y_pred, y)
loss.sum().backward()
update_params(w, b, lr, batch_size)
with torch.no_grad():
loss_all = loss + loss_all
loss_all = loss_all / A
print(f'epoch {epoch + 1}, loss {float(loss_all.mean()):f}')