ML-Andrew Ng 学习笔记(2) Cost Function & Gradient Descent

笔者今年只有大一,虽然课程内有讲ml但是觉得不应止于此,想额外学一部分,故见解不到位的也请谅解

以下将会讨论成本函数(cost function)和梯度下降(gradient descent)的相关问题。

A.成本函数:用于衡量假设函数h(x)准确性的工具

给出一份数据,能够拟合出多条直线,有着多组θ_0, θ_1,但为了更精准地预测,需要找到其中一组,使得数据点尽可能多地落在直线上或者其附近,因此需要成本函数。

cost function
cost function

**     举例**:单变量线性回归(univariate linear regression)

单变量是对于x而言的
单变量是对于x而言的
to minimize
to minimize
  • x_i, y_i为第i组的样本

  • 1/2m 作用仅仅是对后面整体求导时,作为指数的2乘下来与1/2抵消,不会影响到求θ的最小值

    "To make the derivations mathematically more convenient"

cost function (or Squared error function)
cost function (or Squared error function)

**     几何理解:**

对于简化版h(x)=θ_1*x,

post image

实质上是各组h(x_1)=θ_1*x_1与真实值y_1之间高度差绝对值的平方之和乘1/2样本数量

**     可视化:**

对于h(x)=θ_0+θ_1*x,

凸函数(convex function)
凸函数(convex function)
  • 对于线性回归总会是这样的凸函数,只有全局最优解

contour plots
contour plots
  • 右图线上的点虽然有着不同的θ_1, θ_0,但对应的J值是相同的

  • 可看做上方凸函数图像的各个截面

  • 椭圆最中心的点即为成本函数值最小处

计算cost(梯度下降步骤1):

equation 2
equation 2
def compute_cost(x,y,w,b):    #equation 2 (for univariate)
    m = x.shape[0]
    cost = 0
    
    for i in range(m):
        f_wb = w * x[i] + b   #one feature
        cost = cost + (f_wb - y[i])** 2
    total_cost = 1/(2 * m) * cost
    
    return total_cost
def compute_cost(X, y, w, b):   #equation 2 (for multi-var)
    m = X.shape[0]
    cost = 0.0
    
    for i in range(m):
        f_wb_i = np.dot(X[i], w) + b  #multiple features
        cost = cost + (f_wb_i - y[i]) **2
    cost = cost / (2 * m)
    return cost

这里只能算出数据集中总的cost值,但如何具体得到使J值最小的θ_1, θ_0,需要用到梯度下降

B.梯度下降:是为了找到目标点的一种方法,透过一步步靠近目标的方式,最终找到极近似目标的函数

已有的和目标
已有的和目标

**     可视化说明1:**

想要从山顶想要最快速地走到山脚,因此需要找到最陡峭的路前进,对于代价函数来说,路即为斜率k

示意图
示意图
  • 但存在问题:出发点稍微偏离一点,便可以得到完全不同的局部最优解

    “实际应用中,评估所有可能的参数通常不能找到全局最优解。而是使用优化算法找到解,虽然可能不一定是全局最优的,但足够满足目标。另一种办法是用更高级的技术,如SGD或者Adam。”

**     具体算法**:

单一特征或多特征(也就是x的数量)
单一特征或多特征(也就是x的数量)
  • α学习速率(learning rate),改变该值对应着下坡时的跨度

  • 对***θ_1, θ_0 (w,b)同步更新*(simultaneously update)

    • 个人理解: θ_1, θ_0可以看作下坡时的向左向右,减去的α和导数项之积对于θ_1, θ_0是完全相同的,即重新调整的方向的跨度是相同的

正确处理
正确处理
错误处理:这样处理的问题在于,计算θ
错误处理:这样处理的问题在于,计算θ

计算gradient(梯度下降步骤2)

