作为机器学习的一类,深度学习通常基于神经网络模型逐级表示越来越抽象的概念或模式。先从最基础的线性回归学起。
1.线性回归
线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常见,如预测房屋价格、气温、销售额等连续值的问题。与回归问题不同,分类问题中模型的最终输出是一个离散值。我们所说的图像分类、垃圾邮件识别、疾病检测等输出为离散值的问题都属于分类问题的范畴。
1.1模型
w1,w2是权重,b是偏差,模型的输出y尖就是对y的预测。
1.2模型训练
接下来我们需要通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小。这个过程叫作模型训练(model training)。
1.2.1模型训练的三要素
- 训练数据
- 损失函数
- 优化算法
1.3训练数据
通常收集一系列的真实数据,例如多栋房屋的真实售出价格和它们对应的面积和房龄。我们希望在这个数据上面寻找模型参数来使模型的预测价格与真实价格的误差最小。在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),一栋房屋被称为一个样本(sample),其真实售出价格叫作标签(label),用来预测标签的两个因素叫作特征(feature)。
对于索引为 𝑖 的数据,线性回归模型的预测表达式为:
1.4 损失函数
在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。它在评估索引为 𝑖 的样本误差的表达式为:
其中常数 1/2 使对平方项求导后的常数系数为1,这样在形式上稍微简单一些。显然,误差越小表示预测价格与真实价格越相近,且当二者相等时误差为0。给定训练数据集,这个误差只与模型参数相关,因此我们将它记为以模型参数为参数的函数。在机器学习里,将衡量误差的函数称为损失函数(loss function)。这里使用的平方误差函数也称为平方损失(square loss)。
通常,我们用训练数据集中所有样本误差的平均来衡量模型预测的质量,即:
1.5优化算法
当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。
在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。
在训练本节讨论的线性回归模型的过程中,模型的每个参数将作如下迭代:
|B|是小批量样本个数,𝜂 是学习率,都是人为规定的,这种参数叫做超参数。
1.6模型预测
模型训练完成后,我们将模型参数 𝑤1,𝑤2,𝑏 在优化算法停止时的值分别记作 𝑤̂ 1,𝑤̂ 2,𝑏̂ 。注意,这里我们得到的并不一定是最小化损失函数的最优解 𝑤∗1,𝑤∗2,𝑏∗ ,而是对最优解的一个近似。然后,我们就可以使用学出的线性回归模型 𝑥1𝑤̂ 1+𝑥2𝑤̂ 2+𝑏̂ 来估算训练数据集以外任意的数据。这里的估算也叫作模型预测、模型推断或模型测试。
2.线性回归的表示方法
2.1神经网络图
输入分别为 𝑥1 和 𝑥2 ,因此输入层的输入个数为2。输入个数也叫特征数或特征向量维度。图3.1中网络的输出为 𝑜 ,输出层的输出个数为1。需要注意的是,我们直接将图3.1中神经网络的输出 𝑜 作为线性回归的输出,即 𝑦̂ =𝑜 。由于输入层并不涉及计算,按照惯例,图中神经网络的层数为1。所以,线性回归是一个单层神经网络。输出层中负责计算 𝑜 的单元又叫神经元。在线性回归中, 𝑜 的计算依赖于 𝑥1 和 𝑥2 。也就是说,输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫全连接层(fully-connected layer)或稠密层(dense layer)。
2.2矢量计算
通过书中的比较,可看出明显矢量计算省时。
如果我们对训练数据集里的3个样本(索引分别为1、2和3)逐一预测价格,将得到
现在,我们将上面3个等式转化成矢量计算。设
广义上讲,当数据样本数为 𝑛 ,特征数为 𝑑 时,线性回归的矢量计算表达式为
设模型参数 𝜽=[𝑤1,𝑤2,𝑏]⊤ ,我们可以重写损失函数为
小批量随机梯度下降的迭代步骤将相应地改写为
其中梯度是损失有关3个为标量的模型参数的偏导数组成的向量:
3.对书中代码的实现
3.1从零实现线性回归
from IPython import display
from matplotlib import pyplot as plt
from mxnet import autograd, nd
import random
# 生成数据集
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += nd.random.normal(scale=0.01, shape=labels.shape)
# 可视化
def use_svg_display():
display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
use_svg_display()
plt.rcParams['figure.figsize'] = figsize
set_figsize()
plt.scatter(features[:, 1].asnumpy(), labels.asnumpy(), 1);
plt.show()
# 读取数据集
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):
j = nd.array(indices[i: min(i + batch_size, num_examples)])
yield features.take(j), labels.take(j)
# 打印第一个小批量数据
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, y)
break
# 初始化参数
w = nd.random.normal(scale=0.01, shape=(num_inputs, 1))
b = nd.zeros(shape=(1,))
w.attach_grad()
b.attach_grad()
# 方程
def linreg(X, w, b):
return nd.dot(X, w) + b
# 平方损失
def squared_loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# 梯度下降优化算法
def sgd(params, lr, batch_size):
for param in params:
param[:] = param - lr * param.grad / batch_size
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):
with autograd.record():
l = loss(net(X, w, b), y)
l.backward()
sgd([w, b], lr, batch_size)
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().asnumpy()))
# 比较一下训练出来的参数和真实的参数
print(true_w, w)
print(true_b, b)
3.2使用gluon简洁实现线性回归
from mxnet import nd, autograd, init, gluon
from mxnet.gluon import data as gdata
from mxnet.gluon import nn
from mxnet.gluon import loss as gloss
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = nd.random.normal(scale=1, shape=(num_examples, num_inputs))
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += nd.random.normal(scale=0.01, shape=labels.shape)
batch_size = 10
dataset = gdata.ArrayDataset(features, labels)
data_iter = gdata.DataLoader(dataset, batch_size, shuffle=True)
for X, y in data_iter:
print(X, y)
break
net = nn.Sequential()
net.add(nn.Dense(1))
net.initialize(init.Normal(sigma=0.01))
loss = gloss.L2Loss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.03})
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()))
dense = net[0]
print(true_w, dense.weight.data())
print(true_b, dense.bias.data())
help(gluon.loss)
help(init)