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

By [jasonyang](https://paragraph.com/@jasonyang) · 2023-04-27

---

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

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

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

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

![cost function](https://storage.googleapis.com/papyrus_images/8d2c95c6dff6a32f1628a5d9126962e7a70b9962d77b05ba12c58ff0a0dc95f5.png)

cost function

\*\*     举例\*\*：**单变量线性回归（univariate linear regression）**

![单变量是对于x而言的](https://storage.googleapis.com/papyrus_images/6ec781a7eaae649b440c38fc1c7ea1d48df2a39c48b4b3a536b53ea189ebd9ce.png)

单变量是对于x而言的

![to minimize](https://storage.googleapis.com/papyrus_images/f92c746fbc592e2c8cb0276614583bc415b4720ea227e0b4ad88be710da949ee.png)

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)](https://storage.googleapis.com/papyrus_images/233649ebf76df407b678eed32a0829fd88754dcbe2caeaafb5b98a19991da940.png)

cost function (or Squared error function)

\*\*     几何理解：\*\*

对于简化版**_h(x)=θ\_1\*x,_**

![](https://storage.googleapis.com/papyrus_images/2d915bae40fc5e967f233c24ff757c41b57bfa0047fbb82c9d8b1dcb8847ab2c.png)

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

\*\*     可视化：\*\*

对于**_h(x)=θ\_0+θ\_1\*x,_**

![凸函数（convex function）](https://storage.googleapis.com/papyrus_images/211fc619be50d227e2bf8defc5edf4d32de1ca4ef9edda2e417058c087546d61.png)

凸函数（convex function）

*   对于**线性回归**总会是这样的凸函数，只有全局最优解
    

![contour plots](https://storage.googleapis.com/papyrus_images/c325604ccc55dea9ec1c29c35d1f6c1eec0e64deb7a1a640dab2ca4f9c6929d3.png)

contour plots

*   右图线上的点虽然有着不同的**_θ\_1, θ\_0_**，但对应的**_J_**值是相同的
    
*   可看做上方凸函数图像的各个截面
    
*   椭圆最中心的点即为成本函数值最小处
    

**计算cost（梯度下降步骤1）：**

![equation 2](https://storage.googleapis.com/papyrus_images/f972d530c42532adc1f973526154b6a5d679458259392118d4ac2423040c7341.png)

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.梯度下降：是为了找到目标点的一种方法，透过一步步靠近目标的方式，最终找到极近似目标的函数

![已有的和目标](https://storage.googleapis.com/papyrus_images/106a9664d87ebf4f15c0ef7bbf6da3ae620fbcc93ceb8b71aa3001208642a97c.png)

已有的和目标

\*\*     可视化说明1：\*\*

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

![示意图](https://storage.googleapis.com/papyrus_images/3bc1686a2dee050b81e2372b35402f38d16eaa45f1d82ff4ace2ab114a127c55.png)

示意图

*   但存在问题：出发点稍微偏离一点，便可以得到完全不同的局部最优解
    
    > _“实际应用中，评估所有可能的参数通常不能找到全局最优解。而是使用优化算法找到解，虽然可能不一定是全局最优的，但足够满足目标。另一种办法是用更高级的技术，如SGD或者Adam。”_
    

\*\*     具体算法\*\*：

![单一特征或多特征（也就是x的数量）](https://storage.googleapis.com/papyrus_images/b263211075f399e65f2916e86d007bf8b88bf3db2a1db260d77b8344019b48e9.png)

单一特征或多特征（也就是x的数量）

*   **α**为**学习速率**（learning rate），改变该值对应着下坡时的跨度
    
*   对\*\*\*θ\_1, θ\_0 (w,b)**的**同步更新\*（simultaneously update）
    
    *   个人理解： **_θ\_1, θ\_0_**可以看作下坡时的向左向右，减去的**α**和导数项之积对于**_θ\_1, θ\_0_**是完全相同的，即**重新调整的方向的跨度是相同的**。
        

![正确处理](https://storage.googleapis.com/papyrus_images/2700b5d3bc56576a1cef19ad5dcfd9f81bc91bdced77cce50b1a549f255cd204.png)

正确处理

![错误处理：这样处理的问题在于，计算θ](https://storage.googleapis.com/papyrus_images/bbd6a3fd53b152e4cd144846a193c6c662632de0055ba0678c7a344be74aa1f0.png)

错误处理：这样处理的问题在于，计算θ

**计算gradient（梯度下降步骤2）**：

![equation 4 & 5](https://storage.googleapis.com/papyrus_images/63dfb799c13a5c791412b2da9db53724d028ca8b96624f8d43b20c2fc18aec34.png)

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
    

现在有了计算**cost**和**gradient**的函数，就可以进行**descent**的过程

**梯度下降步骤3：**

![equation 3](https://storage.googleapis.com/papyrus_images/b98fc89a84b00acf36e0dbdc3bd86b4ccef41d63f28fd454972a209744a84081.png)

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）第一种情况：斜率为**正值**

![正斜率时](https://storage.googleapis.com/papyrus_images/8d3609c5b8d8b1b4d924de8fe681467b9648fd90983b0a338b81da2a609599d5.png)

正斜率时

*   对**_θ_**进行更新时，由于斜率为正，对应的导数为正数，因此减去该值后是在对**_θ_**向后调整
    
*   每次调整后会得到新的斜率，新的斜率相对于调整前的会更小，因此新的调整幅度会更小最终找到局部最优解
    

b）第二种情况：斜率为**负值**

![负斜率时](https://storage.googleapis.com/papyrus_images/97bd5c1c964d09777caea13c8d77c455d96a00ded844c810cf152e94d73ae5df.png)

负斜率时

*   对**_θ_**进行更新时，由于斜率为负，对应的导数为负数，因此减去该值后是在对**_θ_**向前调整
    
*   新的调整幅度同样会越来越小，最终逼近目标
    

c）第三种情况：斜率为**0**，已经**处在局部或者全局最优处时**

![斜率为0](https://storage.googleapis.com/papyrus_images/1acfdefeffc56b730507364a02a1aa1afe5b4721a539383882407549846cb2bb.png)

斜率为0

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

\*\*     对α的选取：\*\*

a）**不应过小**

![过小时对于下降过于缓慢，甚至可能会出现几乎停滞的状态](https://storage.googleapis.com/papyrus_images/f8d865164a7c83cd7de520d648c821f084f12fa950951dd61fdacb55e038617f.png)

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

b）**不应过大**

![过大时跨步过大，会越来越远离目标点](https://storage.googleapis.com/papyrus_images/2e985935a4fed2e2bcd6d6867d836cd0ee7b6ff4cad094c61ac56da0eec57c52.png)

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

> 假设绿点为初始点，此时的导数值为一个较小的负值，但由于α过大，导致绿点对应的**_θ_**加上了一个比较大的数，得到紫点；
> 
> 紫点对应的导数值为一个较大的正值，同样由于α过大，使得此时的**_θ_**减去一个更大的数字，得到橘点；
> 
> 不断地滚雪球最终离目标点越来越远

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

c) 可能的问题

![学习率过大可能会导致，或者代码有误，如：w_1 = w_1 +αd_1](https://storage.googleapis.com/papyrus_images/b062f44780d78124e72b0b08a793f70638a1033d8df5a2b76554a1eb7c2c1d6c.png)

学习率过大可能会导致，或者代码有误，如：w\_1 = w\_1 +αd\_1

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

![](https://storage.googleapis.com/papyrus_images/c2c7d3a0a504fb3c665d98f1426db472b7337883728514c1b17c7551ffc3e11b.png)

**对梯度下降的优化：**

**特征放缩**（Feature Scaling）：

*   使下降速度更快
    
*   能够消除特征之间的量纲差异性，使得不同特征更具有可比性
    
    （size in feet量级可能是k，而#bedrooms则是个位数，前者会对结果产生较大影响，后者则有可能会被忽略）
    
*   同时也能让数据足够离散（不是主要目的）
    

![放缩与不放缩的对比](https://storage.googleapis.com/papyrus_images/becb369d1c99f729c703cd47bbafe4e13553382e0aae05abc83a56fb9d979dec.png)

放缩与不放缩的对比

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

![300≤x1≤2000](https://storage.googleapis.com/papyrus_images/b7933d04bd63ac8d74eaf1c8aed269fc3864ae3833568b79b2e8ace2c5280b2a.png)

300≤x1≤2000

方案2：均值归一化（Mean Normalization）

![300≤ x1≤2000](https://storage.googleapis.com/papyrus_images/4269422792da97487d14e8f960edf2a11c60e04fac1b869f099b0c2c9ce6db70.png)

300≤ x1≤2000

方案3：Z-score 标准化

![300≤ x1≤2000](https://storage.googleapis.com/papyrus_images/ec64f8f0a81de832bf0d93859ee17ae98feda2bb92e1eeaf165ad27cf304bbc7.png)

300≤ x1≤2000

但放缩结果并无强制要求，接近-1≤ x\_j ≤ 1的范围

![z-score normalization处理](https://storage.googleapis.com/papyrus_images/defb8d5bcb5e2a10a1d198fdbf9399fd8fe3246b29ac0017299028308223cfef.png)

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://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.cnblogs.com/pinard/p/5970503.html)

[https://www.coursera.org/learn/machine-learning](https://www.coursera.org/learn/machine-learning)

---

*Originally published on [jasonyang](https://paragraph.com/@jasonyang/ml-andrew-ng-2-cost-function-gradient-descent)*