equation 4 & 5
equation 4 & 5
def compute_gradient (x,y,w,b):    #equation 4,5 (for univariate)
    m = x.shape[0]
    dj_dw = 0
    dj_db = 0
    
    for i in range(m):
        f_wb = w * x[i] + b
        dj_dw_i = (f_wb - y[i]) * x[i]
        dj_db_i = f_wb - y[i]
        dj_db += dj_db_i
        dj_dw += dj_dw_i
    dj_dw = dj_dw /m
    dj_db = dj_db /m
    
    return dj_dw , dj_db
def compute_gradient(X, y, w, b):  #equation 4,5 (for multi-var)   
    m,n = X.shape           #(# of examples, # of features)
    dj_dw = np.zeros((n,))
    dj_db = 0.

    for i in range(m):                             
        err = (np.dot(X[i], w) + b) - y[i]   
        for j in range(n):                         
            dj_dw[j] = dj_dw[j] + err * X[i, j]    
        dj_db = dj_db + err                        
    dj_dw = dj_dw / m                                
    dj_db = dj_db / m                                
        
    return dj_db, dj_dw

现在有了计算costgradient的函数,就可以进行descent的过程

梯度下降步骤3:

equation 3
equation 3

#equation 3
def gradient_descent(x, y, w_in, b_in, alpha, num_iters, cost_function, gradient_function):  
    """ 
    Args:
      x (ndarray (m,))  : Data, m examples 
      y (ndarray (m,))  : target values
      w_in,b_in (scalar): initial values of model parameters           
         
    Returns:
      w (scalar): Updated value 
      b (scalar): Updated value 
      J_history (List): History of cost values
      p_history (list): History of parameters [w,b] 
      """
    
    w = copy.deepcopy(w_in)   # 避免影响w_in。深拷贝,确保每个对象的独立性,从而避免在原对象和副本之间共享对象所导致的潜在问题)
    
    J_history = []
    p_history = []
    b = b_in
    w = w_in
    
    for i in range(num_iters):
        
        dj_dw, dj_db = gradient_function(x, y, w , b)     

        # Update Parameters using equation (3) above
        b = b - alpha * dj_db                            
        w = w - alpha * dj_dw                            

        # Save cost J at each iteration
        if i<100000:      # prevent resource exhaustion 
            J_history.append( cost_function(x, y, w , b))  #返回total_cost
            p_history.append([w,b])

        # 控制代码的执行频率,确保某些代码只在特定的迭代次数执行。        
        if i % math.ceil(num_iters/10) == 0: 
            print(f"Iteration {i:4}: Cost {J_history[-1]:0.2e} ",
                  f"dj_dw: {dj_dw: 0.3e}, dj_db: {dj_db: 0.3e} ",
                  f"w: {w: 0.3e}, b:{b: 0.5e}")
 
    return w, b, J_history, p_history #return for graphing
def gradient_descent(X, y, w_in, b_in, cost_function, gradient_function, alpha, num_iters): 
    """ 
    Args:
      X (ndarray (m,n))   : Data, m examples with n features
      y (ndarray (m,))    : target values
      w_in (ndarray (n,)), b_in (scalar) : initial parameters  
   
    Returns:
      w (ndarray (n,)) : Updated values 
      b (scalar)       : Updated value 
      """
    
    # An array to store cost J and w's at each iteration primarily for graphing later
    J_history = []
    w = copy.deepcopy(w_in)  #avoid modifying global w within function
    b = b_in
    
    for i in range(num_iters):
  
        dj_db,dj_dw = gradient_function(X, y, w, b)   

        w = w - alpha * dj_dw              
        b = b - alpha * dj_db               
      
        # Save cost J at each iteration
        if i<100000:      # prevent resource exhaustion 
            J_history.append( cost_function(X, y, w, b))

        # 控制代码的执行频率,确保某些代码只在特定的迭代次数执行。 
        if i% math.ceil(num_iters / 10) == 0:
            print(f"Iteration {i:4d}: Cost {J_history[-1]:8.2f}  ")
        
    return w, b, J_history #return for graphing

