线性回归详解

112 阅读2分钟

前言

在常见的算法问题使用中,线性回归使用的非常广泛,本篇文章来进行简单介绍。

正文

线性回归是一种统计分析方法,用于建模两个或多个变量之间的关系,通过寻找一个最佳拟合方程,来描述自变量与因变量之间的关系,目的是为了基于一个或者多个自变量的值来预测因变量的值。

线性回归与最小二乘法

很多人认为最小二乘法就是线性回归,其实并不是,最小二乘法是一种思想,它可以拟合任意函数线性回归的线性方程只是其中一个比较简单且常用的函数,所以最小二乘法一般都是以它为例。

线性回归方程定义为:

hθ(x1,x2,...,xn1)=θ0+θ1x1+...+θn1xn1(θ为参数)h_{\theta}(x_1,x_2,...,x_{n-1}) = {\theta}_0 + {\theta}_1x_1 + ... + {\theta}_{n-1}x_{n-1} ({\theta为参数})

其中hh就是因变量,x0x1...xnx_0、x_1 ... x_n就是自变量,而θ{\theta}是回归系数,表示自变量对因变量的影响程度。

假设现在有m组数据(样本),每个样本有n-1维特征,将数据样本点带入线性回归方程就可以得到:

h1=θ0+θ1x1,1+θ2x1,2+...+θn1x1,n1h2=θ0+θ1x2,1+θ2x2,2+...+θn1x2,n1hm=θ0+θ1xm,1+θ2xm,2+...+θn1xm,n1h_1 = {\theta}_0 + {\theta}_1x_{1,1} + {\theta}_2x_{1,2} + ... + {\theta}_{n-1}x_{1,n-1} \\ h_2 = {\theta}_0 + {\theta}_1x_{2,1} + {\theta}_2x_{2,2} + ... + {\theta}_{n-1}x_{2,n-1} \\ \vdots \\ h_m = {\theta}_0 + {\theta}_1x_{m,1} + {\theta}_2x_{m,2} + ... + {\theta}_{n-1}x_{m,n-1} \\

这里有m个方程,有n个θ{\theta}参数需要求解,记住在juejin.cn/post/730387… 文章中有说,矩阵最初的雏形就是多元一次方程的简写,所以上述m个方程使用矩阵来表达就是:

h=Xθh = X{\theta}

这里我们与方程组进行比较一下,每一个方程,共有n个未知数,所以上述矩阵方程中,hh是一个mx1的向量,代表模型的理论值;θ{\theta}nx1维的矩阵,表示共有n个参数待求解;XXmxn的矩阵,表示数据,即m个样本个数,n表示特征数。

于是目标损失函数用矩阵表示就是:

J(θ)=hY2=XθY2=(XθY)T(XθY)J({\theta}) = ||h-Y||^2 = ||X{\theta}-Y||^2=(X{\theta}-Y)^T(X{\theta}-Y)

其实这个就是最小二乘,当该方程值最小的时候,就是最佳拟合,至于为什么是平方,不是绝对值,也不是立方、次方等,这里涉及一个推到过程,比较复杂,就不说了。

在该方程中,YY是样本数据的实际值,是mx1的矩阵。这里需要求函数极值,即倒数为0的地方,只需要对损失函数求导,并且使其等于0即可解出θ{\theta},首选对目标损失函数进行化简:

J(θ)=θTXTXθθTXTYYTXθ+YTYJ({\theta}) = {\theta}^TX^TX{\theta} - {\theta}^TX^TY-Y^TX{\theta}+Y^TY

求导令其等于0:

δδθJ(θ)=2XTXθ2XTY=0\frac{{\delta}}{{\delta}{\theta}}J({\theta}) = 2X^TX{\theta}-2X^TY = 0

求解上述方程可以得到:

θ=(XTX)1XTY{\theta} = (X^TX)^{-1}X^TY

看到这里你就应该非常高兴了,因为这里的XXYY都是已知的,就可以一下子求出方程的参数θ{\theta}

直线拟合实践

有了这个矩阵公式以及最小二乘法的原理,我们来实践一下。假如我们有3个点,分别为(0,1.1)(1,1.9)(2,3),这3个点在坐标如图:

desmos-graph (1)-16975036863155.png

看到这种点,我们想用线性方程h=kx+bh=kx+b来进行拟合,根据最小二乘法公式,带入图中3个数据点:

L=i=13(hiyi)2=(b1.1)2+(k+b1.9)2+(2k+b3)2L = \sum_{i=1}^{3}{(h_i-y_i)^2} \\ = (b-1.1)^2 + (k+b-1.9)^2 + (2k+b-3)^2 \\

我们可以看到这时的损失函数就成了bbkk的二元方程,这时想得到一个kbk、b值使得损失函数值最小,直接进行求导即可,因为二元方程开口向上,最小值就是倒数为0的地方,所以对kkbb进行求导且设置倒数为0,可以得到:

δLδk=10k+6b15.8=0δLδb=6k+6b13=0\frac{{\delta}L}{{\delta}k} = 10k+6b-15.8 = 0 \\ \\ \frac{{\delta}L}{{\delta}b} = 6k+6b-13 = 0

解出上述方程,可以得到k=0.95,b=1.05,带入拟合方程h=0.95x+1.05h=0.95x+1.05,效果如下:

image-20231017090132944-16975044943277.png

当然这种做法很麻烦,我们可以利用前面刚学习的矩阵来试试,我们已知求解的系数θ{\theta}的方程为:

θ=(XTX)1XTY{\theta} = (X^TX)^{-1}X^TY

其中XX就是把样本值带入到拟合方程的矩阵数据,YY是样本输出结果,所以可以得到:

X=[011121]Y=[1.11.93]X= \left[ \begin{matrix} 0 & 1 \\ 1 & 1 \\ 2 & 1 \end{matrix} \right], Y = \left[ \begin{matrix} 1.1 \\ 1.9 \\ 3 \end{matrix} \right]

我们使用Eigen库来进行计算,可以查看文章:juejin.cn/post/730384… ,代码如下:

//定义一个3x2的矩阵
Eigen::MatrixXf X(3, 2);
//定义一个向量
Eigen::Vector3f Y;

//对矩阵进行初始化
X << 0, 1,
     1, 1,
     2, 1;
Y << 1.1, 1.9, 3;

std::cout << "------ X ------" << std::endl << X << std::endl;
std::cout << "------ Y ------" << std::endl << Y << std::endl;
Eigen::MatrixXf kb = (X.transpose() * X).inverse() * (X.transpose()) * Y;
std::cout << "------ kb ------" << std::endl << kb << std::endl;

运行结果如下:

------ X ------
0 1
1 1
2 1
------ Y ------
1.1
1.9
  3
------ kb ------
0.95
1.05

可以发现这里计算出的方程h=0.95x+1.05h=0.95x+1.05和前面手动计算是完全一样的。

二次多项式拟合实践

我们再来看一下二次多项式拟合的例子,假如有如下4个点:

image-20231017101555060-169750895637211.png

image-20231017101612198-169750897366013.png

这里我们就不使用手动计算了,我们期望的拟合方程是h=ax2+bx+ch=ax^2+bx+c,我们把4组数据带入方程中,可以得到矩阵XXYY

X=[001111421931]Y=[2037]X= \left[ \begin{matrix} 0 & 0 &1 \\ 1 & 1 & 1\\ 4 & 2 & 1\\ 9 & 3 & 1 \end{matrix} \right], Y = \left[ \begin{matrix} 2 \\ 0 \\ 3 \\ 7 \end{matrix} \right]

同样使用Eigen来计算,代码如下:

	//定义一个4x3的矩阵
    Eigen::MatrixXf X(4, 3);
    //定义一个向量
    Eigen::Vector4f Y;

    //对矩阵进行初始化
    X << 0, 0, 1,
         1, 1, 1,
         4, 2, 1,
         9, 3, 1;
    Y << 2, 0, 3, 7;

    std::cout << "------ X ------" << std::endl << X << std::endl;
    std::cout << "------ Y ------" << std::endl << Y << std::endl;
    Eigen::MatrixXf abc = (X.transpose() * X).inverse() * (X.transpose()) * Y;
    std::cout << "------ abc ------" << std::endl << abc << std::endl;

结果如下:

------ X ------
0 0 1
1 1 1
4 2 1
9 3 1
------ Y ------
2
0
3
7
------ abc ------
     1.5
-2.70001
     1.8

拟合方程与原数据的匹配如下:

image-20231017112709412-169751323137619.png

可以发现使用矩阵进行拟合还是非常方便的。

总结

这里通过2个简单的例子,我们可以理解最小二乘法用于线性拟合的实践,同时也可以感受到使用矩阵可以进一步简化表达,让人更容易理解。

更多算法相关总结移步专栏: juejin.cn/column/7415…