机器学习算法笔记(五):简单线性回归

线性回归法(Linear Regression)是解决回归问题的最基础的算法。它思想简单、实现容易,是许多强大的非线性模型的基础。线性回归法的预测结果有着很好的可解释性,也蕴含了机器学习中的很多重要思想。

所谓线性回归,就是寻找一条直线,能最大程度的“拟合”样本特征和样本输出标记之间的关系

房价问题就是一个典型的线性回归问题

一、简单线性回归的定义

若线性回归的样本特征只有一个(比如上图中样本特征只有一个“房屋面积”),我们就称这是“简单线性回归”。通过对简单线性回归的学习,能让我们认识到线性回归算法的很多内容,进而推广到样本特征有多个的情形。

如上图所示,若我们想找一条直线,这条直线能最大程度拟合样本的特征点,那么在平面中,这条直线方程就应该被表示为 y=ax+b 的形式。那么对于每个样本点x(i),根据直线方程,预测值应该为(i)=ax(i)+b。对于每一个x(i),它又有一个真值,我们记作y(i)。若我们需要寻找最佳的拟合方程,就需要y(i)(i)的差距尽可能小。我们以(y(i) (i))2(两者之差的平方)衡量y(i)(i)之间的差距。若我们要考虑所有样本,就应该是将其求和,也就是。将(i)=ax(i)+b代入,现在我们的目标变成了使尽可能小。

在机器学习中,被称为损失函数(loss function)代价函数(cost function),它度量出我们的模型没有拟合的那一部分,也就是损失的部分。而有些算法中,度量的会是拟合的程度,那称这个函数为效用函数(utility function)。不管是损失函数还是效用函数,我们的机器学习算法都是先通过分析问题,确定问题的损失函数与效用函数,再通过最优化损失函数(尽可能小)或者效用函数(尽可能大),获得机器学习模型。这也是近乎所有的参数学习算法的基本套路。

这是一个典型的最小二乘法问题(最小化误差的平方)。通过求导、整理等处理方法,我们可以求解出a和b具体的的数学表达式,结果与x(i) 、y(i)有关。推导后的数学表达式如下:

由于博客编辑器不支持直接显示数学公式,推导过程省略。

二、简单线性回归的实现

下面我们用Python实现简单线性回归,新建一个工程,创建一个main.py文件,具体编码如下:

import numpy as np
import matplotlib.pyplot as plt #加载数据可视化库matplotlib

x = np.array([1., 2., 3., 4., 5.])
y = np.array([1., 3., 2., 3., 5.])

#计算平均值
x_mean = np.mean(x)
y_mean = np.mean(y)

num = 0.0 #y=ax+b中a值的分子
d = 0.0 #a值的分母
for x_i, y_i in zip(x, y):
    num += (x_i - x_mean) * (y_i - y_mean)
    d += (x_i - x_mean) ** 2

a = num/d
b = y_mean - a * x_mean #根据公式计算b的值
y_hat = a * x + b #根据计算得到的a与b拟合直线

#绘制图像
plt.scatter(x, y)
plt.plot(x, y_hat, color='r')
plt.axis([0, 6, 0, 6])
plt.show()

运行后,系统会自动弹出一个窗口显示绘制好的图像:

其中蓝点为训练集,红线为线性回归拟合的结果。

这时我们可以自己设置一个待预测的值,将其放在我们的线性模型中预测:

x_predict = 6
y_predict = a * x_predict + b
print(y_predict) #prints: 5.2

至此我们顺利实现了一个简单线性回归。前面的文章我们使用类封装了kNN算法,在这里为了使用的方便,我们同样将线性回归算法封装成一个类,在工程中新建一个SimpleLinearRegression.py文件,具体实现如下:

import numpy as np

class SimpleLinearRegression1:

    def __init__(self):
        #初始化Simple Linear Regression 模型
        self.a_ = None
        self.b_ = None

    def fit(self, x_train, y_train):
        #根据训练数据集x_train,y_train训练Simple Linear Regression模型
        assert x_train.ndim == 1, \
            "Simple Linear Regressor can only solve single feature training data."
        assert len(x_train) == len(y_train), \
            "the size of x_train must be equal to the size of y_train"

        x_mean = np.mean(x_train)
        y_mean = np.mean(y_train)

        num = 0.0
        d = 0.0
        for x, y in zip(x_train, y_train):
            num += (x - x_mean) * (y - y_mean)
            d += (x - x_mean) ** 2

        self.a_ = num / d
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        #给定待预测数据集x_predict,返回表示x_predict的结果向量
        assert x_predict.ndim == 1, \
            "Simple Linear Regressor can only solve single feature training data."
        assert self.a_ is not None and self.b_ is not None, \
            "must fit before predict!"

        return np.array([self._predict(x) for x in x_predict])

    def _predict(self, x_single):
        #给定单个待预测数据x,返回x的预测结果值
        return self.a_ * x_single + self.b_

    def __repr__(self):
        return "SimpleLinearRegression1()"

封装好后,修改main.py中的代码如下:

import numpy as np
import matplotlib.pyplot as plt #加载数据可视化库matplotlib

x = np.array([1., 2., 3., 4., 5.])
y = np.array([1., 3., 2., 3., 5.])
x_predict = 6

from SimpleLinearRegression import SimpleLinearRegression1

reg1 = SimpleLinearRegression1()
reg1.fit(x, y)
reg1.predict(np.array([x_predict]))
y_hat1 = reg1.predict(x)
plt.scatter(x, y)
plt.plot(x, y_hat1, color='r')
plt.axis([0, 6, 0, 6])
plt.show()

运行结果应该与先前完全一致,这里不再重复。

三、向量化运算

注意到上面a的表达式,我们不难发现a表达式的分子和分母事实上可以分别转化成两个向量点乘的结果,即(x-)(y-)/(x-)(x-)。这给我们提供了一个加快算法效率的思路——使用numpy向量的点乘方法要比使用for循环的效率高很多。将上面封装好的SimpleLinearRegression1类完全复制一份,重命名为SimpleLinearRegression2,修改fit函数中的代码,实现如下:

class SimpleLinearRegression2:

    def __init__(self):
        #初始化Simple Linear Regression 模型
        self.a_ = None
        self.b_ = None

    def fit(self, x_train, y_train):
        #根据训练数据集x_train,y_train训练Simple Linear Regression模型
        assert x_train.ndim == 1, \
            "Simple Linear Regressor can only solve single feature training data."
        assert len(x_train) == len(y_train), \
            "the size of x_train must be equal to the size of y_train"

        x_mean = np.mean(x_train)
        y_mean = np.mean(y_train)
        
        """在这边对num和d进行修改,删去for循环,其他部分不用改动"""
        num = (x_train - x_mean).dot(y_train - y_mean)
        d = (x_train - x_mean).dot(x_train - x_mean)

        self.a_ = num / d
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        #给定待预测数据集x_predict,返回表示x_predict的结果向量
        assert x_predict.ndim == 1, \
            "Simple Linear Regressor can only solve single feature training data."
        assert self.a_ is not None and self.b_ is not None, \
            "must fit before predict!"

        return np.array([self._predict(x) for x in x_predict])

    def _predict(self, x_single):
        #给定单个待预测数据x,返回x的预测结果值
        return self.a_ * x_single + self.b_

    def __repr__(self):
        return "SimpleLinearRegression2()"

修改main.py中对应的代码重新运行后的结果应该与先前保持一致。用向量化的方法处理后,算法的效率会提升数十倍。所以,我们今后应该尽量使用向量化的手段来编写算法。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注