2024.01.08 线性回归

2024.01.08 线性回归

因为学习大模型的微调遇到了障碍,所以回头再快速看一遍深度学习的原理。都是一些基础内容。。。

https://zh.d2l.ai/chapter_linear-networks/linear-regression-scratch.html

线性回归原理

线性回归模型的训练过程就是最小化损失函数的过程。

1 线性回归模型

线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下面的式子:
p r i c e = w a r e a ⋅ a r e a + w a g e ⋅ a g e + b \mathrm{price}=w_{\mathrm{area}}\cdot\mathrm{area}+w_{\mathrm{age}}\cdot\mathrm{age}+b price=wareaarea+wageage+b
其中 w a r e a w_{\mathrm{area}} warea w a g e w_{\mathrm{age}} wage称为权重(weight),权重决定了每个特征对我们预测值的影响。 b b b称为偏置(bias)、偏移量(offset)或截距(intercept)。给定一个数据集,我们的目标是寻找模型的权重 w w w和偏置 b b b, 使得根据模型做出的预测大体符合数据里的真实价格。

而在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。当我们的输入包含 d d d个特征时,我们将预测结果 y ^ \hat{y} y^(通常使用“尖角”符号表示 y y y的估计值)表示为:
y ^ = w 1 x 1 + … + w d x d + b \hat{y}=w_{1}x_{1}+\ldots+w_{d}x_{d}+b y^=w1x1++wdxd+b
将所有特征放到向量 x ∈ R d \mathbf{x}\in\mathbb{R}^{d} xRd中, 并将所有权重放到向量 w ∈ R d \mathbf{w}\in\mathbb{R}^{d} wRd 中, 我们可以用点积形式来简洁地表达模型:
y ^ = w ⊤ x + b \hat{y}=\mathbf{w}^\top\mathbf{x}+b y^=wx+b
其中向量 x \mathbf{x} x对应于单个数据样本的特征。用符号表示的矩阵 X ∈ R n × d \mathbf{X}\in\mathbb{R}^{n\times d} XRn×d可以很方便地引用我们整个数据集的n个样本。其中, X \mathbf{X} X的每一行是一个样本,每一列是一种特征。

对于特征集合 X \mathbf{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

2 损失函数

损失函数(loss function)能够量化目标的实际值与预测值之间的差距。通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。 回归问题中最常用的损失函数是平方误差函数。当样本 i i i的预测值为 y ^ ( i ) \hat{y}^{(i)} y^(i),其相应的真实标签为 y ( i ) y^{(i)} y(i)时, 平方误差可以定义为以下公式:
l ( i ) ( w , b ) = 1 2 ( y ^ ( i ) − y ( i ) ) 2 l^{(i)}(\mathbf{w},b)=\frac{1}{2}\left(\hat{y}^{(i)}-y^{(i)}\right)^2 l(i)(w,b)=21(y^(i)y(i))2
常数 1 2 \frac12 21不会带来本质的差别,但这样在形式上稍微简单一些 (因为当我们对损失函数求导后常数系数为1)。

由于平方误差函数中的二次方项, 估计值 y ^ ( i ) \hat{y}^{(i)} y^(i)和观测值 y ( i ) y^{(i)} y(i)之间较大的差异将导致更大的损失。 为了度量模型在整个数据集上的质量,我们需计算在训练集n个样本上的损失均值(也等价于求和)。
L ( w , b ) = 1 n ∑ i = 1 n l ( i ) ( w , b ) = 1 n ∑ i = 1 n 1 2 ( w ⊤ x ( i ) + b − y ( i ) ) 2 L(\mathbf{w},b)=\frac1n\sum_{i=1}^nl^{(i)}(\mathbf{w},b)=\frac1n\sum_{i=1}^n\frac12\Big(\mathbf{w}^\top\mathbf{x}^{(i)}+b-y^{(i)}\Big)^2 L(w,b)=n1i=1nl(i)(w,b)=n1i=1n21(wx(i)+by(i))2
在训练模型时,我们希望寻找一组参数 ( w ∗ , b ∗ ) \left(\mathbf{w^*}, b^*\right) (w,b), 这组参数能最小化在所有训练样本上的总损失。如下式:
w ∗ , b ∗ = argmin ⁡ w , b L ( w , b ) . \mathbf{w}^{*},b^{*}=\operatorname*{argmin}_{\mathbf{w},b}L(\mathbf{w},b). w,b=w,bargminL(w,b).

3 随机梯度下降

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值) 关于模型参数的导数(在这里也可以称为梯度)。 但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。 因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本, 这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。

在每次迭代中,我们首先随机抽样一个小批量 B \mathcal{B} B, 它是由固定数量的训练样本组成的。 然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。 最后,我们将梯度乘以一个预先确定的正数 η \eta η,并从当前参数的值中减掉。