可视化说明2:

a)第一种情况:斜率为正值

正斜率时
正斜率时
  • θ进行更新时,由于斜率为正,对应的导数为正数,因此减去该值后是在对θ向后调整

  • 每次调整后会得到新的斜率,新的斜率相对于调整前的会更小,因此新的调整幅度会更小最终找到局部最优解

b)第二种情况:斜率为负值

负斜率时
负斜率时
  • θ进行更新时,由于斜率为负,对应的导数为负数,因此减去该值后是在对θ向前调整

  • 新的调整幅度同样会越来越小,最终逼近目标

c)第三种情况:斜率为0,已经处在局部或者全局最优处时

斜率为0
斜率为0
  • 此时对θ更新时,后面的减数由于斜率为0,因此θ的值不变,这也能说明已经达到最优点

**     对α的选取:**

a)不应过小

过小时对于下降过于缓慢,甚至可能会出现几乎停滞的状态
过小时对于下降过于缓慢,甚至可能会出现几乎停滞的状态

b)不应过大

过大时跨步过大,会越来越远离目标点
过大时跨步过大,会越来越远离目标点

假设绿点为初始点,此时的导数值为一个较小的负值,但由于α过大,导致绿点对应的θ加上了一个比较大的数,得到紫点;

紫点对应的导数值为一个较大的正值,同样由于α过大,使得此时的θ减去一个更大的数字,得到橘点;

不断地滚雪球最终离目标点越来越远

(本来想着具体算算,纸上画图、用desmos画图,结果研究了半天,纸上演算一整发现好麻烦,结果用脑子想想道理其实好简单==)

c) 可能的问题

学习率过大可能会导致,或者代码有误,如:w_1 = w_1 +αd_1
学习率过大可能会导致,或者代码有误,如:w_1 = w_1 +αd_1

因此可以先使用非常小的α迭代数次,确定图像趋势正确后,再提高

post image

对梯度下降的优化:

特征放缩(Feature Scaling):

  • 使下降速度更快

  • 能够消除特征之间的量纲差异性,使得不同特征更具有可比性

    (size in feet量级可能是k,而#bedrooms则是个位数,前者会对结果产生较大影响,后者则有可能会被忽略)

  • 同时也能让数据足够离散(不是主要目的)

放缩与不放缩的对比
放缩与不放缩的对比

方案1:除以取值范围最大值

300≤x1≤2000
300≤x1≤2000

方案2:均值归一化(Mean Normalization)

300≤ x1≤2000
300≤ x1≤2000

方案3:Z-score 标准化

300≤ x1≤2000
300≤ x1≤2000

但放缩结果并无强制要求,接近-1≤ x_j ≤ 1的范围

z-score normalization处理
z-score normalization处理

c)固定?不固定?

α不变的情况下随着与其相乘的导数值不断变化,整体是一直动态调整的,所以在最开始设置好后不会出现上述过大过小的情况的话,其实没有必要在训练过程中调整α?

“通常在训练开始前设置好并在过程中保存不变,但对于某些情况可能需要在期间进行调整。”

梯度下降的种类:

a) 批量梯度下降法(Batch Gradient Descent)

  • 对整个样本集进行遍历,更新参数时使用所以的样本来完成这一操作

    batch *n.*一批,一批生产量;*v.*分批处理

b)随机梯度下降法(Stochastic Gradient Descent)

  • 选用一个样本进行梯度下降,对于过大样本使用a方法训练速度过慢

  • 但解不一定最优

c)小批量梯度下降法(Mini-batch Gradient Descent)

  • 上述两种的折衷

细分的种类以后课程内或者课程外学到了再新补充吧

参考来源:

https://datascience.stackexchange.com/questions/52157/why-do-we-have-to-divide-by-2-in-the-ml-squared-error-cost-function

https://www.cnblogs.com/pinard/p/5970503.html

https://www.coursera.org/learn/machine-learning