梯度下降算法

梯度下降算法是众多人工智能算法的基石,它究竟有何特殊之处呢?

梯度下降的主要步骤

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 的取值发生变化时,相应的直线会绕着原点进行旋转。在抛物线图像中,这相当于取值点沿着曲线的运动轨迹。通过定义预测函数,并根据误差公式推导出代价函数,我们成功地将样本点的拟合过程映射到了一个函数图像上。

image.png

在找到代价函数的图像后,接下来的问题是,我们应该朝着哪个方向前进呢?我们的目标是找到最贴近训练数据分布的直线,也就是寻找使误差代价最小的参数 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)

结果

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容