线性神经网络——线性回归模型
为了更好的学习深度神经网络,本文介绍了线性神经网络中较为简单的线性回归模型,并演示了神经网络的整个训练过程,包括:定义简单的神经网络架构、数据处理、指定损失函数和如何训练模型。模型相对简单,但包含深度学习中模型训练的基本思想,为以后学习更复杂的模型打下基础。
一、线性回归模型基本概念
回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。在机器学习领域大多数任务通常与预测(prediction)有关,常见的例子包括:预测价格(房屋、股票等)、预测住院时间(针对住院病人等)、预测需求(零售销量等)。
1. 数据集
通过一个例子来介绍线性回归模型中的基本元素,我们希望根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)。为了开发一个能预测房价的模型,我们需要收集一个真实的数据集(包括房屋销售价格、面积、房龄),该数据集就被称为训练集(training set),数据集中的每一行数据称为样本(sample)。我们需要预测的目标(房屋价格)称为标签(label),预测所依据的自变量(面积和房龄)称为特征(feature)。
- 训练集:训练模型使用的真实数据集
- 样本:数据集中的数据
- 特征:数据的特征
- 标签:预测的目标
2. 线性模型
线性模型简单说就是带有未知参数的一个函数,这个函数反映了自变量和因变量之间的线性关系,拿上面的例子来建立一个房屋价格的线性回归模型如下:
p
r
i
c
e
=
w
area
⋅
area
+
w
age
⋅
age
+
b
\mathrm{price}=w_{\text{area}}\cdot\text{area}+w_{\text{age}}\cdot\text{age}+b
price=warea⋅area+wage⋅age+b
整个函数是一种线性的映射关系(特征和价格)。这个式子就是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,其中的
w
a
r
e
a
w_{\mathbf{area}}
warea和
w
a
g
e
w_{\mathbf{age}}
wage称为权重(weight),为了函数更符合实际情况,在函数最后加上一个偏移量
b
b
b我们称为偏置(bias)。
- 权重:每个特征对预测值的影响程度
- 偏置:偏置是指当所有特征都取值为0时,预测值应该为多少
将函数数学化,用
y
^
\hat{y}
y^表示预测结果,当模型包含
d
d
d个特征时表示为:
y
^
=
w
1
x
1
+
.
.
.
+
w
d
x
d
+
b
\hat{y}=w_1x_1+...+w_dx_d+b
y^=w1x1+...+wdxd+b
在实际问题中,数据的特征往往是多维度的,将所有特征放到向量
x
∈
R
d
\mathbf{x}\in\mathbb{R}^{d}
x∈Rd中,并将所有权重放到向量
w
∈
R
d
\mathbf{w}\in\mathbb{R}^{d}
w∈Rd中,我们可以用点积形式来简洁地表达模型:
y
^
=
w
⊤
x
+
b
\hat{y}=\mathbf{w}^\top\mathbf{x}+b
y^=w⊤x+b
上述是单个样本对应的函数,用矩阵
X
∈
R
n
×
d
\mathbf{X}\in\mathbb{R}^{n\times d}
X∈Rn×d表示我们整个数据集的
n
n
n个样本。其中,
X
X
X的每一行是一个样本,每一列是一种特征。对于整个集合
X
X
X,每个样本的预测值
y
^
∈
R
n
\hat{\mathbf{y}}\in\mathbb{R}^{n}
y^∈Rn可以通过矩阵‐向量乘法表示为:
y ^ = X w + b \hat{\mathbf{y}}=\mathbf{X}\mathbf{w}+b y^=Xw+b
3. 损失函数
为了检验构建的模型(函数)是否拟合数据,我们要一个损失函数(loss function)来量化目标的实际值和预测值之间的差距。通常选择非负数作为损失,且数值越小越好(代表预测越准确),常用的损失计算方法有MAE、MSE。
4. 优化模型
我们训练模型的目的是为了找出所定义模型(函数)的未知参数,通常我们会初始化这些参数,然后使用某种方法更参数,从而找出最优参数。这里介绍一种名为 梯度下降法(gradient descent) 的优化算法,这种方法几乎可以优化所有深度学习模型,它通过不断地在损失函数递减的方向上更新参数来降低误差。
梯度下降核心思想是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(梯度),通过设置学习率(learning rate)(步长)的大小来调整参数,然后再次计算损失,再次更新参数,反复这个过程,直到梯度为0或者达到更新次数限制后停止,从而找到最优的参数。
其中,更新参数之前需要遍历整个数据集,因此我们可以采用将数据集划分并在需要更新时随机抽取一小批样本以此训练的速度,这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。
- 批量大小:batch size
- 学习率 η η η:learning rate
二、训练一个线性回归模型
1. 读取数据集
- 导入必要的的库
%matplotlib inline
import random
import torch
from d2l import torch as d2l
- 生成数据集模块
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中的每一行都包含一个二维数据样本, labels中的每一行都包含一维标签值(一个标量)。
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);
- 读取数据集模块
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
2. 初始化模型参数
用小批量随机梯度下降优化我们的模型参数之前,我们需要先有一些参数。在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。
- 初始化模型参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。 每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。
3. 定义模型
- 定义线性回归模型
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
4. 定义损失函数
- 定义平方损失函数
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
5. 定义优化算法
使用小批量随机梯度下降法优化模型,在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每 一步更新的大小由学习速率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_()
6. 训练模型
这里的迭代周期个数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}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')