Machine Learning Mastery 优化教程(六)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

局部优化和全局优化的对比

原文:https://machinelearningmastery.com/local-optimization-versus-global-optimization/

最后更新于 2021 年 10 月 12 日

优化是指找到目标函数的一组输入,从而得到目标函数的最大或最小输出。

局部相对于全局优化来描述优化问题是很常见的。

同样,根据局部搜索和全局搜索来描述优化算法或搜索算法也很常见。

在本教程中,您将发现局部优化和全局优化之间的实际差异。

完成本教程后,您将知道:

  • 局部优化包括为搜索空间的特定区域寻找最优解,或者为没有局部最优解的问题寻找全局最优解。
  • 全局优化包括寻找包含局部最优解的问题的最优解。
  • 如何以及何时使用局部和全局搜索算法,以及如何同时使用这两种方法。

用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

Let’s get started.外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

局部优化对比全局优化
图片由马尔科·韦奇提供,保留部分权利。

教程概述

本教程分为三个部分;它们是:

  1. 局部优化
  2. 全局优化
  3. 局部优化与全局优化

局部优化

局部最优值是输入空间给定区域目标函数的极值(最小值或最大值),例如最小化问题中的一个流域。

…我们寻找一个仅局部最优的点,这意味着它在它附近的可行点中最小化目标函数…

—第 9 页,凸优化,2004。

一个目标函数可能有多个局部最优解,也可能只有一个局部最优解,在这种情况下,局部最优解也是全局最优解。

  • 局部优化:从被认为包含最优值的起点(例如,一个盆)定位目标函数的最优值。

局部优化或局部搜索是指搜索局部最优。

局部优化算法,也称为局部搜索算法,是一种旨在定位局部最优解的算法。它适用于遍历搜索空间的给定区域,并接近(或准确地找到)该区域中函数的极值。

…局部优化方法被广泛应用于那些即使不是最好的也有价值的应用中。

—第 9 页,凸优化,2004。

局部搜索算法通常对单个候选解进行操作,并且涉及迭代地对候选解进行小的改变,并且评估该改变以查看它是否导致改进并且被作为新的候选解。

局部优化算法将定位全局最优值:

  • 如果本地最优值是全局最优值,或者
  • 如果正在搜索的区域包含全局最优值。

这些定义了使用本地搜索算法的理想用例。

对于什么是本地搜索算法可能会有争论;然而,使用我们的定义的本地搜索算法的三个例子包括:

  • NelderMead 算法
  • BFGS 算法
  • 爬山算法

现在我们已经熟悉了局部优化,让我们来看看全局优化。

全局优化

全局最优是整个输入搜索空间的目标函数的极值(最小值或最大值)。

全局优化,算法通过使用搜索空间中较大部分的机制来搜索全局最优。

—第 37 页,计算智能:导论,2007。

一个目标函数可能有一个或多个全局最优解,如果有多个全局最优解,则称之为多模态优化问题,每个最优解将有不同的输入和相同的目标函数评估。

  • 全局优化:为可能包含局部最优值的目标函数定位最优值。

一个目标函数总是有一个全局最优值(否则我们不会对优化它感兴趣),尽管它也可能有局部最优值,其目标函数评估不如全局最优值。

全局最优值可能与局部最优值相同,在这种情况下,将优化问题称为局部优化而不是全局优化更合适。

局部最优解的存在是定义全局优化问题难度的主要因素,因为定位局部最优解相对容易,而定位全局最优解相对困难。

全局优化或全局搜索是指搜索全局最优解。

全局优化算法,也称为全局搜索算法,旨在定位全局最优解。它适合遍历整个输入搜索空间,并接近(或准确地找到)函数的极值。

全局优化用于变量少的问题,计算时间并不关键,找到真正全局解的价值非常高。

—第 9 页,凸优化,2004。

全局搜索算法可能涉及管理单个或一群候选解,从这些候选解中迭代地生成和评估新的候选解,以查看它们是否导致改进并作为新的工作状态。

关于什么是全局搜索算法,可能会有争论;然而,使用我们的定义的全局搜索算法的三个例子包括:

  • 遗传算法
  • 模拟退火
  • 粒子群优化算法

现在我们已经熟悉了全局优化和局部优化,让我们对两者进行比较和对比。

局部优化与全局优化

局部和全局搜索优化算法解决不同的问题或回答不同的问题。

当您知道自己处于全局最优区域或目标函数包含单个最优值(例如单峰)时,应使用局部优化算法。

当您对目标函数响应面的结构知之甚少时,或者当您知道函数包含局部最优时,应该使用全局优化算法。

局部优化,算法可能陷入局部最优而没有找到全局最优。

—第 37 页,计算智能:导论,2007。

将局部搜索算法应用于需要全局搜索算法的问题将会产生较差的结果,因为局部搜索会被局部最优解捕获(欺骗)。

  • 本地搜索:当你在全局最优区域时。
  • 全局搜索:当你知道有局部最优时。

只要算法所做的假设成立,局部搜索算法通常会给出与定位全局最优解相关的计算复杂度保证。

全局搜索算法通常很少给出关于定位全局最优解的授权。因此,全局搜索通常用于难度足够大的问题,即“T0”好的“T1”或“T2”足够好的“T3”解决方案比根本没有解决方案更受欢迎。这可能意味着相对较好的局部最优解,而不是真正的全局最优解,如果定位全局最优解很难的话。

多次重新运行或重新启动算法并记录每次运行找到的最优解通常是合适的,这样可以让您确信已经找到了相对较好的解决方案。

  • 局部搜索:针对需要全局解的狭窄问题。
  • 全局搜索:寻找全局最优可能难以解决的大问题。

我们通常对目标函数的响应面知之甚少,例如,局部或全局搜索算法是否最合适。因此,可能希望用局部搜索算法建立表现基线,然后探索全局搜索算法,看看它是否能表现得更好。如果不能,它可能表明问题确实是单峰的,或者适合于局部搜索算法。

  • 最佳实践:通过局部搜索建立基线,然后在未知的目标函数上探索全局搜索。

局部优化比全局优化更容易解决。因此,绝大多数关于数学优化的研究都集中在局部搜索技术上。

对一般非线性规划的研究很大一部分集中在局部优化方法上,因此得到了很好的发展。

—第 9 页,凸优化,2004。

全局搜索算法在搜索空间的导航中通常是粗糙的。

许多种群方法在全局搜索中表现良好,能够避免局部极小值并找到设计空间的最佳区域。不幸的是,与下降法相比,这些方法在局部搜索中表现不佳。

—第 162 页,优化算法,2019。

因此,他们可能会为一个好的局部最优值或全局最优值定位流域,但可能无法在流域内找到最佳解决方案。

局部和全局优化技术可以结合形成混合训练算法。

—第 37 页,计算智能:导论,2007。

因此,将局部搜索应用于由全局搜索算法找到的最优候选解是一种良好的做法。

  • 最佳实践:对通过全局搜索找到的解决方案应用本地搜索。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

文章

摘要

在本教程中,您发现了局部优化和全局优化之间的实际差异。

具体来说,您了解到:

  • 局部优化包括为搜索空间的特定区域寻找最优解,或者为没有局部最优解的问题寻找全局最优解。
  • 全局优化包括寻找包含局部最优解的问题的最优解。
  • 如何以及何时使用局部和全局搜索算法,以及如何同时使用这两种方法。

你有什么问题吗?
在下面的评论中提问,我会尽力回答。

如何手动优化机器学习模型超参数

原文:https://machinelearningmastery.com/manually-optimize-hyperparameters/

最后更新于 2021 年 10 月 12 日

机器学习算法有超参数,允许算法适合特定的数据集。

虽然超参数的影响可以被普遍理解,但是它们对数据集的具体影响以及它们在学习过程中的相互作用可能是未知的。因此,作为机器学习项目的一部分,调整算法超参数的值非常重要。

通常使用简单的优化算法来调整超参数,例如网格搜索和随机搜索。另一种方法是使用随机优化算法,如随机爬山算法。

在本教程中,您将发现如何手动优化机器学习算法的超参数。

完成本教程后,您将知道:

  • 随机优化算法可以代替网格和随机搜索用于超参数优化。
  • 如何使用随机爬山算法来调整感知器算法的超参数。
  • 如何手动优化 XGBoost 梯度提升算法的超参数?

用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

Let’s get started.外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何手动优化机器学习模型超参数
图片由约翰·法雷尔·麦克唐纳提供,版权所有。

教程概述

本教程分为三个部分;它们是:

  1. 手动超参数优化
  2. 感知器超参数优化
  3. 超参数优化

手动超参数优化

机器学习模型有您必须设置的超参数,以便根据数据集定制模型。

通常,超参数对模型的一般影响是已知的,但是如何为给定数据集最佳地设置超参数和交互超参数的组合是具有挑战性的。

一种更好的方法是客观地搜索模型超参数的不同值,并选择一个子集,该子集导致在给定数据集上获得最佳表现的模型。这被称为超参数优化,或超参数调整。

可以使用一系列不同的优化算法,尽管最简单和最常见的两种方法是随机搜索和网格搜索。

  • 随机搜索。将搜索空间定义为超参数值的有界域,并在该域中随机采样点。
  • 网格搜索。将搜索空间定义为超参数值网格,并计算网格中的每个位置。

网格搜索非常适合抽查那些通常表现良好的组合。随机搜索非常适合于发现和获得你凭直觉无法猜到的超参数组合,尽管它通常需要更多的时间来执行。

有关超参数优化的网格和随机搜索的更多信息,请参见教程:

网格和随机搜索是原始的优化算法,可以使用我们喜欢的任何优化来调整机器学习算法的表现。例如,可以使用随机优化算法。当需要良好或出色的表现,并且有足够的资源来调整模型时,这可能是可取的。

接下来,让我们看看如何使用随机爬山算法来调整感知器算法的表现。

感知器超参数优化

感知器算法是最简单的人工神经网络。

它是单个神经元的模型,可用于两类分类问题,并为以后开发更大的网络奠定了基础。

在本节中,我们将探讨如何手动优化感知器模型的超参数。

首先,让我们定义一个合成的二分类问题,我们可以将其作为优化模型的重点。

我们可以使用 make_classification()函数定义一个包含 1000 行和 5 个输入变量的二分类问题。

下面的示例创建数据集并总结数据的形状。

# define a binary classification dataset
from sklearn.datasets import make_classification
# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# summarize the shape of the dataset
print(X.shape, y.shape)

运行该示例会打印出创建的数据集的形状,这证实了我们的预期。

(1000, 5) (1000,)

Sklearn 通过感知器类提供感知器模型的实现。

在我们调整模型的超参数之前,我们可以使用默认超参数建立表现基线。

我们将通过重复分层 K 折交叉验证类使用重复分层 K 折交叉验证的良好实践来评估模型。

下面列出了在我们的合成二进制类别数据集上使用默认超参数评估感知器模型的完整示例。

