深度学习实验(一)

  • Post author:
  • Post category:其他





线性回归与 softmax



线性回归



线性回归概要


线性回归

(linear regression)在回归的各种标准工具中最简单而且最流行。它可以追溯到19世纪初。线性回归基于几个简单的假设:首先,假设自变量



x

\mathbf{x}







x






和因变量



y

y






y





之间的关系是线性的,即



y

y






y





可以表示为



x

\mathbf{x}







x






中元素的加权和,这里通常允许包含观测值的一些噪声;其次,我们假设任何噪声都比较正常,如噪声遵循正态分布。

通常,我们使用



n

n






n





来表示数据集中的样本数。对索引为



i

i






i





的样本,其输入表示为



x

(

i

)

=

[

x

1

(

i

)

,

x

2

(

i

)

]

\mathbf{x}^{(i)} = [x_1^{(i)}, x_2^{(i)}]^\top








x












(


i


)












=








[



x










1









(


i


)



















,





x










2









(


i


)




















]























,其对应的标签是



y

(

i

)

y^{(i)}







y











(


i


)















线性模型

线性假设是指目标可以表示为特征的加权和,例如:





p

r

i

c

e

=

w

a

r

e

a

a

r

e

a

+

w

a

g

e

a

g

e

+

b

\mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b







p


r


i


c


e





=









w












a


r


e


a
































a


r


e


a





+









w












a


g


e
































a


g


e





+








b








w

a

r

e

a

w_{\mathrm{area}}







w












a


r


e


a



























w

a

g

e

w_{\mathrm{age}}







w












a


g


e























称为

权重

(weight),



b

b






b





称为

偏置

(bias),或称为

偏移量

(offset)、

截距

(intercept)。权重决定了每个特征对我们预测值的影响。偏置是指当所有特征都取值为0时,预测值应该为多少。

给定一个数据集,我们的目标是寻找模型的权重



w

\mathbf{w}







w






和偏置



b

b






b





,使得根据模型做出的预测大体符合数据里的真实价格。输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。当我们的输入包含



d

d






d





个特征时,我们将预测结果



y

^

\hat{y}














y







^



















(通常使用 “尖角” 符号表示估计值)表示为:





y

^

=

w

1

x

1

+

.

.

.

+

w

d

x

d

+

b

.

\hat{y} = w_1 x_1 + … + w_d x_d + b.














y







^


















=









w










1



















x










1




















+








.


.


.




+









w










d



















x










d




















+








b


.





将所有特征放到向量



x

R

d

\mathbf{x} \in \mathbb{R}^d







x
















R











d












中,并将所有权重放到向量



w

R

d

\mathbf{w} \in \mathbb{R}^d







w
















R











d












中,我们可以用点积形式来简洁地表达模型:





y

^

=

w

x

+

b

.

\hat{y} = \mathbf{w}^\top \mathbf{x} + b.














y







^


















=










w






















x





+








b


.





向量



x

\mathbf{x}







x






对应于单个数据样本的特征。用符号表示的矩阵



X

R

n

×

d

\mathbf{X} \in \mathbb{R}^{n \times d}







X
















R












n


×


d













可以很方便地引用我们整个数据集的



n

n






n





个样本。其中,



X

\mathbf{X}







X






的每一行是一个样本,每一列是一种特征。

对于特征集合



X

\mathbf{X}







X






,预测值



y

^

R

n

\hat{\mathbf{y}} \in \mathbb{R}^n















y








^





























R











n












可以通过矩阵-向量乘法表示为:





y

^

=

X

w

+

b

{\hat{\mathbf{y}}} = \mathbf{X} \mathbf{w} + b
















y








^



















=









X




w





+








b





这个过程中的求和将使用广播机制,给定训练数据特征



X

\mathbf{X}







X






和对应的已知标签



y

\mathbf{y}







y






,线性回归的目标是找到一组权重向量



w

\mathbf{w}







w






和偏置



b

b






b





。当给定从



X

\mathbf{X}







X






的同分布中取样的新样本特征时,找到的权重向量和偏置能够使得新样本预测标签的误差尽可能小。

虽然我们相信给定



x

\mathbf{x}







x






预测



y

y






y





的最佳模型会是线性的,但我们很难找到一个有



n

n






n





个样本的真实数据集,其中对于所有的



1

i

n

1 \leq i \leq n






1













i













n









y

(

i

)

y^{(i)}







y











(


i


)













完全等于



w

x

(

i

)

+

b

\mathbf{w}^\top \mathbf{x}^{(i)}+b








w























x












(


i


)












+








b





。无论我们使用什么手段来观察特征



X

\mathbf{X}







X






和标签



y

\mathbf{y}







y






,都可能会出现少量的观测误差。因此,即使确信特征与标签的潜在关系是线性的,我们也会加入一个噪声项来考虑观测误差带来的影响。



损失函数

在我们开始考虑如何用模型

拟合

(fit)数据之前,我们需要确定一个拟合程度的度量。

损失函数

能够量化目标的

实际

值与

预测

值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。回归问题中最常用的损失函数是平方误差函数。当样本



i

i






i





的预测值为



y

^

(

i

)

\hat{y}^{(i)}















y







^

























(


i


)













,其相应的真实标签为



y

(

i

)

y^{(i)}







y











(


i


)













时,平方误差可以定义为以下公式:





l

(

i

)

(

w

,

b

)

=

1

2

(

y

^

(

i

)

y

(

i

)

)

2

.

l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} – y^{(i)}\right)^2.







l











(


i


)










(



w



,




b


)




=



















2














1

























(












y







^

























(


i


)


















y











(


i


)











)












2











.





常数



1

2

\frac{1}{2}


















2
















1
























不会带来本质的差别,但这样在形式上稍微简单一些,表现为当我们对损失函数求导后常数系数为1。由于训练数据集并不受我们控制,所以经验误差只是关于模型参数的函数。

由于平方误差函数中的二次方项,估计值



y

^

(

i

)

\hat{y}^{(i)}















y







^

























(


i


)













和观测值



y

(

i

)

y^{(i)}







y











(


i


)













之间较大的差异将贡献更大的损失。为了度量模型在整个数据集上的质量,我们需计算在训练集



n

n






n





个样本上的损失均值(也等价于求和)。





L

(

w

,

b

)

=

1

n

i

=

1

n

l

(

i

)

(

w

,

b

)

=

1

n

i

=

1

n

1

2

(

w

x

(

i

)

+

b

y

(

i

)

)

2

.

L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n l^{(i)}(\mathbf{w}, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^\top \mathbf{x}^{(i)} + b – y^{(i)}\right)^2.






L


(



w



,




b


)




=



















n














1































i


=


1


















n




















l











(


i


)










(



w



,




b


)




=



















n














1































i


=


1


















n






























2














1

























