3 线性回归算法

696 阅读5分钟

线性回归分为:

  • 简单线性回归:特征数量只有一个。
y = ax + b
  • 多元线性回归:特征数量有多个。
y = {θ_0+θ_1*x_1^{(l)}+θ_2*x_2^{(l)}+...++θ_m*x_m^{(l)}}

1 简单线性回归

寻找一条直线,最大程度的拟合样本特征与样本输出标记之间的关系。

分类与回归之间的直观性区分

预测值为:\hat{y}^{(i)} = ax^{(i)} + b ,真值为y^{(i)}

我们希望y^{(i)}-\hat{y}^{(i)}的差距尽量小

考虑所有样本:找到a和b,使\sum_1^m(y^{(i)}-\hat{y}^{(i)})^2 = \sum_1^m(y^{(i)}-ax^{(i)} - b)^2 尽可能小

损失函数定义:用来度量预测错误的程度。 近乎所有参数学习算法,都是通过分析问题,确定问题的损失函数,通过最优化损失函数,使得预测错误的程度最小,来获得机器学习的模型。

1.1 最小二乘法

求a,b的过程,就是对a,b分别求导,令极值为0,求方程组的过程。

先给出结论:

b =  \overline{y} - a\overline{x}
a = \sum_1^m\frac{(x^{(i)}-\overline{x})(y^{(i)}-\overline{y}) }{(x^{(i)}-\overline{x})^2}

公式编辑太麻烦,还是直接上图看推导过程吧,

此时求得 b = \overline{y} - a\overline{x},将其带入到对a求导中

此时根据红框里的等式进行变换,最终得到下面的式子

那么化简成这样的意义是什么呢?有什么好处呢?

答:向量化运算 使用for循环的效率是很低的,用向量的运算,效率会大大提高(相差好几十倍)。

1.2 代码起飞

import numpy as np

class SimpleLinearRegression:

    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, \
            "简单线性回归,只有一维"
        assert len(x_train) == len(y_train)

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

        # 公式,用向量点积
        self.a_ = (x_train - x_mean).dot(y_train - y_mean) / (x_train - x_mean).dot(x_train - x_mean)
        self.b_ = y_mean - self.a_ * x_mean

        return self

    def predict(self, x_predict):
        """给定待预测数据集x_predict,返回表示x_predict的结果向量"""
        assert x_predict.ndim == 1
        assert self.a_ is not None and self.b_ is not None

        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 score(self, x_test, y_test):
        """根据测试数据集 x_test 和 y_test 确定当前模型的准确度,使用的是R Square衡量"""

        y_predict = self.predict(x_test)
        return r2_score(y_test, y_predict)

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

2回归算法的评价

2.1 三个指标

衡量标准:\sum_1^m(y_{test}^{(i)} - \hat{y_{test}^{(i)}})^2 但是如果不同的样本个数相比,会有问题,因此以下改进

  • 均方误差 MSE (Mean Squared Error)
\frac{1}{m}\sum_1^m(y_{test}^{(i)} - \hat{y_{test}^{(i)}})^2

还有量纲的问题,假如房价是万元,MSE的值是万^2,进行改进

  • 均方根误差 RMSE (Root Mean Squared Error)
\sqrt{\frac{1}{m}\sum_1^m(y_{test}^{(i)} - \hat{y_{test}^{(i)}})^2} = \sqrt{MSE_{test}}
  • 平均绝对误差 MAE (Mean Absolute Error)
\frac{1}{m}\sum_1^m|y_{test}^{(i)} - \hat{y_{test}^{(i)}}|

那么RMSE与MAE区别是什么呢?

如果错误值非常大,例如两个样本的差距是100的话,RMSE就扩大到10000的差距了,因此RMSE 能放大 样本中预测结果与真实结果之间较大差异 的趋势,而MAE是没有的。因此从某种程度上讲,让RMSE的值更小相对意义更大,表示样本中错误值最大的比较小。

2.2 最好的线性回归评价指标 R Square

问题引入:假如使用RMSE、MAE预测房价误差是5万元,预测学生成绩误差是10分,那我们得到的模型是预测房价好呢还是学生成绩好呢,我们无法判断,这也是他们的局限性。

从而引入: R^2 = 1- \frac{SS_{residual}}{SS_{total}} = 1-\frac{\sum_i^m(\hat{y^{(i)}}-y^{(i)})^2}{\sum_i^m(\overline{y}-y^{(i)})^2}

\sum_i^m(\hat{y^{(i)}}-y^{(i)})^2 使用我们训练出来的模型产生的错误

\sum_i^m(\overline{y}-y^{(i)})^2 使用y=\overline{y}预测产生的错误(也称基准模型)

  • R^2<=1

  • R^2越大越好。当我们预测模型不犯任何错误,R^2得到最大值

  • 当我们的模型等于基准模型时,R^2=0

  • 如果R^2<0,说明我们学习到的模型还不如基准模型,预测的还不如不预测,此时,很有可能我们的数据不存在任何线性关系。

还可以将R^2进行整理下,如下:

R^2 = 1- \frac{SS_{residual}}{SS_{total}} = 1-\frac{\sum_i^m(\hat{y^{(i)}}-y^{(i)})^2}{\sum_i^m(\overline{y}-y^{(i)})^2}=1-  \frac{\frac{\sum_i^m(\hat{y^{(i)}}-y^{(i)})^2}{m}}   {\frac{\sum_i^m(\overline{y}-y^{(i)})^2}{m}}
= 1 - \frac{MSE(\hat{y},y)} {Var(y)}

2.3 代码

import numpy as np
from math import sqrt


def mean_squared_error(y_true, y_predict):
    """MSE"""
    assert len(y_true) == len(y_predict)
    return np.sum((y_true - y_predict)**2) / len(y_true)


def root_mean_squared_error(y_true, y_predict):
    """RMSE"""
    return sqrt(mean_squared_error(y_true, y_predict))


def mean_absolute_error(y_true, y_predict):
    """MAE"""
    assert len(y_true) == len(y_predict)
    return np.sum(np.absolute(y_true - y_predict)) / len(y_true)


def r2_score(y_true, y_predict):
    """R Square"""
    return 1 - mean_squared_error(y_true, y_predict)/np.var(y_true)

3 多元线性回归

x^{(i)} = (X_1^{(i)},X_2^{(i)},...,X_n^{(i)}) :第i个样本的n个特征

y = {θ_0+θ_1x_1+θ_2x_2+...+θ_nx_n}

预测值:\hat{y}^{(i)} = {θ_0+θ_1X_1^{(i)}+θ_2X_2^{(i)}+...+θ_nX_n^{(i)}}

目标:找到θ_0,θ_1,...θ_n使\sum_1^m(y^{(i)}-\hat{y}^{(i)})^2 = \sum_1^m(y^{(i)}-θ_0-θ_1X_1^{(i)}-θ_2X_2^{(i)}-...-θ_nX_n^{(i)})^2尽可能小

3.1 简单变换:

\hat{y}^{(i)} = {θ_0X_0^{(i)}+θ_1X_1^{(i)}+θ_2X_2^{(i)}+...+θ_nX_n^{(i)}} , X_0^{(i)}=1

其中令X_0^{(i)}恒等于1,虚拟的第0个特征,插入到n个特征的第一列

X^{(i)} = (X_0^{(i)},X_1^{(i)},X_2^{(i)},...,X_n^{(i)})

θ = (θ_0,θ_1,θ_2...,θ_n)^T

可以换成向量的点积,写成 \hat{y}^{(i)} = X^{(i)}.θ

上面是第i个向量的预测结果,而所有向量预测结果可以用矩阵表示

X_b的第一列是插入的,虚拟的第0个特征

3.2 推导过程

那么如何求θ = (θ_0,θ_1,θ_2...,θ_n)^T呢,和简单线性回归相似,对所有参数进行求导,令极值=0。

先给结论:

θ = (X_b^TX_b)^{-1} X_b^Ty

推到过程手写了,请看图,不忍直视的请略过

  • 问题:时间复杂度高:O(n^3)
  • 优点:不需要对数据做归一化处理

3.3 代码

θ = (θ_0,θ_1,θ_2...,θ_n)^T

其中θ_0为截距 (θ_1,θ_2...,θ_n)^T 是系数,因为在系数部分,每一个值对应样本的一个特征,可以看到每一个特征的贡献是如何的

import numpy as np

class LinearRegression:

    def __init__(self):
        """初始化Linear Regression模型"""
        self.coef_ = None  # 系数
        self.intercept_ = None  # 截距
        self._theta = None  # θ

    def fit_normal(self, X_train, y_train):
        """根据训练数据集X_train, y_train训练Linear Regression模型"""
        assert X_train.shape[0] == y_train.shape[0]

        # 在X_train矩阵第一列增加全为1的列
        X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
        # 公式θ = (X_T·X)﹣¹·X_T·Y
        self._theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)

        self.intercept_ = self._theta[0]
        self.coef_ = self._theta[1:]
        return self

    def predict(self, X_predict):
        """给定待预测数据集X_predict,返回表示X_predict的结果向量"""
        assert self.intercept_ is not None and self.coef_ is not None
        assert X_predict.shape[1] == len(self.coef_)

        X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
        return X_b.dot(self._theta)

    def score(self, X_test, y_test):
        """确定当前模型的准确度,使用的是R Square衡量"""

        y_predict = self.predict(X_test)
        return r2_score(y_test, y_predict)


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

小结

  • 典型的参数学习 线性回归获取的系数,具有可解释性,可以看到每个系数对结果所占的比重。

  • 只能解决回归问题 对比kNN:既可以解决分类问题,也可以解决回归问题

  • 对数据有假设:假设存在线性 对比kNN,对数据没有假设

  • 如果确实存在线性关系,线性回归会比kNN强不少

  • 问题:时间复杂度高O(n^3),解决方案-梯度下降法

下章预告:梯度下降法

声明:此文章为本人学习笔记,课程来源于慕课网:python3入门机器学习经典算法与应用。在此也感谢bobo老师精妙的讲解。

如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、AI资料、以及感悟,欢迎留言,与大家一起探索AI之路。

AI探索之路