线性回归 Linear Regression

  1. 线性回归
    1. 线性回归的基本要素
      1. 模型
      2. 模型训练
      3. 训练数据
      4. 损失函数
      5. 优化算法
      6. 模型预测
    2. 线性回归的表示方法
      1. 神经网络图
      2. 矢量计算表达式
    3. 小小问答

线性回归

线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常见,如预测房屋价格、气温、销售额等连续值的问题。与回归问题不同,分类问题中模型的最终输出是一个离散值。我们所说的图像分类、垃圾邮件识别、疾病检测等输出为离散值的问题都属于分类问题的范畴。softmax回归则适用于分类问题。

由于线性回归和softmax回归都是单层神经网络,它们涉及的概念和技术同样适用于大多数的深度学习模型。我们首先以线性回归为例,介绍大多数深度学习模型的基本要素和表示方法。

线性回归的基本要素

我们以一个简单的房屋价格预测作为例子来解释线性回归的基本要素。这个应用的目标是预测一栋房子的售出价格(元)。我们知道这个价格取决于很多因素,如房屋状况、地段、市场行情等。为了简单起见,这里我们假设价格只取决于房屋状况的两个因素,即面积(平方米)和房龄(年)。接下来我们希望探索价格与这两个因素的具体关系。

模型

设房屋的面积为$x_1$,房龄为$x_2$,售出价格为$y$。我们需要建立基于输入$x_1$和$x_2$来计算输出$y$的表达式,也就是模型(model)。顾名思义,线性回归假设输出与各个输入之间是线性关系:

$$\hat{y} = x_1 w_1 + x_2 w_2 + b,$$

其中$w_1$和$w_2$是权重(weight),$b$是偏差(bias),且均为标量。它们是线性回归模型的参数(parameter)。模型输出$\hat{y}$是线性回归对真实价格$y$的预测或估计。我们通常允许它们之间有一定误差。

模型训练

接下来我们需要通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小。这个过程叫作模型训练(model training)。下面我们介绍模型训练所涉及的3个要素。

训练数据

我们通常收集一系列的真实数据,例如多栋房屋的真实售出价格和它们对应的面积和房龄。我们希望在这个数据上面寻找模型参数来使模型的预测价格与真实价格的误差最小。在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),一栋房屋被称为一个样本(sample),其真实售出价格叫作标签(label),用来预测标签的两个因素叫作特征(feature)。特征用来表征样本的特点。

假设我们采集的样本数为$n$,索引为$i$的样本的特征为$x_1^{(i)}$和$x_2^{(i)}$,标签为$y^{(i)}$。对于索引为$i$的房屋,线性回归模型的房屋价格预测表达式为

$$\hat{y}^{(i)} = x_1^{(i)} w_1 + x_2^{(i)} w_2 + b.$$

损失函数

在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。它在评估索引为$i$的样本误差的表达式为

$$\ell^{(i)}(w_1, w_2, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2,$$

其中常数$1/2$使对平方项求导后的常数系数为1,这样在形式上稍微简单一些。显然,误差越小表示预测价格与真实价格越相近,且当二者相等时误差为0。给定训练数据集,这个误差只与模型参数相关,因此我们将它记为以模型参数为参数的函数。在机器学习里,将衡量误差的函数称为损失函数(loss function)。这里使用的平方误差函数也称为平方损失(square loss)。

通常,我们用训练数据集中所有样本误差的平均来衡量模型预测的质量,即

$$\ell(w_1, w_2, b) =\frac{1}{n} \sum_{i=1}^n \ell^{(i)}(w_1, w_2, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right)^2.$$

在模型训练中,我们希望找出一组模型参数,记为$w_1^, w_2^, b^*$,来使训练样本平均损失最小:

$$w_1^*, w_2^*, b^* = \operatorname*{argmin}_{w_1, w_2, b}\ \ell(w_1, w_2, b).$$

优化算法

当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。

在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)$\mathcal{B}$,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

在训练本节讨论的线性回归模型的过程中,模型的每个参数将作如下迭代:

$$
\begin{aligned}
w_1 &\leftarrow w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \frac{ \partial \ell^{(i)}(w_1, w_2, b) }{\partial w_1} = w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_1^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\
w_2 &\leftarrow w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \frac{ \partial \ell^{(i)}(w_1, w_2, b) }{\partial w_2} = w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_2^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\
b &\leftarrow b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}} \frac{ \partial \ell^{(i)}(w_1, w_2, b) }{\partial b} = b - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}\left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right).
\end{aligned}
$$