# perceptron default hyperparameters for binary classification
from numpy import mean
from numpy import std
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import Perceptron
# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# define model
model = Perceptron()
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# evaluate model
scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
# report result
print('Mean Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))

运行示例报告评估模型,并报告分类准确率的平均值和标准偏差。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到具有默认超参数的模型实现了大约 78.5%的分类准确率。

我们希望通过优化超参数,我们可以获得比这更好的表现。

Mean Accuracy: 0.786 (0.069)

接下来,我们可以使用随机爬山算法来优化感知器模型的超参数。

有许多超参数我们可以优化,尽管我们将重点关注两个可能对模型的学习行为影响最大的超参数;它们是:

  • 学习率( eta0 )。
  • 正则化(α)。

学习率控制模型基于预测误差的更新量,并控制学习速度。eta 的默认值是 1.0。合理的值大于零(例如大于 1e-8 或 1e-10)并且可能小于 1.0

默认情况下,感知器不使用任何正则化,但是我们将启用“弹性网”正则化,该正则化在学习过程中同时应用了 L1 和 L2 正则化。这将鼓励模型寻求更小的模型权重,并反过来通常获得更好的表现。

我们将调整控制正则化权重的“α”超参数,例如它影响学习的量。如果设置为 0.0,则好像没有使用正则化。合理的值介于 0.0 和 1.0 之间。

首先,我们需要为优化算法定义目标函数。我们将使用重复分层 k 倍交叉验证的平均分类准确率来评估配置。我们将寻求最大限度地提高配置的准确性。

下面的*目标()*函数实现了这一点,获取数据集和配置值列表。配置值(学习率和正则化权重)被解包,用于配置模型,然后对模型进行评估,并返回平均准确率。

# objective function
def objective(X, y, cfg):
	# unpack config
	eta, alpha = cfg
	# define model
	model = Perceptron(penalty='elasticnet', alpha=alpha, eta0=eta)
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
	# calculate mean accuracy
	result = mean(scores)
	return result

接下来,我们需要一个函数在搜索空间中迈出一步。

搜索空间由两个变量定义(ηα)。搜索空间中的一个步骤必须与先前的值有某种关系,并且必须绑定到可感知的值(例如,在 0 和 1 之间)。

我们将使用一个“步长”超参数来控制允许算法从现有配置移动多远。将使用高斯分布概率地选择新的配置,当前值作为分布的平均值,步长作为分布的标准偏差。

我们可以使用 randn() NumPy 函数生成高斯分布的随机数。

下面的 step() 函数实现了这一点,并将在搜索空间中迈出一步,并使用现有配置生成新配置。

# take a step in the search space
def step(cfg, step_size):
	# unpack the configuration
	eta, alpha = cfg
	# step eta
	new_eta = eta + randn() * step_size
	# check the bounds of eta
	if new_eta <= 0.0:
		new_eta = 1e-8
	# step alpha
	new_alpha = alpha + randn() * step_size
	# check the bounds of alpha
	if new_alpha < 0.0:
		new_alpha = 0.0
	# return the new configuration
	return [new_eta, new_alpha]

接下来,我们需要实现随机爬山算法,该算法将调用我们的*目标()*函数来评估候选解,并调用我们的 step() 函数在搜索空间中迈出一步。

搜索首先生成一个随机的初始解,在这种情况下,eta 和 alpha 值在 0 和 1 的范围内。然后评估初始解决方案,并将其作为当前最佳工作解决方案。

...
# starting point for the search
solution = [rand(), rand()]
# evaluate the initial point
solution_eval = objective(X, y, solution)

接下来,该算法迭代固定次数的迭代,作为搜索的超参数。每次迭代都包括采取一个步骤并评估新的候选解决方案。

...
# take a step
candidate = step(solution, step_size)
# evaluate candidate point
candidate_eval = objective(X, y, candidate)

如果新方案优于当前工作方案,则作为新的当前工作方案。

...
# check if we should keep the new point
if candidate_eval >= solution_eval:
	# store the new point
	solution, solution_eval = candidate, candidate_eval
	# report progress
	print('>%d, cfg=%s %.5f' % (i, solution, solution_eval))

搜索结束后,将返回最佳解决方案及其表现。

将这些联系在一起,下面的*爬山()*函数实现了随机爬山算法,用于调整感知器算法,将数据集、目标函数、迭代次数和步长作为参数。

# hill climbing local search algorithm
def hillclimbing(X, y, objective, n_iter, step_size):
	# starting point for the search
	solution = [rand(), rand()]
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = step(solution, step_size)
		# evaluate candidate point
		candidate_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidate_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidate_eval
			# report progress
			print('>%d, cfg=%s %.5f' % (i, solution, solution_eval))
	return [solution, solution_eval]

然后我们可以调用算法并报告搜索结果。

在这种情况下,我们将运行该算法 100 次迭代,并使用 0.1 的步长,这是经过一点反复试验后选择的。

...
# define the total iterations
n_iter = 100
# step size in the search space
step_size = 0.1
# perform the hill climbing search
cfg, score = hillclimbing(X, y, objective, n_iter, step_size)
print('Done!')
print('cfg=%s: Mean Accuracy: %f' % (cfg, score))

将这些联系在一起,手动调整感知器算法的完整示例如下所示。

# manually search perceptron hyperparameters for binary classification
from numpy import mean
from numpy.random import randn
from numpy.random import rand
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import Perceptron

# objective function
def objective(X, y, cfg):
	# unpack config
	eta, alpha = cfg
	# define model
	model = Perceptron(penalty='elasticnet', alpha=alpha, eta0=eta)
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
	# calculate mean accuracy
	result = mean(scores)
	return result

# take a step in the search space
def step(cfg, step_size):
	# unpack the configuration
	eta, alpha = cfg
	# step eta
	new_eta = eta + randn() * step_size
	# check the bounds of eta
	if new_eta <= 0.0:
		new_eta = 1e-8
	# step alpha
	new_alpha = alpha + randn() * step_size
	# check the bounds of alpha
	if new_alpha < 0.0:
		new_alpha = 0.0
	# return the new configuration
	return [new_eta, new_alpha]

# hill climbing local search algorithm
def hillclimbing(X, y, objective, n_iter, step_size):
	# starting point for the search
	solution = [rand(), rand()]
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = step(solution, step_size)
		# evaluate candidate point
		candidate_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidate_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidate_eval
			# report progress
			print('>%d, cfg=%s %.5f' % (i, solution, solution_eval))
	return [solution, solution_eval]

# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# define the total iterations
n_iter = 100
# step size in the search space
step_size = 0.1
# perform the hill climbing search
cfg, score = hillclimbing(X, y, objective, n_iter, step_size)
print('Done!')
print('cfg=%s: Mean Accuracy: %f' % (cfg, score))

每次在搜索过程中看到改进时,运行示例都会报告配置和结果。运行结束时,会报告最佳配置和结果。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到,最佳结果涉及在 1.004 时使用略高于 1 的学习率和大约 0.002 的正则化权重,实现了大约 79.1%的平均准确率,优于实现了大约 78.5%的准确率的默认配置。

能不能得到更好的结果?
在下面的评论里告诉我。

>0, cfg=[0.5827274503894747, 0.260872709578015] 0.70533
>4, cfg=[0.5449820307807399, 0.3017271170801444] 0.70567
>6, cfg=[0.6286475606495414, 0.17499090243915086] 0.71933
>7, cfg=[0.5956196828965779, 0.0] 0.78633
>8, cfg=[0.5878361167354715, 0.0] 0.78633
>10, cfg=[0.6353507984485595, 0.0] 0.78633
>13, cfg=[0.5690530537610675, 0.0] 0.78633
>17, cfg=[0.6650936023999641, 0.0] 0.78633
>22, cfg=[0.9070451625704087, 0.0] 0.78633
>23, cfg=[0.9253366187387938, 0.0] 0.78633
>26, cfg=[0.9966143540220266, 0.0] 0.78633
>31, cfg=[1.0048613895650054, 0.002162219228449132] 0.79133
Done!
cfg=[1.0048613895650054, 0.002162219228449132]: Mean Accuracy: 0.791333

现在我们已经熟悉了如何使用随机爬山算法来调整简单机器学习算法的超参数,接下来让我们看看如何调整更高级的算法,例如 XGBoost。

超参数优化

XGBoost 是极限梯度提升的简称,是随机梯度提升机学习算法的高效实现。

随机梯度提升算法,也称为梯度提升机或树增强,是一种强大的机器学习技术,在一系列具有挑战性的机器学习问题上表现良好,甚至最好。

首先,必须安装 XGBoost 库。

您可以使用 pip 安装它,如下所示:

sudo pip install xgboost

安装完成后,您可以通过运行以下代码来确认安装成功,并且您使用的是现代版本:

# xgboost
import xgboost
print("xgboost", xgboost.__version__)

运行代码时,您应该会看到以下版本号或更高的版本号。

xgboost 1.0.1

虽然 XGBoost 库有自己的 Python API,但是我们可以通过 XGBClassifier 包装类将 XGBoost 模型与 Sklearn API 一起使用。

模型的一个实例可以像任何其他用于模型评估的 Sklearn 类一样被实例化和使用。例如:

...
# define model
model = XGBClassifier()

在我们调整 XGBoost 的超参数之前,我们可以使用默认的超参数建立一个表现基线。

我们将使用上一节中相同的合成二进制类别数据集和重复分层 k-fold 交叉验证的相同测试工具。

下面列出了使用默认超参数评估 XGBoost 表现的完整示例。

# xgboost with default hyperparameters for binary classification
from numpy import mean
from numpy import std
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from xgboost import XGBClassifier
# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# define model
model = XGBClassifier()
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# evaluate model
scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
# report result
print('Mean Accuracy: %.3f (%.3f)' % (mean(scores), std(scores)))

运行该示例评估模型,并报告分类准确率的平均值和标准偏差。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到具有默认超参数的模型实现了大约 84.9%的分类准确率。

我们希望通过优化超参数,我们可以获得比这更好的表现。

Mean Accuracy: 0.849 (0.040)

接下来,我们可以采用随机爬山优化算法来调整 XGBoost 模型的超参数。

对于 XGBoost 模型,我们可能需要优化许多超参数。

有关如何调整 XGBoost 模型的概述,请参见教程:

我们将关注四个关键的超参数;它们是:

  • 学习率(学习 _ 速率)
  • 树的数量(n _ 估计量)
  • 子采样百分比(子采样)
  • 树深(最大 _ 深度)

学习率控制每棵树对集成的贡献。可感知值小于 1.0,略高于 0.0(例如 1e-8)。

树的数量控制着集成的大小,往往越多的树越好到收益递减的地步。可感知值介于 1 棵树和数百或数千棵树之间。

子样本百分比定义了用于训练每棵树的随机样本大小,定义为原始数据集大小的百分比。值介于略高于 0.0 的值(例如 1e-8)和 1.0 之间

树深是每棵树的层数。更深的树对训练数据集来说更具体,可能会过度匹配。矮一些的树通常更好地概括。合理值在 1 到 10 或 20 之间。

首先,我们必须更新 objective() 函数来解包 XGBoost 模型的超参数,对其进行配置,然后评估平均分类准确率。

# objective function
def objective(X, y, cfg):
	# unpack config
	lrate, n_tree, subsam, depth = cfg
	# define model
	model = XGBClassifier(learning_rate=lrate, n_estimators=n_tree, subsample=subsam, max_depth=depth)
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
	# calculate mean accuracy
	result = mean(scores)
	return result

接下来,我们需要定义用于在搜索空间中迈出一步的 step() 函数。

每个超参数都是相当不同的范围,因此,我们将分别为每个超参数定义步长(分布的标准偏差)。为了简单起见,我们还将按行定义步长,而不是作为函数的参数。

树的数量和深度是整数,因此阶梯值是四舍五入的。

所选择的步长是任意的,是经过反复试验后选择的。

更新后的阶跃函数如下所示。

# take a step in the search space
def step(cfg):
	# unpack config
	lrate, n_tree, subsam, depth = cfg
	# learning rate
	lrate = lrate + randn() * 0.01
	if lrate <= 0.0:
		lrate = 1e-8
	if lrate > 1:
		lrate = 1.0
	# number of trees
	n_tree = round(n_tree + randn() * 50)
	if n_tree <= 0.0:
		n_tree = 1
	# subsample percentage
	subsam = subsam + randn() * 0.1
	if subsam <= 0.0:
		subsam = 1e-8
	if subsam > 1:
		subsam = 1.0
	# max tree depth
	depth = round(depth + randn() * 7)
	if depth <= 1:
		depth = 1
	# return new config
	return [lrate, n_tree, subsam, depth]

最后,必须更新*爬山()*算法,以定义具有适当值的初始解。

在这种情况下,我们将用合理的缺省值定义初始解,匹配缺省超参数,或者接近它们。

...
# starting point for the search
solution = step([0.1, 100, 1.0, 7])

将这些联系在一起,下面列出了使用随机爬山算法手动调整 XGBoost 算法的超参数的完整示例。

# xgboost manual hyperparameter optimization for binary classification
from numpy import mean
from numpy.random import randn
from numpy.random import rand
from numpy.random import randint
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from xgboost import XGBClassifier

# objective function
def objective(X, y, cfg):
	# unpack config
	lrate, n_tree, subsam, depth = cfg
	# define model
	model = XGBClassifier(learning_rate=lrate, n_estimators=n_tree, subsample=subsam, max_depth=depth)
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='accuracy', cv=cv, n_jobs=-1)
	# calculate mean accuracy
	result = mean(scores)
	return result

