Pytorch 线性回归

线性回归

回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。 在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。
在机器学习领域中的大多数任务通常都与预测(prediction)有关。 当我们想预测一个数值时,就会涉及到回归问题。 常见的例子包括:预测价格(房屋、股票等)、预测住院时间(针对住院病人等)、 预测需求(零售销量等)。

0. 环境介绍

环境使用 Kaggle 里免费建立的 Notebook

教程使用李沐老师的 动手学深度学习 网站和 视频讲解

小技巧:当遇到函数看不懂的时候可以按 Shift+Tab 查看函数详解。

1. 线性回归的基本元素

为了解释线性回归,我们举一个实际的例子: 我们希望根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)。 为了开发一个能预测房价的模型,我们需要收集一个真实的数据集。 这个数据集包括了房屋的销售价格、面积和房龄。 在机器学习的术语中,该数据集称为训练数据集(training data set) 或训练集(training set)。 每行数据(比如一次房屋交易相对应的数据)称为样本(sample), 也可以称为数据点(data point)或数据样本(data instance)。 我们把试图预测的目标(比如预测房屋价格)称为标签(label)或目标(target)。 预测所依据的自变量(面积和房龄)称为特征(feature)或协变量(covariate)。

1.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_{area} warea w a g e w_{age} wage 称为权重(weight),权重决定了每个特征对我们预测值的影响。

b b b 称为偏置(bias)、偏移量(offset)或截距(intercept)。 偏置是指当所有特征都取值为0时,预测值应该为多少。 即使现实中不会有任何房子的面积是0或房龄正好是0年,我们仍然需要偏置项。 如果没有偏置项,我们模型的表达能力将受到限制。

上述式子是输入特征的一个 仿射变换(affine transformation)。 仿射变换的特点是通过加权和对特征进行线性变换(linear transformation), 并通过偏置项来进行平移(translation)。

在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。 当我们的输入包多个特征时,我们将预测结果 y ^ \hat{y} y^(通常使用“尖角”符号表示 y y y 的估计值)用点积形式简洁表示:
y ^ = w ⊤ x + b . \hat{y} = \mathbf{w}^\top \mathbf{x} + b. y^=wx+b.

1.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 \frac{1}{2} 21 对本质无影响,就是为了方便,求导后的式子系数为 1。

1.3 优化方法(梯度下降)

Pytorch 线性回归_第1张图片
如果每次都遍历整个数据集之后才进行更新参数,计算量大,故常采用小批量随机梯度下降法。
Pytorch 线性回归_第2张图片
梯度下降方法最关键的参数就是学习率批量
学习率设置不能太大也不能太小。
学习率太大会造成收敛缓慢,陷入局部最优解。
学习率太小可能会导致在最优解附近震荡。

批量大小不能太大也不能太小。
批量太小,不适合进行并行计算,不能最大利用计算资源。
批量太大,内存消耗增加,浪费计算。

2. 线性回归从零开始实现

2.0 导入模块

 !pip install -U d2l 
%matplotlib inline
import random
import torch
from d2l import torch as d2l

d2l 模块是李沐老师的库,在 kaggle 中的 notebook 中用 !pip install -U d2l 安装即可。

2.1 生成数据集

使用线性模型参数 w = [ 2 , − 3.4 ] ⊤ \mathbf{w} = [2, -3.4]^\top w=[2,3.4] b = 4.2 b = 4.2 b=4.2 和噪声项 ϵ \epsilon ϵ 生成数据集及其标签:
y = X w + b + ϵ . \mathbf{y}= \mathbf{X} \mathbf{w} + b + \mathbf\epsilon. y=Xw+b+ϵ.

# 生成数据
# w, b, 样本数
def synthetic_data(w, b, num_examples):
    # X 为矩阵,表示生成 样本数*len(w) 的(0,1)正态分布的矩阵。
    # len(w) 表示特征数
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w) + b
    # y = Xw + b + 噪声,噪声呈现标准差为 0.01 的正态分布。
    y += torch.normal(0, 0.01, y.shape)
    # (-1, 1) 代表作为列向量返回
    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)

Pytorch 线性回归_第3张图片

# 查看数据分布
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);
# 最后一个参数为点的大小,也可以传入一个为向量设置每个点的大小

Pytorch 线性回归_第4张图片
detach 是为了将将数据从计算图中拿出来。