(





w























x












(


i


)












+




b










y











(


i


)











)












2











.





在训练模型时,我们希望寻找一组参数 (



w

,

b

\mathbf{w}^*, b^*








w





















,





b























),这组参数能最小化在所有训练样本上的总损失。如下式:





w

,

b

=

*

a

r

g

m

i

n

w

,

b

 

L

(

w

,

b

)

.

\mathbf{w}^*, b^* = \operatorname*{argmin}_{\mathbf{w}, b}\ L(\mathbf{w}, b).








w





















,





b






















=









*







a


r


g


m


i


n













w



,


b





















L


(



w



,




b


)


.







解析解

线性回归刚好是一个很简单的优化问题。与我们将在本书中所讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来,这类解叫作解析解(analytical solution)。首先,我们将偏置



b

b






b





合并到参数



w

\mathbf{w}







w






中。合并方法是在包含所有参数的矩阵中附加一列。我们的预测问题是最小化



y

X

w

2

\|\mathbf{y} – \mathbf{X}\mathbf{w}\|^2










y















X




w















2












。这在损失平面上只有一个临界点,这个临界点对应于整个区域的损失最小值。将损失关于



w

\mathbf{w}







w






的导数设为0,得到解析解(闭合形式):





w

=

(

X

X

)

1

X

y

.

\mathbf{w}^* = (\mathbf X^\top \mathbf X)^{-1}\mathbf X^\top \mathbf{y}.








w























=








(



X




















X



)














1











X





















y



.





像线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。解析解可以进行很好的数学分析,但解析解的限制很严格,导致它无法应用在深度学习里。



小批量随机梯度下降

本书中我们用到一种名为

梯度下降

(gradient descent)的方法,这种方法几乎可以优化所有深度学习模型。它通过不断地在损失函数递减的方向上更新参数来降低误差。

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这里也可以称为梯度)。但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本,这种变体叫做

小批量随机梯度下降

(minibatch stochastic gradient descent)。

在每次迭代中,我们首先随机抽样一个小批量



B

\mathcal{B}







B






,它是由固定数量的训练样本组成的。然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。最后,我们将梯度乘以一个预先确定的正数



η

\eta






η





,并从当前参数的值中减掉。

我们用下面的数学公式来表示这一更新过程(



\partial












表示偏导数):





(

w

,

b

)

(

w

,

b

)

η

B

i

B

(

w

,

b

)

l

(

i

)

(

w

,

b

)

.

(\mathbf{w},b) \leftarrow (\mathbf{w},b) – \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{(\mathbf{w},b)} l^{(i)}(\mathbf{w},b).






(



w



,




b


)













(



w



,




b


)




























B


















η































i






B











































(



w



,


b


)




















l











(


i


)










(



w



,




b


)


.





总结一下,算法的步骤如下:

  1. 初始化模型参数的值,如随机初始化;
  2. 从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。对于平方损失和仿射变换,我们可以明确地写成如下形式:





w

w

η

B

i

B

w

l

(

i

)

(

w

,

b

)

=

w

η

B

i

B

x

(

i

)

(

w

x

(

i

)

+

b

y

(

i

)

)

,

b

b

η

B

i

B

b

l

(

i

)

(

w

,

b

)

=

b

η

B

i

B

(

w

x

(

i

)

+

b

y

(

i

)

)

.

\begin{aligned} \mathbf{w} &\leftarrow \mathbf{w} – \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_{\mathbf{w}} l^{(i)}(\mathbf{w}, b) = \mathbf{w} – \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \mathbf{x}^{(i)} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b – y^{(i)}\right),\\ b &\leftarrow b – \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \partial_b l^{(i)}(\mathbf{w}, b) = b – \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \left(\mathbf{w}^\top \mathbf{x}^{(i)} + b – y^{(i)}\right). \end{aligned}

















w









b



































w

























B


















η































i






B












































w





















l











(


i


)










(



w



,




b


)




=





w

























B


















η































i






B
































x












(


i


)














(





w























x












(


i


)












+




b










y











(


i


)











)






,

















b
























B


















η































i






B










































b



















l











(


i


)










(



w



,




b


)




=




b
























B


















η































i






B
































(





w























x












(


i


)












+




b










y











(


i


)











)






.






















公式中的



w

\mathbf{w}







w










x

\mathbf{x}







x






都是向量。在这里,更优雅的向量表示法比系数表示法(如



w

1

,

w

2

,

,

w

d

w_1, w_2, \ldots, w_d







w










1


















,





w










2


















,









,





w










d





















)更具可读性。



B

|\mathcal{B}|










B









表示每个小批量中的样本数,这也称为

批量大小

(batch size)。



η

\eta






η





表示

学习率

(learning rate)。在训练了预先确定的若干迭代次数后(或者直到满足某些其他停止条件后),我们记录下模型参数的估计值,表示为



w

^

,

b

^

\hat{\mathbf{w}}, \hat{b}















w








^







,












b







^










。但是,即使我们的函数确实是线性的且无噪声,这些估计值也不会使损失函数真正地达到最小值。因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内非常精确地达到最小值。



用学习到的模型进行预测

给定学习到的线性回归模型



w

^

x

+

b

^

\hat{\mathbf{w}}^\top \mathbf{x} + \hat{b}
















w








^


























x





+
















b







^










,现在我们可以通过给定的房屋面积



x

1

x_1







x










1





















和房龄



x

2

x_2







x










2





















来估计一个未包含在训练数据中的新房屋价格。给定特征估计目标的过程通常称为

预测

(prediction)或

推断

(inference)。



矢量化加速

在训练我们的模型时,我们经常希望能够同时处理整个小批量的样本。为了实现这一点,需要(我们对计算进行矢量化,从而利用线性代数库,而不是在Python中编写开销高昂的for循环)。

import math
import time
import numpy as np
import torch
from d2l import torch as d2l

为了说明矢量化为什么如此重要,我们考虑(对向量相加的两种方法)。

我们实例化两个全1的1000维向量。在一种方法中,我们将使用Python的for循环遍历向量。在另一种方法中,我们将依赖对



+

+






+





的调用。

n = 10000
a = torch.ones(n)
b = torch.ones(n)

定义计时器,便于比较‘

