碧海苍梧

V1

2023/02/28阅读:16主题:默认主题

DLT-02-一元线性回归

本文是深度学习入门(deep learning tutorial, DLT)系列的第二篇文章,主要介绍一下线性神经网络。想要学习深度学习或者想要了解机器学习的同学可以关注公众号GeodataAnalysis,我会逐步更新这一系列的文章。

在介绍深度学习之前,我们需要了解一些神经⽹络训练的基础知识。为了更容易学习,我们将从经典算法——线性神经⽹络开始,首先介绍一元线性回归,逐步扩展到多元线性回归和非线性回归。经典统计学习技术中的线性回归和softmax回归都可以视为线性神经⽹络,这些知识将为本系列教程其他部分中更复杂的技术奠定基础。

本文目录如下:

  • 1 线性回归
  • 2 线性回归模型表达
  • 3 生成测试数据
    • 3.1 长期趋势
    • 3.2 季节变动
    • 3.3 不规则变动
  • 4 假设函数
  • 5 损失函数
  • 6 梯度下降
  • 7 随机梯度下降
  • 8 训练
    • 8.1 不使用随机梯度下降
    • 8.2 使用随机梯度下降

1 线性回归

回归(regression)是能为⼀个或多个⾃变量与因变量之间关系建模的⼀类⽅法。在⾃然科学和社会科学领域,回归经常⽤来表⽰输⼊和输出之间的关系。

在机器学习领域中的⼤多数任务通常都与预测(prediction)有关。当我们想预测⼀个数值时,就会涉及到回归问题。常⻅的例⼦如:给定居住面积,预测住宅价格。但不是所有的预测都是回归问题,在后⾯的文章中,我们将介绍分类问题。分类问题的⽬标是预测数据属于⼀组类别中的哪⼀个,如:给定居住面积,预测住宅是别墅还是公寓。

线性回归基于⼏个简单的假设:⾸先,假设⾃变量x和因变量y之间的关系是线性的,即y可以表⽰为x中元素的加权和,这⾥通常允许包含观测值的⼀些噪声;其次,我们假设任何噪声都不是无序的,如噪声遵循正态分布。

2 线性回归模型表达

通常,我们使用 表示输入变量; 表示输出或目标变量; 表示训练集。我们还将使用 X 表示输入值的值域,使用 Y 表示输出值的值域。在此示例中,X = Y = R。

监督学习就是给定一个训练集,学习一个函数 ,这样 的相应值的"良好"预测因子。由于历史原因, 被称为假设函数。

3 生成测试数据

为了简单起见,我们将根据带有噪声的线性模型构造⼀个⼈造数据集。我们的任务是使⽤这个有限样本的数据集来恢复这个模型的参数。我们将使⽤低维数据,这样可以很容易地将其可视化。先下面的代码中,我们生成了一个包含1461个样本的时间序列数据集。由于时间序列数据=长期趋势+季节变动+不规则变动,我们按照该规则生成测试数据。

3.1 长期趋势

长期趋势为y=0.1x,生成四年逐日的数据。

import numpy as np
import matplotlib.pyplot as plt

def trend(time, slope=0):
    return slope * time

time = np.arange(4 * 365 + 1)
baseline = 10
trend_series = trend(time, 0.1)+baseline
plt.plot(time, trend_series);

3.2 季节变动

周期性(cyclicity)也称循环波动(cyclical fluctuation),是时间序列中呈现出来的围绕长期趋势的一种波浪形或震荡式变动。

def seasonal_pattern(season_time):
    return np.where(season_time < 0.4,
                    np.cos(season_time * 2 * np.pi),
                    1 / np.exp(3 * season_time))

def seasonality(time, period, amplitude=1, phase=0):
    season_time = ((time + phase) % period) / period
    return amplitude * seasonal_pattern(season_time)

amplitude = 40
season_series = seasonality(time, period=365, amplitude=amplitude)
plt.plot(time, season_series, time, trend_series+season_series);

3.3 不规则变动

不规则变动是一种无规律可循的变动,包括严格的随机变动和不规则的突发性影响很大的变动两种类型。

def noise(time, noise_level=1):
    return np.random.randn(len(time)) * noise_level

noise_level = 15
noisy_series = trend_series + season_series + noise(time, noise_level)
plt.plot(time, noisy_series);

