深度学习导论(2)深度学习案例:回归问题


这篇文章将介绍深度学习的小案例:回归问题的问题分析、优化以及实现代码。

问题分析


如果只采样两个点则会存在较大偏差(如蓝色线),为减小估计偏差,可通过采样多组样本点:
{ ( x ( 1 ) , y ( 1 ) ) , ( x ( 2 ) , y ( 2 ) ) , . . . , ( x ( n ) , y ( n ) ) } \{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(n)},y^{(n)})\} {(x(1),y(1)),(x(2),y(2)),...,(x(n),y(n))}
然后找出一条“最好”的直线,使得它尽可能地让所有采样点到该直线的误差(Error,或损失Loss)之和最小。
如:求出当前模型的所有采样点上的预测值 w x ( i ) + b wx^{(i)}+b wx(i)+b与真实值 y ( i ) y^{(i)} y(i)之间差的平方和作为总误差L:
L = 1 n ∑ i = 1 n ( w x ( i ) + b − y ( i ) ) 2 L=\frac{1}{n}\sum_{i=1}^{n}(wx^{(i)}+b-y^{(i)})^{2} L=n1i=1n(wx(i)+by(i))2
即:均方差误差(Mean Squared Error,MSE)

优化方法

  • 最简单的方法:暴力搜索,随机试验
  • 常用方法:梯度下降方法(Gradient Descent)

    (a)函数导数为0的点即为f(x)的驻点(函数取得极大、极小值时对应的自变量点)
函数$f(x,y)=-(cos^{2}x+cos^{2}y)^2$及其梯度

(b)函数的梯度(Gradient)定义为对各个自变量的偏导数(Partial Derivative)组成的向量

图中xy平面的红色箭头的长度表示梯度向量的模,箭头的方向表示梯度向量的方向。可以看到,箭头的方向总是指向当前位置函数值增速最大的方向,函数曲面越陡峭,箭头的长度也就越长,梯度的模也越大。

函数在各处的梯度方向▽f总是指向函数值增大的方向,那么梯度的反方向-▽f应指向函数值减少的方向。
按照:
x ′ = x − η ⋅ ∇ f x'=x-\eta\cdot\nabla{f} x=xηf
迭代更新x,就能获得越来越小的函数值。

代码

采样数据

import numpy as np

# 采样数据

data = []  # 保存样本集的列表

for i in range(100):  # 循环采样100个点

    x = np.random.uniform(-10., 10.)  # 随机采样输入x

    eps = np.random.normal(0., 0.1)  # 采样高斯噪声

    y = 1.477 * x + 0.089 +eps  # 得到模型的输出

    data.append([x, y])  # 保存样本点

data = np.array(data)  # 转换为2D Numpy数组

计算误差

# 计算误差

def mse(b, w, points):
    
    # 根据当前的 w,b参数计算均方差损失
    
    totalError = 0
    
    for i in range(0, len(points)):  # 循环迭代所有点

        x = points[i, 0]  # 获得 i号点的输入 x
        
        y = points[i, 1]  # 获得 i号点的输出 y
        
        # 计算差的平方,并累加
        
        totalError += (y - (w * x + b)) ** 2
        
    # 将累加的误差求平均,得到均方差

    return totalError / float(len(points))

计算梯度

# 计算梯度
# b_current:当前 b的值
# w_current:当前 w的值
# points:样本点集合
# lr:学习率

def step_gradient(b_current, w_current, points, lr):
    
    # 计算误差函数在所有点上的导数,并更新 w,b

    b_gradient = 0
    
    w_gradient = 0
    
    M = float(len(points)) # 总样本数
    for i in range(0, len(points)):
        
        x = points[i, 0]
        
        y = points[i, 1]
        
        # 误差函数对 b的导数:grand_b = 2(wx+b-y)
        
        b_gradient += (2/M) * ((w_current * x +b_current) - y)
        
        # 误差函数对 w的导数:grand_w = 2(wx+b-y)*x
        
        w_gradient += (2/M) * x * ((w_current * x +b_current) - y)
        
    # 根据梯度下降算法更新 w,b,其中lr为学习率
        
    new_b = b_current - (lr * b_gradient)
    
    new_w = w_current - (lr * w_gradient)
    
    return [new_b, new_w]

梯度更新

# 梯度更新

def gradient_descent(points, starting_b, starting_w, lr, num_iterations):
    
    # 循环更新 w,b多次
    
    b = starting_b  # b的初始值
    
    w = starting_w  # w的初始值
    
    # 根据梯度下降算法更新多次
    
    for step in range(num_iterations):
        
        # 计算梯度并更新一次
        
        b, w = step_gradient(b, w, np.array(points), lr)
        
        loss = mse(b, w, points)  # 计算当前的均方差,用于监控训练进度
        
        if step%50 == 0:  # 打印误差和实时的 w,b值
            
            print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")
            
    return [b, w]  # 返回最后一次的 w,b

main函数

# main函数
if __name__ == '__main__':
    
    # 加载训练集数据,这些数据是通过真实模型添加观测误差采样得到的
    
    lr = 0.01  # 学习率
    
    initial_b = 0  # 初始化 b为 0
    
    initial_w = 0  # 初始化 w为 0
    
    num_iterations = 1000
    
    # 训练优化1000次,返回最优 w*,b*和训练 Loss的下降过程
    
    [b, w] = gradient_descent(data, initial_b, initial_w, lr, num_iterations)
    
    losses = gradient_descent(data, initial_b, initial_w, lr, num_iterations)
    
    loss = mse(b, w, data)  # 计算最优数值解 w,b上的均方差
    
    print(f'Final loss:{loss}, w:{w}, b:{b}')

结果输出

结果输出如下图所示:
运行结果
在上述迭代过程中,迭代次数与均方差误差MSE之间的关系如下图:

由上图可以看出,虽然我们迭代了1000次,但是在100轮左右就已经收敛了,设在第n轮收敛,则我们只需要记录第n轮时的w和b的值就行,就是我们要求的w和b的值。有了w和b的值后,模型 y = w x + b y=wx+b y=wx+b就有了。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