我们用下面的数学公式来表示这一更新过程( ∂ \partial 表示偏导数):
( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) (\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) (w,b)(w,b)BηiB(w,b)l(i)(w,b)
总结一下,算法的步骤如下: (1)初始化模型参数的值,如随机初始化; (2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。 对于平方损失和仿射变换,我们可以明确地写成如下形式:
w ← w − η ∣ B ∣ ∑ i ∈ B ∂ w l ( i ) ( w , b ) = w − η ∣ B ∣ ∑ i ∈ B x ( i ) ( w ⊤ x ( i ) + b − y ( i ) ) , b ← b − η ∣ B ∣ ∑ i ∈ B ∂ b l ( i ) ( w , b ) = b − η ∣ B ∣ ∑ i ∈ B ( w ⊤ x ( i ) + b − y ( i ) ) . \begin{gathered} \mathbf{w}\leftarrow\mathbf{w}-\frac{\eta}{|\mathcal{B}|}\sum_{i\in\mathcal{B}}\partial_{\mathbf{w}}l^{(i)}(\mathbf{w},b)=\mathbf{w}-\frac{\eta}{|\mathcal{B}|}\sum_{i\in\mathcal{B}}\mathbf{x}^{(i)}\left(\mathbf{w}^{\top}\mathbf{x}^{(i)}+b-y^{(i)}\right), \\ \begin{aligned}b\leftarrow b-\frac\eta{|\mathcal{B}|}\sum_{i\in\mathcal{B}}\partial_bl^{(i)}(\mathbf{w},b)=b-\frac\eta{|\mathcal{B}|}\sum_{i\in\mathcal{B}}\left(\mathbf{w}^\top\mathbf{x}^{(i)}+b-y^{(i)}\right).\end{aligned} \end{gathered} wwBηiBwl(i)(w,b)=wBηiBx(i)(wx(i)+by(i)),bbBηiBbl(i)(w,b)=bBηiB(wx(i)+by(i)).
上述公式中的 w \mathbf{w} w x \mathbf{x} x都是向量。 ∣ B ∣ |\mathcal{B}| B表示每个小批量中的样本数,这也称为批量大小(batch size)。 η \eta η表示学习率(learning rate)。批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。 这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。 调参(hyperparameter tuning)是选择超参数的过程。 超参数通常是我们根据训练迭代结果来调整的, 而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。

线性回归从零开始实现

1 构造数据集

构造一个y=Xw+b+噪声的数据集

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: tensor([-0.7715,  0.4212]) 
label: tensor([1.2321])

2 定义数据集的bach_size

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

输出:

tensor([[-0.5022,  0.7038],
        [-0.2006, -1.7286],
        [-0.1174,  0.8104],
        [ 1.1591,  2.5481],
        [-0.8888, -1.1390],
        [-1.7663,  0.0730],
        [-0.3518,  0.8524],
        [ 1.8939, -0.9266],
        [ 0.6197,  0.0300],
        [-0.9234, -0.1029]]) 
 tensor([[ 0.8005],
        [ 9.6945],
        [ 1.2068],
        [-2.1388],
        [ 6.2942],
        [ 0.4365],
        [ 0.5955],
        [11.1636],
        [ 5.3342],
        [ 2.7098]])

3 初始化模型参数w和b

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

4 定义线性回归模型

def linreg(X, w, b):
    """线性回归模型"""
    return torch.matmul(X, w) + b

5 定义损失函数

def squared_loss(y_hat, y):
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

6 定义优化算法

def sgd(params, lr, batch_size):
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

7 训练

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}')

输出:

epoch 1, loss 0.038077
epoch 2, loss 0.000137
epoch 3, loss 0.000047

误差分析:

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

输出:

w的估计误差: tensor([0.0002, 0.0004], grad_fn=<SubBackward0>)
b的估计误差: tensor([-0.0002], grad_fn=<RsubBackward1>)

线性回归的简洁实现

1 构造数据集

import numpy as np
import torch
from torch.utils import data


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(true_w, true_b, 1000)

2 构造dataset迭代器

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)

batch_size = 10
data_iter = load_array((features, labels), batch_size)

3 定义模型

from torch import nn
net = nn.Sequential(nn.Linear(2,1))

4 初始化参数

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

5 定义损失函数

loss = nn.MSELoss()

6 定义优化算法

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

7 训练

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}')

输出

epoch 1, loss 0.000425
epoch 2, loss 0.000099
epoch 3, loss 0.000098
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

输出

w的估计误差: tensor([0.0004, 0.0006])
b的估计误差: tensor([-9.5367e-06])

欢迎关注公众号

在这里插入图片描述

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值