最后生成的数据如下,x表示输入变量,y表示目标变量,二者都是一维的numpy数组。

x = time.copy()
y = noisy_series.copy()
x.shape, y.shape
((1461,), (1461,))

4 假设函数

线性假设是指⽬标(房屋价格)可以表⽰为特征(房屋⾯积)的加权和,如下⾯的式⼦:

其中, 称为权重(weight),权重决定了每个特征对我们预测值的影响。 称为偏置(bias)、偏移量(offset)或截距(intercept)。偏置是指当所有特征都取值为0时,预测值应该为多少。即使现实中不会有任何房⼦的⾯积是0或房龄正好是0年,我们仍然需要偏置项。如果没有偏置项,我们模型的表达能⼒将受到限制。严格来说,上式是对输⼊特征的⼀个仿射变换(affine transformation)。仿射变换的特点是通过加权和对特征进⾏线性变换(linear transformation),并通过偏置项来进⾏平移(translation)。

给定⼀个数据集,我们的⽬标是寻找模型的权重 和偏置 ,使得根据模型做出的预测⼤体符合数据⾥的真实价格。输出的预测值由输⼊特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。

假设函数用于表示这个线性模型,使其能尽可能的代表数据的分布。对于一元线性回归而言,其假设函数可设为 。其中, 为权重, 为偏置。代码如下:

def predict_fun(x, parameters):
    return parameters['theta0']+parameters['theta1']*x

5 损失函数

在我们开始考虑如何⽤模型拟合数据之前,我们需要确定⼀个拟合程度的度量。损失函数(loss function)能够量化⽬标的实际值与预测值之间的差距,也被称为代价函数。通常我们会选择⾮负数作为损失,且数值越小表⽰损失越小,完美预测时的损失为0。回归问题中最常⽤的损失函数是平⽅误差函数。​​当样本 的预测值为 ,其相应的真实标签为 时,平⽅误差可以定义为以下公式:

此函数也被称为"均方误差函数"。均值乘 是为了方便计算梯度下降,因为平方函数的导数项将抵消

训练模型的目的就是寻找一组参数 ,这组参数能最小化在所有训练样本上的总损失。损失函数的代码如下:

def loss_fun(y, y_predict):
    return np.sum(np.square(y_predict-y))/(2*y.size)

6 梯度下降

大多数深度学习算法都涉及某种形式的优化。优化指的是改变 x 以最小化或最大化某个函数 f(x) 的任务。我们通常以最小化 f(x) 指代大多数最优化问题。最大化可经由最小化算法最小化 −f(x) 来实现。

我们把要最小化或最大化的函数称为 目标函数(objective function)或准则(criterion)。当我们对其进行最小化时,我们也把它称为代价函数(cost function)、损失函数(loss function)或误差函数(error function)。虽然有些机器学习著作赋予这些名称特殊的意义,但在本教程中我们交替使用这些术语。我们通常使用一个上标 ∗ 表示最小化或最大化函数的 x 值。如我们记

假设我们有一个函数 y = f(x),其中 x 和 y 是实数。这个函数的导数(derivative)记为 f′(x) 或 。导数 f′(x) 代表 f(x) 在点 x 处的斜率。换句话说,它表明如何缩放输入的小变化才能在输出获得相应的变化:f(x + ϵ) ≈ f(x) + ϵf′(x)。

因此导数对于最小化一个函数很有用,因为它告诉我们如何更改 x 来略微地改善 y。如下图所示,我们知道对于足够小的 ϵ 来说,f(x − ϵ·sign(f′(x))) 是比 f(x) 小的。因此我们可以将 x 往导数的反方向移动一小步来减小 f(x),这种技术便被称为梯度下降(gradient descent)。

当 f′(x) = 0时,导数无法提供往哪个方向移动的信息。f′(x) = 0 的点称为临界点(critical point)或驻点(stationary point)。一个局部极小点(local minimum)意味着这个点的 f(x) 小于所有邻近点,因此不可能通过移动无穷小的步长来减小f(x)。一个局部极大点(local maximum)意味着这个点的 f(x) 大于所有邻近点,因此不可能通过移动无穷小的步长来增大 f(x)。有些临界点既不是最小点也不是最大点。这些点被称为鞍点(saddle point)。

