文章目录
- 引言
- 一、损失函数
- 1.回归问题损失函数
- 1.1 均方误差—MSE(L2损失)
- 1.2 均方根误差—RMSE
- 1.3 平均绝对值误差—MAE(L1损失)
- 1.4 Huber损失函数—平滑的平均绝对误差
- 1.5 Log-Cosh损失
- 1.6 分位数损失函数
- 2. 分类问题中的损失函数
- 2.1 对数损失函数
- 2.2 交叉熵损失函数
- 二、凸优化
- 1.什么是凸函数?
- 2. 凸函数有什么性质?
- 三、经典优化算法
- 1.梯度下降法
- 2.牛顿法
- 3.随机梯度下降法—SGD
- 4. 小批量梯度下降法
- 四.随机梯度下降法的加速
- 1. 随机梯度下降法偶尔会失效,无法给出满意的训练结果,这是为什么?
- 2.动量(Momentum)方法
- 方法
- 4. Adam方法
插眼:
- 百面机器学习—1.特征工程
- 百面机器学习—2. 特征工程与模型评估要点总结
- 百面机器学习—3.逻辑回归与决策树要点总结
- 百面机器学习—模型基础知识
- 百面机器学习—要点总结
- 百面机器学习—与LDA要点总结
- 百面机器学习—均值算法、EM算法与高斯混合模型要点总结
- 百面机器学习—8.概率图模型之HMM模型
- 百面机器学习—9.前馈神经网络面试问题总结
- 百面机器学习—10.循环神经网络面试问题总结
- 百面机器学习—11.集成学习(GBDT、XGBoost)面试问题总结
- 百面机器学习—12.优化算法
引言
机器学习算法=模型表征+模型评估+优化算法,优化算法所做的事就是在模型表征空间中找到模型评估指标最好的模型。
一、损失函数
损失函数定义了模型评估指标,不同的损失函数优化难度不同,同时,针对不同的问题也需要选择合适的损失函数。在实际应用中,选取损失函数会受到诸多因素的制约,比如是否有异常值、机器学习算法的选择、梯度下降的时间复杂度、求导的难易程度以及预测值的置信度等等。因此,不存在一种损失函数适用于处理所有类型的数据。
1.回归问题损失函数
这一部分参考机器学习大牛最常用的5个回归损失函数,你知道几个?。这一部分掌握MSE、MAE、Huber损失函数就可以,后面两种略作了解。它们三者的关系是MAE相对于MSE对异常点更鲁棒,但是MAE在转折点出不可导。综合考虑可导性与对异常点的鲁邦性,可以采用Huber损失函数。这个损失函数在残差较小时为平方损失,其余情况为线性损失,处处可导,并且对异常点鲁棒。
1.1 均方误差—MSE(L2损失)
均方误差是预测值与真实值之差平方的期望值,是最常用的回归损失函数
M
S
E
=
1
n
∑
i
=
1
n
(
y
i
−
y
i
p
)
2
MSE=\frac{1}{n}\sum_{i=1}^{n}(y_i-y_{i}^{p})^2
MSE=n1i=1∑n(yi−yip)2
1.2 均方根误差—RMSE
均方根误差是均方误差的算术平方根
R
M
S
E
=
1
n
∑
i
=
1
n
(
y
i
−
y
i
p
)
2
RMSE=\sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i-y_{i}^{p})^2}
RMSE=n1i=1∑n(yi−yip)2
1.3 平均绝对值误差—MAE(L1损失)
MAE是目标值和预测值之差的绝对值之和。其只衡量了预测值误差的平均模长,而不考虑方向。MAE对异常点更鲁棒一点,但是在转折点出无法求导数。
M
A
E
=
1
n
∑
i
=
1
n
∣
y
i
−
y
i
p
∣
MAE=\frac{1}{n}\sum_{i=1}^{n}|y_i-y_{i}^{p}|
MAE=n1i=1∑n∣yi−yip∣
1.4 Huber损失函数—平滑的平均绝对误差
在引出Huber损失之前,我们先比较一下MSE与MAE对于异常值的处理。MAE相对于MSE来说,对异常点有更好的鲁棒性。MSE对误差取了平方,MAE对误差取了绝对值,如果数据存在异常点的话,平方造成的影响远大于绝对值。然而MAE存在一个严重的问题(特别是对于神经网络),更新的梯度始终相同,也就是说,即使对于很小的损失值,梯度也很大。这样不利于模型的学习。总而言之,处理异常点时,L1损失函数更稳定,但它的导数不连续,因此求解效率较低。L2损失函数对异常点更敏感,但通过令其导数为0,可以得到更稳定的封闭解。但是在某些情况下,上述两种损失函数都不能满足需求。
例如,若数据中90%的样本对应的目标值为150,剩下10%在0到30之间。那么使用MAE作为损失函数的模型可能会忽视10%的异常点,而对所有样本的预测值都为150。这是因为模型会按中位数来预测。而使用MSE的模型则会给出很多介于0到30的预测值,因为模型会向异常点偏移。上述两种结果在许多商业场景中都是不可取的。
这是就用到了Huber损失函数。
Huber损失对数据中的异常点没有平方误差损失那么敏感。它在0也可微分。本质上,Huber损失是绝对误差,只是在误差很小时,就变为平方误差。误差降到多小时变为二次误差由超参数δ(delta)
来控制。当Huber损失在[0-δ,0+δ]
之间时,等价为MSE,而在[-∞,δ]和[δ,+∞]
时为MAE。
这里超参数delta的选择非常重要,因为这决定了你对与异常点的定义。当残差大于delta,应当采用L1(对较大的异常值不那么敏感)来最小化,而残差小于超参数,则用L2来最小化。
为何要使用Huber损失?
使用MAE训练神经网络最大的一个问题就是不变的大梯度,这可能导致在使用梯度下降快要结束时,错过了最小点。而对于MSE,梯度会随着损失的减小而减小,使结果更加精确。在这种情况下,Huber损失就非常有用。它会由于梯度的减小而落在最小值附近。比起MSE,它对异常点更加鲁棒。因此,Huber损失结合了MSE和MAE的优点。但是,Huber损失的问题是我们可能需要不断调整超参数delta。
1.5 Log-Cosh损失
Log-cosh是另一种应用于回归问题中的,且比L2更平滑的的损失函数。它的计算方式是预测误差的双曲余弦的对数。
优点:对于较小的
x
x
x,
l
o
g
(
c
o
s
h
(
x
)
)
log(cosh(x))
log(cosh(x))近似等于
x
2
2
\frac{x^2}{2}
2x2,对于较大的
x
x
x,近似等于
a
b
s
(
x
)
−
l
o
g
(
2
)
abs(x)-log(2)
abs(x)−log(2)。这意味着logcosh
基本类似于均方误差,但不易受到异常点的影响。它具有Huber损失所有的优点,但不同于Huber损失的是,Log-cosh二阶处处可微。但Log-cosh损失也并非完美,其仍存在某些问题。比如误差很大的话,一阶梯度和Hessian会变成定值,这就导致XGBoost出现缺少分裂点的情况。
1.6 分位数损失函数
当我们更关注区间预测而不仅是点预测时,分位数损失函数就很有用。如何选取合适的分位值取决于我们对正误差和反误差的重视程度。损失函数通过分位值(γ)对高估和低估给予不同的惩罚。例如,当分位数损失函数γ=0.25时,对高估的惩罚更大,使得预测值略低于中值。
γ是所需的分位数,其值介于0和1之间。
这个损失函数也可以在神经网络或基于树的模型中计算预测区间。
2. 分类问题中的损失函数
2.1 对数损失函数
二分类问题中常用对数损失函数
2.2 交叉熵损失函数
二分类问题的交叉熵损失函数,y∈0,1时
L
(
y
,
y
^
)
=
−
y
l
o
g
(
y
^
)
−
(
1
−
y
)
l
o
g
(
1
−
y
^
)
L(y,\hat{y})= -ylog(\hat{y})-(1-y)log(1-\hat{y})
L(y,y^)=−ylog(y^)−(1−y)log(1−y^)
多分类问题的交叉熵损失函数为
L
(
y
,
y
^
)
=
−
∑
k
=
1
K
y
k
l
o
g
p
k
(
x
)
L(y,\hat{y})=-\sum_{k=1}^{K}y_klogp_k(x)
L(y,y^)=−k=1∑Kyklogpk(x)
二、凸优化
1.什么是凸函数?
凸函数的严格定义为:
或者用我们在高中中学过的概念:二阶导数大于等于0
2. 凸函数有什么性质?
对于凸优化问题,所有的局部极小值都是全局极小值,因此这类问题一般认为是比较容易求解的问题。
三、经典优化算法
1.梯度下降法
梯度下降算法是迭代更新参数,以修正对最优解的估计的方法。梯度下降法采用所有训练数据的平均损失来近似目标函数,在对模型参数进行更新时,需要遍历所有训练数据,当数据很大时,需要很大的计算量,耗费很长时间,在实际中基本不可行。
其中,M是训练样本个数,因此梯度下降的迭代公式为:
其中
α
\alpha
α为学习率
梯度下降法的python实现为:
# 以(x - 2.5) ** 2 - 1.为例
import numpy as np
import matplotlib.pyplot as plt
def J(x):
return (x - 2.5) ** 2 - 1.
def dJ(x):
return 2 * (x - 2.5)
def gradient_descent(initial_theta, eta, epsilon=1e-8):
theta = initial_theta
history_theta.append(theta)
while True:
gradient = dJ(theta)
last_theta = theta
theta = theta - eta * gradient
history_theta.append(theta)
# 每步最小移动,小于这个说明达到收敛
if abs(J(last_theta) - J(theta)) < epsilon:
break
def print_history():
plt.plot(plot_x, J(plot_x))
plt.plot(np.array(history_theta), J(np.array(history_theta)), color='r', marker='+')
plt.show()
plot_x = np.linspace(-1., 6, 141)
plot_y = (plot_x - 2.5) ** 2 - 1.
history_theta = []
eta = 0.01
gradient_descent(0, eta)
print_history()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
2.牛顿法
梯度下降法运用的是目标函数的一阶信息,牛顿法运用的是目标函数的二阶信息。
假设当前对最优解的估计值为
θ
t
\theta_{t}
θt,希望求解优化问题
来得到更好的估计值
θ
t
+
1
=
θ
t
+
δ
t
\theta_{t+1}=\theta_{t}+\delta_{t}
θt+1=θt+δt。对函数
L
(
θ
t
+
δ
)
L(\theta_{t}+\delta)
L(θt+δ)做二阶泰勒展开得到近似式
其中
▽
2
L
(
θ
t
)
\bigtriangledown^2{L(\theta_{t}})
▽2L(θt)是二阶导数信息
牛顿法的迭代公式为:
θ
t
+
1
=
θ
t
−
▽
L
(
θ
t
)
▽
2
L
(
θ
t
)
\theta_{t+1} = \theta_{t}-\frac{\bigtriangledown{L(\theta_{t}})}{\bigtriangledown^2{L(\theta_{t}})}
θt+1=θt−▽2L(θt)▽L(θt)
牛顿法的收敛速度一般要快于梯度下降法,但是在高维的情况下, ▽ 2 L ( θ t ) \bigtriangledown^2{L(\theta_{t}}) ▽2L(θt)求逆的计算复杂度很大,而且当目标函数非凸时,二阶法有可能会收敛到鞍点。
牛顿迭代法python代码实现:
import numpy as np
import matplotlib.pyplot as plt
def J(x):
return (x - 2.5) ** 2 - 1.
def dJ_d2J(x):
return (x - 2.5)
def gradient_descent(initial_theta, epsilon=1e-8):
theta = initial_theta
history_theta.append(theta)
while True:
gradient = dJ_d2J(theta)
last_theta = theta
theta = theta - gradient
history_theta.append(theta)
# 每步最小移动,小于这个说明达到收敛
if abs(J(last_theta) - J(theta)) < epsilon:
break
def print_history():
plt.plot(plot_x, J(plot_x))
plt.plot(np.array(history_theta), J(np.array(history_theta)), color='r', marker='+')
plt.show()
plot_x = np.linspace(-1., 6, 141)
plot_y = (plot_x - 2.5) ** 2 - 1.
history_theta = []
gradient_descent(0)
print_history()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
3.随机梯度下降法—SGD
梯度下降算法中,在每次迭代时,需要使用所有训练数据,这非常不适用于大规模数据的优化问题。为了解决该问题,随机梯度下降法用单个训练样本的损失来近似平均损失,因此,随机梯度下降法用单个训练数据即可对模型参数进行一次更新,大大加快收敛效率。
因此随机梯度下降的迭代公式为:
其中
α
\alpha
α为学习率
随机梯度下降法的python实现:
import numpy as np
def dJ_sgd(theta, X_b_i, y_i):
return 2 * X_b_i.T.dot(X_b_i.dot(theta) - y_i)
def sgd(X_b, y, initial_theta, n_iters):
# 此处是为了让学习率越来越小,避免直接跳过最优解
# 这是由于SGD本身的特性决定的
# t0和t1是两个超参数,可自行调节
t0, t1 = 5, 50
def learning_rate(t):
return t0 / (t + t1)
theta = initial_theta
for cur_iter in range(n_iters):
# 随机取一个数据
rand_i = np.random.randint(len(X_b))
gradient = dJ_sgd(theta, X_b[rand_i], y[rand_i])
# 我们选用的是当前步数的倒数作为学习率,这样就实现了学习率越来越小
# 但是为了防止一开始学习率过大,我们分子和分母各增加参数进行调节
theta = theta - learning_rate(cur_iter) * gradient
return theta
m = 100000
x = np.random.normal(size=m)
X = x.reshape(-1, 1)
y = 4. * x + 3 + np.random.normal(0, 3, size=m)
X_b = np.hstack([np.ones((len(X), 1)), X])
initial_theta = np.zeros(X_b.shape[1])
theta = sgd(X_b, y, initial_theta, n_iters=m//3)
print(theta)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
4. 小批量梯度下降法
为了降低随机梯度的方差,从而使得迭代算法更加稳定,也为了充分利用高度优化的矩阵运算操作,在实际应用中我们会同时处理若干训练数据,该方法被称为小批量梯度下降法 ( Mini-Batch Gradient Descent)。
假设需要同时处理m个训练数据
(
x
i
1
,
y
i
1
)
,
.
.
.
,
(
x
i
m
,
y
i
m
)
{(x_{i1},y_{i1}),...,(x_{im},y_{im})}
(xi1,yi1),...,(xim,yim),则目标函数及其梯度为:
对于小批量梯度下降法的使用,有以下三点需要注意的地方:
- 如何选取参数m?在不同的应用中,最优的m通常会不一样,需要通过调参选取。一般m取2的幂次时能充分利用矩阵运算操作,所以可以在2的幂次中挑选最优的取值,例如32、64、128、256等。
- 如何挑选m个训练数据?为了避免数据的特定顺序给算法收敛带来的影响,一般会在每次遍历训练数据之前,先对所有的数据进行随机排序,然后在每次迭代时按顺序挑选m个训练数据直至遍历完所有的数据。
- 如何选取学习速率 α α α?为了加快收敛速率,同时提高求解精度,通常会采用衰减学习速率的方案:一开始算法采用较大的学习速率,当误差曲线进入平台期后,减小学习速率做更精细的调整。最优的学习速率方案也通常需要调参才能得到。
综上,通常采用小批量梯度下降法解决训练数据量过大的问题。每次更新模型参数时,只需要处理m个训练数据即可,其中m是一个远小于总数据量M的常数,这样能够大大加快训练过程。
import numpy as np
def dJ(theta, X_b, y):
res = np.empty(len(theta))
res[0] = np.sum(X_b.dot(theta) - y)
for i in range(1, len(theta)):
res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
return res * 2 / len(X_b)
def mini_batch_gredient_descent(X_b, y, initial_theta, n_iters=1e4, batch_size=10):
t0, t1 = 5, 50
def learning_rate(t):
return t0 / (t + t1)
theta = initial_theta
# 构造一个索引并打乱
indices = np.arange(X_b.shape[0])
np.random.shuffle(indices)
cur_iter = 1
for idx in range(0, len(indices) - batch_size + 1, batch_size):
# 按照batch_size 取索引
mini_batch_index = indices[idx: idx + batch_size]
mini_X_b = X_b[mini_batch_index]
mini_y = y[mini_batch_index]
gradient = dJ(theta, mini_X_b, mini_y)
theta = theta - learning_rate(cur_iter) * gradient
if cur_iter > n_iters:
break
cur_iter += 1
return theta
m = 100000
x = np.random.normal(size=m)
X = x.reshape(-1, 1)
y = 4. * x + 3 + np.random.normal(0, 3, size=m)
X_b = np.hstack([np.ones((len(X), 1)), X])
initial_theta = np.zeros(X_b.shape[1])
theta = mini_batch_gredient_descent(X_b, y, initial_theta, n_iters=m // 3)
print(theta)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
四.随机梯度下降法的加速
随机梯度下降法本质上是采用迭代方式更新参数,每次迭代在当前位置的基础上,沿着某一方向迈一小步抵达下一位置,然后在下一位置重复上述步骤。随机梯度下降法的更新公式表示为:
其中,当前估计的负梯度
−
g
t
-g_t
−gt,表示步子的方向,学习速率
η
\eta
η控制步幅。改造的随机梯度下降法仍然基于这个更新公式。改造的方式是加入惯性保持和环境感知。
1. 随机梯度下降法偶尔会失效,无法给出满意的训练结果,这是为什么?
随机梯度下降法放弃了对梯度准确性的追求,每步仅仅随机采样一个(或少量)样本来估计当前梯度,计算速度快,内存开销小。但由于每步接受的信息量有限,随机梯度下降法对梯度的估计常常出现偏差,造成目标函数曲线收敛得很不稳定,伴有剧烈波动,有时甚至出现不收敛的情况。梯度接近或变成零可能是由于当前解在局部最优解附近造成的。事实上,另一种可能性是当前解在鞍点(saddle point)附近。
2.动量(Momentum)方法
动量方法是为了解决随机梯度下降法山谷震荡和鞍点停滞的问题。
随机梯度下降法可以理解为:
纸团在山谷和鞍点处的运动轨迹,在山谷中纸团受重力作用沿山道滚下,两边是不规则的山壁,纸团不可避免地撞在山壁,由于质量小受山壁弹力的干扰大,从一侧山壁反弹回来撞向另一侧山壁,结果来回震荡地滚下;如果当纸团来到鞍点的一片平坦之地时,还是由于质量小,速度很快减为零。
动量(Momentum)方法可以理解为:
一个铁球,当沿山谷滚下时,不容易受到途中旁力的干扰,轨迹会更稳更直;当来到鞍点中心处,在惯性作用下继续前行,从而有机会冲出这片平坦的陷阱。
动量方法模型参数的迭代公式为:
v
t
=
γ
v
t
−
1
+
η
g
t
v_t=\gamma{v_{t-1}}+\eta{g_t}
vt=γvt−1+ηgt
θ
t
+
1
=
θ
t
−
v
t
\theta_{t+1}=\theta_{t}-v_t
θt+1=θt−vt
− v t -v_t −vt有两部分组成, v t v_t vt直接依赖于 v t − 1 v_{t-1} vt−1和 g t g_t gt,而不仅仅是 g t g_t gt。衰减系数 γ \gamma γ扮演阻力作用
- 一部分是学习速率 η \eta η乘以当前估计梯度 g t g_t gt
- 另一部分是带衰减的前一次步伐 v t − 1 v_{t-1} vt−1
沿山谷滚下的铁球,会受到沿坡道向下的力和与左右山壁碰撞的弹力。向下的力稳定不变,产生的动量不断累积,速度越来越快;左右的弹力总是在不停切换,动量累积的结果是相互抵消,自然减弱了球的来回震荡。因此,与随机梯度下降法相比,动量方法的收敛速度更快,收敛曲线也更稳定
方法
AdaGrad算法,根据自变量在每个维度的梯度值的大小来调整各个维度上的学习率,从而避免统一的学习率难以适应所有维度的问题。
AdaGrad方法采用“历史梯度平方和”来衡量不同参数的梯度的稀疏性,取值越小表明越稀疏,AdaGrad方法模型参数的迭代公式表示为
AdaGrad方法能实现:当梯度很大时,学习率很小,不至于不收敛;当梯度很小时,学习率很大,加快训练过程。大与小是相对的,总体上,学习率是逐渐减小的。
4. Adam方法
Adam方法将惯性保持和环境感知这两个优点集于一身。一方面,Adam记录梯度的一阶矩( first moment ) ,即过往梯度与当前梯度的平均,这体现了惯性保持;另一方面,Adam还记录梯度的二阶矩( second moment),即过往梯度平方与当前梯度平方的平均,这类似AdaGrad方法,体现了环境感知能力,为不同参数产生自适应的学习速率。一阶矩和二阶矩采用类似于滑动窗口内求平均的思想进行融合,即当前梯度和近一段时间内梯度的平均值,时间久远的梯度对当前平均值的贡献呈指数衰减。具体来说,一阶矩和二阶矩采用指数衰退平均( exponential decay average)技术,计算公式为:
对于任意时间步t,我们可以将
v
t
v_t
vt,再除以
1
−
β
1
t
1-\beta_{1}^{t}
1−β1t,从而使过去各时间步小批量随机梯度权值之和为1。这也叫作偏差修正。在Adam算法中,我们对变量
v
t
v_t
vt和
s
t
s_t
st均作偏差修正:
如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!