# take a step in the search space
def step(cfg):
	# unpack config
	lrate, n_tree, subsam, depth = cfg
	# learning rate
	lrate = lrate + randn() * 0.01
	if lrate <= 0.0:
		lrate = 1e-8
	if lrate > 1:
		lrate = 1.0
	# number of trees
	n_tree = round(n_tree + randn() * 50)
	if n_tree <= 0.0:
		n_tree = 1
	# subsample percentage
	subsam = subsam + randn() * 0.1
	if subsam <= 0.0:
		subsam = 1e-8
	if subsam > 1:
		subsam = 1.0
	# max tree depth
	depth = round(depth + randn() * 7)
	if depth <= 1:
		depth = 1
	# return new config
	return [lrate, n_tree, subsam, depth]

# hill climbing local search algorithm
def hillclimbing(X, y, objective, n_iter):
	# starting point for the search
	solution = step([0.1, 100, 1.0, 7])
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = step(solution)
		# evaluate candidate point
		candidate_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidate_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidate_eval
			# report progress
			print('>%d, cfg=[%s] %.5f' % (i, solution, solution_eval))
	return [solution, solution_eval]

# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# define the total iterations
n_iter = 200
# perform the hill climbing search
cfg, score = hillclimbing(X, y, objective, n_iter)
print('Done!')
print('cfg=[%s]: Mean Accuracy: %f' % (cfg, score))

每次在搜索过程中看到改进时,运行示例都会报告配置和结果。运行结束时,会报告最佳配置和结果。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到最佳结果涉及使用大约 0.02 的学习率、52 棵树、大约 50%的子采样率和 53 个级别的大深度。

这种配置的平均准确率约为 87.3%,优于默认配置的 84.9%。

能不能得到更好的结果?
在下面的评论里告诉我。

>0, cfg=[[0.1058242692126418, 67, 0.9228490731610172, 12]] 0.85933
>1, cfg=[[0.11060813799692253, 51, 0.859353656735739, 13]] 0.86100
>4, cfg=[[0.11890247679234153, 58, 0.7135275461723894, 12]] 0.86167
>5, cfg=[[0.10226257987735601, 61, 0.6086462443373852, 17]] 0.86400
>15, cfg=[[0.11176962034280596, 106, 0.5592742266405146, 13]] 0.86500
>19, cfg=[[0.09493587069112454, 153, 0.5049124222437619, 34]] 0.86533
>23, cfg=[[0.08516531024154426, 88, 0.5895201311518876, 31]] 0.86733
>46, cfg=[[0.10092590898175327, 32, 0.5982811365027455, 30]] 0.86867
>75, cfg=[[0.099469211050998, 20, 0.36372573610040404, 32]] 0.86900
>96, cfg=[[0.09021536590375884, 38, 0.4725379807796971, 20]] 0.86900
>100, cfg=[[0.08979482274655906, 65, 0.3697395430835758, 14]] 0.87000
>110, cfg=[[0.06792737273465625, 89, 0.33827505722318224, 17]] 0.87000
>118, cfg=[[0.05544969684589669, 72, 0.2989721608535262, 23]] 0.87200
>122, cfg=[[0.050102976159097, 128, 0.2043203965148931, 24]] 0.87200
>123, cfg=[[0.031493266763680444, 120, 0.2998819062922256, 30]] 0.87333
>128, cfg=[[0.023324201169625292, 84, 0.4017169945431015, 42]] 0.87333
>140, cfg=[[0.020224220443108752, 52, 0.5088096815056933, 53]] 0.87367
Done!
cfg=[[0.020224220443108752, 52, 0.5088096815056933, 53]]: Mean Accuracy: 0.873667

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

教程

蜜蜂

文章

摘要

在本教程中,您发现了如何手动优化机器学习算法的超参数。

具体来说,您了解到:

  • 随机优化算法可以代替网格和随机搜索用于超参数优化。
  • 如何使用随机爬山算法来调整感知器算法的超参数。
  • 如何手动优化 XGBoost 梯度提升算法的超参数?

你有什么问题吗?
在下面的评论中提问,我会尽力回答。

如何手动优化神经网络模型

原文:https://machinelearningmastery.com/manually-optimize-neural-networks/

最后更新于 2021 年 10 月 12 日

深度学习神经网络模型使用随机梯度下降优化算法拟合训练数据。

使用误差算法的反向传播来更新模型的权重。优化和权重更新算法的组合是精心选择的,并且是已知的最有效的拟合神经网络的方法。

然而,可以使用替代优化算法将神经网络模型拟合到训练数据集。这可能是一个有用的练习,以了解更多关于神经网络如何工作以及优化在应用机器学习中的核心性质。对于具有非常规模型结构和不可微传递函数的神经网络,也可能需要它。

在本教程中,您将发现如何手动优化神经网络模型的权重。

完成本教程后,您将知道:

  • 如何从零开始开发神经网络模型的正向推理通路?
  • 如何为二进制分类优化感知器模型的权重。
  • 如何使用随机爬山优化多层感知器模型的权重。

用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

Let’s get started.外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何手动优化神经网络模型
图片由土地管理局提供,保留部分权利。

教程概述

本教程分为三个部分;它们是:

  1. 优化神经网络
  2. 优化感知器模型
  3. 优化多层感知器

优化神经网络

深度学习或神经网络是机器学习的一种灵活类型。

它们是由节点和层组成的模型,灵感来自大脑的结构和功能。神经网络模型的工作原理是通过一个或多个层传播给定的输入向量,以产生可以解释为分类或回归预测建模的数字输出。

通过反复将模型暴露于输入和输出的示例中,并调整权重以最小化模型输出与预期输出相比的误差,来训练模型。这被称为随机梯度下降优化算法。使用微积分中的特定规则来调整模型的权重,该规则将误差按比例分配给网络中的每个权重。这被称为反向传播算法

使用反向传播进行权重更新的随机梯度下降优化算法是训练神经网络模型的最佳方法。然而,这不是训练神经网络的唯一方法。

可以使用任意优化算法来训练神经网络模型。

也就是说,我们可以定义一个神经网络模型架构,并使用给定的优化算法来为模型找到一组权重,从而使预测误差最小或分类准确率最大。

平均而言,使用交替优化算法的效率低于使用带反向传播的随机梯度下降算法。然而,在某些特定情况下,例如非标准网络架构或非差分传输功能,它可能更有效。

它也可以是一个有趣的练习,展示优化在训练机器学习算法,特别是神经网络中的核心性质。

接下来,让我们探索如何使用随机爬山训练一个简单的单节点神经网络,称为感知器模型。

优化感知器模型

感知器算法是最简单的人工神经网络。

它是单个神经元的模型,可用于两类分类问题,并为以后开发更大的网络奠定了基础。

在本节中,我们将优化感知器神经网络模型的权重。

首先,让我们定义一个合成的二分类问题,我们可以将其作为优化模型的重点。

我们可以使用 make_classification()函数定义一个包含 1000 行和 5 个输入变量的二分类问题。

下面的示例创建数据集并总结数据的形状。

# define a binary classification dataset
from sklearn.datasets import make_classification
# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# summarize the shape of the dataset
print(X.shape, y.shape)

运行该示例会打印出创建的数据集的形状,这证实了我们的预期。

(1000, 5) (1000,)

接下来,我们需要定义一个感知器模型。

感知器模型有一个节点,该节点对数据集中的每一列都有一个输入权重。

每个输入乘以相应的权重,得到加权和,然后加上偏差权重,就像回归模型中的截距系数一样。这个加权和称为激活。最后,激活被解释并用于预测类别标签,1 表示正激活,0 表示负激活。

在优化模型权重之前,我们必须开发模型,并对其工作方式充满信心。

让我们从定义一个解释模型激活的函数开始。

这被称为激活函数,或传递函数;后一个名字更传统,也是我的偏好。

下面的 transfer() 函数获取模型的激活,并返回一个类标签,类=1 表示正激活或零激活,类=0 表示负激活。这被称为阶跃传递函数。

# transfer function
def transfer(activation):
	if activation >= 0.0:
		return 1
	return 0

接下来,我们可以开发一个函数,为数据集的给定输入数据行计算模型的激活。

该函数将获取该行数据和模型的权重,并计算输入加上偏差权重的加权和。下面的*激活()*功能实现了这一点。

:我们有意使用简单的 Python 列表和命令式编程风格,而不是 NumPy 数组或列表压缩,以使代码对 Python 初学者来说更具可读性。请随意优化它,并在下面的评论中发布您的代码。

# activation function
def activate(row, weights):
	# add the bias, the last weight
	activation = weights[-1]
	# add the weighted input
	for i in range(len(row)):
		activation += weights[i] * row[i]
	return activation

接下来,我们可以一起使用 activate()transfer() 函数来为给定的数据行生成预测。下面的 predict_row() 函数实现了这一点。

# use model weights to predict 0 or 1 for a given row of data
def predict_row(row, weights):
	# activate for input
	activation = activate(row, weights)
	# transfer for activation
	return transfer(activation)

接下来,我们可以为给定数据集中的每一行调用 predict_row() 函数。下面的*预测 _ 数据集()*函数实现了这一点。

同样,为了可读性,我们有意使用简单的命令式编码风格,而不是列表压缩。

# use model weights to generate predictions for a dataset of rows
def predict_dataset(X, weights):
	yhats = list()
	for row in X:
		yhat = predict_row(row, weights)
		yhats.append(yhat)
	return yhats

最后,我们可以使用该模型对我们的合成数据集进行预测,以确认它都工作正常。

我们可以使用 rand()函数生成一组随机的模型权重。

回想一下,我们需要为每个输入(这个数据集中的五个输入)加上一个额外的偏置权重。

...
# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# determine the number of weights
n_weights = X.shape[1] + 1
# generate random weights
weights = rand(n_weights)

然后,我们可以使用数据集的这些权重来进行预测。

...
# generate predictions for dataset
yhat = predict_dataset(X, weights)

我们可以评估这些预测的分类准确性。

...
# calculate accuracy
score = accuracy_score(y, yhat)
print(score)

就这样。

我们可以将所有这些联系在一起,并演示我们简单的用于分类的感知器模型。下面列出了完整的示例。

# simple perceptron model for binary classification
from numpy.random import rand
from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score

# transfer function
def transfer(activation):
	if activation >= 0.0:
		return 1
	return 0

# activation function
def activate(row, weights):
	# add the bias, the last weight
	activation = weights[-1]
	# add the weighted input
	for i in range(len(row)):
		activation += weights[i] * row[i]
	return activation

# use model weights to predict 0 or 1 for a given row of data
def predict_row(row, weights):
	# activate for input
	activation = activate(row, weights)
	# transfer for activation
	return transfer(activation)

# use model weights to generate predictions for a dataset of rows
def predict_dataset(X, weights):
	yhats = list()
	for row in X:
		yhat = predict_row(row, weights)
		yhats.append(yhat)
	return yhats

# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# determine the number of weights
n_weights = X.shape[1] + 1
# generate random weights
weights = rand(n_weights)
# generate predictions for dataset
yhat = predict_dataset(X, weights)
# calculate accuracy
score = accuracy_score(y, yhat)
print(score)

运行示例会为训练数据集中的每个示例生成预测,然后打印预测的分类准确率。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在给定一组随机权重和每个类中具有相同数量示例的数据集的情况下,我们期望大约 50%的准确性,这大约是我们在本例中看到的。

0.548

我们现在可以优化数据集的权重,以在该数据集上获得良好的准确率。

首先,我们需要将数据集拆分成训练和测试集。重要的是保留一些未用于优化模型的数据,以便我们可以在用于对新数据进行预测时对模型的表现进行合理的估计。

我们将使用 67%的数据进行训练,剩下的 33%作为测试集来评估模型的表现。

...
# split into train test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33)

接下来,我们可以开发一个随机爬山算法。

优化算法需要一个目标函数来优化。它必须采用一组权重,并返回一个对应于更好模型的最小化或最大化分数。

在这种情况下,我们将使用给定的一组权重来评估模型的准确性,并返回分类准确性,分类准确性必须最大化。

给定数据集和一组权重,下面的 objective() 函数实现了这一点,并返回模型的准确率

# objective function
def objective(X, y, weights):
	# generate predictions for dataset
	yhat = predict_dataset(X, weights)
	# calculate accuracy
	score = accuracy_score(y, yhat)
	return score

接下来,我们可以定义随机爬山算法