在上式中,$|\mathcal{B}|$代表每个小批量中的样本个数(批量大小,batch size),$\eta$称作学习率(learning rate)并取正数。需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学出的,因此叫作超参数(hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。在少数情况下,超参数也可以通过模型训练学出。本书对此类情况不做讨论。

模型预测

模型训练完成后,我们将模型参数$w_1, w_2, b$在优化算法停止时的值分别记作$\hat{w}_1, \hat{w}_2, \hat{b}$。注意,这里我们得到的并不一定是最小化损失函数的最优解$w_1^*, w_2^*, b^*$,而是对最优解的一个近似。然后,我们就可以使用学出的线性回归模型$x_1 \hat{w}_1 + x_2 \hat{w}_2 + \hat{b}$来估算训练数据集以外任意一栋面积(平方米)为$x_1$、房龄(年)为$x_2$的房屋的价格了。这里的估算也叫作模型预测、模型推断或模型测试。

线性回归的表示方法

我们已经阐述了线性回归的模型表达式、训练和预测。下面我们解释线性回归与神经网络的联系,以及线性回归的矢量计算表达式。

神经网络图

在深度学习中,我们可以使用神经网络图直观地表现模型结构。为了更清晰地展示线性回归作为神经网络的结构,图3.1使用神经网络图表示本节中介绍的线性回归模型。神经网络图隐去了模型参数权重和偏差。

线性回归是一个单层神经网络

在图3.1所示的神经网络中,输入分别为$x_1$和$x_2$,因此输入层的输入个数为2。输入个数也叫特征数或特征向量维度。图3.1中网络的输出为$o$,输出层的输出个数为1。需要注意的是,我们直接将图3.1中神经网络的输出$o$作为线性回归的输出,即$\hat{y} = o$。由于输入层并不涉及计算,按照惯例,图3.1所示的神经网络的层数为1。所以,线性回归是一个单层神经网络。输出层中负责计算$o$的单元又叫神经元。在线性回归中,$o$的计算依赖于$x_1$和$x_2$。也就是说,输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫全连接层(fully-connected layer)或稠密层(dense layer)。

矢量计算表达式

在模型训练或预测时,我们常常会同时处理多个数据样本并用到矢量计算。在介绍线性回归的矢量计算表达式之前,让我们先考虑对两个向量相加的两种方法。

下面先定义两个1000维的向量。

import numpy as np
import torch
# 引入logger
from utils.logconfig import getLogger
import random
from IPython import display
from matplotlib import pyplot as plt

"""线性回归
"""
logger = getLogger()
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = np.random.normal(scale=1, size=(num_examples, num_inputs))
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += np.random.normal(scale=0.01, size=labels.shape)
features = torch.tensor(features)
labels = torch.tensor(labels)

logger.info("features[0]: {},labels[0]: {}".format(features[0], labels[0]))

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):
        j = np.array(indices[i: min(i + batch_size, num_examples)])
        yield features[j], labels[j]  # torch的切片操作

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    logger.info("\nX: {}, \ny: {}".format(X, y))
    break

w = np.random.normal(0, 0.01, (num_inputs, 1))
b = torch.zeros(1)
w = torch.tensor(w, requires_grad=True)
b = torch.tensor(b, requires_grad=True)

def linreg(X, w, b):  # 本函数已保存在d2lzh包中方便以后使用
    return torch.matmul(X, w) + b

def squared_loss(y_hat, y):  # 本函数已保存在d2lzh包中方便以后使用
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

def sgd(params, lr, batch_size):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    for param in params:
        param.data -= lr * param.grad / batch_size

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss

for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
    # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
    # 和y分别是小批量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  # l是有关小批量X和y的损失
        l.backward()  # 小批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降迭代模型参数
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean()))

小小问答

PyTorch中在反向传播前为什么要手动将梯度清零?
这种模式可以让梯度玩出更多花样,比如说梯度累加(gradient accumulation)
一个batch训练的步骤:

  1. 获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数;
  2. optimizer.zero_grad() 清空过往梯度;
  3. loss.backward() 反向传播,计算当前梯度;
  4. optimizer.step() 根据梯度更新网络参数

    for i,(images,target) in enumerate(train_loader):
     # 1. input output
     images = images.cuda(non_blocking=True)
     target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
     outputs = model(images)
     loss = criterion(outputs,target)
    
     # 2. backward
     optimizer.zero_grad()   # reset gradient
     loss.backward()
     optimizer.step()
    

    使用梯度累加的batch训练:

  5. 获取loss:输入图像和标签,通过infer计算得到预测值,计算损失函数的1/accumulation_steps;
  6. loss.backward() 反向传播,计算当前梯度;
  7. 多次循环步骤1-2,不清空梯度,使梯度累加在已有梯度上;
  8. 梯度累加了一定次数后,先optimizer.step() 根据累计的梯度更新网络参数,然后optimizer.zero_grad() 清空过往梯度,为下一波梯度累加做准备;
for i,(images,target) in enumerate(train_loader):
    # 1. input output
    images = images.cuda(non_blocking=True)
    target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
    outputs = model(images)
    loss = criterion(outputs,target)

    # 2.1 loss regularization 每次只传 1/5
    loss = loss/accumulation_steps  
    # 2.2 back propagation
    loss.backward()
    # 3. update parameters of net
    if((i+1)%accumulation_steps)==0:
        # optimizer the net
        optimizer.step()        # update parameters of net
        optimizer.zero_grad()   # reset gradient

As far as I know, batch norm statistics get updated on each forward pass, so no problem if you don’t do .backward() every time.
accumulation_steps=8和真实的batchsize放大八倍相比,效果自然是差一些,毕竟八倍Batchsize的BN估算出来的均值和方差肯定更精准一些。
这种梯度累加的思路是对内存的极大友好,是由FAIR的设计理念出发的。

  1. UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requiresgrad(True), rather than torch.tensor(sourceTensor).
    imporve:
    w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)))
    b = torch.zeros(1)
    w.requires_grad_(requires_grad=True)
    b.requires_grad_(requires_grad=True)
    
  2. Error : Leaf variable has been moved into the graph interior
    improve:

    param.data -= lr * param.grad / batch_size
    
  3. Error : grad can be implicitly created only for scalar outputs
    loss.sum()解决
    Try printing the losses, it should be a tensor with single number
    https://discuss.pytorch.org/t/runtimeerror-grad-can-be-implicitly-created-only-for-scalar-outputs/21217

  4. 参考链接
    https://www.zhihu.com/question/303070254


请多多指教。

文章标题:线性回归 Linear Regression

本文作者:顺强

发布时间:2019-12-06, 01:20:12

原始链接:http://shunqiang.ml/cnn-linear-regression/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