使 f(x) 取得绝对的最小值(相对所有其他值)的点是全局最小点(global minimum)。函数可能只有一个全局最小点或存在多个全局最小点,还可能存在不是全局最优的局部极小点。在深度学习的背景下,我们要优化的函数可能含有许多不是最优的局部极小点,或者还有很多处于非常平坦的区域内的鞍点。尤其是当输入是多维的时候,所有这些都将使优化变得困难。因此,我们的目的是寻找使 f 非常小的点,但这在任何形式意义下并不一定是最小。

我们经常最小化具有多维输入的函数: 。为了使 ‘‘最小化’’ 的概念有意义,输出必须是一维的 (标量)。

针对具有多维输入的函数,我们需要用到偏导数(partial derivative)的概念。偏导数 衡量点 x 处只有 增加时 f(x) 如何变化。 梯度(gradient)是相对一个向量求导的导数: f 的导数是包含所有偏导数的向量,记为 。梯度的第i 个元素是 f 关于 的偏导数。在多维情况下,临界点是梯度中所有元素都为零的点。

在 u(单位向量)方向的 方向导数(directional derivative)是函数 f 在 u 方向的斜率。换句话说,方向导数是函数 关于 的导数(在 = 0 时取得)。使用链式法则,我们可以看到当 时,

为了最小化 f,我们希望找到使 f 下降得最快的方向。计算方向导数:

其中 θ 是 u 与梯度的夹角。将 代入,并忽略与 u 无关的项,就能简化得到 。这在 u 与梯度方向相反时取得最小。换句话说,梯度向量指向上坡,负梯度向量指向下坡。我们在负梯度方向上移动可以减小 f。这被称为最速下降法(method of steepest descent) 或 梯度下降(gradient descent)。

最速下降建议新的点为:

其中 为 学习率(learning rate),是一个确定步长大小的正标量。我们可以通过几种不同的方式选择 。普遍的方式是选择一个小常数。有时我们通过计算,选择使方向导数消失的步长。还有一种方法是根据几个 计算 ,并选择其中能产生最小目标函数值的 。这种策略被称为线搜索。

最速下降在梯度的每一个元素为零时收敛(或在实践中,很接近零时)。在某些情况下,我们也许能够避免运行该迭代算法,并通过解方程 直接跳到临界点。

虽然梯度下降被限制在连续空间中的优化问题,但不断向更好的情况移动一小步(即近似最佳的小移动)的一般概念可以推广到离散空间。递增带有离散参数的目标函数被称为爬山(hill climbing)算法。

对我们要讲的一元线性回归而言,损失函数(数据集中所有样本的损失均值)关于模型参数的导数便是梯度。因此,在每⼀次更新参数之前,我们必须遍历整个数据集。对于损失函数 而言,其梯度分别为:

总结⼀下,梯度下降算法的步骤如下:(1)初始化模型参数的值,如随机初始化;(2)选择一个极小的数作为学习率;(3)以整个数据集作为样本,在负梯度的⽅向上更新参数,并不断迭代这⼀步骤。对于一元线性回归,我们可以将参数更新明确地写成如下形式:

使用梯度下降更新模型参数的代码如下:

def gradient(x, y, parameters, learning_rate):
 y_predict = predict_fun(x, parameters)
    
    theta0_d = learning_rate*np.sum(y_predict-y)/y.size
 theta0 = parameters['theta0'] - theta0_d
    theta1_d = learning_rate*np.sum((y_predict-y)*x)/y.size
 theta1 = parameters['theta1'] - theta1_d
    
 parameters['theta0'] = theta0
 parameters['theta1'] = theta1

    return parameters

7 随机梯度下降

机器学习中反复出现的一个问题是好的泛化需要大的训练集,但大的训练集的计算代价也更大。

机器学习算法中的代价函数通常可以分解成每个样本的代价函数的总和。例如,

对于这些相加的代价函数,梯度下降需要计算:

这个运算的计算代价是 O(m)。随着训练集规模增长为数十亿的样本,计算一步梯度也会消耗相当长的时间。

随机梯度下降(SGD)的核心是,梯度是期望。期望可使用小规模的样本近似估计。具体而言,在算法的每一步,我们从训练集中均匀抽出一小批量(minibatch)样本 。小批量的数目 m′通常是一个相对较小的数,从一到几百。重要的是,当训练集大小 m 增长时,m′通常是固定的。我们可能在拟合几十亿的样本时,每次更新计算只用到几百个样本。