该算法将需要一个初始解(例如,随机权重),并将反复不断地对解进行小的改变,并检查它是否会产生一个表现更好的模型。当前解的变化量由步长超参数控制。该过程将持续固定次数的迭代,也作为超参数提供。

下面的*爬山()*函数实现了这一点,将数据集、目标函数、初始解和超参数作为参数,并返回找到的最佳权重集和估计的表现。

# hill climbing local search algorithm
def hillclimbing(X, y, objective, solution, n_iter, step_size):
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = solution + randn(len(solution)) * step_size
		# evaluate candidate point
		candidte_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidte_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidte_eval
			# report progress
			print('>%d %.5f' % (i, solution_eval))
	return [solution, solution_eval]

然后我们可以调用这个函数,传入一组权重作为初始解,将训练数据集作为数据集来优化模型。

...
# define the total iterations
n_iter = 1000
# define the maximum step size
step_size = 0.05
# determine the number of weights
n_weights = X.shape[1] + 1
# define the initial solution
solution = rand(n_weights)
# perform the hill climbing search
weights, score = hillclimbing(X_train, y_train, objective, solution, n_iter, step_size)
print('Done!')
print('f(%s) = %f' % (weights, score))

最后,我们可以在测试数据集上评估最佳模型并报告表现。

...
# generate predictions for the test dataset
yhat = predict_dataset(X_test, weights)
# calculate accuracy
score = accuracy_score(y_test, yhat)
print('Test Accuracy: %.5f' % (score * 100))

将这些联系在一起,下面列出了在合成二进制优化数据集上优化感知器模型权重的完整示例。

# hill climbing to optimize weights of a perceptron model for classification
from numpy import asarray
from numpy.random import randn
from numpy.random import rand
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# transfer function
def transfer(activation):
	if activation >= 0.0:
		return 1
	return 0

# activation function
def activate(row, weights):
	# add the bias, the last weight
	activation = weights[-1]
	# add the weighted input
	for i in range(len(row)):
		activation += weights[i] * row[i]
	return activation

# # use model weights to predict 0 or 1 for a given row of data
def predict_row(row, weights):
	# activate for input
	activation = activate(row, weights)
	# transfer for activation
	return transfer(activation)

# use model weights to generate predictions for a dataset of rows
def predict_dataset(X, weights):
	yhats = list()
	for row in X:
		yhat = predict_row(row, weights)
		yhats.append(yhat)
	return yhats

# objective function
def objective(X, y, weights):
	# generate predictions for dataset
	yhat = predict_dataset(X, weights)
	# calculate accuracy
	score = accuracy_score(y, yhat)
	return score

# hill climbing local search algorithm
def hillclimbing(X, y, objective, solution, n_iter, step_size):
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = solution + randn(len(solution)) * step_size
		# evaluate candidate point
		candidte_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidte_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidte_eval
			# report progress
			print('>%d %.5f' % (i, solution_eval))
	return [solution, solution_eval]

# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# split into train test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33)
# define the total iterations
n_iter = 1000
# define the maximum step size
step_size = 0.05
# determine the number of weights
n_weights = X.shape[1] + 1
# define the initial solution
solution = rand(n_weights)
# perform the hill climbing search
weights, score = hillclimbing(X_train, y_train, objective, solution, n_iter, step_size)
print('Done!')
print('f(%s) = %f' % (weights, score))
# generate predictions for the test dataset
yhat = predict_dataset(X_test, weights)
# calculate accuracy
score = accuracy_score(y_test, yhat)
print('Test Accuracy: %.5f' % (score * 100))

每次对模型进行改进时,运行该示例都会报告迭代次数和分类准确率。

在搜索结束时,报告最佳权重集在训练数据集上的表现,并计算和报告相同模型在测试数据集上的表现。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到优化算法找到了一组权重,在训练数据集上获得了大约 88.5%的准确率,在测试数据集上获得了大约 81.8%的准确率。

...
>111 0.88060
>119 0.88060
>126 0.88209
>134 0.88209
>205 0.88209
>262 0.88209
>280 0.88209
>293 0.88209
>297 0.88209
>336 0.88209
>373 0.88209
>437 0.88358
>463 0.88507
>630 0.88507
>701 0.88507
Done!
f([ 0.0097317 0.13818088 1.17634326 -0.04296336 0.00485813 -0.14767616]) = 0.885075
Test Accuracy: 81.81818

现在我们已经熟悉了如何手动优化感知器模型的权重,让我们看看如何扩展示例来优化多层感知器(MLP)模型的权重。

优化多层感知器

多层感知器(MLP)模型是具有一层或多层的神经网络,其中每层具有一个或多个节点。

它是感知器模型的扩展,可能是应用最广泛的神经网络(深度学习)模型。

在本节中,我们将在上一节中所学的基础上优化 MLP 模型的权重,每层具有任意数量的层和节点。

首先,我们将开发模型并用随机权重进行测试,然后使用随机爬山来优化模型权重。

当使用 MLPs 进行二进制分类时,通常使用 sigmoid 传递函数(也称为逻辑函数)来代替感知器中使用的阶跃传递函数。

该函数输出 0-1 之间的实数值,表示二项式概率分布,例如一个例子属于类=1 的概率。下面的*转移()*功能实现了这一点。

# transfer function
def transfer(activation):
	# sigmoid transfer function
	return 1.0 / (1.0 + exp(-activation))

我们可以使用上一节中相同的*激活()*功能。这里,我们将使用它来计算给定层中每个节点的激活。

predict_row() 函数必须用更复杂的版本替换。

该函数获取一行数据和网络,并返回网络的输出。

我们将把我们的网络定义为一系列列表。每一层都是一个节点列表,每个节点都是一个权重列表或数组。

为了计算网络的预测,我们简单地枚举层,然后枚举节点,然后计算每个节点的激活和传输输出。在这种情况下,我们将对网络中的所有节点使用相同的传递函数,尽管并非必须如此。

对于具有多个层的网络,前一层的输出被用作下一层中每个节点的输入。然后返回网络中最后一层的输出。

下面的 predict_row() 函数实现了这一点。

# activation function for a network
def predict_row(row, network):
	inputs = row
	# enumerate the layers in the network from input to output
	for layer in network:
		new_inputs = list()
		# enumerate nodes in the layer
		for node in layer:
			# activate the node
			activation = activate(inputs, node)
			# transfer activation
			output = transfer(activation)
			# store output
			new_inputs.append(output)
		# output from this layer is input to the next layer
		inputs = new_inputs
	return inputs[0]

差不多了。

最后,我们需要定义一个要使用的网络。

例如,我们可以定义一个具有单个隐藏层和单个节点的 MLP,如下所示:

...
# create a one node network
node = rand(n_inputs + 1)
layer = [node]
network = [layer]

这实际上是一个感知器,虽然有一个乙状元传递函数。相当无聊。

让我们定义一个具有一个隐藏层和一个输出层的 MLP。第一个隐藏层将有 10 个节点,每个节点将从数据集获取输入模式(例如 5 个输入)。输出层将具有单个节点,该节点从第一隐藏层的输出中获取输入,然后输出预测。

...
# one hidden layer and an output layer
n_hidden = 10
hidden1 = [rand(n_inputs + 1) for _ in range(n_hidden)]
output1 = [rand(n_hidden + 1)]
network = [hidden1, output1]

然后,我们可以使用该模型对数据集进行预测。

...
# generate predictions for dataset
yhat = predict_dataset(X, network)

在计算分类准确率之前,我们必须将预测四舍五入到类别标签 0 和 1。

...
# round the predictions
yhat = [round(y) for y in yhat]
# calculate accuracy
score = accuracy_score(y, yhat)
print(score)

将所有这些联系在一起,下面列出了在我们的合成二进制类别数据集上用随机初始权重评估 MLP 的完整示例。

# develop an mlp model for classification
from math import exp
from numpy.random import rand
from sklearn.datasets import make_classification
from sklearn.metrics import accuracy_score

# transfer function
def transfer(activation):
	# sigmoid transfer function
	return 1.0 / (1.0 + exp(-activation))

# activation function
def activate(row, weights):
	# add the bias, the last weight
	activation = weights[-1]
	# add the weighted input
	for i in range(len(row)):
		activation += weights[i] * row[i]
	return activation

# activation function for a network
def predict_row(row, network):
	inputs = row
	# enumerate the layers in the network from input to output
	for layer in network:
		new_inputs = list()
		# enumerate nodes in the layer
		for node in layer:
			# activate the node
			activation = activate(inputs, node)
			# transfer activation
			output = transfer(activation)
			# store output
			new_inputs.append(output)
		# output from this layer is input to the next layer
		inputs = new_inputs
	return inputs[0]

# use model weights to generate predictions for a dataset of rows
def predict_dataset(X, network):
	yhats = list()
	for row in X:
		yhat = predict_row(row, network)
		yhats.append(yhat)
	return yhats

# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# determine the number of inputs
n_inputs = X.shape[1]
# one hidden layer and an output layer
n_hidden = 10
hidden1 = [rand(n_inputs + 1) for _ in range(n_hidden)]
output1 = [rand(n_hidden + 1)]
network = [hidden1, output1]
# generate predictions for dataset
yhat = predict_dataset(X, network)
# round the predictions
yhat = [round(y) for y in yhat]
# calculate accuracy
score = accuracy_score(y, yhat)
print(score)

运行示例会为训练数据集中的每个示例生成一个预测,然后打印预测的分类准确率。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

同样,在给定一组随机权重和每个类中具有相同数量示例的数据集的情况下,我们期望大约 50%的准确性,这大约是我们在本例中看到的。

0.499

接下来,我们可以将随机爬山算法应用于数据集。

这与将爬山应用于感知器模型非常相似,只是在这种情况下,一个步骤需要修改网络中的所有权重。

为此,我们将开发一个新的函数,创建一个网络副本,并在创建副本时变异网络中的每个权重。

下面的*步骤()*功能实现了这一点。

# take a step in the search space
def step(network, step_size):
	new_net = list()
	# enumerate layers in the network
	for layer in network:
		new_layer = list()
		# enumerate nodes in this layer
		for node in layer:
			# mutate the node
			new_node = node.copy() + randn(len(node)) * step_size
			# store node in layer
			new_layer.append(new_node)
		# store layer in network
		new_net.append(new_layer)
	return new_net

修改网络中的所有权重是激进的。

搜索空间中一个不太激进的步骤可能是对模型中的权重子集做一个小的改变,也许由一个超参数控制。这是作为扩展留下的。

然后我们可以从爬山()函数中调用这个新的 step() 函数。

# hill climbing local search algorithm
def hillclimbing(X, y, objective, solution, n_iter, step_size):
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = step(solution, step_size)
		# evaluate candidate point
		candidte_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidte_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidte_eval
			# report progress
			print('>%d %f' % (i, solution_eval))
	return [solution, solution_eval]

将这些联系在一起,下面列出了应用随机爬山来优化二分类的 MLP 模型权重的完整示例。

# stochastic hill climbing to optimize a multilayer perceptron for classification
from math import exp
from numpy.random import randn
from numpy.random import rand
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# transfer function
def transfer(activation):
	# sigmoid transfer function
	return 1.0 / (1.0 + exp(-activation))

# activation function
def activate(row, weights):
	# add the bias, the last weight
	activation = weights[-1]
	# add the weighted input
	for i in range(len(row)):
		activation += weights[i] * row[i]
	return activation

# activation function for a network
def predict_row(row, network):
	inputs = row
	# enumerate the layers in the network from input to output
	for layer in network:
		new_inputs = list()
		# enumerate nodes in the layer
		for node in layer:
			# activate the node
			activation = activate(inputs, node)
			# transfer activation
			output = transfer(activation)
			# store output
			new_inputs.append(output)
		# output from this layer is input to the next layer
		inputs = new_inputs
	return inputs[0]

# use model weights to generate predictions for a dataset of rows
def predict_dataset(X, network):
	yhats = list()
	for row in X:
		yhat = predict_row(row, network)
		yhats.append(yhat)
	return yhats

# objective function
def objective(X, y, network):
	# generate predictions for dataset
	yhat = predict_dataset(X, network)
	# round the predictions
	yhat = [round(y) for y in yhat]
	# calculate accuracy
	score = accuracy_score(y, yhat)
	return score

