对最小二乘法拟合直线的一些思考与改进
最小二乘法是我在工作中做图像处理最常用的一种拟合算法,同时也是在机器学习中线性回归的一种重要算法。我们先来回顾一下传统的最小二乘拟合直线模型算法:
如上图所示,为了从 五个点中拟合出最佳的直线方程,按照传统的做法,假设这些点符合线性关系 ,要拟合出最佳的直线方程,也就是让每个点在 处与 的偏差都很小,也即让总偏差的和最小,总结一句话就是如何找到合适的 与 ,让
最小,也即:
但是这个式子带着绝对值,有正有负不太方便,所以我们一般让它们的平方最小,也即:
这就是最小二乘名字的由来(额,再鄙视一下这个蹩脚的翻译,为什么翻译成「最小二乘」,我觉得翻译成「最小平方」才能表达其本意)。
为了求出合适的 与 ,我们定义函数
根据多元函数求极值方法,我们把 与 看成未知量,并分别对他们求偏导,让它们等于0,得到:
化简,整理:
也即:
我们分别求出
我们进行一次测试:下张图片是汽车电子的某个工业零件,为了测量左边的下半圆孔到右边斜边的距离,我们首先要拟合出那条斜边的直线方程,我们使用上面刚推导出来的斜截式公式进行拟合,代码如下(C++描述,下面雷同):
by @yaxi_liu
void FitLine(vector<POINT> vec,
double &k, double &b)
{
double sum_xx = 0.0f;
double sum_x = 0.0f;
double sum_y = 0.0f;
double sum_xy = 0.0f;
int N = vec.size();
for (int i = 0; i < N; i++)
{
sum_xx += vec[i].x*vec[i].x;
sum_x += vec[i].x;
sum_y += vec[i].y;
sum_xy += vec[i].x * vec[i].y;
}
k=(sum_xy*N-sum_x*sum_y)
(sum_xx*N-sum_x*sum_x);
b =(sum_xx*sum_y-sum_xy*sum_x)
(sum_xx*N-sum_x*sum_x);
}
拟合结果如下:
绿线是我们根据拟合出来的直线方程结果,可见拟合出来的结果还算不错。
但是,在这里不知道大家有没有产生过这样的疑问:
第一,为什么我们想要让纵向的「距离」最小,而不是垂直的「距离」最小,后者不是更直观吗?
第二,当我们需要拟合的直线是垂直线或者接近垂直线(在图像处理中我经常遇到这种情况),这种方法可行吗?
对于第一个问题,有一种的解释是,在实际工程应用中,
而第二个问题,答案是显而易见的,由于这个回归模型的直线方程
对于解决这个问题,与上面一样,基本框架思路是一样的,只是我们需要把问题简化成求距离的最小,也即我们需要找到合适的a,b,c使得
成立.
那么我们首先需要定义直线方程
我们按照惯例,对
我们将上方程组的第三个计算的结果:
其中
为了方便,我们令
上式化简为:
注意到左边的矩阵是一个对称阵,因此它一定有对应的特征值与特征向量,但是这个矩阵明显有两个不同的特征值,我们到底该取哪一个呢?注意观察,这里有一个很有意思的巧合,我们为了让
最小,我们再对上述方程组左乘特征向量的转置矩阵
by @yaxi_liu
void FitLine(vector<POINT> vec,
double &a, double &b, double &c)
{
if (vec.size() < 2)
{
return;
}
double sum_x=0.0f, sum_y=0.0f;
double avg_x = 0.0f, avg_y = 0.0f;
for (int i = 0; i < vec.size(); i++)
{
sum_x += vec[i].x;
sum_y += vec[i].y;
}
avg_x = sum_x / vec.size();
avg_y = sum_y / vec.size();
double L_xx = 0.0f;
double L_yy = 0.0f;
double L_xy = 0.0f;
int N = vec.size();
for (int i = 0; i <N; i++)
{
L_xx += (vec[i].x - avg_x) *
(vec[i].x - avg_x);
L_xy += (vec[i].x - avg_x) *
(vec[i].y - avg_y);
L_yy += (vec[i].y - avg_y) *
(vec[i].y - avg_y);
}
double lamd1, lamd2;
double lamd;
double m, n;
m = L_xx + L_yy;
n = L_xx*L_yy-L_xy*L_xy;
lamd1=(m+sqrt(m*m-4*n))/2;
lamd2=(m-sqrt(m*m-4*n))/2;
lamd=lamd1<lamd2?lamd1:lamd2;
double d=sqrt((L_xx-lamd)*
(L_xx-lamd)+L_xy*L_xy);
if (abs(d) < 1e-6)
{
a = 1;
b = 0;
c = -a * avg_x - b * avg_y;
}
else
{
if (lamd >= L_xx)
{
a = L_xy / d;
b = (lamd - L_xx) / d;
c = -a * avg_x - b * avg_y;
}
else
{
a = -L_xy / d;
b = (L_xx - lamd) / d;
c = -a * avg_x - b * avg_y;
}
}
}
我们来看下拟合的结果:
上图左侧绿线是我们拟合出来的结果,发现效果非常不错,而如果我们用斜截式来拟合的话,由于斜率无穷大,根本拟合不出来结果。
但是这里还有个问题,由于我们采用的是距离的平方最小进行拟合的,因此这个算法对离群的一些噪点是非常敏感的,尤其是对离群较大的点集合来说,它们的权重非常大,稍不注意就有可能把我们所需要的结果给拉偏了,例如下图,为了拟合出斜边方程,但是此斜边周围有一些常见的噪点,如果我们对这些噪点不加以处理,那么结果也就不太理想:
上图由于噪点的干扰,我们所需要的的直线明显被拉偏了。因此我们迫切需要对这些噪点进行处理,具体的做法就是对我们获取的这些点做权重分析,对离群较大的点给他们较小的权重,但这里涉及到权重大小的取值问题,具体做法我会在下篇文章中细致分析。
如果你想要本文的源代码,请关扫描下方二维码,注我的公众号,回复“最小二乘”四个字即可领取。