梯度下降算法是众多人工智能算法的基石,它究竟有何特殊之处呢?
梯度下降的主要步骤
1.定义预测函数
首先,我们要确定一个小目标预测函数,这是机器学习的一个常见任务。通过学习算法,我们可以自动地发现数据背后的规律,并不断改进模型以做出更准确的预测。为了更好地理解,我们举一个简单的例子:在二维直角坐标系中,我们有一组样本点,横纵坐标分别代表一组有因果关系的变量,比如房房价和面积。常识告诉我们,它们的分布应该是正比例的,也就是一条通过原点的直线,即 y = w * x。我们的任务是设计一个算法,使机器能够拟合这些数据,从而帮助我们计算出直线的参数 w。一个简单的方法是,先随机选取一条通过原点的直线,然后计算所有样本点与该直线的偏离程度。接着,我们可以根据误差的大小来调整直线的斜率 w。总之,在这个问题中,直线 y = w * x 就是我们所谓的预测函数。
2.计算梯度
其次,我们需要量化数据的偏离程度,也就是所谓的误差。在这方面,最常见的方法是使用 均方误差,其含义是将误差的平方和求取平均值。设第一个点 为p1。这个点的坐标为 x1 和 y1,相应的误差记为 e1,以此类推,最终我们可以得到
image.png image.png image.png image.png这个就是误差函数,意思是学习所需要付出的代价。
因为二次项系数 a 是 x 的平方和大于 0,所以这个函数所对应的图像是一个开口向上的抛物线。当 w 的取值发生变化时,相应的直线会绕着原点进行旋转。在抛物线图像中,这相当于取值点沿着曲线的运动轨迹。通过定义预测函数,并根据误差公式推导出代价函数,我们成功地将样本点的拟合过程映射到了一个函数图像上。
在找到代价函数的图像后,接下来的问题是,我们应该朝着哪个方向前进呢?我们的目标是找到最贴近训练数据分布的直线,也就是寻找使误差代价最小的参数 w。在代价函数图像上,这相当于找到了代价函数的最低点。寻找这一最低点的过程,就是梯度下降所要执行的任务。设想在曲线上的任意一点作为起始点,根据我们的经验,选择朝着陡峭程度最大的方向前进,以更快地接近最低点。这种陡峭程度就是梯度,也就是导数或斜率。
3.设置学习率
接下来,我们需要考虑学习率。一旦方向确定,就需要前进了,但是步子应该迈得多大呢?
以往的研究结论告诉我们,如果步子太大,会导致左右反复跳动;而如果步子太小,则会在计算和时间方面付出较大的代价。
4.重复第2、第3步直到找到最低点
这个流程就是所谓的梯度下降算法
为什么不直接求解?
也许你在这里会产生一些疑问,既然我们已经知道代价函数是一个一元二次抛物线,那为什么不使用数学方法直接求解呢?
这是因为在实际问题中,训练样本的分布千差万别,而代价函数的形态也可能变化多端。绝大多数的时候它不仅仅是一条简单的抛物线。
比如我们上面的例子,房价不仅仅和面积有关,还有地段、朝向、周围配套设施有关,甚至是当时的政策或销售的颜值都有关。
代价函数可能会在10维、甚至更高维度中变化。这使得将其可视化展示出来变得十分困难。但无论维度有多高,我们都可以通过梯度下降法来寻找使误差最小化的点。
示例代码
# -*- coding: utf-8 -*-
import numpy as np
def create_data():
data_array = np.array([
[1.0, 15.0],
[2.0, 18.0],
[3.0, 21.0],
[4.0, 24.0],
[5.0, 27.0],
[6.0, 30.0],
[7.0, 33.0],
[8.0, 36.0],
[9.0, 39.0],
[10.0, 42.0],
[11.0, 45.0],
[12.0, 48.0],
[13.0, 51.0],
[14.0, 54.0],
[15.0, 57.0]
])
x_value_array = data_array[:, 0:1]
y_value_array = data_array[:, 1:2]
return x_value_array, y_value_array
class my_train_class:
# 初始化方法
def __init__(self, k, b, learning_rate = 0.01, show_log = False) -> None:
self.k = k
self.b = b
self.learning_rate = learning_rate
self.show_log = show_log
# 计算预测值
def get_calculat_value_array(self, x_value_array):
calculate_value_array = np.dot(x_value_array, self.k) + self.b
if self.show_log:
print(f"x_value_array == {x_value_array}")
print(f"calculate_value_array == {calculate_value_array}")
return calculate_value_array
# 计算均值平方
def get_MSE(self, reality_value_array, calculate_value_array):
if len(reality_value_array) != len(calculate_value_array):
raise Exception('2个数组个数不一致')
difference = reality_value_array - calculate_value_array
mse = np.mean(difference * difference)
if self.show_log:
print(f"MSE == {mse}")
return mse
# 计算梯度
def gradient(self, x_value_array, reality_value_array, calculate_value_array):
if len(reality_value_array) != len(calculate_value_array):
raise Exception('reality_value_array 和 calculate_value_array 个数不一致')
dk = np.mean((calculate_value_array - reality_value_array) * x_value_array)
db = np.mean(calculate_value_array - reality_value_array)
if self.show_log:
print(f"dk == {dk}")
print(f"db == {db}")
return dk, db
# 更新k和b的值
def update_param(self, dk, db):
self.k = self.k - self.learning_rate * dk
self.b = self.b - self.learning_rate * db
# 训练
def train(self, x_value_array, y_value_array, trainCount = 2000):
for i in range(trainCount):
# 获取预测值的数组
calculate_array = self.get_calculat_value_array(x_value_array)
# 计算均值平方
mse = self.get_MSE(y_value_array, calculate_array)
# 计算梯度
dk, db = self.gradient(x_value_array, y_value_array, calculate_array)
# 更新k和b的值
self.update_param(dk, db)
if(i % 1000 == 0):
print('第{}次mse:'.format(i+1), mse)
print('第{}次梯度值:'.format(i+1), dk, db)
print('第{}次k和b的值:'.format(i+1), self.k, self.b, "\n")
print(f"训练结束, k == {self.k}, b == {self.b}")
x_value_array, y_value_array = create_data()
train_obj = my_train_class(1, 1, show_log = False)
train_obj.train(x_value_array, y_value_array, len(x_value_array) * 600)
网友评论