# take a step in the search space
def step(network, step_size):
	new_net = list()
	# enumerate layers in the network
	for layer in network:
		new_layer = list()
		# enumerate nodes in this layer
		for node in layer:
			# mutate the node
			new_node = node.copy() + randn(len(node)) * step_size
			# store node in layer
			new_layer.append(new_node)
		# store layer in network
		new_net.append(new_layer)
	return new_net

# hill climbing local search algorithm
def hillclimbing(X, y, objective, solution, n_iter, step_size):
	# evaluate the initial point
	solution_eval = objective(X, y, solution)
	# run the hill climb
	for i in range(n_iter):
		# take a step
		candidate = step(solution, step_size)
		# evaluate candidate point
		candidte_eval = objective(X, y, candidate)
		# check if we should keep the new point
		if candidte_eval >= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidte_eval
			# report progress
			print('>%d %f' % (i, solution_eval))
	return [solution, solution_eval]

# define dataset
X, y = make_classification(n_samples=1000, n_features=5, n_informative=2, n_redundant=1, random_state=1)
# split into train test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33)
# define the total iterations
n_iter = 1000
# define the maximum step size
step_size = 0.1
# determine the number of inputs
n_inputs = X.shape[1]
# one hidden layer and an output layer
n_hidden = 10
hidden1 = [rand(n_inputs + 1) for _ in range(n_hidden)]
output1 = [rand(n_hidden + 1)]
network = [hidden1, output1]
# perform the hill climbing search
network, score = hillclimbing(X_train, y_train, objective, network, n_iter, step_size)
print('Done!')
print('Best: %f' % (score))
# generate predictions for the test dataset
yhat = predict_dataset(X_test, network)
# round the predictions
yhat = [round(y) for y in yhat]
# calculate accuracy
score = accuracy_score(y_test, yhat)
print('Test Accuracy: %.5f' % (score * 100))

每次对模型进行改进时,运行该示例都会报告迭代次数和分类准确率。

在搜索结束时,报告最佳权重集在训练数据集上的表现,并计算和报告相同模型在测试数据集上的表现。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到优化算法找到了一组权重,在训练数据集上获得了大约 87.3%的准确率,在测试数据集上获得了大约 85.1%的准确率。

...
>55 0.755224
>56 0.765672
>59 0.794030
>66 0.805970
>77 0.835821
>120 0.838806
>165 0.840299
>188 0.841791
>218 0.846269
>232 0.852239
>237 0.852239
>239 0.855224
>292 0.867164
>368 0.868657
>823 0.868657
>852 0.871642
>889 0.871642
>892 0.871642
>992 0.873134
Done!
Best: 0.873134
Test Accuracy: 85.15152

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

教程

蜜蜂

摘要

在本教程中,您发现了如何手动优化神经网络模型的权重。

具体来说,您了解到:

  • 如何从零开始开发神经网络模型的正向推理通路?
  • 如何为二进制分类优化感知器模型的权重。
  • 如何使用随机爬山优化多层感知器模型的权重。

你有什么问题吗?
在下面的评论中提问,我会尽力回答。

使用 Sklearn 建模管道优化

原文:https://machinelearningmastery.com/modeling-pipeline-optimization-with-Sklearn/

最后更新于 2021 年 10 月 22 日

本教程介绍了数据科学和自动学习中的两个基本概念。一是机器学习管道,二是其优化。这两个原则是实现任何成功的基于机器学习的智能系统的关键。

可以通过将训练机器学习模型所涉及的一系列步骤组合在一起来创建机器学习管道。它可以用来自动化机器学习工作流程。流水线可以包括预处理、特征选择、分类/回归和后处理。更复杂的应用程序可能需要在这个管道中加入其他必要的步骤。

我们所说的优化是指调整模型以获得最佳表现。任何学习模式的成功都取决于选择能给出最佳结果的最佳参数。优化可以从搜索算法的角度来看,它在一个参数空间中穿行,并从中寻找最佳的。

完成本教程后,您应该:

  • 理解管道及其优化的重要性。
  • 能够建立机器学习管道。
  • 能够优化管道。
  • 了解分析优化结果的技巧。

用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

The tutorial is simple and easy to follow. It should not take you too long to go through it. So enjoy!

教程概述

本教程将向您展示如何

  1. 使用 sklearn.pipeline 中的 pipeline 对象设置管道
  2. 使用 sklearn.model_selection 中的 GridSearchCV()执行网格搜索以获得最佳参数
  3. 分析 GridSearchCV()的结果,并将其可视化

在演示以上所有内容之前,让我们编写导入部分:

from pandas import read_csv # For dataframes
from pandas import DataFrame # For dataframes
from numpy import ravel # For matrices
import matplotlib.pyplot as plt # For plotting data
import seaborn as sns # For plotting data
from sklearn.model_selection import train_test_split # For train/test splits
from sklearn.neighbors import KNeighborsClassifier # The k-nearest neighbor classifier
from sklearn.feature_selection import VarianceThreshold # Feature selector
from sklearn.pipeline import Pipeline # For setting up pipeline
# Various pre-processing steps
from sklearn.preprocessing import Normalizer, StandardScaler, MinMaxScaler, PowerTransformer, MaxAbsScaler, LabelEncoder
from sklearn.model_selection import GridSearchCV # For optimization

数据集

我们将使用来自 UCI 机器学习资源库Ecoli 数据集来演示本教程的所有概念。该数据集由中井贤三维护。让我们首先将 Ecoli 数据集加载到 Pandas DataFrame 中,并查看前几行。

# Read ecoli dataset from the UCI ML Repository and store in
# dataframe df
df = read_csv(
    'https://archive.ics.uci.edu/ml/machine-learning-databases/ecoli/ecoli.data',
    sep = '\s+',
    header=None)
print(df.head())

运行示例时,您应该会看到以下内容:

            0     1     2     3    4     5     6     7   8
0   AAT_ECOLI  0.49  0.29  0.48  0.5  0.56  0.24  0.35  cp
1  ACEA_ECOLI  0.07  0.40  0.48  0.5  0.54  0.35  0.44  cp
2  ACEK_ECOLI  0.56  0.40  0.48  0.5  0.49  0.37  0.46  cp
3  ACKA_ECOLI  0.59  0.49  0.48  0.5  0.52  0.45  0.36  cp
4   ADI_ECOLI  0.23  0.32  0.48  0.5  0.55  0.25  0.35  cp

我们将忽略指定序列名称的第一列。最后一列是类标签。让我们将要素从类标签中分离出来,并将数据集分成 2/3 个训练实例和 1/3 个测试实例。

...
# The data matrix X
X = df.iloc[:,1:-1]
# The labels
y = (df.iloc[:,-1:])

# Encode the labels into unique integers
encoder = LabelEncoder()
y = encoder.fit_transform(ravel(y))

# Split the data into test and train
X_train, X_test, y_train, y_test = train_test_split(
    X,  
    y, 
    test_size=1/3,
    random_state=0)

print(X_train.shape)
print(X_test.shape)

运行示例时,您应该会看到以下内容:

(224, 7)
(112, 7)

太好了。现在我们在训练集中有 224 个样本,在测试集中有 112 个样本。我们选择了一个小数据集,这样我们就可以专注于概念,而不是数据本身。

在本教程中,我们选择了 k 近邻分类器来执行这个数据集的分类。

无流水线分类器及其优化

首先,让我们检查 k 最近邻在训练集和测试集上的表现。这将为我们提供一个表现基线。

...
knn = KNeighborsClassifier().fit(X_train, y_train)
print('Training set score: ' + str(knn.score(X_train,y_train)))
print('Test set score: ' + str(knn.score(X_test,y_test)))

运行示例时,您应该会看到以下内容:

Training set score: 0.9017857142857143
Test set score: 0.8482142857142857

我们应该记住,分类器表现的真正评判标准是测试集分数,而不是训练集分数。测试集得分反映了分类器的泛化能力。

设置机器学习管道

在本教程中,我们将设置一个非常基本的管道,它由以下序列组成:

  1. 定标器:对数据进行预处理,即使用标准定标器()将数据变换为零均值和单位方差。
  2. 特征选择器:使用方差阈值()丢弃方差小于某个定义阈值的特征。
  3. 分类器:kneighgborcsclassifier(),实现 k 近邻分类器,选择最接近测试示例的 k 个点的大部分的类。
...
pipe = Pipeline([
('scaler', StandardScaler()),
('selector', VarianceThreshold()),
('classifier', KNeighborsClassifier())
])

管道对象很容易理解。它说,规模第一,选择特征第二,最后分类。让我们在训练数据上调用管道对象的 fit()方法,并获得训练和测试分数。

...
pipe.fit(X_train, y_train)

print('Training set score: ' + str(pipe.score(X_train,y_train)))
print('Test set score: ' + str(pipe.score(X_test,y_test)))

运行示例时,您应该会看到以下内容:

Training set score: 0.8794642857142857
Test set score: 0.8392857142857143

因此,看起来这个管道的表现比原始数据上的单个分类器的表现更差。我们不仅增加了额外的处理,但这一切都是徒劳的。不要绝望,管道的真正好处来自于它的调优。下一节将解释如何做到这一点。

优化和调整管道

在下面的代码中,我们将显示以下内容:

  1. 我们可以寻找最好的定标器。我们可以尝试 MinMaxScaler(),Normalizer()和 MaxAbsScaler(),而不仅仅是 StandardScaler()。
  2. 我们可以搜索在选择器中使用的最佳方差阈值,即 variance threshold()。
  3. 我们可以为 KNeighborsClassifier()搜索 k 的最佳值。

下面的参数变量是一个字典,它指定了键:值对。请注意,必须写入关键字,用双下划线 __ 分隔我们在管道()中选择的模块名称及其参数。请注意以下几点:

  1. 缩放器没有双下划线,因为我们在那里指定了一个对象列表。
  2. 我们将搜索选择器的最佳阈值,即变量阈值()。因此,我们指定了一个值列表[0,0.0001,0.001,0.5]供选择。
  3. 为 KNeighborsClassifier()的 n_neighbors、p 和 leaf_size 参数指定了不同的值。
...
parameters = {'scaler': [StandardScaler(), MinMaxScaler(),
	Normalizer(), MaxAbsScaler()],
	'selector__threshold': [0, 0.001, 0.01],
	'classifier__n_neighbors': [1, 3, 5, 7, 10],
	'classifier__p': [1, 2],
	'classifier__leaf_size': [1, 5, 10, 15]
}

管道和上面的参数列表随后被传递给 GridSearchCV()对象,该对象在参数空间中搜索最佳参数集,如下所示:

...
grid = GridSearchCV(pipe, parameters, cv=2).fit(X_train, y_train)

print('Training set score: ' + str(grid.score(X_train, y_train)))
print('Test set score: ' + str(grid.score(X_test, y_test)))

运行示例时,您应该会看到以下内容:

Training set score: 0.8928571428571429
Test set score: 0.8571428571428571

通过调整管道,我们实现了对简单分类器和非优化管道的相当大的改进。分析优化过程的结果很重要。

不要太担心通过运行上面的代码得到的警告。生成它是因为我们只有很少的训练样本,并且交叉验证对象没有足够的样本用于它的一个折叠的类。

分析结果

让我们看看调优后的网格对象,了解一下 GridSearchCV()对象。

该对象之所以如此命名,是因为它建立了一个多维网格,每个角代表要尝试的参数组合。这定义了一个参数空间。举个例子,如果我们有三个 n_neighbors 的值,即{1,3,5},两个 leaf_size 的值,即{1,5},两个 threshold 的值,即{0,0.0001},那么我们有一个 3x2x2=12 个角的 3D 网格。每个角代表不同的组合。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GridSearchCV 计算网格每个角的分数

对于上面网格的每个角,GridSearchCV()对象计算未看到的示例的平均交叉验证分数,并选择给出最佳结果的角/参数组合。下面的代码显示了如何访问网格的最佳参数和我们任务的最佳管道。

...
# Access the best set of parameters
best_params = grid.best_params_
print(best_params)
# Stores the optimum model in best_pipe
best_pipe = grid.best_estimator_
print(best_pipe)

运行示例时,您应该会看到以下内容:

