【深度学习】:《PyTorch入门到项目实战》第五天:从0到1实现Softmax回归(含源码)
【深度学习】:《PyTorch入门到项目实战》第五天:从0到1实现Softmax回归
- ✨本文收录于【深度学习】:《PyTorch入门到项目实战》专栏,此专栏主要记录如何使用PyTorch实现深度学习笔记,尽量坚持每周持续更新,欢迎大家订阅!
- 🌸个人主页:JoJo的数据分析历险记
- 📝个人介绍:小编大四统计在读,目前保研到统计学top3高校继续攻读统计研究生
- 💌如果文章对你有帮助,欢迎✌关注、👍点赞、✌收藏、👍订阅专栏
参考资料:本专栏主要以沐神《动手学深度学习》为学习资料,记录自己的学习笔记,能力有限,如有错误,欢迎大家指正。同时沐神上传了的教学视频和教材,大家可以前往学习。

 
文章目录
写在前面
| 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∑yilogy^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,它是处理计算机视觉常用的一个库。沐神在这里使用了FashionMnist数据集,我在这里还是使用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:
            # PIL图片
            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回归定义,我们可以通过以下三步实现:
- 1.对每一项求指数
- 2.求和
- 3.用每一行的数除以和
具体实现代码如下
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∑yilogy^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来进行索引,我们来看看一个具体的例子
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)#计算softmax回归结果 
    cost = cross_entropy(z,y)#计算损失函数
    # SGD求解参数
    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))#nn.Flatten()的作用是将输入的特征转换为一个向量
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')
# 定义SGD优化器
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)#计算损失函数
    # SGD求解参数
    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
本章的介绍到此介绍,如果文章对你有帮助,请多多点赞、收藏、评论、关注支持!!
 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号