本文结合对ChatGTP的提问,用自己的理解讲一讲最小二乘法。
最小二乘法:
yi是实际值,yhat是理论值,就是拟合值,比方说一次函数做拟合,那就是在这个x点位置时的值。累加所有yi-yhat的平方,得到E并保证E最小,即最小二乘。
当然这个E的结果并不是固定的,不同的函数,不同的参数都会导致结果不同。
然后再将拟合函数带入原公式,假如用一次函数带入原始公式,那就对a求偏导即可。
代码实现
所以这其实是一个数学问题,代码实现也只是对偏导公式的一个数值解而已,不过结合ChatGTP,它还是给出了结合协方差和方差的拟合方案,用Unity实现一下:
using UnityEngine;
public class LeastSquaresMethod : MonoBehaviour
{
public Vector2[] samplePoints;//采样点,需要在编辑器面板自己设置
void OnDrawGizmos()
{
float xSum = 0f, ySum = 0f;
for (int i = 0; i < samplePoints.Length; i++)
{
var item = samplePoints[i];
xSum += item.x;
ySum += item.y;
}
//x和y的平均值
var xAve = xSum / samplePoints.Length;
var yAve = ySum / samplePoints.Length;
//sx是x轴的方差,sy是y轴的方差,方差反应了数据的离散程度
//sxy是协方差,协方差大于零y与x正相关,小于零是负相关,等于零是无关
float sx = 0f, sy = 0f, sxy = 0f;
for (int i = 0; i < samplePoints.Length; i++)
{
sx += Mathf.Pow(samplePoints[i].x - xAve, 2);
sy += Mathf.Pow(samplePoints[i].y - yAve, 2);
sxy += (samplePoints[i].x - xAve) * (samplePoints[i].y - yAve);
}
Debug.Log("sxy:" + sxy);
//此处套用公式
var r = sxy / (Mathf.Sqrt(sx) * Mathf.Sqrt(sy));
var a = r * (sy / sx);
var b = yAve - a * xAve;
//Unity绘制逻辑
Vector3? lastPos = null;
for (float i = 0f, step = 0.1f; i <= 1f; i += step)
{
var x = i / 1f;
var y = a * x + b;
var pos = new Vector3(x, y, 0f);
if (lastPos.HasValue)
Gizmos.DrawLine(lastPos.Value, pos);
lastPos = pos;
}
for (int i = 0; i < samplePoints.Length; i++)
{
var item = samplePoints[i];
Gizmos.DrawWireSphere(new Vector3(item.x, item.y, 0f), 0.03f);
}
}
}
最后在面板上填入一些采样点,可以得到最接近的拟合函数参数。