Python实现线性回归算法

1,120 阅读5分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」。

机器学习

机器学习是现在比较热门的一个词,相信各位读者都已经听腻了,那你有真正了解过机器学习吗?知道机器学习要做些什么吗?今天我们就来聊一聊这个话题。

机器学习的任务其实就是找一个函数,然后利用这个函数来预测。

那我们要怎么找这个函数呢?

首先第一步需要定义一个函数集,或者说是模型,比如最简单的:

y=wx+by = wx + b

这是一个简单的线性方程。其中 w 和 b 是常数,我们可以通过 w 和 b 的不同取值得到一个函数集。

找到了函数集后,我们就需要在函数集中寻找一个最优的函数。简单说就是找一组最优的 w 和 b。比如 w=2,b=5,这样我们就得到了目标函数:

y=2x+5y = 2x + 5

然后使用这个函数来预测。

那我们要怎么找这个函数呢?这就需要用到我们的数据了。

监督学习

其实机器学习的任务有很多种,而找函数是监督学习中的主要任务。那什么是监督学习呢?

监督学习是数据驱动的学习,因此我们需要准备许多数据。通常我们会把数据分成训练集和测试集,其中训练集就是用来寻找最优函数的。

比如我们要训练一个预测 PM2.5 的模型,我们就需要准备最近几个月 PM2.5 的数据。我们把一天的数据单独拿出来就叫做一个样本。

一个样本通常会包含两个部分,特征值和目标值。对于 PM2.5 的案例,特征值我们可以理解为当天的温度、空气状况、天气等因素。而目标值就是我们当天的 PM2.5 数值。

那有了这些数据我们应该怎么优化函数呢?

我们需要额外定义一个函数,来评估一个函数的好坏。我们把 w 和 b 输入到这个函数,然后这个函数会给我们返回这组 w 和 b 的分数。我们把这样一个函数成为损失函数。损失函数可以写成下面的样子:

loss=[y(wx+b)]2loss = [y - (wx + b)]^2

其中 x 是特征值,y 是目标值,w 和 b 是我们模型的参数。我们通过 wx+b 计算预测值,然后用实际值 y 减预测值再平方就得到了我们的损失值。上面只是一种损失函数,在实际应用中会根据场景选择不同的损失函数。

有了这个损失函数,我们就可以对模型进行监督了。

梯度下降

我们可以通过求损失函数的偏导来实现参数的优化。假如我们损失函数的图像如下:

在这里插入图片描述

图中是一个参数的情况。我们可以看到,随着 w 的不断增加,loss 值在不断波动。直观上来看 C 点是 loss 最低的点。我们来关注 A、B 点的情况。

其中 A 的导数小于 0,我们需要将参数 w 增加才能到达 C 点。而 B 点的导数大于 0,我们需要将参数 w 减少才能达到 C 点。

因此我们可以每次更新让 w 减去我们的导数。这样就能向正确的方向调整参数。在 A 到 C 的过程中我们需要不断增加 w,随着 w 的增加我们的导数的绝对值会越来越小,因此调整的幅值也越来越小这也这样我们就能更接近 C 点。

但是我们的导数值还是比较大,如果我们直接减去导数的话会导致参数调整的赋值过大,就可能出现下图的情况:

在这里插入图片描述

其中左边是我们预期的调整方式,右边是调整赋值过大导致 loss 无法下降到 C。

因此我们通常的做法是添加一个学习率η,我们用 w 减去η倍的 loss 函数的导数。

对于我们一个参数的模型,函数如下:

y=wxy = wx

loss 函数应该如下:

loss=(ywx)2loss = (y - wx)^2

那我们 w 参数更新后的结果是:

w_=w+2ηw(ywx)w\_ = w + 2ηw(y - wx)

对于两个参数的模型,我们更新参数的方式也是一样的。我们只需要求 loss 函数对两个参数的偏导,然后再分别求Δw 和Δb,然后更新参数就好了。

线性回归

线性回归是机器学习中常用的一种算法,我们可以用来预测一些连续的结果。比如天气、PM2.5、股票等。

其实它就是我们上面介绍的函数:

y=wx+by = wx + b