{'classifier__leaf_size': 1, 'classifier__n_neighbors': 7, 'classifier__p': 2, 'scaler': StandardScaler(), 'selector__threshold': 0}
Pipeline(steps=[('scaler', StandardScaler()),
                ('selector', VarianceThreshold(threshold=0)),
                ('classifier',
                 KNeighborsClassifier(leaf_size=1, n_neighbors=7))])

分析结果的另一种有用的技术是从网格中构造一个数据框架。让我们查看这个数据框的列。

...
result_df = DataFrame.from_dict(grid.cv_results_, orient='columns')
print(result_df.columns)

运行示例时,您应该会看到以下内容:

Index(['mean_fit_time', 'std_fit_time', 'mean_score_time', 'std_score_time',
       'param_classifier__leaf_size', 'param_classifier__n_neighbors',
       'param_classifier__p', 'param_scaler', 'param_selector__threshold',
       'params', 'split0_test_score', 'split1_test_score', 'mean_test_score',
       'std_test_score', 'rank_test_score'],
      dtype='object')

这个数据框非常有价值,因为它向我们显示了不同参数的分数。带有 mean_test_score 的列是交叉验证期间所有折叠的测试集得分的平均值。数据框可能太大,无法手动可视化,因此绘制结果总是一个好主意。让我们看看 n_neighbors 如何影响不同定标器和不同 p 值的表现。

...
sns.relplot(data=result_df,
	kind='line',
	x='param_classifier__n_neighbors',
	y='mean_test_score',
	hue='param_scaler',
	col='param_classifier__p')
plt.show()

运行示例时,您应该会看到以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

管线 GridSearchCV 结果的线图

这些图清楚地表明,使用 StandardScaler(),n_neighbors=7,p=2,会得到最好的结果。让我们再做一组叶子大小的图。

...
sns.relplot(data=result_df,
            kind='line',
            x='param_classifier__n_neighbors',
            y='mean_test_score',
            hue='param_scaler',
            col='param_classifier__leaf_size')
plt.show()

运行示例时,您应该会看到以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

完整示例

将这些结合在一起,下面列出了完整的代码示例。

from pandas import read_csv                           # For dataframes
from pandas import DataFrame                       # For dataframes
from numpy import ravel                                  # For matrices
import matplotlib.pyplot as plt                        # For plotting data
import seaborn as sns                                     # For plotting data
from sklearn.model_selection import train_test_split    # For train/test splits
from sklearn.neighbors import KNeighborsClassifier    # The k-nearest neighbor classifier
from sklearn.feature_selection import VarianceThreshold # Feature selector
from sklearn.pipeline import Pipeline                                  # For setting up pipeline
# Various pre-processing steps
from sklearn.preprocessing import Normalizer, StandardScaler, MinMaxScaler, PowerTransformer, MaxAbsScaler, LabelEncoder
from sklearn.model_selection import GridSearchCV      # For optimization

# Read ecoli dataset from the UCI ML Repository and store in
# dataframe df
df = read_csv(
    'https://archive.ics.uci.edu/ml/machine-learning-databases/ecoli/ecoli.data',
    sep = '\s+',
    header=None)
print(df.head())

# The data matrix X
X = df.iloc[:,1:-1]
# The labels
y = (df.iloc[:,-1:])

# Encode the labels into unique integers
encoder = LabelEncoder()
y = encoder.fit_transform(ravel(y))

# Split the data into test and train
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=1/3,
    random_state=0)

print(X_train.shape)
print(X_test.shape)

knn = KNeighborsClassifier().fit(X_train, y_train)
print('Training set score: ' + str(knn.score(X_train,y_train)))
print('Test set score: ' + str(knn.score(X_test,y_test)))

pipe = Pipeline([
('scaler', StandardScaler()),
('selector', VarianceThreshold()),
('classifier', KNeighborsClassifier())
])

pipe.fit(X_train, y_train)

print('Training set score: ' + str(pipe.score(X_train,y_train)))
print('Test set score: ' + str(pipe.score(X_test,y_test)))

parameters = {'scaler': [StandardScaler(), MinMaxScaler(),
	Normalizer(), MaxAbsScaler()],
	'selector__threshold': [0, 0.001, 0.01],
	'classifier__n_neighbors': [1, 3, 5, 7, 10],
	'classifier__p': [1, 2],
	'classifier__leaf_size': [1, 5, 10, 15]
}

grid = GridSearchCV(pipe, parameters, cv=2).fit(X_train, y_train)

print('Training set score: ' + str(grid.score(X_train, y_train)))
print('Test set score: ' + str(grid.score(X_test, y_test)))

# Access the best set of parameters
best_params = grid.best_params_
print(best_params)
# Stores the optimum model in best_pipe
best_pipe = grid.best_estimator_
print(best_pipe)

result_df = DataFrame.from_dict(grid.cv_results_, orient='columns')
print(result_df.columns)

sns.relplot(data=result_df,
	kind='line',
	x='param_classifier__n_neighbors',
	y='mean_test_score',
	hue='param_scaler',
	col='param_classifier__p')
plt.show()

sns.relplot(data=result_df,
            kind='line',
            x='param_classifier__n_neighbors',
            y='mean_test_score',
            hue='param_scaler',
            col='param_classifier__leaf_size')
plt.show()

摘要

在本教程中,我们学习了以下内容:

  1. 如何构建机器学习管道?
  2. 如何利用 GridSearchCV 优化管道?
  3. 如何分析和比较使用不同参数集获得的结果?

本教程使用的数据集非常小,只有几个示例点,但结果仍然比简单的分类器好。

进一步阅读

对于感兴趣的读者,这里有一些资源:

教程

蜜蜂

数据集

机器学习没有免费午餐定理

原文:https://machinelearningmastery.com/no-free-lunch-theorem-for-machine-learning/

最后更新于 2021 年 10 月 12 日

没有免费的午餐定理在优化和机器学习领域经常被抛来抛去,通常对它的含义或隐含意义知之甚少。

该定理指出,当所有优化算法的表现在所有可能的问题上取平均值时,它们的表现同样良好。

这意味着没有单一的最佳优化算法。由于优化、搜索和机器学习之间的紧密关系,这也意味着对于分类和回归等预测建模问题,没有单一的最佳机器学习算法。

在本教程中,您将发现用于优化和搜索的“没有免费午餐”定理。

完成本教程后,您将知道:

  • 没有免费的午餐定理表明,在某些特定的约束条件下,所有优化算法的表现都是相同的。
  • 不存在单一的最佳优化算法或机器学习算法。
  • 假设我们对所有可能的目标函数的一个小子集感兴趣,这个定理的实际意义可能是有限的。

用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

Let’s get started.外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

机器学习没有免费午餐定理
图片由 Francisco Anzola 提供,保留部分权利。

教程概述

本教程分为三个部分;它们是:

  1. 什么是不免费午餐定理?
  2. 优化的含义
  3. 对机器学习的启示

什么是不免费午餐定理?

没有免费的午餐定理,通常缩写为 NFL 或 NFLT,是一个理论发现,表明当所有优化算法的表现在所有可能的目标函数上平均时,它们的表现同样好。

美国国家橄榄球联盟表示,在一定的限制条件下,在所有可能的问题的空间内,每种优化技术的平均表现都和其他技术一样好(包括随机搜索)

—第 203 页,元试探法精要,2011。

该定理一般适用于优化和搜索问题,因为优化可以被描述或框定为搜索问题。

言外之意就是你喜欢的算法的表现和完全幼稚的算法是一样的,比如随机搜索。

粗略地说,我们表明,对于静态和时间相关的优化问题,任何一对算法在所有可能问题上的平均表现完全相同。

——优化没有免费的午餐定理,1997。

思考这一发现的一个简单方法是考虑一个像 excel 中那样的大表。

在表格的顶部,每一列代表不同的优化算法。在表格的下方,每一行代表一个不同的目标函数。表中的每个单元格都是算法在目标函数上的表现,使用您喜欢的任何表现度量,只要它在整个表中是一致的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

把没有免费午餐定理描述成一张算法和问题表

你可以想象这张桌子会无限大。然而,在这个表中,我们可以从其列中的所有值计算任何算法的平均表现,并且它将与任何其他算法列的平均表现相同。

如果一种算法在一类问题上的表现优于另一种算法,那么它在另一类问题上的表现就会更差

—第 6 页,优化算法,2019。

既然我们已经熟悉了“没有免费午餐”定理,那么让我们来看看优化的具体含义。

优化的含义

所谓的黑盒优化算法是通用的优化算法,可以应用于许多不同的优化问题,并且对目标函数的假设很少。

黑盒算法的例子包括遗传算法、模拟退火和粒子群优化。

没有免费的午餐定理是在 20 世纪 90 年代后期的一个环境中提出的,当时人们经常声称一种黑盒优化算法比另一种优化算法更好。该定理反推了这一点,表明没有最佳优化算法,这是证明不可能的。

该定理确实指出,平均而言,没有任何优化算法比任何其他优化算法更好。

…被称为“没有免费午餐”的定理,为学习器的能力设定了一个极限。极限很低:没有一个学习器能比随机猜测更好!

—第 63 页,主算法,2018 年。

问题在于,算法的应用并没有假设任何关于问题的东西。事实上,算法是在没有先验知识的情况下应用于目标函数的,甚至是目标函数是最小化还是最大化。这是定理的一个硬约束。

我们经常有一些关于优化目标函数的知识。事实上,如果在实践中我们真的对目标函数一无所知,我们就无法选择优化算法。

*> 正如沃伯特和麦克瑞德的“没有免费的午餐”定理所阐述的那样,没有理由偏爱一种算法,除非我们对可能的目标函数空间上的概率分布做出假设。

—第 6 页,优化算法,2019。

建议优化领域的初学者尽可能多地学习和使用优化算法中的问题。

我们对问题的算法了解得越多,掌握得越多,技术就越适合问题,算法就越有可能在问题上表现良好。没有免费午餐定理支持这个建议。

我们不关心所有可能的世界,只关心我们生活的那个世界。如果我们对世界有所了解,并将其融入到我们的学习器中,它现在比随机猜测更有优势。

—第 63 页,主算法,2018 年。

此外,表现在所有可能的目标函数和所有可能的优化算法上是平均的。而在实践中,我们感兴趣的是目标函数的一个小子集,它可能具有特定的结构或形式以及为这些函数定制的算法。

……我们再怎么强调也不为过,本文中并未就各种搜索算法在实践中的运行情况提出任何主张。本文的重点在于无需任何假设就能先验地得出的结论,以及仅从数学原理上得出的关于搜索算法实用性的结论。

——优化没有免费的午餐定理,1997。

这些暗示让一些实践者注意到这个定理有限的实用价值。

这在理论上很有意义,但我认为实际价值有限,因为所有可能问题的空间可能包括许多极不寻常和病态的问题,这些问题在实践中很少见到。

—第 203 页,元试探法精要,2011。

既然我们已经回顾了没有免费午餐定理对优化的影响,那么让我们回顾一下对机器学习的影响。

对机器学习的启示

学习可以被描述或框架为一个优化问题,大多数机器学习算法的核心是解决一个优化问题。

用于优化和搜索的“没有免费午餐”定理被应用于机器学习,特别是监督学习,这是分类和回归预测建模任务的基础。

这意味着所有的机器学习算法对所有可能的预测问题都同样有效,例如,随机森林和随机预测一样好。

因此,所有的学习算法都是相同的:(1)根据“平均”的几种定义,所有的算法都具有相同的平均脱离训练集的错误分类风险,(2)因此,对于所有的学习算法,没有一种学习算法比另一种学习算法具有更低的风险

——监督学习无免费午餐定理,2002。

这也对评估或选择算法的方式有影响,例如通过 k 倍交叉验证测试工具选择学习算法与否。

…使用交叉验证在一组预先设定的学习算法中进行选择的算法,平均来说并不比没有这样做的算法做得更好。

——监督学习无免费午餐定理,2002。

它还对构成“好的”机器学习模型的常见启发式方法有所启示,例如避免过拟合或选择表现良好的最简单的可能模型。

另一组例子是由人们为监督学习想出的所有启发式方法提供的,避免过拟合,喜欢更简单的模型而不是更复杂的模型等。[没有免费的午餐]说所有这样的试探法失败的次数和成功的次数一样多。

——监督学习无免费午餐定理,2002。

考虑到在所有可能的预测问题中没有最佳的单一机器学习算法,这激发了继续开发新的学习算法和更好地理解已经开发的算法的需求。

