算法动态图
㈠ 机器学习中有哪些重要的优化算法
梯度下降是非常常用的优化算法。作为机器学习的基础知识,这是一个必须要掌握的算法。借助本文,让我们来一起详细了解一下这个算法。
前言
本文的代码可以到我的Github上获取:
https://github.com/paulQuei/gradient_descent
本文的算法示例通过Python语言实现,在实现中使用到了numpy和matplotlib。如果你不熟悉这两个工具,请自行在网上搜索教程。
关于优化
大多数学习算法都涉及某种形式的优化。优化指的是改变x以最小化或者最大化某个函数的任务。
我们通常以最小化指代大多数最优化问题。最大化可经由最小化来实现。
我们把要最小化或最大化的函数成为目标函数(objective function)或准则(criterion)。
我们通常使用一个上标*表示最小化或最大化函数的x值,记做这样:
[x^* = arg; min; f(x)]
优化本身是一个非常大的话题。如果有兴趣,可以通过《数值优化》和《运筹学》的书籍进行学习。
模型与假设函数
所有的模型都是错误的,但其中有些是有用的。– George Edward Pelham Box
模型是我们对要分析的数据的一种假设,它是为解决某个具体问题从老洞数据中学习到的,因此它是机器学习最核心的概念。
针对一个问题,通常有大量的模型可以选择。
本文不会深入讨论这方面的内容,关于各种模型请参阅机器学习的相关书籍。本文仅以最简单的线性模型为基础来讨论梯度下降算法。
这里我们先介绍一下在监督学习(supervised learning)中常见的三个符号:
m,描述训练样本的数量
x,描述输入变量或特征
y,描述输出变量或者叫目标值
- 请注意,一个样本笑或可能有很多的特征,因此x和y通常是一个向量。不过在刚开始学习的时候,为了便于理解,你可以暂时理解为这就是一个具体的数值。
- 代价函数也叫损失函数。
- 不同的模型可能会用不同的损失函数。例如,logistic回归的假设函数是这样的:。其代价函数是这样的:
对于一个函数,怎么确定下行的方向?
每一步该往前走多远?
有没有可能停留在半山腰的平台上?
- 这里的下标i表示第i个参数。 上标k指的是第k步的计算结果,而非k次方。在能够理解的基础上,下文的公式中将省略上标k。
收敛是指函数的变化率很小。具体选择多少合适需要根据具体的项目来确定。在演示项目中我们可以选择0.01或者0.001这样的值。不同的值将影响算法的迭代次数,因为在梯度下降的最后,我们会越来越接近平坦的地方,这个时候函数的变化率也越来越小。如果选择一个很小的值,将可能导致算法迭代次数暴增。
公式中的 称作步长,也称作学习率(learning rate)。它决定了每一步往前走多远,关于这个值我们会在下文中详细讲解。你可以暂时人为它是一个类似0.01或0.001的固定值。
在具体的项目,我们不会让算法无休止的运行下去,所以通常会设置一个迭代次数的最大上限。
我们随机选择了 都为10作为起点
设置最多迭代1000次
收敛的范围设为0.001
学习步长设为0.01
如果样本数量较小(例如小于等于2000),选择BGD即可。
如果样本数量很大,选择 来进行MBGD,例如:64,128,256,512。
- 《深度学习》一书中是这样描述的:“与其说是科学,这更像是一门艺术,我们应该谨慎地参考关于这个问题的大部分指导。”。
对于凸函数或者凹函数来说,不存在局部极值的问题。其局部极值一定是全局极值。
最近的一些研究表明,某些局部极值并没有想象中的那么糟糕,它们已经非常的接近全局极值所带来的结果了。
Wikipeida: Gradient descent
Sebastian Ruder: An overview of gradient descent optimization algorithms
吴恩达:机器学习
吴恩达:深度学习
Peter Flach:机器学习
李宏毅 - ML Lecture 3-1: Gradient Descent
PDF: 李宏毅 - Gradient Descent
Intro to optimization in deep learning: Gradient Descent
Intro to optimization in deep learning: Momentum, RMSProp and Adam
Stochastic Gradient Descent – Mini-batch and more
刘建平Pinard - 梯度下降(Gradient Descent)小结
多元函数的偏导数、方向导数、梯度以及微分之间的关系思考
[Machine Learning] 梯度下降法的三种形式BGD、SGD以及MBGD
- 作者:阿Paul https://paul.pub/gradient-descent/
训练集会包含很多的样本,我们用 表示其中第i个样本。
x是数据样本的特征,y是其目标值。例如,在预测房价的模型中,x是房子的各种信息,例如:面积,楼层,位置等等,y是房子的价格。在图像识别的任务中,x是图形的所有像素点数据,y是图像中包含的目标对象。
我们是希望寻找一个函数,将x映射到y,这个函数要足够的好,以至于能够预测对应的y。由于历史原因,这个函数叫做假设函数(hypothesis function)。
学习的过程如下图所示。即:首先根据已有的数据(称之为训练集)训练我们的算法模型,然后根据模型的假设函数来进行新数据的预测。
线性模型(linear model)正如其名称那样:是希望通过一个直线的形式来描述模式。线性模型的假设函数如下所示:
[h_{ heta}(x) = heta_{0} + heta_{1} * x]
这个公式对于大家来说应该都是非常简单的。如果把它绘制出来,其实就是一条直线。
下图是一个具体的例子,即: 的图形:
在实际的机器学习工程中碰含伍,你会拥有大量的数据。这些数据会来自于某个数据源。它们存储在csv文件中,或者以其他的形式打包。
但是本文作为演示使用,我们通过一些简单的代码自动生成了需要的数据。为了便于计算,演示的数据量也很小。
import numpy as np
max_x = 10
data_size = 10
theta_0 = 5
theta_1 = 2
def get_data:
x = np.linspace(1, max_x, data_size)
noise = np.random.normal(0, 0.2, len(x))
y = theta_0 + theta_1 * x + noise
return x, y
这段代码很简单,我们生成了x范围是 [1, 10] 整数的10条数据。对应的y是以线性模型的形式计算得到,其函数是:。现实中的数据常常受到各种因素的干扰,所以对于y我们故意加上了一些高斯噪声。因此最终的y值为比原先会有轻微的偏离。
最后我们的数据如下所示:
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y = [6.66, 9.11, 11.08, 12.67, 15.12, 16.76, 18.75, 21.35, 22.77, 24.56]
我们可以把这10条数据绘制出来这样就有一个直观的了解了,如下图所示:
虽然演示用的数据是我们通过公式计算得到的。但在实际的工程中,模型的参数是需要我们通过数据学习到的。所以下文我们假设我们不知道这里线性模式的两个参数是什么,而是通过算法的形式求得。
最后再跟已知的参数进行对比以验证我们的算法是否正确。
有了上面的数据,我们可以尝试画一条直线来描述我们的模型。
例如,像下面这样画一条水平的直线:
很显然,这条水平线离数据太远了,非常的不匹配。
那我们可以再画一条斜线。
我们初次画的斜线可能也不贴切,它可能像下面这样:
最后我们通过不断尝试,找到了最终最合适的那条,如下所示:
梯度下降算法的计算过程,就和这种本能式的试探是类似的,它就是不停的迭代,一步步的接近最终的结果。
代价函数
上面我们尝试了几次通过一条直线来拟合(fitting)已有的数据。
二维平面上的一条直线可以通过两个参数唯一的确定,两个参数的确定也即模型的确定。那如何描述模型与数据的拟合程度呢?答案就是代价函数。
代价函数(cost function)描述了学习到的模型与实际结果的偏差程度。以上面的三幅图为例,最后一幅图中的红线相比第一条水平的绿线,其偏离程度(代价)应该是更小的。
很显然,我们希望我们的假设函数与数据尽可能的贴近,也就是说:希望代价函数的结果尽可能的小。这就涉及到结果的优化,而梯度下降就是寻找最小值的方法之一。
对于每一个样本,假设函数会依据计算出一个估算值,我们常常用来表示。即 。
很自然的,我们会想到,通过下面这个公式来描述我们的模型与实际值的偏差程度:
[(h_ heta(x^i) - y^i)^2 = (widehat{y}^{i} - y^i)^2 = ( heta_{0} + heta_{1} * x^{i} - y^{i})^2]
请注意, 是实际数据的值, 是我们的模型的估算值。前者对应了上图中的离散点的y坐标,后者对应了离散点在直线上投影点的y坐标。
每一条数据都会存在一个偏差值,而代价函数就是对所有样本的偏差求平均值,其计算公式如下所示:
[L( heta) = frac {1}{m} sum_{i=1}^{m}(h_ heta(x^i) - y^i)^2 = frac {1}{m} sum_{i=1}^{m}( heta_{0} + heta_{1} * x^{i} - y^{i})^2]
当损失函数的结果越小,则意味着通过我们的假设函数估算出的结果与真实值越接近。这也就是为什么我们要最小化损失函数的原因。
借助上面这个公式,我们可以写一个函数来实现代价函数:
def cost_function(x, y, t0, t1):
cost_sum = 0
for i in range(len(x)):
cost_item = np.power(t0 + t1 * x[i] - y[i], 2)
cost_sum += cost_item
return cost_sum / len(x)
这个函数的代码应该不用多做解释,它就是根据上面的完成计算。
我们可以尝试选取不同的 和 组合来计算代价函数的值,然后将结果绘制出来:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
theta_0 = 5
theta_1 = 2
def draw_cost(x, y):
fig = plt.figure(figsize=(10, 8))
ax = fig.gca(projection='3d')
scatter_count = 100
radius = 1
t0_range = np.linspace(theta_0 - radius, theta_0 + radius, scatter_count)
t1_range = np.linspace(theta_1 - radius, theta_1 + radius, scatter_count)
cost = np.zeros((len(t0_range), len(t1_range)))
for a in range(len(t0_range)):
for b in range(len(t1_range)):
cost[a][b] = cost_function(x, y, t0_range[a], t1_range[b])
t0, t1 = np.meshgrid(t0_range, t1_range)
ax.set_xlabel('theta_0')
ax.set_ylabel('theta_1')
ax.plot_surface(t0, t1, cost, cmap=cm.hsv)
在这段代码中,我们对 和 各自指定了一个范围进行100次的采样,然后以不同的 组合对来计算代价函数的值。
如果我们将所有点的代价函数值绘制出来,其结果如下图所示:
从这个图形中我们可以看出,当 越接近 [5, 2]时其结果(偏差)越小。相反,离得越远,结果越大。
直观解释
从上面这幅图中我们可以看出,代价函数在不同的位置结果大小不同。
从三维的角度来看,这就和地面的高低起伏一样。最高的地方就好像是山顶。
而我们的目标就是:从任意一点作为起点,能够快速寻找到一条路径并以此到达图形最低点(代价值最小)的位置。
而梯度下降的算法过程就和我们从山顶想要快速下山的做法是一样的。
在生活中,我们很自然会想到沿着最陡峭的路往下行是下山速度最快的。如下面这幅图所示:
针对这幅图,细心的读者可能很快就会有很多的疑问,例如:
这些问题也就是本文接下来要讨论的内容。
算法描述
梯度下降算法最开始的一点就是需要确定下降的方向,即:梯度。
我们常常用 来表示梯度。
对于一个二维空间的曲线来说,梯度就是其切线的方向。如下图所示:
而对于更高维空间的函数来说,梯度由所有变量的偏导数决定。
其表达式如下所示:
[ abla f({ heta}) = ( frac{partial f({ heta})}{partial heta_1} , frac{partial f({ heta})}{partial heta_2} , ... , frac{partial f({ heta})}{partial heta_n} )]
在机器学习中,我们主要是用梯度下降算法来最小化代价函数,记做:
[ heta ^* = arg min L( heta)]
其中,L是代价函数,是参数。
梯度下降算法的主体逻辑很简单,就是沿着梯度的方向一直下降,直到参数收敛为止。
记做:
[ heta ^{k + 1}_i = heta^{k}_i - lambda abla f( heta^{k})]
这里有几点需要说明:
线性回归的梯度下降
有了上面的知识,我们可以回到线性模型代价函数的梯度下降算法实现了。
首先,根据代价函数我们可以得到梯度向量如下:
[ abla f({ heta}) = (frac{partial L( heta)}{ partial heta_{0}}, frac{ partial L( heta)}{ partial heta_{1}}) = (frac {2}{m} sum_{i=1}^{m}( heta_{0} + heta_{1} * x^{i} - y^{i}) , frac {2}{m} sum_{i=1}^{m}( heta_{0} + heta_{1} * x^{i} - y^{i}) x^{i})]
接着,将每个偏导数带入迭代的公式中,得到:
[ heta_{0} := heta_{0} - lambda frac{partial L( heta_{0})}{ partial heta_{0}} = heta_{0} - frac {2 lambda }{m} sum_{i=1}^{m}( heta_{0} + heta_{1} * x^{i} - y^{i}) heta_{1} := heta_{1} - lambda frac{partial L( heta_{1})}{ partial heta_{1}} = heta_{1} - frac {2 lambda }{m} sum_{i=1}^{m}( heta_{0} + heta_{1} * x^{i} - y^{i}) x^{i}]
由此就可以通过代码实现我们的梯度下降算法了,算法逻辑并不复杂:
learning_rate = 0.01
def gradient_descent(x, y):
t0 = 10
t1 = 10
delta = 0.001
for times in range(1000):
sum1 = 0
sum2 = 0
for i in range(len(x)):
sum1 += (t0 + t1 * x[i] - y[i])
sum2 += (t0 + t1 * x[i] - y[i]) * x[i]
t0_ = t0 - 2 * learning_rate * sum1 / len(x)
t1_ = t1 - 2 * learning_rate * sum2 / len(x)
print('Times: {}, gradient: [{}, {}]'.format(times, t0_, t1_))
if (abs(t0 - t0_) < delta and abs(t1 - t1_) < delta):
print('Gradient descent finish')
return t0_, t1_
t0 = t0_
t1 = t1_
print('Gradient descent too many times')
return t0, t1
这段代码说明如下:
如果我们将算法迭代过程中求得的线性模式绘制出来,可以得到下面这幅动态图:
最后算法得到的结果如下:
Times: 657, gradient: [5.196562662718697, 1.952931052920264]
Times: 658, gradient: [5.195558390180733, 1.9530753071808193]
Times: 659, gradient: [5.194558335124868, 1.9532189556399233]
Times: 660, gradient: [5.193562479839619, 1.9533620008416623]
Gradient descent finish
从输出中可以看出,算法迭代了660次就收敛了。这时的结果[5.193562479839619, 1.9533620008416623],这已经比较接近目标值 [5, 2]了。如果需要更高的精度,可以将delta的值调的更小,当然,此时会需要更多的迭代次数。
高维扩展
虽然我们举的例子是二维的,但是对于更高维的情况也是类似的。同样是根据迭代的公式进行运算即可:
[ heta_{i} = heta_{i} - lambda frac {partial L( heta)}{partial heta_i} = heta_{i} - frac{2lambda}{m} sum_{i=1}^{m}(h_ heta(x^{k})-y^k)x_i^k]
这里的下标i表示第i个参数,上标k表示第k个数据。
梯度下降家族BGD
在上面的内容中我们看到,算法的每一次迭代都需要把所有样本进行遍历处理。这种做法称为之Batch Gradient Descent,简称BGD。作为演示示例只有10条数据,这是没有问题的。
但在实际的项目中,数据集的数量可能是几百万几千万条,这时候每一步迭代的计算量就会非常的大了。
于是就有了下面两个变种。
SGD
Stochastic Gradient Descent,简称SGD,这种算法是每次从样本集中仅仅选择一个样本来进行计算。很显然,这样做算法在每一步的计算量一下就少了很多。
其算法公式如下:
[ heta_{i} = heta_{i} - lambda frac {partial L( heta)}{partial heta_i} = heta_{i} - lambda(h_ heta(x^k)-y^k)x_i^k]
当然,减少算法计算量也是有代价的,那就是:算法结果会强依赖于随机取到的数据情况,这可能会导致算法的最终结果不太令人满意。
MBGD
以上两种做法其实是两个极端,一个是每次用到了所有数据,另一个是每次只用一个数据。
我们自然就会想到两者取其中的方法:每次选择一小部分数据进行迭代。这样既避免了数据集过大导致每次迭代计算量过大的问题,也避免了单个数据对算法的影响。
这种算法称之为Mini-batch Gradient Descent,简称MBGD。
其算法公式如下:
[ heta_{i} = heta_{i} - lambda frac {partial L( heta)}{partial heta_i} = heta_{i} - frac{2lambda}{m} sum_{i=a}^{a + b}(h_ heta(x^k)-y^k)x_i^k]
当然,我们可以认为SGD是Mini-batch为1的特例。
针对上面提到的算法变种,该如何选择呢?
下面是Andrew Ng给出的建议:
下表是 Optimization for Deep Learning 中对三种算法的对比
方法准确性更新速度内存占用在线学习BGD好慢高否SGD好(with annealing)快低是MBGD好中等中等是
算法优化
式7是算法的基本形式,在这个基础上有很多人进行了更多的研究。接下来我们介绍几种梯度下降算法的优化方法。
Momentum
Momentum是动量的意思。这个算法的思想就是借助了动力学的模型:每次算法的迭代会使用到上一次的速度作为依据。
算法的公式如下:
[v^t = gamma v^{t - 1} + lambda abla f( heta) heta = heta - v_t]
对比式7可以看出,这个算法的主要区别就是引入了,并且,每个时刻的受前一个时刻的影响。
从形式上看,动量算法引入了变量 v 充当速度角色——它代表参数在参数空间移动的方向和速率。速度被设为负梯度的指数衰减平均。名称动量来自物理类比,根据牛顿运动定律,负梯度是移动参数空间中粒子的力。动量在物理学上定义为质量乘以速度。在动量学习算法中,我们假设是单位质量,因此速度向量 v 也可以看作是粒子的动量。
对于可以取值0,而是一个常量,设为0.9是一个比较好的选择。
下图是momentum算法的效果对比:
对原来的算法稍加修改就可以增加动量效果:
def gradient_descent_with_momentum(x, y):
t0 = 10
t1 = 10
delta = 0.001
v0 = 0
v1 = 0
gamma = 0.9
for times in range(1000):
sum1 = 0
sum2 = 0
for i in range(len(x)):
sum1 += (t0 + t1 * x[i] - y[i])
sum2 += (t0 + t1 * x[i] - y[i]) * x[i]
v0 = gamma * v0 + 2 * learning_rate * sum1 / len(x)
v1 = gamma * v1 + 2 * learning_rate * sum2 / len(x)
t0_ = t0 - v0
t1_ = t1 - v1
print('Times: {}, gradient: [{}, {}]'.format(times, t0_, t1_))
if (abs(t0 - t0_) < delta and abs(t1 - t1_) < delta):
print('Gradient descent finish')
return t0_, t1_
t0 = t0_
t1 = t1_
print('Gradient descent too many times')
return t0, t1
以下是该算法的输出:
Times: 125, gradient: [4.955453758569991, 2.000005017897775]
Times: 126, gradient: [4.955309381126545, 1.9956928964532015]
Times: 127, gradient: [4.9542964317327005, 1.9855674828684156]
Times: 128, gradient: [4.9536358220657, 1.9781180992510465]
Times: 129, gradient: [4.95412496254411, 1.9788858350530971]
Gradient descent finish
从结果可以看出,改进的算法只用了129次迭代就收敛了。速度比原来660次快了很多。
同样的,我们可以把算法计算的过程做成动态图:
对比原始的算法过程可以看出,改进算法最大的区别是:在寻找目标值时会在最终结果上下跳动,但是越往后跳动的幅度越小,这也就是动量所产生的效果。
Learning Rate 优化
至此,你可能还是好奇该如何设定学习率的值。
事实上,这个值的选取需要一定的经验或者反复尝试才能确定。
关键在于,这个值的选取不能过大也不能过小。
如果这个值过小,会导致每一次迭代的步长很小,其结果就是算法需要迭代非常多的次数。
那么,如果这个值过大会怎么样呢?其结果就是:算法可能在结果的周围来回震荡,却落不到目标的点上。下面这幅图描述了这个现象:
事实上,学习率的取值未必一定要是一个常数,关于这个值的设定有很多的研究。
下面是比较常见的一些改进算法。
AdaGrad
AdaGrad是Adaptive Gradient的简写,该算法会为每个参数设定不同的学习率。它使用历史梯度的平方和作为基础来进行计算。
其算法公式如下:
[ heta_i = heta_i - frac{lambda}{sqrt{G_t + epsilon}} abla f( heta)]
对比式7,这里的改动就在于分号下面的根号。
根号中有两个符号,第二个符号比较好理解,它就是为了避免除0而人为引入的一个很小的常数,例如可以设为:0.001。
第一个符号的表达式展开如下:
[G_t = sum_{i = 1}^{t} abla f( heta){i} abla f( heta){i}^{T}]
这个值其实是历史中每次梯度的平方的累加和。
AdaGrad算法能够在训练中自动的对learning rate进行调整,对于出现频率较低参数采用较大的学习率;相反,对于出现频率较高的参数采用较小的学习率。因此,Adagrad非常适合处理稀疏数据。
但该算法的缺点是它可能导致学习率非常小以至于算法收敛非常的慢。
关于这个算法的直观解释可以看李宏毅教授的视频课程:ML Lecture 3-1: Gradient Descent。
RMSProp
RMS是Root Mean Square的简写。RMSProp是AI教父Geoff Hinton提出的一种自适应学习率方法。AdaGrad会累加之前所有的梯度平方,而RMSProp仅仅是计算对应的平均值,因此可缓解Adagrad算法学习率下降较快的问题。
该算法的公式如下:
[E[ abla f( heta_{i})^2]^{t} = gamma E[ abla f( heta_{i})^2]^{t - 1} + (1-gamma)( abla f( heta_{i})^{t})^{2} heta_i = heta_i - frac{lambda}{sqrt{E[g^2]^{t+1} + epsilon}} abla f( heta_{i})]
类似的,是为了避免除0而引入。 是衰退参数,通常设为0.9。
这里的 是t时刻梯度平方的平均值。
Adam
Adam是Adaptive Moment Estimation的简写。它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。
Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
该算法公式如下:
[m^{t} = eta_{1} m^{t-1} + (1-eta_{1}) abla f( heta) v^{t} = eta_{2} v^{t-1} + (1-eta_{2}) abla f( heta)^2 widehat{m}^{t} = frac{m^{t}}{1 - eta^{t}_1} widehat{v}^{t} = frac{v^{t}}{1 - eta^{t}_2} heta = heta - frac{lambda}{sqrt{widehat{v}^{t}} + epsilon}widehat{m}^{t}]
,分别是对梯度的一阶矩估计和二阶矩估计。, 是对,的校正,这样可以近似为对期望的无偏估计。
Adam算法的提出者建议 默认值为0.9,默认值为0.999,默认值为 。
在实际应用中 ,Adam较为常用,它可以比较快地得到一个预估结果。
优化小结
这里我们列举了几种优化算法。它们很难说哪种最好,不同的算法适合于不同的场景。在实际的工程中,可能需要逐个尝试一下才能确定选择哪一个,这个过程也是目前现阶段AI项目要经历的工序之一。
实际上,该方面的研究远不止于此,如果有兴趣,可以继续阅读 《Sebastian Ruder: An overview of gradient descent optimization algorithms》 这篇论文或者 Optimization for Deep Learning 这个Slides进行更多的研究。
由于篇幅所限,这里不再继续展开了。
算法限制
梯度下降算法存在一定的限制。首先,它要求函数必须是可微分的,对于不可微的函数,无法使用这种方法。
除此之外,在某些情况下,使用梯度下降算法在接近极值点的时候可能收敛速度很慢,或者产生Z字形的震荡。这一点需要通过调整学习率来回避。
另外,梯度下降还会遇到下面两类问题。
局部最小值
局部最小值(Local Minima)指的是,我们找到的最小值仅仅是一个区域内的最小值,而并非全局的。由于算法的起点是随意取的,以下面这个图形为例,我们很容易落到局部最小值的点里面。
这就是好像你从上顶往下走,你第一次走到的平台未必是山脚,它有可能只是半山腰的一个平台的而已。
算法的起点决定了算法收敛的速度以及是否会落到局部最小值上。
坏消息是,目前似乎没有特别好的方法来确定选取那个点作为起点是比较好的,这就有一点看运气的成分了。多次尝试不同的随机点或许是一个比较好的方法,这也就是为什么做算法的优化这项工作是特别消耗时间的了。
但好消息是:
鞍点
除了Local Minima,在梯度下降的过程中,还有可能遇到另外一种情况,即:鞍点(Saddle Point)。鞍点指的是我们找到点某个点确实是梯度为0,但它却不是函数的极值,它的周围既有比它小的值,也有比它大的值。这就好像马鞍一样。
如下图所示:
多类随机函数表现出以下性质:在低维空间中,局部极值很普遍。但在高维空间中,局部极值比较少见,而鞍点则很常见。
不过对于鞍点,可以通过数学方法Hessian矩阵来确定。关于这点,这里就不再展开了,有兴趣的读者可以以这里提供的几个链接继续探索。
参考资料与推荐读物
㈡ 动态图上的异常检测文献综述(2015)
动态图上的异常检测任务包括:发现异常的对象、关系、时点。动态图上的异常检测与静态图上的异常检测不同的地方在于:
本文首先将异常类型分为:anomalous vertices, edges, subgraphs, and events(or change),将使用的方法分为:community detection, MDL(minimum description length) and compression, decompression, distance, probabilistic, 按每种方法使用的异常类型进行了文献学分类。各方法的主要参考文献见表1:
本文假设不同时点的节点和边都有唯一标签从而不会混淆,定义 为图序列,其中 为总时间步, , 为节点集, 为边集, 时称 为图流。本文的主要记号见表2:
给定 ,节点集 ,打分函数 ,定义异常节点集为 ,使得对于 , ,其中 为得分 的摘要式统计。
一个典型的异常节点如图1,其可由基于社区检测的方法识别,即: 其中 为节点所属的社会划分, 为异或操作。
给定 ,边集 ,打分函数 ,定义异常边集为 ,使得对于 , ,其中 为得分 的摘要式统计。
一个典型的异常边如图2,可令 ,其中 为时间步 时 的权重,可以为边的概率。
给定 ,子图集 ,打分函数 ,定义异常集为 ,使得对于 , ,其中 为得分 的摘要式统计。
两种典型的异常子图如图3,其中(a)为图的收缩,(b)为图的分裂。图的收缩可根据子图中的的数量衡量,即 ,图的分裂可由不同时间点社区的数量衡量。
与异常节点、边、子图检测不同,异常事件或异常突变检测检验的是时点。
给定 ,打分函数 ,若时点 满足: , ,则称时点 为一个事件。
给定 ,打分函数 ,若时点 满足: , ,则称时点 为一个突变。
通常的异常检测都使用两步法:第一步,基于特征的图表示;第二,基于机器学习的异常检测。
基于社区检测的方法关注的是社区和关联节点的演化过程,特征向量的生成亦基于图中的社区结构。不同社区检测方法的区别在于:(1)社区结构的领域,如社区内的连接性v.s.单个节点在每一步所属的社区;(2)社区结构的定义,如基于概率的软社区定义v.s.硬社区定义。基于社区检测的方法可用于异常定点、子图、突变的检测。
基于软社区匹配并单独考察每一个社区,我们可以在连续时间步内计算每个节点归属的平均变化,如果某个节点归属的平均变化显着异于其他节点,则称其为演化社区异常点。
节点社区归属的变化可以构造一个时间模式,称为软时序模式。一些文献使用了最小描述长度(MDL)结合非负矩阵分解的方法来自动检测节点角色及构造转移模型。多数文献通过抽取图中不同节点的共同模式,并比较每个节点与共同模式之间的差异来定义异常节点。部分文献使用了交替迭代优化替代常用的两步法。部分文献使用了corenet的概念,该概念不同于单纯使用density,molarity,hop-distance等概念,而是使用了节点间的加权路径,即一个节点的corenet包含该节点与权重大于给定阈值的两跳邻居。假设两个强连接的节点通常属于同一社区,则如果移除一个节点的两个邻居,一个邻域具有较高的边权重,另一个具有较低的边权重,则移除较高权重邻居的影响应更大,在每一步,每个节点首先被赋予一个异常得分,该得分衡量了其corenet的变化,异常得分较高的 各节点将被视为异常节点。
文献【69】定义了六种基于社区的异常:shrink, grow, merge, split, born, and vanish。其使用图和社区代表(representatives)进行比较以减少计算量,图代表为出现在t时刻,同时还出现在t-1、t+1或t+1与t-1时刻的节点集,社区代表是出现在其他社区最少的定点集合,基于社区代表和图代表,基于规则,判断社区是否落在六种异常中。
文献【73】定义了一种基于社区的异常:comet,周期性出现或消失的社区,演化图可表示为一个张量,然后基于低秩张量分解和MDL原则进行comet检测。
文献【3】基于多种信息源构造时序复网络,识别跨时间和网络的稳定社区结构。行为相似的网络可以用聚类或前验知识分组,如何一个社区结构在组内跨时间步稳定,但在组外没有对应社区,则该社区即为异常,如何两个社区共享一定比例的定点则称为对应。
社交网络可以根据特定时间窗口内的发文量定义事件,一个经历共同事件的组即构成一个异常子图。
通过划分图流为一致的分割来检测,分割是依据划分的相似性。
通过将最新图的顶点分区与当前增长分割中的图的分区进行比较,可以在线找到这些分割。【67】基于可返回随机的相关矩阵和molarity最大化来进行定点划分,当新图的划分与当前分割的划分有很大不同时,一个新段开始,并将新图的时间点输出为检测到的突变。两个划分的相似度使用Jaccard系数定义。GraphScope思路类似,但基于MDL来指导划分和分割。
基于MDL原则和基于该原则的压缩技术利用数据中的模式和规律性实现紧凑的图表示,其主要通过将图的邻接矩阵表示为一个二进制串,如果矩阵的行和列可以重新排列使矩阵的二进制字符串表示的熵最小化,那么压缩损失(也称为编码损失)就会最小化。数据指向的特征都来自于图或其特定子结构的编码代价;因此,异常被定义为抑制可压缩性的图或子结构(如边)
对于一条边和对应子图,如果包含该边的编码损失比不包含该边的编码损失高,则称该边为异常边。
【74】使用了一种两步交替迭代法进行节点的自动划分,当节点划分的熵收敛时,根据包含和不包含该边的编码损失,该方法也给出了边的异常度得分。
突变检测的主要思路是:连续时间步间的图是相似的,因而可以分为一组,从而降低压缩比。压缩比的上升表明新一个时间步的图与已有的图差异明显,因此是一个突变。
该方法将图集合表示为一个tensor,在该tensor上进行矩阵分解或降维,基于分解或降维后的图发现其模式和规律性,该方法可以融合更多属性信息,最常用的方法是SVD和PARAFAC(广义SVD)。
矩阵分解可用于计算每个节点的活跃(activity)向量,如果某个节点的活跃向量在连续时间步间变化明显,则称为异常节点。
【87】首先抽取每个节点的边相关矩阵 ,即该节点的每个邻域都有一行一列,对于节点 的矩阵中的一个entry 代表了边 和 间加权频率的相关性,加权频率由衰减函数获得,时间越近权重越高。M的最大特征值和对应特征向量即顶点的活跃向量的summary及边的相关性。通过寻找这些值的变化而形成的时间序列用于计算每个时间步长中每个顶点的分数,得分高于阈值的顶点将被输出为异常。
基于分解的异常事件检测有两种方法:(1)先基于分解方法来近似原始数据,然后以重建损失作为近似优劣的指标。如果某个子张量、切片或元素的重建损失很高,则即可以视其与周围数据不同特征不同,将其标记为异常事件、子图或节点。(2)跟踪奇异值和向量,以及特征值和特征向量,以检测异常顶点的显着变化。
为解决 intermediate blowup 问题(即计算中输入和输出张量超过内存限制),【81】提出了momery-efficient tucker(MET)分解方法,该方法源于Tucker分解,Tucker分解将高阶tensor用一个core tensor和每个mode(维度)矩阵表示。【80】使用了Compact Matrix Decomposition(CMD),其可以用来计算给定矩阵的稀疏低秩矩阵。使用CMD对图流中的每个邻接矩阵进行分解,可得到重建值的时间序列,基于重建值序列可进程事件检测,典型应用有COLIBRI, PARCUBE,其中后者在斑点(spotting)异常中的表现更高效。
【84】使用了随机图模型进行基于概率模型的检测,其将真实图邻接矩阵和期望图的邻接矩阵间的差异构造为残差矩阵,对残差矩阵执行SVD,再使用线性Ramp滤波器,基于top奇异值即可进行异常时间窗口检测,通过检查正确的奇异向量来确定相应的顶点。
除以上方法,我们还可以基于分解空间的显着变化来识别事件。【77】通过对数据执行PCA,计算的特征向量可以分为正常和异常两个集合,方法是检验数据中的值映射到特征向量。在每个时间步,根据特征值对特征向量进程降序排列,第一个特征向量则包含一个在其余值的3个标准差之外的投影点,此后的每个特征向量,都构成了异常集。第二步即是将数据映射到正常和异常子空间,一旦完成了这些操作,当从上一个时间步长到当前时间步异常成分的修改超过一个阈值时,即将其视为一个事件。【83】扩展了该方法,提出了联合稀疏PCA和图引导的联合稀疏PCA来定位异常和识别对应的顶点。通过为异常集使用稀疏的成分集,可以更容易识别负责的顶点。顶点根据它们在异常子空间中对应行的值得到一个异常分数,由于异常分量是稀疏的,不异常的顶点得分为0。
图的活跃向量 为主成分,左奇异向量对应最大奇异值,奇异值和奇异向量通过对加权邻接矩阵进行SVD得到。当活跃向量大幅异于“正常活跃"向量时,即定义该时点为突变点,”正常活跃“向量由前序向量得到。
正常活跃向量 ,它是对最后W时间步中活动向量形成的矩阵进行SVD得到的左奇异向量。每个时点都定义一个得分 ,其代表了当前活跃向量与正常向量的差异。异常可以使用动态阈值方案在线发现,其中得分高于阈值的时间点被输出为变化。通过计算正常向量和活动向量之间的变化比率来找到负责的顶点,与变化最大的索引所对应的顶点被标记为异常,类似的方法也可以用于节点-节点相关矩阵的活跃向量,或基于邻居相似度的节点-节点相关矩阵。
基于距离的异常检测算法的不同点在于选择用于提取和比较距离度量,以及它们用于确定异常值和相应图的方法。
如果一些边的属性演化异于正常演化,则该边就是一个异常边。
边之间的权重使用衰减函数定义,在每个时间步长中,根据相似度得分的变化之和计算每条边的异常值得分,使用阈值或简单的 作为异常值标准。
将网络视为边的流,意味着网络没有固定的拓扑,一个边的频率和持久性可以用来作为其新颖性的指标,【48】定义了集合系统不一致性指标来度量频率和持久性,当一条边到达时,计算其差异,并与活动边集的平均不一致性值进行比较,如果边的加权不一致性大于平均不一致性的阈值水平,则声明该边为异常边,基于异常边,可以进一步识别其他异常图元素(如顶点,边,子图)。
具有许多“异常”边的子图即是异常的子图。
【52】将边的权重视为异常得分,每个时间步长上的每条边都有它自己的异常分数,给定了该边权值在所有图序列的分布,该分数表示在该特定的边上看到该特定权值的概率函数。或者,为网络中的边分配异常值分数的现有方法的输出可以用作为该方法的输入。后一种方法允许应用于任何能够为边分配异常值分数的网络,一旦完成每条边的异常打分,即可发现显着异常的区域(SARs),即一个窗口内的固定子图,其类似于HDSs。【112】提出了一种迭代算法,该算法首先固定子图发现最优时间窗口,然后固定时间窗口发现最优子图。【97】拓展了该方法,允许子图渐变,即在相邻时间步间增加或移除顶点。
定义函数 为测度图距离的函数,将其应用于连续图序列,即得到距离序列,基于该距离序列应用一些启发式算法(如基于移动平均阈值的 取值)即可得到异常事件。
称每个顶点及其egonet的特征为局部特征,整张图的特征为全局特征。每个顶点的局部特征可聚合为一个向量,基于该向量的各阶矩可构造signature向量,利用signature向量间的Canberra距离(归一化的曼哈顿距离)可构造图之间的距离函数【93】。【92】利用全局特征,定义了一种基于dK-2序列的距离测度,将高于阈值的特征视为异常点。
【96】使用了顶点亲和度(即一个顶点对另一个顶点的影响,可以用于快速信念传播)得分作为signature向量,其基于连续时间步技术顶点亲和度,基于马氏距离度量两个图的相似度,亲和度得分的变化反应并适应变化的影响水平,例如桥边的移除比正常边移除的得分更高。利用单个移动范围的质量控制,可以对相似度得分的时间序列设置一个移动阈值,如指数移动平均加权。
作为特征相似度的补充,我们也可以比较两个图的结构差异来度量突变的大小,这类方法致力于发现定义距离的函数而非发现特征向量。【88】计算了异常网络的10种距离函数,使用ARMA模型构造特征值的正常模型,然后基于正常模型计算时点的残差,残差超过给定阈值的时间即可标记为异常。10种距离函数中,基于最大共有子图的方法表现最好。【90】使用了五中得分函数(顶点/边重叠,顶点排序,向量相似度,序列相似度,signature相似度)来检测三种异常(子图缺失,顶点缺失,连通性变化),表现最好的方案是抽取每个顶点和边的特征构造signature向量,使用SimHash定义距离。
我们还可以通过计算每个图的稳健性序列来检测事件,稳健性序列是图连通性的测度,具有高稳健性的图即使在去除一些顶点或边的情况下,也能保持相同的一般结构和连通性,事件检测即发现稳健性值异常变化的时点【95】。【89】使用的是图半径的变体作为稳健性指标,图半径的定义是基于所有顶点的平均离心度,而非常用的最大离心度。
基于概率理论、分布、扫描统计学等方法可以构造“正常”样本的模型,偏离该模型的样本即视为异常,这类方法的主要区别在于构造方法、建模对象、离群值定义。
主要有两种方法:一,构造扫描统计时间序列并检测离均值若干标准差的点;二,顶点分类。
扫描统计常称为滑动窗口分析,其在数据的特征区域中发现测度统计量的局部最小或最大值。对某个特定图,扫描统计量可以是图不变特征的最大值,如边的数量。
【8】使用了一个适应测度统计量的变量,即每个节点的0-2度邻居数,然后对每个顶点的局部统计量使用近期值的均值和标准差进行标准化,图的扫描统计量即最大的标准化局部统计量。标准化可以解释每个顶点的历史信息,代表每个顶点的统计量只与自己的历史信息有关而与其他顶点无关。这保证测度的最大变化与变化的绝对量无关而与比例有关。基于扫描统计量标准化时间序列,将序列均值的五个标准差作为异常值。最负责的顶点被确定为为整个图的扫描统计值所选择的顶点。
类似于使用邻居进行扫描统计,我们还可以用Markov随机场(MRF)来发现节点的状态,并通过信念传播算法推断最大似然分配,其中,每个顶点标签取决于其邻居节点。【99】通过发现二部核来检测异常点(即诈骗犯),二部核定义为诈骗犯与从犯间的交互。利用边的插入或删除只影响局部子图这一事实,它在添加新边时逐步更新模型。在传播矩阵中,一个顶点可以处于三种状态之一:欺诈者、共犯者或诚实者。
边异常检测通常使用计数过程建模,统计上显着异于该模型的边标记为异常边。
【50】用贝叶斯离散时间计数过程来建模顶点间的通信次数(边权重),并根据新图更新模型。基于学习到的计数的分布,对新观测的边进行预测 值计算,基于 值标记异常顶点对。
首先用固定的子图,多重图,累积图来构造预期行为的模型,对模型的偏离可作为子图异常检测的依据。
【104】结合扫描统计量和隐马尔可夫模型(HMM)建模边行为,其使用的局部扫描统计量是基于两种图形状:k-path图和星型图,其将滑动窗口的扫描统计数据与其过去的值进行比较,并使用在线阈值系统识别局部异常,局部异常是所有统计上显着的子图(代表k个路径或恒星)的并集。
另一个建模动态图的方法是基于多重图,其中平行边对应于两个连续时间步顶点间的通信,初始的多重图可分解为多个针对每个时间窗口的叠套子图(TSG),TSG满足两个条件:(1)对于任何两个有共同点的边,首先开始通信的边最后完成通信;(2)存在一个根顶点r,它没有传入的边,并且有一条到TSG中每个顶点的路径。出现概率低的TSG视为异常子图。【102】
累积图即为包含直到当前时点的所有边的图,边权重依据衰减函数定义,通过识别“持久模式”来定义子图的正常行为。该持久模型识别模型如下:首先构造一种图,该图每个边根据时间来加权,然后基于该图迭代抽取最重连接成分来发现。随着累积图的发展,提取的子图将被监控,并将其当前活动与基于最近行为的预期活动进行比较来进行子图异常检测。【101】
事件检测可以基于偏离图似然模型或特征值分布的偏差来进行。
【103】提出了一种新的蓄水池抽样方法来抽取图流的结构摘要,这种在线抽样方法维持多个网络划分以构造统计上显着的摘要,当一个新图进入图流,每个边都根据不同分区的边生成模型计算出一种似然性,然后以这些似然性的几何均值作为全局图似然性。
【98】使用了类似的边生成模型,每个边 的概率都存储在矩阵 中,概率基于期望最大化估计,基于所有收发对的分布,然后为每个收发对给出潜在得分,基于所有边似然得分的均值即得到每个图的得分。
【100】计算了特征值和压缩特征等式的分布(而非计算收发对的分布),基于每个顶点都存在一个顶点局部特征时间序列的假设,可在每个时间步构造一个顶点-顶点相关矩阵,通过保留最大特征值和一组低维矩阵(每个顶点对应一个矩阵),可对相关矩阵的特征方程进行压缩,通过学习特征值和矩阵的分布,即可发现异常顶点和事件。当特征值偏离期望分布时,即认为发生了事件,当顶点的矩阵偏离矩阵分布时,可认为该顶点为异常顶点。
㈢ 网页常见gif出处
GIF出自网,各种网络热门GIF图片出自、来源介绍,邪恶GIF,影视GIF,动态图出处,美女动态图大全。
在早期,GIF所用的LZW压缩算法,是Compuserv所开发的一种免费算法。然而令很多软件开发商感到意外的是,GIF文件所采用的压缩算法忽然成了Unisys公司的专利。
据Unisys公司称,他们已注册了LZW算法中的W部分。如果要开发生成(或显示)GIF文件的程序,则需向该公司支付版税。由此,人们开始寻求一种新技术,以减少开发成本。
PNG(Portable Network Graphics,便携网络图形)[2]标准就在这个背景下应运而生了。它一方面满足了市场对更少的法规限制的需要,另一方面也带来了更少的技术上的限制,如颜色的数量等。
㈣ 遗传算法的迭代次数是怎么确定的,与什么有关
1. 遗传算法简介
遗传算法是用于解决最优化问题的一种搜索算法,算法的整体思路是建立在达尔文生物进化论“优胜劣汰”规律的基础上。它将生物学中的基因编码、染色体交叉、基因变异以及自然选择等概念引入最优化问题的求解过程中,通过不断的“种群进化”,最终得到问题的最优解。
2. 遗传算法实现步骤
在讲下面几个基于生物学提出的概念之前,首先我们需要理解为什么需要在最优化问题的求解中引入生物学中的各种概念。
假设我们需要求一个函数的最大值,但这个函数异常复杂以至于无法套用一般化的公式,那么就会想到:如果可以将所有可能的解代入方程,那么函数最大值所对应的那个解就是问题的最优解。但是,对于较复杂的函数来说,其可能的解的个数的数量级是我们所无法想象的。因此,我们只好退而求其次,只代入部分解并在其中找到最优解。那么这样做的核心就在于如何设定算法确定部分解并去逼近函数的最优解或者较好的局部最优解。
遗传算法就是为了解决上述问题而诞生的。假设函数值所对应的所有解是一个容量超级大的种群,而种群中的个体就是一个个解,接下去遗传算法的工作就是让这个种群中的部分个体去不断繁衍,在繁衍的过程中一方面会发生染色体交叉而产生新的个体。另一方面,基因变异也会有概率会发生并产生新的个体。接下去,只需要通过自然选择的方式,淘汰质量差的个体,保留质量好的个体,并且让这个繁衍的过程持续下去,那么最后就有可能进化出最优或者较优的个体。这么看来原来最优化问题居然和遗传变异是相通的,而且大自然早已掌握了这样的机制,这着实令人兴奋。为了将这种机制引入最优化问题并利用计算机求解,我们需要将上述提到的生物学概念转化为计算机能够理解的算法机制。
下面介绍在计算机中这种遗传变异的机制是如何实现的:
基因编码与解码:
在生物学中,交叉与变异能够实现是得益于染色体上的基因,可以想象每个个体都是一串超级长的基因编码,当两个个体发生交叉时,两条基因编码就会发生交换,产生的新基因同时包含父亲和母亲的基因编码。在交叉过程中或者完成后,某些基因点位又会因为各种因素发生突变,由此产生新的基因编码。当然,发生交叉和变异之后的个体并不一定优于原个体,但这给了进化(产生更加优秀的个体)发生的可能。
因此,为了在计算机里实现交叉和变异,就需要对十进制的解进行编码。对于计算机来说其最底层的语言是由二进制0、1构成的,而0、1就能够被用来表示每个基因点位,大量的0、1就能够表示一串基因编码,因此我们可以用二进制对十进制数进行编码,即将十进制的数映射到二进制上。但是我们并不关心如何将十进制转换为二进制的数,因为计算机可以随机生成大量的二进制串,我们只需要将办法将二进制转化为十进制就可以了。
二进制转换为十进制实现方式:
假设,我们需要将二进制映射到以下范围:
首先,将二进制串展开并通过计算式转化为[0,1]范围内的数字:
将[0,1]范围内的数字映射到我们所需要的区间内:
交叉与变异:
在能够用二进制串表示十进制数的基础上,我们需要将交叉与变异引入算法中。假设我们已经获得两条二进制串(基因编码),一条作为父亲,一条作为母亲,那么交叉指的就是用父方一半的二进制编码与母方一半的二进制编码组合成为一条新的二进制串(即新的基因)。变异则指的是在交叉完成产生子代的过程中,二进制串上某个数字发生了变异,由此产生新的二进制串。当然,交叉与变异并不是必然发生的,其需要满足一定的概率条件。一般来说,交叉发生的概率较大,变异发生的概率较小。交叉是为了让算法朝着收敛的方向发展,而变异则是为了让算法有几率跳出某种局部最优解。
自然选择:
在成功将基因编码和解码以及交叉与变异引入算法后,我们已经实现了让算法自动产生部分解并优化的机制。接下去,我们需要解决如何在算法中实现自然选择并将优秀的个体保留下来进而进化出更优秀的个体。
首先我们需要确定个体是否优秀,考虑先将其二进制串转化为十进制数并代入最初定义的目标函数中,将函数值定义为适应度。在这里,假设我们要求的是最大值,则定义函数值越大,则其适应度越大。那是否在每一轮迭代过程中只需要按照适应度对个体进行排序并选出更加优秀的个体就可以了呢?事实上,自然选择的过程中存在一个现象,并没有说优秀的个体一定会被保留,而差劲的个体就一定被会被淘汰。自然选择是一个概率事件,越适应环境则生存下去的概率越高,反之越低。为了遵循这样的思想,我们可以根据之前定义的适应度的大小给定每个个体一定的生存概率,其适应度越高,则在筛选时被保留下来的概率也越高,反之越低。
那么问题就来了,如何定义这种生存概率,一般来说,我们可以将个体适应度与全部个体适应度之和的比率作为生存概率。但我们在定义适应度时使用函数值进行定义的,但函数值是有可能为负的,但概率不能为负。因此,我们需要对函数值进行正数化处理,其处理方式如下:
定义适应度函数:
定义生存概率函数:
注:最后一项之所以加上0.0001是因为不能让某个个体的生存概率变为0,这不符合自然选择中包含的概率思想。
3. 遗传算例
在这里以一个比较简单的函数为例,可以直接判断出函数的最小值为0,最优解为(0,0)
若利用遗传算法进行求解,设定交叉概率为0.8,变异概率为0.005,种群内个体数为2000,十进制数基因编码长度为24,迭代次数为500次。
从遗传算法收敛的动态图中可以发现,遗传算法现实生成了大量的解,并对这些解进行试错,最终收敛到最大值,可以发现遗传算法的结果大致上与最优解无异,结果图如下:
4. 遗传算法优缺点
优点:
1、 通过变异机制避免算法陷入局部最优,搜索能力强
2、 引入自然选择中的概率思想,个体的选择具有随机性
3、 可拓展性强,易于与其他算法进行结合使用
缺点:
1、 遗传算法编程较为复杂,涉及到基因编码与解码
2、 算法内包含的交叉率、变异率等参数的设定需要依靠经验确定
3、 对于初始种群的优劣依赖性较强