一,线性回归
回归的一种能为一个或者多个自变量与因变量之间建立关系建模的方法。常用来表示和预测输入和输出之间的关系。
线性回归基于几个简单的假设:首先,假设自变量x和因变量y之间的关系是线性的, 即y可以表示为x中元素的加权和,这里通常允许包含观测值的一些噪声; 其次,我们假设任何噪声都比较正常,如噪声遵循正态分布。
假设我们想通过房龄和面积预测房屋价格,给我提供了一些已知的统计数据(原始数据)。
几个基本的概念
训练集(training set):又叫训练数据集(training data set),是一个已知的原始数据的集合。
样本(sample):又叫数据点(data point)或数据样本(data instance),数据集中的每行数据。
标签(label):又叫目标(target),我们试图预测的目标和值。
特征(feature):又叫协变量(covariate),是预测所依靠的自变量。
线性模型
$$
price = w_{area}\cdot area + w_{age}\cdot age + b \tag {3.1}
$$
这里的两个w称为权重(weight),权重决定每个特征对预测值的影响。b称为偏移量(offset)、偏置(bias)或截距(intercept),代表当特征值为0时的预测值,但是即便特征值为0时貌似没有b(比如房子的房龄和面积都为0,按理说b也为0,但是其实显示中没有这种极端情况),偏移量更多代表的是一种模型的表达能力。
我们的目的就是找到最好的模型参数(model parameters)w和b。
但是找参数的前提是我们必须知道什么是更好的模型(模型质量度量方法——损失函数)、怎么达到更好的模型(更新模型的方法——随机梯度下降法(当然还有很多其他方法))
损失函数
损失函数表示的是实际值与预测值之间的差距。通常我们选择非负数作为损失值(单纯为了好比较),损失值越小越好。
$$
l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2.
$$
样本预测值为$ \hat{y}^{(i)} $,真实值为$y^{(i)}$。这里的$\frac{1}{2}$没有什么本质上的作用,只是为了方便计算(求导之后常数为1)
但是由于损失函数平方项的存在,预测值与实际值较大的差异会导致更大的损失。所以我们想要的参数现在变成了能够最小化在所有训练样本上的总损失。
解析解
线性回归的解叫做解析解,线性回归也算是一个简单的优化问题。预测问题:最小化$|\mathbf{y} - \mathbf{X}\mathbf{w}|^2$
首先我们把b合并到参数w中,操作方法是在包含所有参数的矩阵中附加一列。
$$
\mathbf{w}^* = (\mathbf X^\top \mathbf X)^{-1}\mathbf X^\top \mathbf{y}.
$$
随机梯度下降法
梯度下降法几乎可以优化所有深度学习模型,它通过不断在损失函数递减的方向上更新参数来降低误差。 但是这是一个不精确的算法,它会使损失向最小值缓慢收敛,但是不能在有限的步数内精确地到达最小值。
在每次迭代中,我们随机抽取一个小批量样本$\mathcal{B}$,我们计算这个小批量样本的平均损失关于模型参数的导数(梯度)。然后我们将梯度乘一个预先确定的正数$\eta$,并从当前参数的值中减掉。
$$
(\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).
$$
$|\mathcal{B}|$表示每个小批量样本内的样本总数,称为批量大小(batch size)l。$\eta$表示学习率(learning rate),这两个值都是预先手动指定,不会变化的。这些可以调整但是不在训练过程中更新的参数称为超参数(hyperparameter),我们所说的调参(hyperparameter tuning)就是选择超参数的过程。我们会根据训练迭代的结果来调整超参数。
简单的思路:
- 初始化模型参数的值,随机就行。
- 随机选取数据集中的一个小批量样本,在负梯度方向上(因为我们要减少损失函数,所以我们就要让它下降,梯度方向是上升最快的方向,负梯度就是下降最快的方向)更新参数。
- 检查参数是否满足边界条件(比如我们手动设置一个损失比较小的值,一旦发现损失小于这个值,就终止更新,否则继续更新。)
矢量化加速
我们在训练模型的时候,经常希望同时处理整个小批量的样本。所以我们可以对计算进行矢量化,从而能够使用线性代数库而不是使用python开销很大的for循环。
这里举个例子比较一下两种方法:
%matplotlib inline
import math
import time
import numpy as np
import torch
from d2l import torch as d2l
# 实例化两个10000维向量
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()
# 对工作负载进行基准测试
# for循环方法
c = torch.zeros(n)
timer = Timer()
for i in range(n):
c[i] = a[i] + b[i]
f'{timer.stop():.5f} sec'
#重载的+方法,加号应该是在d2l的torch里被重载了
timer.start()
d = a + b
f'{timer.stop():.5f} sec'
正态分布和平方损失
正态分布如下:
$$
p(x) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (x - \mu)^2\right).
$$
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)
#可视化正态分布
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]