2.2 读取数据集

# 每次抽取一小批量样本,并使用它们来更新我们的模型
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序,用 shuffle 打乱顺序
    random.shuffle(indices) 
    for i in range(0, num_examples, batch_size):
        # min 函数为了解决样本数不能整除批量的情况,最后一个 batch 截取剩余部分
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        # yield 的作用就是把一个函数变成一个 generator
        yield features[batch_indices], labels[batch_indices]

我们定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。 每个小批量包含一组特征和标签。
Pytorch 线性回归_第5张图片

2.3 初始化模型参数

随机初始化参数,并计算梯度。
划重点: 需要 w w w b b b 进行更新,所以才将 requires_grad设置为 True

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

2.4 定义模型

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

2.5 定义损失函数

# y_hat 为预测值,y 为真实值
def squared_loss(y_hat, y): 
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

reshape 是为了统一形状。

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_()

Python参数传递采用的肯定是“传对象引用”的方式。这种方式相当于传值和传引用的一种综合。
如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值,相当于通过“传引用”来传递对象。
如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象,相当于通过“传值’来传递对象。

2.7 训练

在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法 sgd 来更新模型参数。

概括一下,我们将执行以下循环:

  1. 初始化参数

  2. 重复以下训练,直到完成
    2.1 计算梯度: g ← ∂ ( w , b ) 1 ∣ B ∣ ∑ i ∈ B l ( x ( i ) , y ( i ) , w , b ) \mathbf{g} \leftarrow \partial_{(\mathbf{w},b)} \frac{1}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} l(\mathbf{x}^{(i)}, y^{(i)}, \mathbf{w}, b) g(w,b)B1iBl(x(i),y(i),w,b)

    2.2 更新参数: ( w , b ) ← ( w , b ) − η g (\mathbf{w}, b) \leftarrow (\mathbf{w}, b) - \eta \mathbf{g} (w,b)(w,b)ηg

# 学习率
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]的梯度,sum 求总梯度,sgd 里会根据平均梯度进行优化。
        l.sum().backward()
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数
    with torch.no_grad():
        # 这里 labels 是真实值
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

在这里插入图片描述
Pytorch 线性回归_第6张图片

3. 线性回归的简洁实现

3.1 生成数据集

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

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

3.2 读取数据集

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)

next(iter(data_iter))

Pytorch 线性回归_第7张图片
TensorDataset 提供一种方式去获取数据和 labels。
DataLoader 为后面的训练提供数据形式。
星号 ∗ * 的作用是为了解包,并且解包后的结果可以用于传递参数。
Pytorch 线性回归_第8张图片

3.3 定义模型

在 PyTorch 中,全连接层在 Linear 类中定义。 值得注意的是,我们将两个参数传递到 nn.Linear 中。 第一个指定输入特征形状,即 2,第二个指定输出特征形状,输出特征形状为单个标量,因此为 1

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

3.4 初始化模型参数

我们通过 net[0] 选择网络中的第一个图层, 然后使用 weight.databias.data 方法访问参数。 我们还可以使用替换方法 normal_fill_ 来重写参数值。

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

在这里插入图片描述

3.5 定义损失函数

计算均方误差使用的是 MSELoss 类,也称为平方范数。 默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()

3.6 定义优化算法

当我们实例化一个 SGD 实例时,我们要指定优化的参数 (可通过 net.parameters() 从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置 lr值,这里设置为 0.03

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

3.7 训练

在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:

  1. 通过调用 net(X) 生成预测并计算损失 l(前向传播)。
  2. 通过进行反向传播来计算梯度。
  3. 通过调用优化器来更新模型参数。
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)
    # l:f 代表输出 float
    print(f'epoch {epoch + 1}, loss {l:f}')

在这里插入图片描述
Pytorch 线性回归_第9张图片

4. 练习

4.1 如果将小批量的总损失替换为小批量损失的平均值,需要如何更改学习率?

如果用 nn.MSELoss(reduction=‘sum’) 替换 nn.MSELoss() ,应该把学习率除以 batch_size,因为默认参数是 mean,换成 sum 需要除以批量数,一般会采用默认,因为这样学习率可以跟 batch_size 解耦。

4.2 如何访问线性回归的梯度?

print(net[0].weight.grad, net[0].bias.grad)

你可能感兴趣的:(Pytorch,pytorch)