梯度的估计可以表示成:

使用来自小批量 B 的样本。然后,随机梯度下降算法使用如下的梯度下降估计:

其中, 是学习率。

梯度下降往往被认为很慢或不可靠。以前,将梯度下降应用到非凸优化问题被认为很鲁莽或没有原则。现在,我们知道梯度下降用于本文第七节中的训练时效果不错。优化算法不一定能保证在合理的时间内达到一个局部最小值,但它通常能及时地找到代价函数一个很小的值,并且是有用的。

随机梯度下降在深度学习之外有很多重要的应用。它是在大规模数据上训练大型线性模型的主要方法。对于固定大小的模型,每一步随机梯度下降更新的计算量不取决于训练集的大小 m。在实践中,当训练集大小增长时,我们通常会使用一个更大的模型,但这并非是必须的。达到收敛所需的更新次数通常会随训练集规模增大而增加。然而,当 m 趋向于无穷大时,该模型最终会在随机梯度下降抽样完训练集上的所有样本之前收敛到可能的最优测试误差。继续增加 m 不会延长达到模型可能的最优测试误差的时间。从这点来看,我们可以认为用 SGD 训练模型的渐近代价是关于 m 的函数的 O(1) 级别。

8 训练

8.1 不使用随机梯度下降

现在我们已经准备好了模型训练所有需要的要素,可以实现主要的训练过程部分了。理解这段代码⾄关重要,因为从事深度学习后,你会⼀遍⼜⼀遍地看到⼏乎相同的训练过程。我们首先初始化模型参数和学习率。在每次迭代中,我们读取训练样本,并通过我们的模型(假设函数)来获得⼀组预测。之后计算每个参数的梯度,进而使用梯度下降算法更新模型参数。在每次迭代后,我们都会计算并保存损失值,方便后续查看训练效果。

parameters = {'theta0': np.random.random(), 
     'theta1': np.random.random()}
learning_rate = 0.000001
losses = []

for i in range(10):
 parameters = gradient(x, y, parameters, 
       learning_rate)
 losses.append(loss_fun(x, y, parameters))
plt.plot(losses);

每次迭代的损失值的可视化结果如下,在第二次迭代后损失值就已经降到了一个很低的程度。

最终的线性回归可视化结果如下:

plt.scatter(x, y, s=2)
plt.plot(x, predict_fun(x, parameters), 'r-');

8.2 使用随机梯度下降

目前深度学习使用的训练数据越来越多,使用随机梯度下降几乎是一个必然的选择,因此理解下面的代码对以后的学习十分重要。

首先还是初始化模型参数和学习率。

但在开始训练之前,我们还定义了两个参数:sample_num,表示随机梯度下降每次抽取的样本个数;epoch_size,表示所有的数据送入网络中, 完成了一次参数更新的过程。使用epoch_size是因为将所有数据迭代训练一次是不够的, 需要反复多次才能拟合、收敛。

之后便开始第一个epoch的训练,根据sample_num的大小确定需要的batch的数量。batch指SGD抽取的样本,需要注意的是,由于我们使用的是时间序列数据,因此不能连续的抽取数据,如先抽取前sample_num个样本作为第一个batch,再抽取后面的sample_num个样本作为第二个batch

后面的过程与不使用SGD一样,先使用小批量训练样本通过我们的模型来获得⼀组预测。之后计算每个参数的梯度,进而使用梯度下降算法更新模型参数。

parameters = {'theta0'0'theta1'1}
learning_rate = 0.000001
losses = []

sample_num = 365
epoch_size = 2

for epoch in range(epoch_size):
    for i in range(x.size//sample_num+1):
        random_samples = np.random.choice(x.size, 
                                          sample_num)
        parameters = gradient(x[random_samples], 
                              y[random_samples], 
                              parameters, 
                              learning_rate)
        losses.append(loss_fun(x[random_samples], 
                               y[random_samples], 
                               parameters))
plt.plot(losses);
plt.scatter(x, y, s=2)
plt.plot(x, predict_fun(x, parameters), 'r-');

参考书目:

动⼿学深度学习,https://github.com/d2l-ai/d2l-zh。 深度学习,https://github.com/exacity/deeplearningbook-chinese。

分类:

后端

标签:

Python

作者介绍

碧海苍梧
V1