【深度学习】:《100天一起学习PyTorch》第五天:从0到1实现Softmax回归
参考资料:本专栏主要以沐神《动手学深度学习》为学习资料,记录自己的学习笔记,能力有限,如有错误,欢迎大家指正。同时沐神上传了的教学视频和教材,大家可以前往学习。
写在前面
softmax回归模型是logistic回归模型在多分类问题上的推广,在多分类问题中,类标签y可以取两个以上的值。本文基于MNIST手写数字数据集来演示如何使用Pytorch实现softmax回归。🎄 |
🍓1. 数据集导入
首先我们来简单的介绍一些softmax 回归基本模型,基本思路如下:
P
(
c
l
a
s
s
=
i
)
=
e
i
∑
e
i
P(class=i) = \frac{e^i}{\sum e^i}
P(class=i)=∑eiei?
损失函数使用交叉熵 :
l
(
y
,
y
^
)
=
?
1
m
∑
y
i
l
o
g
y
^
i
l(y,\hat y) = -\frac{1}{m}\sum y_ilog{\hat y_i}
l(y,y^?)=?m1?∑yi?logy^?i?
import torch
import torch.nn as nn
from torchvision import datasets,transforms
from torch.utils import data
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
在这里与之前不同的是我们导入了torchvision ,它是处理计算机视觉常用的一个库。沐神在这里使用了FashionMnis t数据集,我在这里还是使用Mnist 数据集,具体的下载代码如下所示。其中train参数可以设置训练集和测试集
trans = transforms.ToTensor()
train = datasets.MNIST(root='./data',download=True,train=True,transform=trans)
test = datasets.MNIST(root='./data',download=True,train=False,transform=trans)
Mnist数据集 由10个数字的图像组成的。其中训练集有60000张图片,测试集有10000张图片。训练集用于模型的拟合,测试集用于评估模型的好坏
len(train), len(test)
(60000, 10000)
每张图片的像素均是28*28 ,并且是灰度图像,所以通道数为1
train[0][0].shape
torch.Size([1, 28, 28])
我们来看一下训练集中的特征和标签,.
X, y = next(iter(data.DataLoader(train, batch_size=25)))
y
tensor([5, 0, 4, 1, 9, 2, 1, 3, 1, 4, 3, 5, 3, 6, 1, 7, 2, 8, 6, 9, 4, 0, 9, 1,
1])
y代表的是0-9 的数字,下面我们将图形绘制出来
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
"""绘制图像列表"""
figsize = (num_cols * scale, num_rows * scale)
_, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
ax.imshow(img.numpy())
else:
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
X, y = next(iter(data.DataLoader(train, batch_size=25)))
show_images(X.reshape(25, 28, 28), 2, 9)
? ?
可以看到第一张图片是5,第二张图片是0。接下来我们想要做的事情是,给电脑一张图片,如何让其返回一个正确的数字。
🍅2.初始化参数
因为softmax 回归需要输入的数据是一个向量,因此首先我们需要将数据进行转换,下面要注意初始化参数的大小。
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回归
根据softmax 回归定义,我们可以通过以下三步实现:
具体实现代码如下
def softmax(X):
X_exp = torch.exp(X)
s = X_exp.sum(1, keepdims=True)
return X_exp / s
下面我们举一个简单的例子看一下softmax 函数是如何工作的
z = torch.rand(3, 5)
h = softmax(z)
print(h)
tensor([[0.1768, 0.1426, 0.2773, 0.2582, 0.1450],
[0.1580, 0.1307, 0.2118, 0.2411, 0.2583],
[0.1863, 0.2572, 0.1148, 0.1996, 0.2420]])
这样就得出了每一个样本中每一类的概率
进一步定义softmax 回归模型
def nex(X):
return softmax((X.reshape((-1,W.shape[0])).matmul(W)+b))
🍑4. 损失函数定义
在这里我们依然使用交叉熵函数处理多分类问题 损失函数:
l
(
y
,
y
^
)
=
?
1
m
∑
y
i
l
o
g
y
^
i
l(y,\hat y) = -\frac{1}{m}\sum y_ilog{\hat y_i}
l(y,y^?)=?m1?∑yi?logy^?i? 其中
y
i
=
0
,
1
y_i=0,1
yi?=0,1,
y
^
i
\hat{y}_i
y^?i?是预测的概率
在这里我想介绍两种方法计算损失函数,一种的沐神介绍的,通过索引来进行计算,具体如下所示
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
这里我们使用了y来进行索引,我们来看看一个具体的例子
y_true = torch.tensor([0,1])
y_hat = torch.tensor([[0.1,0.2,0.7],[0.3,0.5,0.2]])
y_hat[[0,1],y_true]
tensor([0.1000, 0.5000])
这里返回的是第一个样本中第一类是正确分类的,和第二个样本中的第二类是正确分类的。所以交叉熵的计算就是
?
1
2
(
1
×
l
o
g
(
0.1
)
+
1
×
l
o
g
(
0.5
)
)
-\frac{1}{2}(1\times log(0.1)+ 1\times log(0.5))
?21?(1×log(0.1)+1×log(0.5))
cross_entropy(y_hat,y_true).mean()
tensor(1.4979)
等价于:
(-np.log(0.1)-np.log(0.5))/2
1.4978661367769954
上面这种方式虽然简洁,但是可能不太好理解,下面介绍一种更直观的方式。首先我们要将y转换成one-hot 编码。
y_true = torch.tensor([0,1])
y_hat = torch.tensor([[0.1,0.2,0.7],[0.3,0.5,0.2]])
y_one_hot = torch.zeros_like(y_hat)
y_one_hot.scatter_(1, y_true.unsqueeze(1), 1)
y_one_hot
tensor([[1., 0., 0.],
[0., 1., 0.]])
可以看出此时的y_one_hot和y_hat维度相同,并且y_one_hot对应类上的元素是1,其余元素为0,此时再根据公式计算交叉熵即可
?
1
2
(
1
×
l
o
g
(
0.1
)
+
0
×
l
o
g
(
0.2
)
+
0
×
l
o
g
(
0.7
)
+
0
×
l
o
g
(
0.2
)
+
1
×
l
o
g
(
0.5
)
+
0
×
l
o
g
(
0.3
)
-\frac{1}{2}(1\times log(0.1)+0\times log(0.2)+0\times log(0.7) +0 \times log(0.2) +1\times log(0.5)+0\times log(0.3)
?21?(1×log(0.1)+0×log(0.2)+0×log(0.7)+0×log(0.2)+1×log(0.5)+0×log(0.3)
cost = (y_one_hot * -torch.log(y_hat)).sum(dim=1).mean()
cost
tensor(1.4979)
可以看出两种方法得到的结果一致
def opt(W,b):
return optim.SGD([W,b],lr=0.1)
🍐5.训练模型
'''
初始化参数
'''
W = torch.zeros((784, 10), requires_grad=True)
b = torch.zeros(10, requires_grad=True)
'''
定义SGD优化器
'''
optimizer = optim.SGD([W, b], lr=0.1)
'''
训练模型
'''
nb_epochs = 1000
for epoch in range(nb_epochs + 1):
z = net(X)
cost = cross_entropy(z,y)
optimizer.zero_grad()
cost.mean().backward()
optimizer.step()
if epoch % 100 == 0 :
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.mean().item()
))
Epoch 0/1000 Cost: 2.302585
Epoch 100/1000 Cost: 0.055274
Epoch 200/1000 Cost: 0.026265
Epoch 300/1000 Cost: 0.017182
Epoch 400/1000 Cost: 0.012762
Epoch 500/1000 Cost: 0.010150
Epoch 600/1000 Cost: 0.008425
Epoch 700/1000 Cost: 0.007202
Epoch 800/1000 Cost: 0.006290
Epoch 900/1000 Cost: 0.005582
Epoch 1000/1000 Cost: 0.005018
🍏6.模型预测
首先我们从测试集中随机抽取10个样本
X_test, y_test = next(iter(data.DataLoader(test, batch_size=10)))
show_images(X_test.reshape(10, 28, 28), 2, 5)
array([<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
<AxesSubplot:>, <AxesSubplot:>], dtype=object)
? ?
测试集拿到的十个数字为7,2,1,0,4,1,4,9,5,9 下面我们用刚刚训练好的模型来预测,看看结果如何
z = net(X_test)
predict = z.argmax(dim=1)
predict
tensor([7, 3, 1, 0, 4, 1, 4, 1, 4, 7])
可以看出预测的结果有六个正确,四个错误,模型效果一般。因为我们刚刚只使用了训练集中的25个样本,所以在训练集上预测效果并不好。如何提升预测精度问题将在后续讨论。
🍎7.使用内置api简单实现softmax回归
上面我们演示了如何从0到1实现softmax 回归,在pytorch 中,有内置的api可以直接帮我们更简洁的实现,具体代码如下
from torch import nn
X, y = next(iter(data.DataLoader(train, batch_size=25)))
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)
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
nb_epochs = 1000
for epoch in range(nb_epochs + 1):
z = net(X)
cost = loss(z,y)
trainer.zero_grad()
cost.mean().backward()
trainer.step()
if epoch % 100 == 0 :
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.mean().item()
))
Epoch 0/1000 Cost: 2.318002
Epoch 100/1000 Cost: 0.062154
Epoch 200/1000 Cost: 0.028716
Epoch 300/1000 Cost: 0.018596
Epoch 400/1000 Cost: 0.013739
Epoch 500/1000 Cost: 0.010891
Epoch 600/1000 Cost: 0.009021
Epoch 700/1000 Cost: 0.007699
Epoch 800/1000 Cost: 0.006715
Epoch 900/1000 Cost: 0.005955
Epoch 1000/1000 Cost: 0.005349
本章的介绍到此介绍,如果文章对你有帮助,请多多点赞、收藏、评论、关注支持!!
|