相信大家对这个函数已经非常了解了。那下面我们来用代码实现一下线性回归算法。我们先定了好一个类,代码如下:

import random
import numpy as np


class LinearRegression(object):
    """
    线性回归模型的类
    """

    def __init__(self):
        # 特征值
        self.features = None
        # 目标值
        self.targets = None
        # 权重
        self.weight = None
        # 偏置值
        self.bias = None
        # 最大学习次数
        self.max_learning_times = 10 ** 5

    def __init_parameter(self):
        """
        初始化模型的参数
        :return:
        """
        # 生成随机参数
        self.weight = np.random.rand(*self.features[0].shape)
        self.bias = np.random.rand(1)

    def __loss(self, features, targets):
        """
        损失函数,计算模型的损失值
        公式:loss = target - (wx + b)
        :param features: 参与计算的特征值
        :param targets: 参与计算的目标值
        :return: 损失值
        """
        # 预测值
        loss = (targets - (features * self.weight + self.bias)) ** 2
        return np.nanmean(loss ** 1/2)

    def __gradient_decent(self, learning_rate=0.0001):
        """
        实现梯度下降算法
        :param learning_rate: 学习率
        :return:
        """
        w_ = -learning_rate * (-2 * self.features * (self.targets - (self.weight * self.features + self.bias))).mean()
        b_ = -learning_rate * (-2 * (self.targets - (self.weight * self.features + self.bias))).mean()
        self.weight += w_
        self.bias += b_

    def fit(self, features, targets):
        """
        填充数据并训练
        :param features:
        :param targets:
        :return:
        """
        self.features = features
        self.targets = targets
        self.__init_parameter()
        loss = self.__loss(self.features, self.targets)
        while self.max_learning_times:
            self.max_learning_times -= 1
            print(f"weight = {self.weight}, bias = {self.bias}, loss = {loss}")
            self.__gradient_decent()

    def predict(self, feature):
        return feature * self.weight + self.bias


if __name__ == '__main__':
    X = np.linspace(0, 99, 100).reshape((100, 1))
    y = X * 3 + 9 + random.random() ** 2 * 10
    lr = LinearRegression()
    lr.fit(X, y)
    y_pred = lr.predict(np.array([9.1, 4.3, 2.1], dtype=np.float32))
    print(y_pred)

我们使用上面写的算法进行预测。在主程序中,我们通过:

y=3x+9y = 3x + 9

生成了一系列数据,并加上了一个随机值。然后我们开始训练模型,运行结果如下:

weight = [3.00095592], bias = [10.19301125], loss = 9531.483394741059
weight = [3.00095588], bias = [10.19301446], loss = 9531.483394741059
weight = [3.00095583], bias = [10.19301768], loss = 9531.483394741059
weight = [3.00095578], bias = [10.1930209], loss = 9531.483394741059
weight = [3.00095573], bias = [10.19302412], loss = 9531.483394741059
weight = [3.00095568], bias = [10.19302733], loss = 9531.483394741059
weight = [3.00095563], bias = [10.19303055], loss = 9531.483394741059
weight = [3.00095559], bias = [10.19303377], loss = 9531.483394741059
weight = [3.00095554], bias = [10.19303698], loss = 9531.483394741059
weight = [3.00095549], bias = [10.1930402], loss = 9531.483394741059
weight = [3.00095544], bias = [10.19304341], loss = 9531.483394741059
weight = [3.00095539], bias = [10.19304663], loss = 9531.483394741059
weight = [3.00095534], bias = [10.19304984], loss = 9531.483394741059
weight = [3.00095529], bias = [10.19305306], loss = 9531.483394741059
weight = [3.00095525], bias = [10.19305627], loss = 9531.483394741059
weight = [3.0009552], bias = [10.19305949], loss = 9531.483394741059
weight = [3.00095515], bias = [10.1930627], loss = 9531.483394741059
weight = [3.0009551], bias = [10.19306592], loss = 9531.483394741059
weight = [3.00095505], bias = [10.19306913], loss = 9531.483394741059
[37.50176403 23.09717944 16.49507757]

可以看到最后的 w 和 b 很接近 3 和 9。最后我们预测的结果和计算的结果也非常接近,总体来说还算成功了。