曲线拟合

Context

有个叫做应变片的东西,每个应变片的物理特性都不同,所以收到相同的力时,不同的应变片得到的信号可能不太一样。

所以,每个应变片都需要做一些配置。

所以,我们需要一个程序来收集数据。

比如我们用一组标准力来测量信号,就可以获得到一组数据对:(N1,Sig1),(N2,Sig2)…

已知: 这是N和Sig是线性关系,所以这个要拟合的不是曲线,是条直线~

Math

是时候,上主题了。

凭我模模糊糊的记忆,我感觉好像是,最优化的内容。好像是,牛顿法?

所以我就去查了牛顿逼近法,查了半个下午,才发现这个是求方程根的~

后来模模糊糊的想起来,好像是线性规划,只要求Yi和F(Xi)的差的和的最小值,应该就是最接近的解了~

正在这个时候,我突然查查看Apple有没有做什么牛逼的函数给我~

Accelerate

果然Apple是有这么一个库的,叫做Accelerate,专门用来算各种各样的高等数学。

一开始看到了一个Sparse Solvers是个解方程的东西,于是就列了KX=B的一个矩阵式,使用accelerate提供的解方程函数来计算结果,结果是错的~

因为,直接解方程如果可以解出来,那不就是一个固定的k和b了嘛?然而我是要估计一个k和b,所以肯定不是直接解方程的。

Least squares

最小二乘法,终于让我想起来了这个东西,毕竟是大一时候微积分学的,思考太久很正常。

设Zi=(Yi-F(Xi))^2,将所有的Zi加在一起,这个值如果是最小的,那就意味着估计的曲线和误差是最小的。要使得这个值是最小的,那就可以求所有未知参数的偏导,并使得每个偏导的方程都等于0。

再经过数学推导得到了y=ax+b通过最小二乘法得到a和b的估计值的表达式。

由于我懒得用数学公式编辑器,也懒得截图,所以表达式用纯文字表达。

n:样本个数
X:Xi的和
Y:Yi的和
XX:Xi平方的和
XY:XiYi的和

a=(n*XY-X*Y)/(n*XX-X*X)
b=(Y-X*a)/n

这里还是用了accelerate的函数,做了矩阵的乘法来计算XiYi的和

void vDSP_mmul(const float *__A, vDSP_Stride __IA, const float *__B, vDSP_Stride __IB, float *__C, vDSP_Stride __IC, vDSP_Length __M, vDSP_Length __N, vDSP_Length __P);

虽然这里可以用另一个函数,就是向量的乘法,但是我一开始写的时候,没找到这个函数(文档都是英文的,还是英文版的高数,看着费力啊~)就是下面这个函数:

void vDSP_vsmul(const float *__A, vDSP_Stride __IA, const float *__B, float *__C, vDSP_Stride __IC, vDSP_Length __N);

Function

啰嗦了半天贴个代码吧。

void calculateEquation(double*x,double*y,double* key){
    double xy[1];
    double sum_x=0,sum_y=0,sum_xx=0; //这里就对应了上面说的 X,Y,XX,XY
    int n=7;
    vDSP_mmulD(x, 1, y, 1, xy, 1, 1, 1, n);
    for (int i=0; i<n; i++) {
        sum_x+=x[i];
        sum_xx+=x[i]*x[i];
        sum_y+=y[i];
    }
    key[0]=(n*xy[0]-sum_x*sum_y)/(n*sum_xx-sum_x*sum_x);  //k
    key[1]=(sum_y-key[0]*sum_x)/n;  //b
    NSLog(@"y=%lfx+%lf",key[0],key[1]);
}

写的不一定好,但是应该是对的~

Test

作为一个严谨的人,我还是要做点测试的。在网上找了两组数据做了单元测试,因为浮点型,所以可能最后几位数字不一样,就会导致单元测试判断错误。所以得,做点儿近似值~

- (void)testExample {
    double xS[]={0,1,2,3,4,5,6};
    double yS[]={1,2,3,4,5,6,7};
    double k[2];
    calculateEquation(xS,yS,k);
    XCTAssertEqual(k[0], 1.0);
    XCTAssertEqual(k[1], 1.0);
}

-(void)testExample2{
    double xS[]={34.1, 34.137, 34.174, 34.211, 34.248, 34.285, 34.322};
    double yS[]={74.4, 74.40321739130435, 74.4064347826087, 74.40965217391305, 74.41286956521739, 74.41608695652174, 74.41930434782608};
    double k[2];
    calculateEquation(xS,yS,k);
    XCTAssertEqual(round(k[0]*100000), round(0.08695652173913038*100000));
    XCTAssertEqual(round(k[1]*100000), round(71.43478260869566*100000));
}

-(void)testExample3{
    double xS[]={-1.5, -1.38, -1.26, -1.14, -1.02, -0.9, -0.78};
    double yS[]={57.30000000000001, 57.34640000000001, 57.39280000000001, 57.43920000000001, 57.48560000000001, 57.53200000000001, 57.57840000000001};
    double k[2];
    calculateEquation(xS,yS,k);
    XCTAssertEqual(round(k[0]*100000), round(0.38666666666666627*100000));
    XCTAssertEqual(round(k[1]*100000), round(57.88000000000001*100000));
}

End

数学,还是很有用的!