由于没有免费的午餐定理,我们需要开发许多不同类型的模型,以涵盖现实世界中出现的各种各样的数据。对于每个模型,可能有许多不同的算法,我们可以用来训练模型,这使得不同的速度-准确率-复杂性权衡。

—第 24-25 页,机器学习:概率视角,2012。

它还支持为给定的预测建模问题测试一套不同的机器学习算法。

没有免费的午餐”定理认为,在没有关于建模问题的实质性信息的情况下,没有哪一个模型总是比任何其他模型做得更好。正因为如此,可以提出一个强有力的案例来尝试各种各样的技术,然后确定关注哪个模型。

—第 25-26 页,应用预测建模,2013 年。

然而,与优化一样,该定理的含义基于对所解决问题一无所知的学习算法的选择。

实际上,情况并非如此,我们鼓励初学机器学习的实践者查看可用的数据,以便了解一些可以纳入学习算法的问题。

我们甚至可能想更进一步,说没有一些先验知识,学习是不可能的,光有数据是不够的。

与此同时,“没有免费的午餐”定理的实际后果是,没有知识就没有学习。光有数据是不够的。

—第 64 页,主算法,2018 年。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

文章

摘要

在本教程中,您发现了优化和搜索的“没有免费午餐”定理。

具体来说,您了解到:

  • 没有免费的午餐定理表明,在某些特定的约束条件下,所有优化算法的表现都是相同的。
  • 不存在单一的最佳优化算法或机器学习算法。
  • 假设我们对所有可能的目标函数的一个小子集感兴趣,这个定理的实际意义可能是有限的。

你有什么问题吗?
在下面的评论中提问,我会尽力回答。*

机器学习优化速成班

原文:https://machinelearningmastery.com/optimization-for-machine-learning-crash-course/

最后更新于 2021 年 10 月 30 日

机器学习速成班的优化。

7 天内用 Python 找到函数 optima。

所有机器学习模型都涉及优化。作为从业者,我们优化最合适的超参数或特征子集。决策树算法的分割优化。权重的神经网络优化。最有可能的是,我们使用计算算法来优化。

数值优化的方法有很多。SciPy 在这方面有很多便利的功能。我们也可以尝试自己实现优化算法。

在这个速成课程中,您将发现如何在七天内开始并自信地运行算法来使用 Python 优化一个函数。

这是一个又大又重要的岗位。你可能想把它做成书签。

用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

机器学习优化(7 天迷你课程)
图片由布鲁斯特·马列维奇提供,保留部分权利。

这个速成班是给谁的?

在我们开始之前,让我们确保你在正确的地方。

本课程面向可能了解一些应用机器学习的开发人员。也许您已经构建了一些模型,并端到端地完成了一些项目,或者从流行工具的现有示例代码中进行了修改,以解决您自己的问题。

本课程中的课程假设了您的一些情况,例如:

  • 你对编程的基本 Python 很熟悉。
  • 您可能知道一些用于数组操作的基本 NumPy。
  • 你听说过梯度下降、模拟退火、BFGS 或其他一些优化算法,想加深你的理解。

你不需要:

  • 数学天才!
  • 机器学习专家!

这门速成课程将把你从一个懂一点机器学习的开发人员带到一个能有效和胜任地应用函数优化算法的开发人员。

注意:本速成课程假设您有一个至少安装了 NumPy 的工作 Python 3 SciPy 环境。如果您需要环境方面的帮助,可以遵循这里的逐步教程:

速成班概述

这门速成课分为七节课。

您可以每天完成一节课(推荐)或一天内完成所有课程(硬核)。这真的取决于你有多少时间和你的热情程度。

下面是用 Python 进行优化的七个经验教训:

  • 第 01 课:为什么要优化?
  • 第 02 课:网格搜索
  • 第 03 课:SciPy 中的优化算法
  • 第 04 课 : BFGS 算法
  • 第 05 课:爬山算法
  • 第 06 课:模拟退火
  • 第 07 课:梯度下降

每节课可能需要你 60 秒或 30 分钟。慢慢来,按照自己的节奏完成课程。提问,甚至在下面的评论中发布结果。

这些课程可能期望你去发现如何做事。我会给你一些提示,但是每节课的部分要点是迫使你学习去哪里寻找关于 Python 中的算法和最佳工具的帮助。(提示 : 我有这个博客所有的答案;使用搜索框。)

在评论中发布你的结果;我会为你加油的!

坚持住。不要放弃。

第 01 课:为什么要优化?

在本课中,您将发现我们为什么以及何时想要进行优化。

机器学习不同于其他类型的软件项目,因为它在我们应该如何编写程序方面不那么琐碎。编程中一个有趣的例子是编写一个 for 循环来打印从 1 到 100 的数字。您确切地知道您需要一个变量来计数,并且应该有 100 次循环迭代来计数。机器学习中的一个玩具例子是使用神经网络进行回归,但是你不知道你到底需要多少次迭代来训练模型。你可能设置得太少或太多,你没有一个规则来告诉什么是正确的数字。因此,许多人认为机器学习模型是一个黑箱。结果是,虽然模型有许多我们可以调整的变量(例如超参数),但我们不知道什么是正确的值,直到我们测试出来。

在本课中,您将发现为什么机器学习实践者应该学习优化来提高他们的技能和能力。优化在数学上也称为函数优化,旨在定位某个函数的最大值或最小值。对于不同性质的功能,可以应用不同的方法。

机器学习是关于开发预测模型。无论一个模型比另一个更好,我们都有一些评估指标来衡量一个模型在特定数据集下的表现。从这个意义上说,如果我们将创建模型的参数视为输入,将模型的内部算法和相关数据集视为常数,将从模型评估的度量视为输出,那么我们就构建了一个函数。

以决策树为例。我们知道它是一棵二叉树,因为每个中间节点都在问一个是或否的问题。这是不变的,我们无法改变。但是这棵树应该有多深是我们可以控制的超参数。我们允许决策树使用数据中的哪些特征和多少特征是另一回事。这些超参数的不同值将改变决策树模型,从而给出不同的度量,例如分类问题中 k 倍交叉验证的平均准确率。然后我们定义了一个函数,将超参数作为输入,准确率作为输出。

从决策树库的角度来看,一旦您提供了超参数和训练数据,它还可以将它们视为常数,并且选择每个节点上的特征和分割阈值作为输入。度量仍然是这里的输出,因为决策树库共享做出最佳预测的相同目标。因此,该库也定义了一个功能,但不同于上面提到的功能。

这里的函数并不意味着需要在编程语言中明确定义一个函数。概念性的就够了。我们接下来要做的是对输入进行操作,并检查输出,直到我们发现实现了最佳输出。就机器学习而言,最好的意思是

  • 最高的准确度、精确度或召回率
  • 中华民国最大的澳柯币
  • 分类中最大的 F1 分数或回归中的 R 2 分数
  • 最少的错误或日志丢失

或者这一行的其他内容。我们可以通过随机方法(如采样或随机扰动)来操纵输入。我们还可以假设函数具有某些属性,并尝试一系列输入来利用这些属性。当然,我们也可以检查所有可能的输入,当我们用尽可能性时,我们将知道最佳答案。

这些是我们为什么要做优化的基础,它是关于什么的,以及我们如何做。你可能没有注意到,但是训练机器学习模型是在做优化。您也可以显式执行优化来选择特征或微调超参数。如您所见,优化在机器学习中非常有用。

你的任务

在本课中,您必须找到一个机器学习模型,并列出三个可能使用优化或有助于训练和使用该模型的示例。这些可能与上面的一些原因有关,也可能是你自己的个人动机。

在下面的评论中发表你的答案。我想看看你有什么想法。

在下一课中,您将发现如何对任意函数执行网格搜索。

第二课:网格搜索

在本课中,您将发现用于优化的网格搜索。

让我们从这个函数开始:

f ( xy)=xT8】2+yT12】2

这是一个二维输入( xy )一维输出的函数。我们怎么做才能找到这个函数的最小值?换句话说,对于什么 xy ,我们能有最少的 f ( xy )?

不看 f ( xy )是什么,我们可以先假设 xy 在某个有界区域,比如说-5 到+5。然后我们可以在这个范围内检查 xy 的每个组合。如果我们记住 f ( xy )的值,并跟踪我们见过的最少的,那么我们可以在耗尽该区域后找到它的最小值。在 Python 代码中,它是这样的:

from numpy import arange, inf

# objective function
def objective(x, y):
    return x**2.0 + y**2.0

# define range for input
r_min, r_max = -5.0, 5.0
# generate a grid sample from the domain sample = list()
step = 0.1
for x in arange(r_min, r_max+step, step):
    for y in arange(r_min, r_max+step, step):
        sample.append([x,y])
# evaluate the sample
best_eval = inf
best_x, best_y = None, None
for x,y in sample:
    eval = objective(x,y)
    if eval < best_eval:
        best_x = x
        best_y = y
        best_eval = eval
# summarize best solution
print('Best: f(%.5f,%.5f) = %.5f' % (best_x, best_y, best_eval))

