《动手学习深度学习》
21天系列 (week1 11月13日 至 11月20日)#
第三章 深度学习基础
作为机器学习的一类,深度学习通常基于神经网络模型逐级表示越来越抽象的概念或模式。
先介绍单层神经网络:线性回归和softmax回归。再介绍深度学习中应对过拟合的常用方法:权重衰减和丢弃法。 为了进一步理解深度学习模型训练的本质,我们将详细解释正向传播和反向传播。
4.1 线性回归
线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常⻅,如预测房屋价 格、气温、销售额等连续值的问题。与回归问题不同,分类问题中模型的最终输出是一个离散值。 我们所说的图像分类、垃圾邮件识别、疾病检测等输出为离散值的问题都属于分类问题的范畴。softmax回归则适用于分类问题。
4.1.1 线性回归的基本要素
模型:从自变量得到因变量的函数,类似于 y=x1w1 + x2w2 + b
模型训练:通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小。这一过程叫做训练。
模型预测:通过训练后得到最优解的一个近似,然后代入原模型开始估算。估算也叫作模型预测、模型推断或模型测试。
模型训练一般涉及三个要素:训练数据、损失函数、优化算法。
在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),一栋房屋被称为一个样本(sample),其真实售出价格叫作标签(label),用来预测标签的两个因素叫作特征(feature)。特征用来表征样本的特点。
注意:标签(数据样本标签)的定义和平时的理解有偏差,这里表示真实的y值。
损失函数(loss function):在机器学习里,将衡量误差的函数称为损失函数。
在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为 误差,且数值越小表示误差越小。一个常用的选择是平方函数(square loss)。
在模型训练中,我们希望找出一组模型参数,来使训练样本平均损失最小。
优化算法:
当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而, 大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)
在求数值解的优化算法中,小批量随机梯度下降(sgd, mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。
其中涉及的概念,如批量大小、学习率等(叫做超参数),最好是结合内容来看。线性回归的基本要素
我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。
4.1.2 线性回归的表示方法
一种是神经网络图,另一种是矢量计算表达式。都接触过。
线性回归是一个单层神经网络。可以分为输入层和输出层,由于输入层并不涉及计算,不算入神经网络的层数。
输出层中负责计算 o 的单元又叫神经元。输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫全连接层(fully-connected layer)或稠密层 (dense layer)。
神经网络图隐去了模型参数权重和偏差。
4.2 线性回归的简洁实现
4.2.1 数据读取
Gluon提供了data包来读取数据。由于data常用作变量名,我们将导入的data模块用添加了Gluon首字母的假名gdata代替。在每一次迭代中,我们将随机读取包含10个数据样本的小批量。
In [2]:
from mxnet.gluon import data as gdata
batch_size = 10
# 将训练数据的特征和标签组合
dataset = gdata.ArrayDataset(features, labels)
# 随机读取小批量
data_iter = gdata.DataLoader(dataset, batch_size, shuffle=True)
4.2.2 定义模型
Gluon提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。
首先,导入nn模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。
在Gluon中,Sequential实例可以看作是一个串联各个层的容器。在构造模型时,我们在该容器中依次添加层。当给定输入数据时,容器中的每一层将依次计算并将输出作为下一层的输入。
from mxnet.gluon import nn
net = nn.Sequential()
在Gluon中,全连接层是一个Dense实例。我们定义该层输出个数为1。
net.add(nn.Dense(1))
在Gluon中我们无须指定每一层输入的形状,例如线性回归的输入个数。当模型得到数据时,例如后面执行net(X)时,模型将自动推断出每一层的输入个数。
4.2.3 初始化模型参数
在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。我们从MXNet导入init模块。该模块提供了模型参数初始化的各种方法。这里的init是initializer的缩写形式。我们通过init.Normal(sigma=0.01)
指定权重参数每个元素将在初始化时随机采样于均值为0、标准差为0.01的正态分布。偏差参数默认会初始化为零。
In [6]:
from mxnet import init
net.initialize(init.Normal(sigma=0.01))
4.2.4 定义损失函数
在Gluon中,loss模块定义了各种损失函数。我们用假名gloss代替导入的loss模块,并直接使用它提供的平方损失作为模型的损失函数。
In [7]:
from mxnet.gluon import loss as gloss
loss = gloss.L2Loss() # 平方损失又称L2范数损失
4.2.5 定义优化算法
同样,我们也无须实现小批量随机梯度下降。在导入Gluon后,我们创建一个Trainer实例,并指定学习率为0.03的小批量随机梯度下降(sgd)为优化算法。该优化算法将用来迭代net实例所有通过add函数嵌套的层所包含的全部参数。这些参数可以通过collect_params函数获取。
In [8]:
from mxnet import gluon
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.03})
4.2.6 训练模型
在使用Gluon训练模型时,我们通过调用Trainer实例的step函数来迭代模型参数。上一节中我们提到,由于变量l是长度为batch_size的一维NDArray,执行l.backward()等价于执行l.sum().backward()。按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均。
In [9]:
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
with autograd.record():
l = loss(net(X), y)
l.backward()
trainer.step(batch_size)
l = loss(net(features), labels)
print('epoch %d, loss: %f' % (epoch, l.mean().asnumpy()))
epoch 1, loss: 0.040592
epoch 2, loss: 0.000156
epoch 3, loss: 0.000051
(其中,trainer.steop(batch_size)可以这样理解:相当于把计算出来的梯度除以batch size,因为loss.backward()相当于nd.sum(loss).bacward(),也就是把一个batch的loss都加起来求的梯度,所以通过除以batch size能够弱化batch size在更新参数时候的影响。)
下面我们分别比较学到的模型参数和真实的模型参数。我们从net获得需要的层,并访问其权重(weight)和偏差(bias)。学到的参数和真实的参数很接近。
In [10]:
dense = net[0]
true_w, dense.weight.data()
Out[10]:
([2, -3.4],
[[ 1.9993386 -3.399609 ]]
<NDArray 1x2 @cpu(0)>)
In [11]:
true_b, dense.bias.data()
Out[11]:
(4.2,
[4.19955]
<NDArray 1 @cpu(0)>)
4.2.7 读者感悟
真TM强大,模型都不用定义。 但还是记不住。
4.3 softmax回归
softmax回归是一个分类问题,输出值并不是连续的,而是离散的。
softmax回归是一个单层神经网络,输出层也是一个全连接层。
为什么不能直接用最大的输出值作为置信度?
直接使用输出层的输出有两个问题。一方面,由于输出层的输出值的范围不确定,我们难以直观上判断这些值的意义。(例如,输出是0、10,那10是准确的吗?会不会取值范围是1000,这是10并不准确)
另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。
softmax运算符(softmax operator)解决了以上两个问题。公式见link
因此softmax运算不改变预测类别输出,同时又能将值规范成百分比概率。
y = argmax f(t) 代表:y 是f(t)函式中,会产生最大output的那个参数t
softmax运算实现:
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(axis=1, keepdims=True)
return X_exp / partition # 这里应用了广播机制
函数中,矩阵X的行数是样本数,列数是输出个数。为了表达样本预测各个输出的概率,softmax运算会先通过exp函数对每个元素做指数运算,再对exp矩阵同行元素求和,最后令矩阵每行各元素与该行元素之和相除。这样一来,最终得到的矩阵每行元素和为1且非负。因此,该矩阵每行都是合法的概率分布。softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率。
4.3.1 交叉熵损失函数
对于分类问题,对于损失函数的选择上,平方损失函数不是最优的轩子。因为想要预测分类结果正确,我们其实并不需要预测概率完全等于标签概率,只需要预测概率大于其他分类的即可。这是平方损失函数显得过于严格。
交叉熵(cross entropy)是一个常用的衡量方法。交叉熵只关心对正确类别的预测概率,因为只要其值足够大,就可以确保分类结果正确。
如果一个事件的发生概率为 p,则其交叉熵为:
CE = - ln(p)
由于对一个概率值取自然对数的结果为一个 ≤ 0 的数,因此加上一个负号则可以使得函数最终的计算结果为一个正数。
由于交叉熵函数的取值和我们期望的误差的取值是一致的,因此可以用交叉熵函数作为一个损失函数,而针对整个样本集来说,则可以定义加总形式的交叉熵函数作为成本函数。
最小化交叉熵损失函数等价于最大化训练数据集所有标签类别的联合预测概率。
以向量化的方式实现交叉熵函数的代码为:
import numpy as np
# Y and P are lists of labels and estimations
# returns the float corresponding to their cross-entropy.
def cross_entropy(Y, P):
Y = np.float_(Y)
P = np.float_(P)
# np.log() is based in e in numpy
return -np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P)) / len(Y)
在mxnet中的实现:
from mxnet import autograd, nd
def cross_entropy(y_hat, y):
return -nd.pick(y_hat, y).log()
4.3.2 模型预测以及评价
使用准确率(accuracy)来评价模型的表现。它等于正确预测数量与总预测数量之比。
交叉熵适合衡量两个概率分布的差异。
4.3.3 和最大似然估计对比
最大似然估计(maximum likelihood estimation, MLE)一种重要而普遍的求估计量的方法。最大似然法明确地使用概率模型,其目标是寻找能够以较高概率产生观察数据的系统发生树。最大似然法是一类完全基于统计的系统发生树重建方法的代表。
以交叉熵函数作为成本函数的数学基础来自于统计学中的最大似然估计。
总结起来,利用最大似然函数求解总体参数的估计值的一般步骤为:
- 获取似然函数
- 对似然函数取自然对数
- 将对数似然函数求(偏)导数,令其为0,得到似然方程
- 求解似然方程,得到的一个或多个数值即为总体参数的最大似然估计值
需要注意的是与任何参数估计方法一样,最大似然估计的准确性也受到样本数量的影响,一般样本数量越大越准确。
简单来说交叉熵是用来计算两个函数或者概率之间的距离,计算的方式也是使用的KL Divergence,在机器学习的世界里面大概可以认为交叉熵和最大似然估计是一回事,如果看到这两个术语应该把他们联系在一起。