基于Pytorch深度学习——Softmax回归

本文章来源于对李沐动手深度学习代码以及原理的理解,并且由于李沐老师的代码能力很强,以及视频中讲解代码的部分较少,所以这里将代码进行尽量逐行详细解释
并且由于pytorch的语法有些小伙伴可能并不熟悉,所以我们会采用逐行解释+小实验的方式来给大家解释代码

大家都知道二分类问题我们在机器学习里面使用到的是逻辑回归这个算法,但是针对于多分类问题,我们常用的是Softmax技术,大家不要被这个名字给迷惑了,softmax回归并不是一种回归技术,而是一种分类技术

tips:本文需要下载d2l包,大家可以按照下面的指令安装

pip install -U d2l

导入模块

import torch
from IPython import display
from d2l import torch as d2l

这里如果存在报错的话,可以自己pip install对应的模块,如果还出现了其他问题,可以在评论区中问出来

导入数据集

这里我们使用的是手写数字识别分类数据集mnist,李沐老师在d2l包李沐已经帮我们写好了下载并且导入这个数据集的函数,我们只需要指定batch_size,然后再导入对应的模块即可

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

这个代码里面load_data_fashion_mnist返回的是两个迭代器,如果对迭代器并不是特别了解的同学,可以先去看看python基础课

batch_size

相信初学者们肯定对batch_size这个这个超参数感到非常的疑惑(超参数这个名词我们在以后的文章也会解释到)。
batch_size的官方定义是深度学习模型在训练过程中一次性输入给模型的样本数量,因为深度学习通常使用的样本都是上万的数据量,如果一口气全部给模型进行训练或者推理,那么GPU/CPU肯定会吃不消,所以我们通常会分批次将数据输入给模型
当然有关于如何选择batch_size的问题也是一个值得探究的问题,一般而言,如果你的CPU/GPU的内存比较小的话,建议选择比较小的batch_size,如果你的内存比较大的话,可以选择较大的batch_size,较大的batch_size可以加快你的训练速度

初始化模型参数

在我们导入的数据集中,每个样本都是 28 × 28 28 \times 28 28×28的图像,但是在我们一般的处理中,我们会选择将展平每个图像,把它们看作长度为784的向量。
需要我们注意的是Softmax回归,我们的输出与输入的类别的数目是一样多的
因此,权重将构成一个 784 × 10 784 \times 10 784×10的矩阵,偏置将构成一个 1 × 10 1 \times 10 1×10的行向量。
于是我们可以得到下面的矩阵计算公式
X 1 × 784 × W 784 × 10 + b 1 × 10 = Y 1 × 10 (1.1) X_{1 \times 784} \times W_{784 ×10} + b_{1×10}= Y_{1×10} \tag{1.1} X1×784×W784×10+b1×10=Y1×10(1.1)
这个式子和我们的线性回归模型十分相似,但是存在一定的不一样,因为线性回归的输入类别和输出类别都是2

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)

在这个代码里面,我们将W初始化为(784×10)的一个矩阵,并且里面的每个元素都服从均值为0,标准差为0.01的正态分布;将b初始化为(1×10)的一个全零向量

Softmax实现

想要实现softmax,我们需要完成三个步骤,这三个步骤是当我们已经计算出了Y的值之后
Step1:对Y的每个维度进行求e指数
Step2:需要对每一行进行求和,得到一个规范化的常数
Step3:将每一行都与规范化的常数进行相除,确定结果的和为1
用公式来表达就是下面的这个公式
s o f t m a x ( X ) i j = exp ⁡ ( X i j ) ∑ k exp ⁡ ( X i k ) . \mathrm{softmax}(\mathbf{X})_{ij} = \frac{\exp(\mathbf{X}_{ij})}{\sum_k \exp(\mathbf{X}_{ik})}. softmax(X)ij=kexp(Xik)exp(Xij).

def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True)
    return X_exp / partition  # 这里应用了广播机制

在上面的这个定义中,其实都还比较好理解,唯一可能让初学者产生疑问的就是有关于sum函数里面的维度参数的使用,于是我们做一个小实验来看看这个参数它具体有什么作用

小实验/维度参数

我们可以先用torch.tensor创造一个tensor数据类型的二维数组

X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])

为了我们后面好观察,我们在这里直观一点,将矩阵的形式用公式打出来:
1 2 3 4 5 6 (1) \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{matrix} \tag1 142536(1)
首先,我们先不对维度参数做出赋值,看看sum会产生怎么样的结果