该代码从范围-5 的下限扫描到上限+5,每一步的增量为 0.1。这个范围对于 xy 都是一样的。这将创建( xy 对的大量样本。这些样品是由一定范围内的 xy 组合而成。如果我们把它们的坐标画在图形纸上,它们就形成了一个网格,因此我们称之为网格搜索。

用样本网格,然后我们对( xy 的每个样本评估目标函数 f ( xy )。我们跟踪价值,记住我们所见过的最不重要的东西。一旦我们用完了网格上的样本,我们就会想起优化后找到的最少的值。

你的任务

在本课中,您应该查找如何使用 numpy.meshgrid()函数,并重写示例代码。然后可以尝试将目标函数替换为 f ( xyz)=(xy+1)2+z2,这是一个 3D 输入的函数。

在下面的评论中发表你的答案。我想看看你有什么想法。

在下一课中,您将学习如何使用 scipy 优化函数。

第 03 课:SciPy 中的优化算法

在本课中,您将发现如何利用 SciPy 来优化您的功能。

文献中有很多优化算法。每一种都有它的优点和缺点,每一种都适合不同的情况。重用我们在上一课中介绍的相同功能,

f ( xy)=xT8】2+yT12】2

我们可以利用 SciPy 中的一些预定义算法来找到它的最小值。可能最简单的是 NelderMead 算法。该算法基于一系列规则来确定如何探索函数的表面。无需赘述,我们可以简单地调用 SciPy 并应用 Nelder-Mead 算法来寻找函数的最小值:

from scipy.optimize import minimize
from numpy.random import rand

# objective function
def objective(x):
	return x[0]**2.0 + x[1]**2.0

# define range for input
r_min, r_max = -5.0, 5.0
# define the starting point as a random sample from the domain
pt = r_min + rand(2) * (r_max - r_min)
# perform the search
result = minimize(objective, pt, method='nelder-mead')
# summarize the result
print('Status : %s' % result['message'])
print('Total Evaluations: %d' % result['nfev'])
# evaluate solution
solution = result['x']
evaluation = objective(solution)
print('Solution: f(%s) = %.5f' % (solution, evaluation))

在上面的代码中,我们需要用一个向量参数编写我们的函数。因此,实际上该功能变得

f(x【0】,x【1】)=(x【0】)2+(x【1】)2

NelderMead 算法需要一个起点。我们为此选择一个在-5 到+5 范围内的随机点(rand(2)是 numpy 生成 0 到 1 之间的随机坐标对的方法)。函数 minimize()返回一个 OptimizeResult 对象,该对象包含关于可通过键访问的结果的信息。“message”键提供关于搜索成功或失败的人类可读消息,“nfev”键告知优化过程中执行的功能评估的数量。最重要的一个是“x”键,它指定达到最小值的输入值。

NelderMead 算法对于凸函数效果很好,其形状比较光滑,像一个盆。对于更复杂的函数,算法可能停留在局部最优处,但无法找到真正的全局最优。

你的任务

在本课中,您应该用以下内容替换上面示例代码中的目标函数:

from numpy import e, pi, cos, sqrt, exp
def objective(v):
    x, y = v
    return ( -20.0 * exp(-0.2 * sqrt(0.5 * (x**2 + y**2)))
             - exp(0.5 * (cos(2*pi*x)+cos(2*pi*y))) + e + 20 )

这就定义了阿克利函数。全局最小值位于 v=[0,0]。然而,NelderMead 很可能找不到它,因为这个函数有许多局部极小值。试着重复你的代码几次,观察输出。每次运行程序时,您应该会得到不同的输出。

在下面的评论中发表你的答案。我想看看你有什么想法。

在下一课中,您将学习如何使用相同的 SciPy 函数来应用不同的优化算法。

第 04 课:BFGS 算法

在本课中,您将发现如何利用 SciPy 应用 BFGS 算法来优化您的函数。

正如我们在上一课中所看到的,我们可以使用 scipy.optimize 中的 minimize()函数来使用 Nelder-Mead 算法优化函数。这是简单的“模式搜索”算法,不需要知道函数的导数。

一阶导数是指对目标函数进行一次微分。同样,二阶导数是将一阶导数再微分一次。如果我们有目标函数的二阶导数,我们可以应用牛顿法找到它的最佳值。

还有一类优化算法可以从一阶导数逼近二阶导数,并利用逼近来优化目标函数。它们被称为准牛顿法。BFGS 是这个班最有名的一个。

重温我们在前面课程中使用的相同目标函数,

f ( xy)=xT8】2+yT12】2

我们可以看出,一阶导数是:

f=【2】x、2

这是两个分量的向量,因为函数 f ( xy )接收两个分量的向量值( xy )并返回标量值。

如果我们为一阶导数创建一个新函数,我们可以调用 SciPy 并应用 BFGS 算法:

from scipy.optimize import minimize
from numpy.random import rand

# objective function
def objective(x):
	return x[0]**2.0 + x[1]**2.0

# derivative of the objective function
def derivative(x):
	return [x[0] * 2, x[1] * 2]

# define range for input
r_min, r_max = -5.0, 5.0
# define the starting point as a random sample from the domain
pt = r_min + rand(2) * (r_max - r_min)
# perform the bfgs algorithm search
result = minimize(objective, pt, method='BFGS', jac=derivative)
# summarize the result
print('Status : %s' % result['message'])
print('Total Evaluations: %d' % result['nfev'])
# evaluate solution
solution = result['x']
evaluation = objective(solution)
print('Solution: f(%s) = %.5f' % (solution, evaluation))

目标函数的一阶导数被提供给带有“jac”参数的最小化()函数。该自变量以雅可比矩阵命名,这就是我们如何称取一个向量并返回一个向量的函数的一阶导数。BFGS 算法将利用一阶导数来计算黑森矩阵的逆(即向量函数的二阶导数),并利用它来寻找最优解。

除了 BFGS,还有 L-BFGS-B。它是前者的一个版本,使用较少的内存(“L”),并且域被限制在一个区域(“B”)。要使用这个变体,我们只需替换方法的名称:

...
result = minimize(objective, pt, method='L-BFGS-B', jac=derivative)

你的任务

对于本课,您应该创建一个具有更多参数的函数(即函数的向量参数远不止两个分量),并观察 BFGS 和 L-BFGS-B 的表现。您注意到速度的差异了吗?这两种方法的结果有什么不同?如果你的函数不是凸的,但是有很多局部最优解,会发生什么?

在下面的评论中发表你的答案。我想看看你有什么想法。

第五课:爬山算法

在本课中,您将发现如何实现爬山算法并使用它来优化您的功能。

爬山的思想是从目标函数上的一个点开始。然后我们在随机方向上稍微移动点。万一此举让我们找到更好的解决方案,我们会保留新的职位。否则我们和老人呆在一起。在做了足够多的迭代之后,我们应该足够接近这个目标函数的最优值。这种进步之所以被命名,是因为它就像我们在爬山,只要有可能,我们就会朝任何方向向上(或向下)爬。

在 Python 中,我们可以将上面的最小化爬山算法写成一个函数:

from numpy.random import randn

def in_bounds(point, bounds):
	# enumerate all dimensions of the point
	for d in range(len(bounds)):
		# check if out of bounds for this dimension
		if point[d] < bounds[d, 0] or point[d] > bounds[d, 1]:
			return False
	return True

def hillclimbing(objective, bounds, n_iterations, step_size):
	# generate an initial point
	solution = None
	while solution is None or not in_bounds(solution, bounds):
		solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
	# evaluate the initial point
	solution_eval = objective(solution)
	# run the hill climb
	for i in range(n_iterations):
		# take a step
		candidate = None
		while candidate is None or not in_bounds(candidate, bounds):
			candidate = solution + randn(len(bounds)) * step_size
		# evaluate candidate point
		candidte_eval = objective(candidate)
		# check if we should keep the new point
		if candidte_eval <= solution_eval:
			# store the new point
			solution, solution_eval = candidate, candidte_eval
			# report progress
			print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
	return [solution, solution_eval]

这个函数允许传递任何目标函数,只要它接受一个向量并返回一个标量值。“界限”参数应该是 n ×2 维的 numpy 数组,其中 n 是目标函数期望的向量大小。它告诉我们应该寻找最小值的范围的下限和上限。例如,对于期望二维向量(像上一课中的向量)和向量的分量在-5 到+5 之间的目标函数,我们可以如下设置界限:

bounds = np.asarray([[-5.0, 5.0], [-5.0, 5.0]])

这个“爬山”函数将在边界内随机选择一个初始点,然后在迭代中测试目标函数。只要它能找到目标函数产生一个较小的值,解就会被记住,下一个要测试的点会从它的邻域中生成。

你的任务

对于本课,您应该提供自己的目标函数(例如复制上一课的目标函数),设置“n _ 迭代”和“步长”,并应用“爬山”函数来找到最小值。观察算法如何找到解决方案。尝试使用不同的“步长”值,并比较达到最终解的接近度所需的迭代次数。

在下面的评论中发表你的答案。我想看看你有什么想法。

第六课:模拟退火

在本课中,您将发现模拟退火是如何工作的以及如何使用它。

对于非凸函数,你在前几课学到的算法很容易陷入局部最优,找不到全局最优。原因是因为算法的贪婪本质:每当找到更好的解,它就不会放过。因此,如果存在更好的解决方案,但不在附近,算法将无法找到它。

模拟退火试图通过在探索开发之间取得平衡来改善这种行为。在开始时,当算法不太了解要优化的函数时,它更喜欢探索其他解决方案,而不是停留在找到的最佳解决方案上。在后期,随着更多的解被探索,找到更好的解的机会减少了,算法将更喜欢保持在它找到的最佳解的附近。

以下是模拟退火作为 Python 函数的实现:

from numpy.random import randn, rand

def simulated_annealing(objective, bounds, n_iterations, step_size, temp):
	# generate an initial point
	best = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
	# evaluate the initial point
	best_eval = objective(best)
	# current working solution
	curr, curr_eval = best, best_eval
	# run the algorithm
	for i in range(n_iterations):
		# take a step
		candidate = curr + randn(len(bounds)) * step_size
		# evaluate candidate point
		candidate_eval = objective(candidate)
		# check for new best solution
		if candidate_eval < best_eval:
			# store new best point
			best, best_eval = candidate, candidate_eval
			# report progress
			print('>%d f(%s) = %.5f' % (i, best, best_eval))
		# difference between candidate and current point evaluation
		diff = candidate_eval - curr_eval
		# calculate temperature for current epoch
		t = temp / float(i + 1)
		# calculate metropolis acceptance criterion
		metropolis = exp(-diff / t)
		# check if we should keep the new point
		if diff < 0 or rand() < metropolis:
			# store the new current point
			curr, curr_eval = candidate, candidate_eval
	return [best, best_eval]

类似于上一课的爬山算法,该函数从一个随机的起始点开始。同样与上一课类似,该算法在由计数“n_iterations”规定的循环中运行。在每次迭代中,选取当前点的一个随机邻域点,并在其上评估目标函数。在变量“最佳”和“最佳评估”中记住了曾经找到的最佳解决方案。爬山算法的不同之处在于,每次迭代中的当前点“curr”不一定是最佳解。该点是移动到一个邻域还是停留取决于一个概率,该概率与我们所做的迭代次数以及邻域可以做出多大的改进有关。由于这种随机性质,我们有机会走出局部极小值,获得更好的解。最后,不管我们在哪里结束,我们总是返回模拟退火算法迭代中找到的最佳解。

事实上,机器学习中遇到的大多数超参数调整或特征选择问题都不是凸的。因此,模拟退火应该比爬山更适合这些优化问题。

你的任务

对于本课,您应该使用上面的模拟退火代码重复上一课中的练习。试着用目标函数 f ( xy)=xT8】2+y2,这是一个凸函数。你看模拟退火还是爬山迭代少?用第 03 课中介绍的阿克利函数替换目标函数。你看模拟退火或者爬山找到的最小值小吗?

在下面的评论中发表你的答案。我想看看你有什么想法。

第 07 课:梯度下降

在本课中,您将发现如何实现梯度下降算法。

梯度下降算法是用来训练神经网络的算法。虽然有很多变体,但它们都是基于函数的梯度或一阶导数。这个想法在于函数梯度的物理意义。如果函数取一个向量,返回一个标量值,那么函数在任意一点的梯度都会告诉你方向函数增长最快。因此,如果我们的目标是找到函数的最小值,我们应该探索的方向正好与梯度相反。

*在数学方程中,如果我们寻找 f ( x )的最小值,其中 x 是一个向量,而 f ( x )的梯度用∇ f ( x )(也是一个向量)表示,那么我们就知道了

x =x×(x**

**会比 x 更接近最小值。现在让我们尝试在 Python 中实现这一点。重复使用我们在第 4 天学习的样本目标函数及其导数,这是梯度下降算法及其用于寻找目标函数最小值的用法:

from numpy import asarray
from numpy import arange
from numpy.random import rand

# objective function
def objective(x):
	return x[0]**2.0 + x[1]**2.0

# derivative of the objective function
def derivative(x):
	return asarray([x[0]*2, x[1]*2])

# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size):
	# generate an initial point
	solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
	# run the gradient descent
	for i in range(n_iter):
		# calculate gradient
		gradient = derivative(solution)
		# take a step
		solution = solution - step_size * gradient
		# evaluate candidate point
		solution_eval = objective(solution)
		# report progress
		print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
	return [solution, solution_eval]

# define range for input
bounds = asarray([[-5.0, 5.0], [-5.0, 5.0]])
# define the total iterations
n_iter = 40
# define the step size
step_size = 0.1
# perform the gradient descent search
solution, solution_eval = gradient_descent(objective, derivative, bounds, n_iter, step_size)
print("Solution: f(%s) = %.5f" % (solution, solution_eval))

该算法不仅依赖于目标函数,还依赖于其导数。因此,它可能不适合所有类型的问题。该算法对步长也很敏感,相对于目标函数过大的步长可能导致梯度下降算法无法收敛。如果发生这种情况,我们将看到进展并没有朝着更低的价值发展。

有几种变体可以使梯度下降算法更加稳健,例如:

  • 在过程中加入一个动量,这个移动不仅是在跟随梯度,也是前几次迭代中梯度的部分平均值。
  • 使向量的每个分量的步长不同 x
  • 使步长适应进度

你的任务

对于本课,您应该使用不同的“步长”和“n_iter”运行上面的示例程序,并观察算法进度的差异。在什么“步长”下你会看到上面的程序不收敛?然后尝试在 gradient _ download()函数中添加一个新参数 β 作为动量权重,现在更新规则变成了

x 【新】=【f】)**

***其中 g 是例如前五次迭代中∇ f ( x )的平均值。你看到这个优化有什么改进吗?是不是利用动量的合适例子?

在下面的评论中发表你的答案。我想看看你有什么想法。

这是最后一课。

末日!

( 看你走了多远

你成功了。干得好!

花一点时间,回头看看你已经走了多远。

你发现了:

  • 优化在应用机器学习中的重要性。
  • 如何通过穷尽所有可能的解决方案来进行网格搜索优化?
  • 如何用 SciPy 优化自己的功能?
  • 如何实现爬山算法进行优化?
  • 如何利用模拟退火算法进行优化?
  • 什么是梯度下降,如何使用,以及该算法的一些变体。

摘要

你觉得迷你课程怎么样?
你喜欢这个速成班吗?

你有什么问题吗?有什么症结吗?
让我知道。请在下面留言。******

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值