第1关:模型优化基础
任务描述
相关知识
什么是模型优化?
常见的模型优化方法
编程要求
测试说明
任务描述
模型优化在一些复杂的训练模型中是不可避免的,本关主要是让大家对模型优化的方法有个基本的了解。
本关任务:根据相关知识,完成右侧选择题任务。
相关知识
我们每个人都会在我们的生活或者工作中遇到各种各样的最优化问题,比如每个企业和个人都要考虑的一个问题“在一定成本下,如何使利润最大化”等。
最优化方法是一种数学方法,它是研究在给定约束之下如何寻求某些因素(的量),以使某一(或某些)指标达到最优的一些学科的总称。
随着学习的深入,我们会发现学习和工作中遇到的大多问题都可以建模成一种最优化模型进行求解,比如我们现在学习的机器学习算法,大部分的机器学习算法的本质都是建立优化模型,通过最优化方法对目标函数(或损失函数)进行优化,从而训练出最好的模型。
什么是模型优化?
模型优化问题:为某种特定的情形寻找最佳模型。举个例子,给定数据集上的模型残差最小时,即为“最佳”。下面是对模型优化的介绍视频:
课程视频《模型优化》
实际应用举例:用多项式函数建模一台家用电器的功耗和运转时间之间的关系。
(1) 使用一次函数建模:y=kx+d
(2) 使用二次函数建模:y=px
2
+qx+t,实际上,二次函数可能是y=x
2
+2x+3或者y=2x
2
+5x+1等等。
(3) 使用三次函数建模:y=ax
3
+bx
2
+cx+t
假设我们选择三次函数建模,那么怎么判定哪一个是最优化模型呢?我们通常采用函数残差作为评价标准。也就是说在y=wx
3
+px
2
+qx+t中哪个函数(哪组参数 [w,p,q,t] )最好?这就是最优化问题,确定了 wpqt 之后,我们就确定了最终的模型。
常见的模型优化方法
常见的最优化方法有:
梯度下降法;
梯度下降法的优化思想是用当前位置负梯度方向作为搜索方向,因为该方向为当前位置的最快下降方向,所以也被称为是“最速下降法“最速下降法越接近目标值,步长越小,前进越慢。
牛顿法和拟牛顿法;
牛顿法是一种在实数域和复数域上近似求解方程的方法,拟牛顿法的本质思想是改善牛顿法每次需要求解复杂的 Hessian 矩阵的逆矩阵的缺陷,它使用正定矩阵来近似 Hessian 矩阵的逆,从而简化了运算的复杂度。
共轭梯度法;
共轭梯度法是介于最速下降法与牛顿法之间的一个方法,它仅需利用一阶导数信息,但克服了最速下降法收敛慢的缺点,又避免了牛顿法需要存储和计算海塞矩阵并求逆的缺点,共轭梯度法不仅是解决大型线性方程组最有用的方法之一,也是解大型非线性最优化最有效的算法之一。
启发式优化方法;
启发式方法指人在解决问题时所采取的一种根据经验规则进行发现的方法。其特点是在解决问题时,利用过去的经验,选择已经行之有效的方法,而不是系统地,以确定的步骤去寻求答案。启发式优化方法种类繁多,包括经典的模拟退火方法,遗传算法,蚁群算法以及粒子群算法等等。
解决约束优化问题 - 拉格朗日乘数法。
作为一种优化算法,拉格朗日乘子法主要用于解决约束优化问题,它的基本思想就是通过引入拉格朗日乘子来将含有 n 个变量和 k 个约束条件的约束优化问题转化为含有 (n+k) 个变量的无约束优化问题。拉格朗日乘子背后的数学意义是其为约束方程梯度线性组合中每个向量的系数。
在下面的关卡中,我们会详细介绍使用最为频繁的梯度下降法。
编程要求
根据相关知识,按照要求完成右侧选择题任务。
测试说明
平台会对你选择的答案进行判断,全对则通过测试。
开始你的任务吧,祝你成功!
BCD A
第2关:梯度下降
任务描述
相关知识
梯度下降的思想
估算梯度
选择正确步长
随机梯度下降法
编程要求
测试说明
任务描述
本关任务:使用梯度下降拟合输入数据 x,y 。
相关知识
从事数据科学工作时,常常会面临这样的需要:为某种特定的情形寻找最佳模型。“最佳”常常会被解读为某种类似于“最小化模型残差”或者“最大化数据的可能性”。换句话说,它代表了优化某种问题的解决方案。
这意味着我们需要解决一连串的最优化问题。我们采用的方法是一种叫作梯度下降( gradient descent )的技术,下面是对梯度下降的介绍视频:
课程视频《梯度下降》
梯度下降的思想
假设我们拥有某个函数 f ,这个函数输入一个实数向量,输出一个实数。一个简单的例子如下:
def sum_of_squares(v):
return sum(v_i ** 2 for v_i in v)
我们常常需要最大化(或最小化)这个函数。这意味着我们需要找出能计算出最大(或最小)可能值的输入 v。对我们的函数来说,梯度给出了输入值的方向,在这个方向上,函数增长得最快。
相应地,最大化函数的算法首先从一个随机初始点开始,计算梯度,在梯度方向上跨越一小步,再从一个新的初始点开始重复这个过程。同样,你也可以在相反方向上逐步最小化函数,如下图所示:
需要注意,如果一个函数有一个全局最小点,那么梯度下降很可能会找到它。但是函数有多个(局部)最小点,那么梯度下降可能找不到这个点,但是可以通过多尝试一些初始点来重复运行。如果一个函数没有最小点,计算也许会陷入死循环。
估算梯度
如果 f 是单变量函数,那么它在 x 点的导数衡量了当 x 发生变化时,f(x) 变化了多少。导数通过差商的极限来定义:
def difference_quotient(f, x, h):
return (f(x + h) - f(x)) / h
其中 h 趋近于 0 。如上图所示:导数就是在点 (x, f (x)) 的切线的斜率,而差商就是通过点 (x, f (x)) 和点 (x+h, f (x+h)) 的割线的斜率。当 h 越来越小,割线与切线就越来越接近。
很多函数可以精确地计算导数,比如平方函数 square :
def square(x):#计算平方
return x * x
它的导数为:
def derivative(x):
return 2 * x
python 中无法直接运算极限,但可以通过计算一个很小的变动 e 的差商来估算微分。
import matplotlib.pyplot as plt
from functools import partial
plt.rcParams[‘font.sans-serif’]=[‘simhei’]
plt.rcParams[‘font.family’]=‘sans-serif’
plt.rcParams[‘axes.unicode_minus’]=False #画图可中文
derivative_estimate = partial(difference_quotient, square, h=0.00001)
绘出导入matplotlib.pyplot作为plt的基本相同的形态
x = range(-10,10)
plt.title(“精确的导数值与估计值”)
plt.plot(x, list(map(derivative, x)), ‘rx’, label=‘Actual’) # 用 x 表示
plt.plot(x, list(map(derivative_estimate, x)), ‘b+’, label=‘Estimate’) # 用 + 表示
plt.legend(loc=9)
plt.show()
当 f 是一个多变量函数时,它有多个偏导数,每个偏导数表示仅有一个输入变量发生微小变化时函数 f 的变化。我们把导数看成是其第 i 个变量的函数,其他变量保持不变,以此来计算它第 i 个偏导数:
def partial_difference_quotient(f, v, i, h):
w = [v_j + (h if j == i else 0) # 只对v的第i个元素增加h
for j, v_j in enumerate(v)]
return (f(w) - f(v)) / h
再以同样的方法估算它的梯度函数:
def estimate_gradient(f, v, h=0.00001):
return [partial_difference_quotient(f, v, i, h)
for i, _ in enumerate(v)]
选择正确步长
尽管向梯度的反向移动的逻辑已经清楚了,但移动多少还不明了。事实上,选择合适的步长更像艺术而非科学。主流的选择方法有:
使用固定步长;
随时间增长逐步减小步长;
在每一步中通过最小化目标函数的值来选择合适的步长。
随机梯度下降法
通常而言,我们有一些 target_fn 函数,需要对其进行最小化,也有梯度函数 gradient_fn 。比如,函数 target_fn 可能代表模型的残差,它是参数的函数。我们可能需要找到能使残差尽可能小的参数。此外,假设我们(以某种方式)为参数 theta_0 设定了某个初始值,那么可以如下使用批量梯度下降:
def minimize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
step_sizes = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001, 0.00001]
theta = theta_0 # 设定theta为初始值
target_fn = safe(target_fn) # target_fn的安全版
value = target_fn(theta) # 我们试图最小化的值
while True:
gradient = gradient_fn(theta)
next_thetas = [step(theta, gradient, -step_size)
for step_size in step_sizes]
选择一个使残差函数最小的值
next_theta = min(next_thetas, key=target_fn)
next_value = target_fn(next_theta)
当“收敛”时停止
if abs(value - next_value) < tolerance:
return theta
else:
theta, value = next_theta, next_value
有时候,我们需要最大化某个函数,这只需要最小化这个函数的负值(相应的梯度函数也需取负):
def negate(f):
return lambda *args, **kwargs: -f(*args, **kwargs)
def negate_all(f):
return lambda *args, **kwargs: [-y for y in f(*args, **kwargs)]
def maximize_batch(target_fn, gradient_fn, theta_0, tolerance=0.000001):
return minimize_batch(negate(target_fn),negate_all(gradient_fn),theta_0,tolerance)
在每一步梯度计算中,都会搜索整个数据集,这使每一步都会耗费很长的时间。现在,这些残差函数常常具有可加性,意味着整个数据集上的预测残差恰好是每个数据点的预测残差之和。
在这种情形下,我们转而使用一种称为随机梯度下降的技术,它每次仅计算一个点的梯度,这个计算会反复循环,直到达到一个停止点。在每个循环中,我们都会在整个数据集上按照一个随机序列迭代:
def in_random_order(data):
indexes = [i for i, _ in enumerate(data)] # 生成索引列表
random.shuffle(indexes) # 随机打乱数据
for i in indexes: # 返回序列中的数据
yield data[i]
我们对每个数据点都会进行一步梯度计算。这种方法留有这样一种可能性,即也许会在最小值附近一直循环下去,所以,每当停止获得改进,我们都会减小步长并最终退出:
def minimize_stochastic(target_fn, gradient_fn, x, y, theta_0, alpha_0=0.01):
data = zip(x, y)
theta = theta_0 # 初始值猜测
alpha = alpha_0 # 初始步长
min_theta, min_value = None, float(“inf”) # 迄今为止的最小值
iterations_with_no_improvement = 0
如果循环超过100次仍无改进,停止
while iterations_with_no_improvement < 100:
value = sum( target_fn(x_i, y_i, theta) for x_i, y_i in data )
if value < min_value:
# 如果找到新的最小值,记住它
# 并返回到最初的步长
min_theta, min_value = theta, value
iterations_with_no_improvement = 0
alpha = alpha_0
else:
# 尝试缩小步长,否则没有改进
iterations_with_no_improvement += 1
alpha *= 0.9
在每个数据点上向梯度方向前进一步
for x_i, y_i in in_random_order(data):
gradient_i = gradient_fn(x_i, y_i, theta)
theta = vector_subt\fract(theta, scalar_multiply(alpha, gradient_i))
return min_theta
随机化通常比批处理化快很多。当然,我们也希望获得最大化的结果:
def maximize_stochastic(target_fn, gradient_fn, x, y, theta_0, alpha_0=0.01):
return minimize_stochastic(negate(target_fn),negate_all(gradient_fn),x, y, theta_0, alpha_0)
编程要求
请仔细阅读右侧代码,结合相关知识,在 Begin-End 区域内进行代码补充,根据输入数据 x,y 使用批梯度下降计算出 a,b 的值。公式为:
y=a∗x+b
测试说明
平台会对你编写的代码进行测试,为了评判准确请将结果四舍五入转换为整数:
测试输入:
[5,9,12,35,21,16,7,33]
[25, 37, 46, 115, 73, 58, 31, 109]
预期输出:
3
10
开始你的任务吧,祝你成功!
def student(x, y):
epsilon = 0.00001 # 迭代阀值,当两次迭代损失函数之差小于该阀值时停止迭代
alpha = 0.01 # 学习率
a = 0
b = 0
m = len(x)
error0 = 0
error1 = 0
# ********* Begin *********#
while True:
# 参数迭代计算
for i in range(m):
# 计算残差
diff1 = ((a * x[i] + b) - y[i]) * x[i] * (2 / m)
diff2 = ((a * x[i] + b) - y[i]) * (2 / m)
a -= alpha * diff1
b -= alpha * diff2
# 计算损失函数
error1 = 0
for lp in range(len(x)):
error1 += (y[lp] - (a * x[lp] + b))
if abs(error1 - error0) < epsilon:
print(int(round(a)))
print(int(round(b)))
break
else:
error0 = error1
# ********* End *********#