X.sum()
>>> tensor(21.)

从这个例子我们可以看出,如果不对维度参数赋值的话,sum就是把X里面的所有元素进行累加,也就是 1 + 2 + 3 + 4 + 5 + 6 = 21 1+2+3+4+5+6=21 1+2+3+4+5+6=21

然后我们再举一个例子,此时我们不赋值keepdim参数的值,我们只选择0和1参数

X.sum(0),X.sum(1)
>>>(tensor([5., 7., 9.]), tensor([ 6., 15.]))

我们先来看看X.sum(0)代表的是什么,根据矩阵(1)我们可以很直观的看到,[5,7,9]代表的是将原来的矩阵的每一列进行求和最后组成一个行向量
同理可得X.sum(1)就是把原来矩阵的每一行进行求和,最后组成一个行向量

最后我们添加上keepdim参数

X.sum(0, keepdim=True), X.sum(1, keepdim=True)
>>>(tensor([[5., 7., 9.]]),
 tensor([[ 6.],
         [15.]]))

我们不难看出,添加了keepdim参数之后,结果里面元素的值并没有发生变化,发生变化的是矩阵的维度,所以我们可以知道keepdim是用来保存原矩阵加和的维度的
也就是说,假如你使用0进行将每一列进行求和,那么你得到的就会是一个行向量,因为你相当于对着每一行进行叠加,这里和pandas的求和方法是一样的

定义模型

def net(X):
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

这个代码的大致也很好理解,就是将公式(1.1)进行了计算机化表现,对于初学者而言,可能比较难理解的是有关于reshape的应用,下面我们还是使用几个小实验来进行探索

小实验/reshape

reshape操作是只改变行和列的维度,但是不会改变矩阵中总元素的个数,也就是说如果你想让3×5的矩阵变成4×4的矩阵,这样是做不到的
我们可以借用(1)矩阵来讲解,这里为了大家好观察,我再将矩阵(1)拿过来
1 2 3 4 5 6 (1) \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{matrix} \tag1 142536(1)
我们可以先来试试把这个矩阵给展成一个行向量,需要注意的是reshape里面的参数第一个是你想要改变后的行数,第二个是列数

X.reshape(1,6)
>>>tensor([[1., 2., 3., 4., 5., 6.]])

然后我们可以试试把它改成3×2的矩阵

X.reshape(3,2)
>>>tensor([[1., 2.],
        [3., 4.],
        [5., 6.]])

接下来我们可以看看在代码里面的-1是什么意思

X.reshape(1,-1)
>>>tensor([[1., 2., 3., 4., 5., 6.]])

从这里我们可以看出来,如果reshape里面的参数是-1,这个函数就会自己计算合适的维度,让元素的总个数不产生变化

最后我们可以验证一下reshape操作并不可以改变元素的总个数

X.reshape(1,8)
>>>RuntimeError: shape '[1, 8]' is invalid for input of size 6

可以看到发生了报错

定义损失函数

与线性回归模型不同,线性回归使用的损失函数是MSE,但是在softmax分类里面我们采用的是交叉熵损失函数
李沐老师课上讲的函数表达式非常的简洁,但是并不是特别好理解,所以我么采用吴恩达老师讲述的交叉熵损失函数的公式,实际上他们是一个式子
L ( y , p ) = − ( y l o g ( p ) + ( 1 − y ) l o g ( 1 − p ) ) L(y,p) = -(ylog(p) + (1-y)log(1-p)) L(y,p)=(ylog(p)+(1y)log(1p))
李沐老师在代码实现这个函数的时候采用了一个非常巧妙的想法,下面我们来仔细的研究一下

def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])

cross_entropy(y_hat, y)

我相信大家对y_hat[range(len(y_hat))的疑问很大

小实验/索引匹配

我们可以先自己定义一个y和y_hat来看看上面这个代码的表达是什么意思

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]
>>> tensor([0.1000, 0.5000])

这个是什么意思呢?
我们可以来看最后面的y_hat[[0, 1], y],我们可以把这个代码理解为把前面的**[0,1]以及y都看作是一个迭代器**,并且这个迭代器满足索引匹配,也就是这个函数等价于y_hat[0,0]与y_hat[1,2]

定义优化器

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)
  • 26
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值