1. 线性回归
简化模型房价预测的模型可以理解为用一些能够表示房子特点的因素如卧室个数、卫生间个数、面积等
n
n
n 个属性组成的特征向量:
x
=
[
x
1
x
2
?
x
n
]
\mathbf{x}=\left[ \begin{matrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{matrix}\right]
x=??????x1?x2??xn????????
房价的预测是这些属性的加权和:
y
=
w
1
x
1
+
w
2
x
2
+
?
+
w
n
x
n
+
b
y=w_1x_1+w_2x_2+\cdots+w_nx_n + b
y=w1?x1?+w2?x2?+?+wn?xn?+b
用向量表示为:
y
=
?
w
,
x
?
+
b
y=\langle\mathbf{w},\mathbf{x}\rangle+b
y=?w,x?+b
这个线性模型可以看为一个单层神经网络,如下图所示,之所以称为单层是因为我们只将带权重的层记为一层。
有了模型后,就可以用来比较模型的效果怎么样,通过该样本预测值和真实值之间的差距衡量模型的效果,如下,这个表达式也称为平方损失。
l
(
y
,
y
^
)
=
1
2
(
y
?
y
^
)
2
l(y,\hat{y})=\frac{1}{2}(y-\hat{y})^2
l(y,y^?)=21?(y?y^?)2
为了获取其中的权重参数
w
\mathbf{w}
w 我们可以收集
m
m
m 个训练集样本,数据集和对应的标签可以表示如下:
X
=
[
x
1
x
2
?
x
m
]
,
y
=
[
y
1
y
2
?
y
n
]
\mathbf{X}= \left[ \begin{matrix} \mathbf{x}_1 \\ \mathbf{x}_2 \\ \vdots \\ \mathbf{x}_m \end{matrix} \right],\quad \mathbf{y}=\left[ \begin{matrix} y_1 \\ y_2 \\ \vdots \\ y_n \end{matrix} \right]
X=??????x1?x2??xm????????,y=??????y1?y2??yn????????
通过这些数据,我们可以通过最小化损失函数的形式求解模型中的参数
w
\mathbf{w}
w 和
b
\mathbf{b}
b,即:
w
?
,
b
?
=
arg
?
min
?
w
,
b
l
(
X
,
y
,
w
,
b
)
\mathbf{w}^*,\mathbf{b}^* = \arg\min_{\mathbf{w},b}l(\mathbf{X},\mathbf{y},\mathbf{w},b)
w?,b?=argw,bmin?l(X,y,w,b)
为了简化计算,我们将偏差移入权重矩阵中,记
X
→
[
X
,
1
]
\mathbf{X}\rightarrow \left[\begin{matrix}\mathbf{X}, \mathbf{1}\end{matrix}\right]
X→[X,1?] 且
w
←
[
w
b
]
\mathbf{w} \leftarrow \left[\begin{matrix}\mathbf{w} \\ b\end{matrix}\right]
w←[wb?],那么所有样本的损失函数可以表示为:
l
(
X
,
y
,
w
)
=
1
2
∥
y
?
X
w
∥
2
l(\mathbf{X},\mathbf{y},\mathbf{w})= \frac{1}{2}\|\mathbf{y}-\mathbf{Xw}\|^2
l(X,y,w)=21?∥y?Xw∥2
对于线性回归,上式的损失是凸函数,所以我们可以直接通过梯度的方式求解
w
?
\mathbf{w}^*
w? 的最优解,
?
?
w
l
(
X
,
y
,
w
)
=
1
n
(
y
?
X
w
)
T
X
\begin{aligned} \frac{\partial}{\partial \mathbf{w}}l(\mathbf{X},\mathbf{y},\mathbf{w})= \frac{1}{n}\left(\mathbf{y}-\mathbf{Xw}\right)^T\mathbf{X} \end{aligned}
?w??l(X,y,w)=n1?(y?Xw)TX?
令梯度为
0
0
0 时满足的
w
?
\mathbf{w}^*
w? 即可,如下所示:
?
?
w
l
(
X
,
y
,
w
)
=
0
?
1
n
(
y
?
X
w
)
T
X
=
0
?
w
?
=
(
X
T
X
)
?
1
X
T
y
\begin{aligned} \frac{\partial}{\partial \mathbf{w}}l(\mathbf{X},\mathbf{y},\mathbf{w})=0 \\ \Leftrightarrow \frac{1}{n}\left(\mathbf{y}-\mathbf{Xw}\right)^T\mathbf{X} = 0 \\ \Leftrightarrow \mathbf{w}^* = \left(\mathbf{X}^T\mathbf{X}\right)^{-1}\mathbf{X}^T\mathbf{y} \end{aligned}
?w??l(X,y,w)=0?n1?(y?Xw)TX=0?w?=(XTX)?1XTy?
只有线性模型才能直接通过梯度方式直接找到最优解,但是这种方式因为比较简单,所以不能处理复杂的问题。
2. 线性回归基础优化算法
最常见的对参数优化的算法就是梯度下降算法,其基本过程如下:
-
对其中的一个参数
w
\mathbf{w}
w,首先随机初始为任意值
w
0
\mathbf{w}_0
w0? -
重复迭代更新参数
t
=
1
,
2
,
3
,
?
t=1,2,3,\cdots
t=1,2,3,?
w
t
=
w
t
?
1
?
η
?
l
?
w
t
?
1
\mathbf{w}_t = \mathbf{w}_{t-1}-\eta\frac{\partial l}{\partial \mathbf{w}_{t-1}}
wt?=wt?1??η?wt?1??l? 梯度表示函数值增加最快的方向,减去梯度的值就是向函数值减少最快的方向移动;
η
\eta
η 是学习率,表示沿梯度方向走多远。
在整个训练集计算梯度太浪费资源,所以一般采用 小批量随机梯度下降 方法,通过随机采样
b
b
b 个样本
i
1
,
i
2
,
?
?
,
i
b
i_1,i_2,\cdots,i_b
i1?,i2?,?,ib? 来近似损失更新参数,如下所示,这样就可以减少计算量。
1
b
∑
i
∈
I
b
l
(
x
i
,
y
i
,
w
)
\frac{1}{b}\sum_{i\in I_b}l(\mathbf{x}_i,y_i,\mathbf{w})
b1?i∈Ib?∑?l(xi?,yi?,w)
批量不能太小,太小会使每次的计算量太小,不适合并行最大化利用计算资源。也不能太大,太大会增大内存的消耗,浪费资源。
学习率和批量大小是小批量梯度下降的两个最重要的超参数。
3. 从零开始实现线性回归
-
根据带有噪声的线性模型构造一个人造数据集,数据集用于后面的回归。这里使用包含参数
w
=
[
2
,
?
3.4
]
T
、
b
=
4.2
\mathbf{w}=\left[\begin{matrix}2,-3.4\end{matrix}\right]^T、b=4.2
w=[2,?3.4?]T、b=4.2 的线性模型:
y
=
X
w
+
b
+
?
\mathbf{y}=\mathbf{Xw}+b+\epsilon
y=Xw+b+? 来生成数据集,其中
?
\epsilon
? 表示噪声项。 代码实现为: import random
import torch
import matplotlib.pyplot as plt
def synthetic_data(w,b,num_examples):
"""生成 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(w,b,1000)
plt.scatter(features[:,(1)].numpy(),labels.numpy(),1)
-
将数据划分为大小 batch_size 的批量数据 def data_iter(batch_size, features, labels):
"""将 features 和 labels 分为大小为 batch_size 的批数据"""
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]
-
初始化模型参数 因为上面我们创建的数据 X 的特征维度为
2
2
2,所以这里随机初始化权重 w 的维度为
2
2
2。 w = torch.normal(0, 0.01, size=(2, 1), requires_grad = True)
b = torch.zeros(1,requires_grad = True)
-
定义模型 def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
-
定义损失函数和优化算法 这里选择均方差损失函数: def squared_loss(y_hat, y):
"""均方差损失函数
y_hat : 表示预测值
y : 表示真实值
"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2
优化算法选择随机梯度下降算法,注意,在更新参数时会出现新的表达式,为了避免 backward() 时相对该表达式计算梯度,将这些过程使用 with torch.no_grad() 包围。 def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
-
训练模型 训练的过程主要为:
- 根据现有参数计算损失
- 用当前得到的损失求相对于每个参数的梯度
- 用梯度更新参数
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
batch_size = 10
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y)
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}')
"""
epoch 1, loss0.000120
epoch 2, loss0.000051
epoch 3, loss0.000050
"""
print(f'w 的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b 的估计误差:{true_b - b}')
"""
w 的估计误差:tensor([-0.0003, 0.0007], grad_fn=<SubBackward0>)
b 的估计误差:tensor([-6.9618e-05], grad_fn=<RsubBackward1>)
"""
在以上的训练过程中,我们可以通过改变 学习率、批的大小等不同参数的值来得到不同的训练结果。
4. 简洁实现
通过使用 PyTorch 深度学习框架简洁实现线性回归模型,生成数据集。
import torch
from torch.utils import data
from torch import nn
def synthetic_data(w,b,num_examples):
"""生成 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))
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w,true_b,1000)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
net = nn.Sequential(nn.Linear(2, 1))
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr = 0.03)
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}')
|