[深度学习系列]线性回归模型

220 阅读4分钟

线性回归模型初探

回归问题与分类问题一直是深度学习的两大主要问题,那么回归问题中,最简单的线性回归模型究竟为何物?

案例引入

我们看这么一个案例,假说我们希望对于商品X的价格进行预测,我们简单地认为该商品的价格与原材料价格、当地购买力水平、店铺房租这3个因素有关系,我们简单地认为每个因素对价格的影响比重不同,我们可以先待定这些比重,然后通过大量地数据优化权重,得到一个比较靠谱的权重,如此来预测商品价格

这正是线性回归模型的分析方式,我们根据这一想法,不难写出下面的公式:

y(预测)=w1x1+w2x2+w3x3+by(预测)=w_{1}x_{1}+w_{2}x_{2}+w_{3}x_{3}+b

上式中,我们加入了偏置b,来调整曲线的位置,获得更好的拟合效果

那么,问题随之而来,我们如何确定出最优的参数呢?

均方误差

高中阶段,我们对于这样的问题就已经有了思考,并且知道了最小二乘法,最小二乘法中,我们提出了均方误差的概念:

loss=1ni=1n(y(预测)y(实际))2loss=\frac{1}{n}\sum_{i=1}^{n}(y(预测)-y(实际))^{2}

上式便是均方误差的表达式,在线性回归问题中,我们就选用这样的误差评估方式,至于选择均方误差的好处,我们以后再说

梯度下降算法

既然有了误差,我们自然而然地就想到要去求误差的最小值,这么多年的数学知识,让我们几乎没有任何思考地就想到,这个函数要么不存在最值,否则,最值一定在某个极值点处取得,直接对每个参数求偏导,并让偏导数等于0,解方程,答案就出来了

然而,我们参数的数量比较多,解这样的方程并不是什么轻松的事情,如果误差函数再复杂一些,难度就更大了,因此这种方法或许适合用于理论计算,但不适合用计算机编程的方式去计算

我们再次思考一下,我们都知道,计算机的很多计算,都是靠近似、逼近这种方式实现的,往这方面去想的话,我们就想到了梯度

再次复习一下,何为梯度?梯度就是曲线增大最陡的方向,数值上则是函数对每一个参数求偏导后组成的向量,那么,问题就好办了,梯度的反方向,不正是递减最快的方向吗?我们只需要向着这一方向走出那么一小步,再更新一下梯度,反复执行这一过程,我们也就在逼近函数的最小值点了

用公式描述这一过程就是:

w=wηLww'=w-\eta\frac{\partial L}{\partial w}

这里的w代表权重矩阵,对于参数b也是同理操作,而eta则是用于更新的一个很小的值,通常取0.001,0.01这些,它又被叫做学习率

那么如何计算梯度呢?

大家直接记住下面的公式即可,推导也并不困难

image-20221109111516238.png

image-20221109111534969.png

像这样,通过不断更新梯度来接近最小值的方法就是梯度下降算法

如果数据集量非常大,我们还有对应的优化方法,下个文章进行讲解

理论模型化

我们把一个“影响因素”叫做一个神经元,x的取值叫输入,而进行预测的计算过程叫做前向传播,结果叫做输出

我们可以得到如下的网络模型:

image-20221109112151186.png

至此,我们再次梳理一下线性回归的全过程:

1.前向传播(就是算出预测值)

2.计算损失

3.计算梯度

4.更新参数

5.反复执行上述过程直到训练集数据跑完

代码实现(Numpy版本)

import numpy as np
​
class Network(object):
    def __init__(self, num_of_weights):
        self.w = np.random.randn(num_of_weights, 1)
        self.b = 0.
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, z, y):
        error = z - y
        num_samples = error.shape[0]
        cost = error * error
        cost = np.sum(cost) / num_samples
        return cost
    
    def gradient(self, x, y):
        z = self.forward(x)
        N = x.shape[0]
        gradient_w = 1. / N * np.sum((z-y) * x, axis=0)
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = 1. / N * np.sum(z-y)
        return gradient_w, gradient_b
    
    def update(self, gradient_w, gradient_b, eta = 0.01):
        self.w = self.w - eta * gradient_w
        self.b = self.b - eta * gradient_b
                       
    def train(self, training_data, num_epochs, batch_size=10, eta=0.01):
        n = len(training_data)
        losses = []
        for iter_id,data_ in enumerate(training_data):
            x = data_[:, :-1]
            y = data_[:, -1:]
            a = self.forward(x)
            loss = self.loss(a, y)
            gradient_w, gradient_b = self.gradient(x, y)
            self.update(gradient_w, gradient_b, eta)
            losses.append(loss)
            print('Epoch {:3d} / iter {:3d}, loss = {:.4f}'.format(epoch_id, iter_id, loss))
        return losses