class Timer:  #@save
    """记录多次运行时间。"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """启动计时器。"""
        self.tik = time.time()

    def stop(self):
        """停止计时器并将时间记录在列表中。"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """返回平均时间。"""
        return sum(self.times) / len(self.times)

    def sum(self):
        """返回时间总和。"""
        return sum(self.times)

    def cumsum(self):
        """返回累计时间。"""
        return np.array(self.times).cumsum().tolist()

基准测试

c = torch.zeros(n)
timer = Timer()
for i in range(n):
    c[i] = a[i] + b[i]
f'{timer.stop():.5f} sec'

或使用重载的



+

+






+




timer.start()
d = a + b
f'{timer.stop():.5f} sec'

方法二要快很多

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXfFpsO6-1636466983091)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\1.jpg)]



正态分布与平方损失

正态分布和线性回归之间的关系很密切。

简单的说,若随机变量



x

x






x





具有均值



μ

\mu






μ





和方差



σ

2

\sigma^2







σ










2












(标准差



σ

\sigma






σ





),其正态分布概率密度函数如下:





p

(

x

)

=

1

2

π

σ

2

exp

(

1

2

σ

2

(

x

μ

)

2

)

p(x) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (x – \mu)^2\right)






p


(


x


)




=



























2


π



σ










2











































1






















exp






(

















2



σ










2





















1




















(


x









μ



)










2










)







下面进行计算

def normal(x, mu, sigma):
    p = 1 / math.sqrt(2 * math.pi * sigma**2)
    return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)

# 再次使用numpy进行可视化
x = np.arange(-7, 7, 0.01)

# 均值和标准差对
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x, [normal(x, mu, sigma) for mu, sigma in params], xlabel='x',
         ylabel='p(x)', figsize=(4.5, 2.5),
         legend=[f'mean {mu}, std {sigma}' for mu, sigma in params])

就像我们所看到的,改变均值会产生沿



x

x






x





轴的偏移,增加方差将会分散分布、降低其峰值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8nTkycvN-1636466983093)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\2.jpg)]

均方误差损失函数(简称均方损失)可以用于线性回归的一个原因是:我们假设了观测中包含噪声,其中噪声服从正态分布。噪声正态分布如下式:





y

=

w

x

+

b

+

ϵ

 where 

ϵ

N

(

0

,

σ

2

)

y = \mathbf{w}^\top \mathbf{x} + b + \epsilon \text{ where } \epsilon \sim \mathcal{N}(0, \sigma^2)






y




=










w






















x





+








b




+








ϵ



where



ϵ














N



(


0


,





σ










2









)





因此,我们现在可以写出通过给定的



x

\mathbf{x}







x






观测到特定



y

y






y







可能性

(likelihood):





P

(

y

x

)

=

1

2

π

σ

2

exp

(

1

2

σ

2

(

y

w

x

b

)

2

)

P(y \mid \mathbf{x}) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (y – \mathbf{w}^\top \mathbf{x} – b)^2\right)






P


(


y














x



)




=



























2


π



σ










2











































1






















exp






(

















2



σ










2





















1




















(


y











w






















x










b



)










2










)







现在,根据最大似然估计法,参数



w

\mathbf{w}







w










b

b






b





的最优值是使整个数据集的

可能性

最大的值:





P

(

y

X

)

=

i

=

1

n

p

(

y

(

i

)

x

(

i

)

)

P(\mathbf y \mid \mathbf X) = \prod_{i=1}^{n} p(y^{(i)}|\mathbf{x}^{(i)})






P


(


y













X


)




=

















i


=


1



















n




















p


(



y











(


i


)















x












(


i


)










)





根据最大似然估计法选择的估计量称为

最大似然估计量



虽然使许多指数函数的乘积最大化看起来很困难,但是我们可以在不改变目标的前提下,通过最大化似然对数来简化。

由于历史原因,优化通常是说最小化而不是最大化。我们可以改为

最小化负对数似然




log

P

(

y

X

)

-\log P(\mathbf y \mid \mathbf X)











lo

g





P


(


y













X


)





。由此可以得到的数学公式是:





log

P

(

y

X

)

=

i

=

1

n

1

2

log

(

2

π

σ

2

)

+

1

2

σ

2

(

y

(

i

)

w

x

(

i

)

b

)

2

-\log P(\mathbf y \mid \mathbf X) = \sum_{i=1}^n \frac{1}{2} \log(2 \pi \sigma^2) + \frac{1}{2 \sigma^2} \left(y^{(i)} – \mathbf{w}^\top \mathbf{x}^{(i)} – b\right)^2











lo

g





P


(


y













X


)




=

















i


=


1


















n






























2














1






















lo

g



(


2


π



σ










2









)




+



















2



σ










2





















1

























(




y











(


i


)



















w























x












(


i


)

















b



)












2














现在我们只需要假设



σ

\sigma






σ





是某个固定常数就可以忽略第一项,因为第一项不依赖于



w

\mathbf{w}







w










b

b






b





。现在第二项除了常数



1

σ

2

\frac{1}{\sigma^2}



















σ










2























1
























外,其余部分和前面介绍的平方误差损失是一样的。

幸运的是,上面式子的解并不依赖于



σ

\sigma






σ





。因此,在高斯噪声的假设下,最小化均方误差等价于对线性模型的最大似然估计。



线性回归的从零开始实现

在这一节中,我们将只使用张量和自动求导。在之后的章节中,我们会充分利用深度学习框架的优势,介绍更简洁的实现方式。

import random
import torch
from d2l import torch as d2l



生成数据集

在下面的代码中,我们生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征。我们的合成数据集是一个矩阵



X

R

1000

×

2

\mathbf{X}\in \mathbb{R}^{1000 \times 2}







X
















R












1


0


0


0


×


2













你可以将



ϵ

\epsilon






ϵ





视为捕获特征和标签时的潜在观测误差。在这里我们认为标准假设成立,即



ϵ

\epsilon






ϵ





服从均值为



0

0






0





的正态分布。

为了简化问题,我们将标准差设为



0.01

0.01






0


.


0


1





。下面的代码生成合成数据集。

def synthetic_data(w, b, num_examples):  #@save
    """生成 y = Xw + b + 噪声。"""
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape)
    return X, y.reshape((-1, 1))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

print('features:', features[0], '\nlabel:', labels[0])
# 通过生成第二个特征 `features[:, 1]` 和 `labels` 的散点图,可以直观地观察到两者之间的线性关系。
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(),
                labels.detach().numpy(), 1);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QR6WI788-1636466983095)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\3.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDvdR12m-1636466983097)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\4.jpg)]



读取数据集

回想一下,训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数,该函数能打乱数据集中的样本并以小批量方式获取数据。

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(indices[i:min(i +
                                                   batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]
        
batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXE9GIwo-1636466983098)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\5.jpg)]



初始化模型参数

在我们开始用小批量随机梯度下降优化我们的模型参数之前,我们需要先有一些参数。

在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。

每次更新都需要计算损失函数关于模型参数的梯度。有了这个梯度,我们就可以向减小损失的方向更新每个参数。

因为手动计算梯度很枯燥而且容易出错,所以没有人会手动计算梯度。我们使用 :numref:

sec_autograd

中引入的自动微分来计算梯度。



定义参数

接下来,我们必须定义模型,将模型的输入和参数同模型的输出关联起来。

回想一下,要计算线性模型的输出,我们只需计算输入特征



X

\mathbf{X}







X






和模型权重



w

\mathbf{w}







w






的矩阵-向量乘法后加上偏置



b

b






b





。注意,上面的



X

w

\mathbf{Xw}







X


w






是一个向量,而



b

b






b





是一个标量。广播机制使得当我们用一个向量加一个标量时,标量会被加到向量的每个分量上。

def linreg(X, w, b):  #@save
    """线性回归模型。"""
    return torch.matmul(X, w) + b



定义损失函数

因为要更新模型。需要计算损失函数的梯度,所以我们应该先定义损失函数。

def squared_loss(y_hat, y):  #@save
    """均方损失。"""
    return (y_hat - y.reshape(y_hat.shape))**2 / 2



定义优化算法

正如我们在 :numref:

sec_linear_regression

中讨论的,线性回归有解析解。然而,这是一本关于深度学习的书,而不是一本关于线性回归的书。

由于这本书介绍的其他模型都没有解析解,下面我们将在这里介绍小批量随机梯度下降的工作示例。

在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的方向更新我们的参数。

下面的函数实现小批量随机梯度下降更新。该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率

lr

决定。

因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(

batch_size

)来归一化步长,这样步长大小就不会取决于我们对批量大小的选择。

def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降。"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()



训练

概括一下,我们将执行以下循环:

  1. 初始化参数

  2. 重复,直到完成

    计算梯度



    g

    (

    w

    ,

    b

    )

    1

    B

    i

    B

    l

    (

    x

    (

    i

    )

    ,

    y

    (

    i

    )

    ,

    w

    ,

    b

    )

    \mathbf{g} \leftarrow \partial_{(\mathbf{w},b)} \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} l(\mathbf{x}^{(i)}, y^{(i)}, \mathbf{w}, b)







    g



























    (



    w



    ,


    b


    )



































    B




















    1




































    i






    B






















    l


    (




    x












    (


    i


    )










    ,





    y











    (


    i


    )










    ,





    w



    ,




    b


    )




    更新参数



    (

    w

    ,

    b

    )

    (

    w

    ,

    b

    )

    η

    g

    (\mathbf{w}, b) \leftarrow (\mathbf{w}, b) – \eta \mathbf{g}






    (



    w



    ,




    b


    )













    (



    w



    ,




    b


    )













    η



    g





在每个

迭代周期

(epoch)中,我们使用

data_iter

函数遍历整个数据集,并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。这里的迭代周期个数

num_epochs

和学习率

lr

都是超参数,分别设为3和0.03。设置超参数很棘手,需要通过反复试验进行调整。

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # `X`和`y`的小批量损失
        # 因为`l`形状是(`batch_size`, 1),而不是一个标量。`l`中的所有元素被加到一起,
        # 并以此计算关于[`w`, `b`]的梯度
        l.sum().backward()
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LG204kOC-1636466983099)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\6.jpg)]

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rc3Paho4-1636466983100)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\7.jpg)]



线性回归的简洁实现



生成数据集

首先生成数据集

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)



读取数据集

我们可以调用框架中现有的API来读取数据。我们将

features



labels

作为API的参数传递,并在实例化数据迭代器对象时指定

batch_size

。此外,布尔值

is_train

表示是否希望数据迭代器对象在每个迭代周期内打乱数据。

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个PyTorch数据迭代器。"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNQzOfWY-1636466983100)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\8.jpg)]

这里我们使用

iter

构造Python迭代器,并使用

next

从迭代器中获取第一项。



定义模型

对于标准操作,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。我们首先定义一个模型变量

net

,它是一个

Sequential

类的实例。

Sequential

类为串联在一起的多个层定义了一个容器。当给定输入数据,

Sequential

实例将数据传入到第一层,然后将第一层的输出作为第二层的输入,依此类推。在下面的例子中,我们的模型只包含一个层,因此实际上不需要

Sequential

。但是由于以后几乎所有的模型都是多层的,在这里使用

Sequential

会让你熟悉标准的流水线。这一单层被称为

全连接层

(fully-connected layer),因为它的每一个输入都通过矩阵-向量乘法连接到它的每个输出。

在 PyTorch 中,全连接层在

Linear

类中定义。值得注意的是,我们将两个参数传递到

nn.Linear

中。第一个指定输入特征形状,第二个指定输出特征形状。

from torch import nn
net = nn.Sequential(nn.Linear(2, 1))



初始化模型参数

在使用

net

之前,我们需要初始化模型参数。如在线性回归模型中的权重和偏置。

深度学习框架通常有预定义的方法来初始化参数。

在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。

正如我们在构造

nn.Linear

时指定输入和输出尺寸一样。现在我们直接访问参数以设定初始值。我们通过

net[0]

选择网络中的第一个图层,然后使用

weight.data



bias.data

方法访问参数。然后使用替换方法

normal_



fill_

来重写参数值。

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)



定义损失函数

计算均方误差使用的是

MSELoss

类,也称为平方



L

2

L_2







L










2





















范数。默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()



定义优化算法

小批量随机梯度下降算法是一种优化神经网络的标准工具,PyTorch 在

optim

模块中实现了该算法的许多变种。当我们实例化

SGD

实例时,我们要指定优化的参数(可通过

net.parameters()

从我们的模型中获得)以及优化算法所需的超参数字典。小批量随机梯度下降只需要设置

lr

值,这里设置为 0.03。

trainer = torch.optim.SGD(net.parameters(), lr=0.03)



训练

通过深度学习框架的高级 API 来实现我们的模型只需要相对较少的代码。我们不必单独分配参数、不必定义我们的损失函数,也不必手动实现小批量随机梯度下降。当我们需要更复杂的模型时,高级 API 的优势将大大增加。当我们有了所有的基本组件,训练过程代码与我们从零开始实现时所做的非常相似。

回顾一下:在每个迭代周期里,我们将完整遍历一次数据集(

train_data

),不停地从中获取一个小批量的输入和相应的标签。对于每一个小批量,我们会进行以下步骤:

  1. 通过调用

    net(X)

    生成预测并计算损失

    l

    (正向传播)。
  2. 通过进行反向传播来计算梯度。
  3. 通过调用优化器来更新模型参数。

为了更好的衡量训练效果,我们计算每个迭代周期后的损失,并打印它来监控训练过程。

num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X), y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CGJlgRQH-1636466983101)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\9.jpg)]

比较生成数据集的真实参数和通过有限数据训练获得的模型参数,要访问参数,我们首先从

net

访问所需的层,然后读取该层的权重和偏置。正如在从零开始实现中一样,我们估计得到的参数与生成数据的真实参数非常接近。

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njOOEwi5-1636466983102)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\10.jpg)]



小结

  • 我们可以使用 PyTorch 的高级 API更简洁地实现模型。
  • 在 PyTorch 中,

    data

    模块提供了数据处理工具,

    nn

    模块定义了大量的神经网络层和常见损失函数。
  • 我们可以通过

    _

    结尾的方法将参数替换,从而初始化参数。



softmax回归



softmax 综述

事实上,我们经常对分类问题感兴趣:不是问“多少”,而是问“哪一个”:

通常,机器学习实践者用

分类

这个词来描述两个有微妙差别的问题:

  1. 我们只对样本的硬性类别感兴趣,即属于哪个类别;
  2. 我们希望得到软性类别,即得到属于每个类别的概率。这两者的界限往往很模糊。其中的一个原因是,即使我们只关心硬类别,我们仍然使用软类别的模型。



网络结构

为了估计所有可能类别的条件概率,我们需要一个有多个输出的模型,每个类别对应一个输出。

为了解决线性模型的分类问题,我们需要和输出一样多的仿射函数(affine function)。

每个输出对应于它自己的仿射函数。

在我们的例子中,由于我们有4个特征和3个可能的输出类别,我们将需要12个标量来表示权重(带下标的



w

w






w





),3个标量来表示偏置(带下标的



b

b






b





)。

下面我们为每个输入计算三个

未归一化的预测

(logits):



o

1

o_1







o










1

























o

2

o_2







o










2

























o

3

o_3







o










3



























o

1

=

x

1

w

11

+

x

2

w

12

+

x

3

w

13

+

x

4

w

14

+

b

1

,

o

2

=

x

1

w

21

+

x

2

w

22

+

x

3

w

23

+

x

4

w

24

+

b

2

,

o

3

=

x

1

w

31

+

x

2

w

32

+

x

3

w

33

+

x

4

w

34

+

b

3

.

\begin{aligned} o_1 &= x_1 w_{11} + x_2 w_{12} + x_3 w_{13} + x_4 w_{14} + b_1,\\ o_2 &= x_1 w_{21} + x_2 w_{22} + x_3 w_{23} + x_4 w_{24} + b_2,\\ o_3 &= x_1 w_{31} + x_2 w_{32} + x_3 w_{33} + x_4 w_{34} + b_3. \end{aligned}

















o










1

























o










2

























o










3













































=





x










1



















w











1


1





















+





x










2



















w











1


2





















+





x










3



















w











1


3





















+





x










4



















w











1


4





















+





b










1


















,












=





x










1



















w











2


1





















+





x










2



















w











2


2





















+





x










3



















w











2


3





















+





x










4



















w











2


4





















+





b










2


















,












=





x










1



















w











3


1





















+





x










2



















w











3


2





















+





x










3



















w











3


3





















+





x










4



















w











3


4





















+





b










3


















.






















与线性回归一样,softmax回归也是一个单层神经网络。由于计算每个输出



o

1

o_1







o










1

























o

2

o_2







o










2

























o

3

o_3







o










3





















取决于所有输入



x

1

x_1







x










1

























x

2

x_2







x










2

























x

3

x_3







x










3

























x

4

x_4







x










4





















,所以softmax回归的输出层也是全连接层。

为了更简洁地表达模型,我们仍然使用线性代数符号。

通过向量形式表达为



o

=

W

x

+

b

\mathbf{o} = \mathbf{W} \mathbf{x} + \mathbf{b}







o





=









W




x





+









b






,这是一种更适合数学和编写代码的形式。我们已经将所有权重放到一个



3

×

4

3 \times 4






3




×








4





矩阵中。对于给定数据样本的特征



x

\mathbf{x}







x






,我们的输出是由权重与输入特征进行矩阵-向量乘法再加上偏置



b

\mathbf{b}







b






得到的。



全连接层的参数开销

正如我们将在后续章节中看到的,在深度学习中,全连接层无处不在。

然而,顾名思义,全连接层是“完全”连接的,可能有很多可学习的参数。

具体来说,对于任何具有



d

d






d





个输入和



q

q






q





个输出的全连接层,参数开销为



O

(

d

q

)

\mathcal{O}(dq)







O



(


d


q


)





,在实践中可能高得令人望而却步。

幸运的是,将



d

d






d





个输入转换为



q

q






q





个输出的成本可以减少到



O

(

d

q

n

)

\mathcal{O}(\frac{dq}{n})







O



(














n
















d


q





















)





,其中超参数



n

n






n





可以由我们灵活指定,以在实际应用中平衡参数节约和模型。



softmax运算

在这里要采取的主要方法是将模型的输出视作为概率。我们将优化参数以最大化观测数据的概率。为了得到预测结果,我们将设置一个阈值,如选择具有最大概率的标签。

我们希望模型的输出



y

^

j

\hat{y}_j















y







^
























j





















可以视为属于类



j

j






j





的概率。然后我们可以选择具有最大输出值的类别



*

a

r

g

m

a

x

j

y

j

\operatorname*{argmax}_j y_j







*







a


r


g


m


a


x











j



















y










j





















作为我们的预测。例如,如果



y

^

1

\hat{y}_1















y







^
























1

























y

^

2

\hat{y}_2















y







^
























2

























y

^

3

\hat{y}_3















y







^
























3





















分别为 0.1、0.8 和 0.1,那么我们预测的类别是2。

为了将未归一化的预测变换为非负并且总和为1,同时要求模型保持可导。我们首先对每个未归一化的预测求幂,这样可以确保输出非负。为了确保最终输出的总和为1,我们再对每个求幂后的结果除以它们的总和。如下式:





y

^

=

s

o

f

t

m

a

x

(

o

)

其中

y

^

j

=

exp

(

o

j

)

k

exp

(

o

k

)

\hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{o})\quad \text{其中}\quad \hat{y}_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)}















y








^


















=









s


o


f


t


m


a


x



(



o



)





其中














y







^
























j




















=































k




















exp


(



o










k


















)














exp


(



o










j


















)

























容易看出对于所有的



j

j






j





总有



0

y

^

j

1

0 \leq \hat{y}_j \leq 1






0






















y







^
























j





























1





。因此,



y

^

\hat{\mathbf{y}}















y








^



















可以视为一个正确的概率分布。softmax 运算不会改变未归一化的预测



o

\mathbf{o}







o






之间的顺序,只会确定分配给每个类别的概率。因此,在预测过程中,我们仍然可以用下式来选择最有可能的类别。





*

a

r

g

m

a

x

j

y

^

j

=

*

a

r

g

m

a

x

j

o

j

\operatorname*{argmax}_j \hat y_j = \operatorname*{argmax}_j o_j







*







a


r


g


m


a


x











j


























y






^
























j




















=









*







a


r


g


m


a


x











j



















o










j























尽管 softmax 是一个非线性函数,但 softmax 回归的输出仍然由输入特征的仿射变换决定。因此,softmax 回归是一个线性模型。



小批量样本的矢量化

为了提高计算效率并且充分利用GPU,我们通常会针对小批量数据执行矢量计算。假设我们读取了一个批量的样本



X

\mathbf{X}







X






,其中特征维度(输入数量)为



d

d






d





,批量大小为



n

n






n





。此外,假设我们在输出中有



q

q






q





个类别。那么小批量特征为



X

R

n

×

d

\mathbf{X} \in \mathbb{R}^{n \times d}







X
















R












n


×


d













,权重为



W

R

d

×

q

\mathbf{W} \in \mathbb{R}^{d \times q}







W
















R












d


×


q













,偏置为



b

R

1

×

q

\mathbf{b} \in \mathbb{R}^{1\times q}







b
















R












1


×


q













。softmax回归的矢量计算表达式为:





O

=

X

W

+

b

,

Y

^

=

s

o

f

t

m

a

x

(

O

)

.

\begin{aligned} \mathbf{O} &= \mathbf{X} \mathbf{W} + \mathbf{b}, \\ \hat{\mathbf{Y}} & = \mathrm{softmax}(\mathbf{O}). \end{aligned}

















O


















Y








^


































=





X




W





+





b



,












=





s


o


f


t


m


a


x



(



O



)


.
























相对于一次处理一个样本,小批量样本的矢量化加快了



X

W

\mathbf{X}和\mathbf{W}







X







W






的矩阵-向量乘法。由于



X

\mathbf{X}







X






中的每一行代表一个数据样本,所以softmax运算可以

按行

(rowwise)执行:对于



O

\mathbf{O}







O






的每一行,我们先对所有项进行幂运算,然后通过求和对它们进行标准化。





X

W

+

b

\mathbf{X} \mathbf{W} + \mathbf{b}







X




W





+









b






的求和会使用广播,小批量的未归一化预测



O

\mathbf{O}







O






和输出概率



Y

^

\hat{\mathbf{Y}}















Y








^










都是形状为



n

×

q

n \times q






n




×








q





的矩阵。



损失函数

接下来,我们需要一个损失函数来度量预测概率的效果。我们将依赖最大似然估计,这与我们在为线性回归(中的均方误差目标提供概率证明时遇到的概念完全相同。



对数似然

softmax函数给出了一个向量



y

^

\hat{\mathbf{y}}















y








^



















,我们可以将其视为给定任意输入



x

\mathbf{x}







x






的每个类的估计条件概率。例如,



y

^

1

\hat{y}_1















y







^
























1





















=



P

(

y

=

x

)

P(y=\text{猫} \mid \mathbf{x})






P


(


y




=

























x



)





。假设整个数据集



{

X

,

Y

}

\{\mathbf{X}, \mathbf{Y}\}






{




X



,





Y



}





具有



n

n






n





个样本,其中索引



i

i






i





的样本由特征向量



x

(

i

)

\mathbf{x}^{(i)}








x












(


i


)













和独热标签向量



y

(

i

)

\mathbf{y}^{(i)}








y












(


i


)













组成。我们可以将估计值与实际值进行比较:





P

(

Y

X

)

=

i

=

1

n

P

(

y

(

i

)

x

(

i

)

)

.

P(\mathbf{Y} \mid \mathbf{X}) = \prod_{i=1}^n P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}).






P


(



Y















X



)




=

















i


=


1


















n



















P


(




y












(


i


)























x












(


i


)










)


.





根据最大似然估计,我们最大化



P

(

Y

X

)

P(\mathbf{Y} \mid \mathbf{X})






P


(



Y















X



)





,相当于最小化负对数似然:





log

P

(

Y

X

)

=

i

=

1

n

log

P

(

y

(

i

)

x

(

i

)

)

=

i

=

1

n

l

(

y

(

i

)

,

y

^

(

i

)

)

,

-\log P(\mathbf{Y} \mid \mathbf{X}) = \sum_{i=1}^n -\log P(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}) = \sum_{i=1}^n l(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}),











lo

g





P


(



Y















X



)




=

















i


=


1


















n
























lo

g





P


(




y












(


i


)























x












(


i


)










)




=

















i


=


1


















n



















l


(




y












(


i


)










,














y








^

























(


i


)










)


,





其中,对于任何标签



y

\mathbf{y}







y






和模型预测



y

^

\hat{\mathbf{y}}















y








^



















,损失函数为:





l

(

y

,

y

^

)

=

j

=

1

q

y

j

log

y

^

j

.

l(\mathbf{y}, \hat{\mathbf{y}}) = – \sum_{j=1}^q y_j \log \hat{y}_j.






l


(



y



,













y








^
















)




=






















j


=


1


















q




















y










j




















lo

g














y







^
























j


















.







在本节稍后的内容会讲到, :eqref:

eq_l_cross_entropy

中的损失函数通常被称为

交叉熵损失

(cross-entropy loss)。由于



y

\mathbf{y}







y






是一个长度为



q

q






q





的独热编码向量,所以除了一个项以外的所有项



j

j






j





都消失了。由于所有



y

^

j

\hat{y}_j















y







^
























j





















都是预测的概率,所以它们的对数永远不会大于



0

0






0







因此,如果正确地预测实际标签,即,如果实际标签



P

(

y

x

)

=

1

P(\mathbf{y} \mid \mathbf{x})=1






P


(



y















x



)




=








1





,则损失函数不能进一步最小化。

注意,这往往是不可能的。例如,数据集中可能存在标签噪声(某些样本可能被误标),或输入特征没有足够的信息来完美地对每一个样本分类。



softmax及其导数

由于softmax和相关的损失函数很常见,因此值得我们更好地理解它的计算方式。将

eq_softmax_y_and_o

代入损失 :eqref:

eq_l_cross_entropy

中。利用softmax的定义,我们得到:





l

(

y

,

y

^

)

=

j

=

1

q

y

j

log

exp

(

o

j

)

k

=

1

q

exp

(

o

k

)

=

j

=

1

q

y

j

log

k

=

1

q

exp

(

o

k

)

j

=

1

q

y

j

o

j

=

log

k

=

1

q

exp

(

o

k

)

j

=

1

q

y

j

o

j

.

\begin{aligned} l(\mathbf{y}, \hat{\mathbf{y}}) &= – \sum_{j=1}^q y_j \log \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)} \\ &= \sum_{j=1}^q y_j \log \sum_{k=1}^q \exp(o_k) – \sum_{j=1}^q y_j o_j\\ &= \log \sum_{k=1}^q \exp(o_k) – \sum_{j=1}^q y_j o_j. \end{aligned}
















l


(



y



,













y








^
















)









































=


















j


=


1


















q




















y










j




















lo

g





























k


=


1









q




















exp


(



o










k


















)














exp


(



o










j


















)






























=













j


=


1


















q




















y










j




















lo

g














k


=


1


















q



















exp


(



o










k


















)


















j


=


1


















q




















y










j



















o










j




























=




lo

g














k


=


1


















q



















exp


(



o










k


















)


















j


=


1


















q




















y










j



















o










j


















.






















为了更好地理解发生了什么,考虑相对于任何未归一化的预测



o

j

o_j







o










j





















的导数。我们得到:





o

j

l

(

y

,

y

^

)

=

exp

(

o

j

)

k

=

1

q

exp

(

o

k

)

y

j

=

s

o

f

t

m

a

x

(

o

)

j

y

j

.

\partial_{o_j} l(\mathbf{y}, \hat{\mathbf{y}}) = \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)} – y_j = \mathrm{softmax}(\mathbf{o})_j – y_j.




















o










j



































l


(



y



,













y








^
















)




=
































k


=


1









q




















exp


(



o










k


















)














exp


(



o










j


















)
































y










j




















=









s


o


f


t


m


a


x



(



o




)










j






























y










j


















.





换句话说,导数是我们模型分配的概率(由softmax得到)与实际发生的情况(由独热标签向量表示)之间的差异。从这个意义上讲,与我们在回归中看到的非常相似,其中梯度是观测值



y

y






y





和估计值



y

^

\hat{y}














y







^



















之间的差异。



模型预测和评估

在训练softmax回归模型后,给出任何样本特征,我们可以预测每个输出类别的概率。通常我们使用预测概率最高的类别作为输出类别。如果预测与实际类别(标签)一致,则预测是正确的。在接下来的实验中,我们将使用

准确率

来评估模型的性能。准确率等于正确预测数与预测的总数之间的比率。

  • softmax运算获取一个向量并将其映射为概率。
  • softmax回归适用于分类问题。它使用了softmax运算中输出类别的概率分布。
  • 交叉熵是一个衡量两个概率分布之间差异的很好的度量。它测量给定模型编码数据所需的比特数。



softmax回归的从零开始实现

我们使用刚刚在

sec_fashion_mnist

中引入的 Fashion-MNIST 数据集,并设置数据迭代器的批量大小为



256

256






2


5


6





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

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



初始化模型参数

和之前线性回归的例子一样,这里的每个样本都将用固定长度的向量表示。原始数据集中的每个样本都是



28

×

28

28 \times 28






2


8




×








2


8





的图像。在本节中,我们将展平每个图像,把它们看作长度为784的向量。

在softmax回归中,我们的输出与类别一样多。。因此,权重将构成一个



784

×

10

784 \times 10






7


8


4




×








1


0





的矩阵,偏置将构成一个



1

×

10

1 \times 10






1




×








1


0





的行向量。与线性回归一样,我们将使用正态分布初始化我们的权重

W

,偏置初始化为0。

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)



定义softmax操作

在实现softmax回归模型之前,让我们简要地回顾一下

sum

运算符如何沿着张量中的特定维度工作。

X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim=True), X.sum(1, keepdim=True)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-44PlA1tN-1636466983103)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\11.jpg)]

softmax 由三个步骤组成:

  1. 对每个项求幂(使用

    exp

    );
  2. 对每一行求和(小批量中每个样本是一行),得到每个样本的归一化常数;
  3. 将每一行除以其归一化常数,确保结果的和为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})}.







s


o


f


t


m


a


x



(



X




)











i


j





















=































k




















exp


(




X












i


k



















)














exp


(




X












i


j



















)




















.





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

正如你所看到的,对于任何随机输入,我们将每个元素变成一个非负数。此外,依据概率原理,每行总和为1。

X = torch.normal(0, 1, (2, 5))
X_prob = softmax(X)
X_prob, X_prob.sum(1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vI06H2ho-1636466983103)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\12.jpg)]



定义模型

在将数据传递到我们的模型之前,我们使用

reshape

函数将每张原始图像展平为向量。

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



定义损失函数

接下来,我们需要实现

sec_softmax

中引入的交叉熵损失函数。这可能是深度学习中最常见的损失函数,因为目前分类问题的数量远远超过回归问题。

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]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yv6bpASA-1636466983104)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\13.jpg)]

交叉熵损失函数

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

cross_entropy(y_hat, y)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFGnMb5V-1636466983104)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\14.jpg)]



分类准确率

给定预测概率分布

y_hat

,当我们必须输出硬预测(hard prediction)时,我们通常选择预测概率最高的类。当预测与标签分类

y

一致时,它们是正确的。分类准确率即正确预测数量与总预测数量之比。虽然直接优化准确率可能很困难(因为准确率的计算不可导),但准确率通常是我们最关心的性能衡量标准,我们在训练分类器时几乎总是会报告它。

为了计算准确率,我们执行以下操作。首先,如果

y_hat

是矩阵,那么假定第二个维度存储每个类的预测分数。我们使用

argmax

获得每行中最大元素的索引来获得预测类别。然后我们将预测类别与真实

y

元素进行比较。由于等式运算符

==

对数据类型很敏感,因此我们将

y_hat

的数据类型转换为与

y

的数据类型一致。结果是一个包含 0(错)和 1(对)的张量。进行求和会得到正确预测的数量。

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())

我们将继续使用之前定义的变量

y_hat



y

分别作为预测的概率分布和标签。我们可以看到,第一个样本的预测类别是2(该行的最大元素为0.6,索引为2),这与实际标签0不一致。第二个样本的预测类别是2(该行的最大元素为0.5,索引为 2),这与实际标签2一致。因此,这两个样本的分类准确率率为0.5。

accuracy(y_hat, y) / len(y)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPKmeb2c-1636466983105)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\15.jpg)]

def evaluate_accuracy(net, data_iter):  #@save
    """计算在指定数据集上模型的精度。"""
    if isinstance(net, torch.nn.Module):
        net.eval()  # 将模型设置为评估模式
    metric = Accumulator(2)  # 正确预测数、预测总数
    for X, y in data_iter:
        metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

这里

Accumulator

是一个实用程序类,用于对多个变量进行累加。

在上面的

evaluate_accuracy

函数中,我们在

Accumulator

实例中创建了 2 个变量,用于分别存储正确预测的数量和预测的总数量。当我们遍历数据集时,两者都将随着时间的推移而累加。

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]

由于我们使用随机权重初始化

net

模型,因此该模型的准确率应接近于随机猜测。例如在有10个类别情况下的准确率为0.1。

evaluate_accuracy(net, test_iter)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uUXlEuK8-1636466983105)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\16.jpg)]



训练

如果你看过

sec_linear_scratch

中的线性回归实现,softmax回归的训练过程代码应该看起来非常熟悉。在这里,我们重构训练过程的实现以使其可重复使用。首先,我们定义一个函数来训练一个迭代周期。请注意,

updater

是更新模型参数的常用函数,它接受批量大小作为参数。它可以是封装的

d2l.sgd

函数,也可以是框架的内置优化函数。

def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    # 将模型设置为训练模式
    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.backward()
            updater.step()
            metric.add(
                float(l) * len(y), accuracy(y_hat, y),
                y.size().numel())
        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]

接下来我们实现一个训练函数,它会在

train_iter

访问到的训练数据集上训练一个模型

net

。该训练函数将会运行多个迭代周期(由

num_epochs

指定)。在每个迭代周期结束时,利用

test_iter

访问到的测试数据集对模型进行评估。我们将利用

Animator

类来可视化训练进度。

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
    """训练模型(定义见第3章)。"""
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    train_loss, train_acc = train_metrics
    assert train_loss < 0.5, train_loss
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc

作为一个从零开始的实现,我们使用 :numref:

sec_linear_scratch

中定义的小批量随机梯度下降来优化模型的损失函数,设置学习率为0.1。

lr = 0.1
def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

现在,我们训练模型10个迭代周期。请注意,迭代周期(

num_epochs

)和学习率(

lr

)都是可调节的超参数。通过更改它们的值,我们可以提高模型的分类准确率。

num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n3zTaKxX-1636466983106)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\17.jpg)]



预测

def predict_ch3(net, test_iter, n=6):  #@save
    """预测标签(定义见第3章)。"""
    for X, y in test_iter:
        break
    trues = d2l.get_fashion_mnist_labels(y)
    preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
    titles = [true + '\n' + pred for true, pred in zip(trues, preds)]
    d2l.show_images(X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

predict_ch3(net, test_iter)



小结

  • 借助 softmax 回归,我们可以训练多分类的模型。
  • softmax 回归的训练循环与线性回归中的训练循环非常相似:读取数据、定义模型和损失函数,然后使用优化算法训练模型。正如你很快就会发现的那样,大多数常见的深度学习模型都有类似的训练过程。



softmax 回归的简洁实现

通过深度学习框架的高级API也能更方便地实现分类模型。

import torch
from torch import nn
from d2l import torch as d2l

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



初始化模型参数

如我们在 :numref:

sec_softmax

所述,[

softmax 回归的输出层是一个全连接层

]。因此,为了实现我们的模型,我们只需在

Sequential

中添加一个带有10个输出的全连接层。同样,在这里,

Sequential

并不是必要的,但我们可能会形成这种习惯。因为在实现深度模型时,

Sequential

将无处不在。我们仍然以均值0和标准差0.01随机初始化权重。

PyTorch不会隐式地调整输入的形状。因此我们在线性层前定义了展平层(flatten),来调整网络输入的形状.

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);



重新审视 Softmax 的实现

回想一下,softmax 函数



y

^

j

=

exp

(

o

j

)

k

exp

(

o

k

)

\hat y_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)}














y






^
























j




















=
































k




















exp


(



o










k


















)
















exp


(



o










j


















)
























,其中



y

^

j

\hat y_j














y






^
























j





















是预测的概率分布。



o

j

o_j







o










j





















是未归一化的预测



o

\mathbf{o}







o






的第



j

j






j





个元素。如果



o

k

o_k







o










k





















中的一些数值非常大,那么



exp

(

o

k

)

\exp(o_k)






exp


(



o










k


















)





可能大于数据类型容许的最大数字(即

上溢

(overflow))。这将使分母或分子变为

inf

(无穷大),我们最后遇到的是0、

inf



nan

(不是数字)的



y

^

j

\hat y_j














y






^
























j





















。在这些情况下,我们不能得到一个明确定义的交叉熵的返回值。

解决这个问题的一个技巧是,在继续softmax计算之前,先从所有



o

k

o_k







o










k





















中减去



max

(

o

k

)

\max(o_k)






max


(



o










k


















)





。你可以证明每个



o

k

o_k







o










k





















按常数进行的移动不会改变softmax的返回值。在减法和归一化步骤之后,可能有些



o

j

o_j







o










j





















具有较大的负值。由于精度受限,



exp

(

o

j

)

\exp(o_j)






exp


(



o










j


















)





将有接近零的值,即

下溢

(underflow)。这些值可能会四舍五入为零,使



y

^

j

\hat y_j














y






^
























j





















为零,并且使得



log

(

y

^

j

)

\log(\hat y_j)






lo

g



(










y






^
























j


















)





的值为

-inf

。反向传播几步后,我们可能会发现自己面对一屏幕可怕的

nan

结果。

尽管我们要计算指数函数,但我们最终在计算交叉熵损失时会取它们的对数。

通过将softmax和交叉熵结合在一起,可以避免反向传播过程中可能会困扰我们的数值稳定性问题。如下面的等式所示,我们避免计算



exp

(

o

j

)

\exp(o_j)






exp


(



o










j


















)





,而可以直接使用



o

j

o_j







o










j





















。因为



log

(

exp

(

)

)

\log(\exp(\cdot))






lo

g



(


exp


(





)


)





被抵消了。





log

(

y

^

j

)

=

log

(

exp

(

o

j

)

k

exp

(

o

k

)

)

=

log

(

exp

(

o

j

)

)

log

(

k

exp

(

o

k

)

)

=

o

j

log

(

k

exp

(

o

k

)

)

.

\begin{aligned} \log{(\hat y_j)} & = \log\left( \frac{\exp(o_j)}{\sum_k \exp(o_k)}\right) \\ & = \log{(\exp(o_j))}-\log{\left( \sum_k \exp(o_k) \right)} \\ & = o_j -\log{\left( \sum_k \exp(o_k) \right)}. \end{aligned}
















lo

g






(










y






^
























j


















)










































=




lo

g







(


























k




















exp


(



o










k


















)














exp


(



o










j


















)





















)














=




lo

g






(


exp


(



o










j


















)


)










lo

g








(











k




























exp


(



o










k


















)



)















=





o










j

























lo

g








(











k




























exp


(



o










k


















)



)





.






















我们也希望保留传统的softmax函数,以备我们需要评估通过模型输出的概率。

loss = nn.CrossEntropyLoss()



优化算法

在这里,我们使用学习率为0.1的小批量随机梯度下降作为优化算法。这与我们在线性回归例子中的相同,这说明了优化器的普适性。

trainer = torch.optim.SGD(net.parameters(), lr=0.1)



训练

num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

和以前一样,这个算法收敛到一个相当高的精度。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LB0Yb4np-1636466983106)(C:\Users\Lunatic\Desktop\深度学习实验报告\实验1\18.jpg)]



小结

  • 使用高级 API,我们可以更简洁地实现 softmax 回归。
  • 从计算的角度来看,实现softmax回归比较复杂。在许多情况下,深度学习框架在这些著名的技巧之外采取了额外的预防措施,来确保数值的稳定性。这使我们避免了在实践中从零开始编写模型时可能遇到的陷阱。



版权声明:本文为qq_44458671原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。