C语言编程:最小二乘法拟合直线

本文研究通过C语言实现最小二乘法拟合直线。

文章目录

  • 1 引入
  • 2 公式推导
  • 3 C语言代码实现
  • 4 测试验证
  • 5 总结

1 引入

最小二乘法,简单来说就是根据一组观测得到的数值,寻找一个函数,使得函数与观测点的误差的平方和达到最小。在工程实践中,这个函数通常是比较简单的,例如一次函数或二次函数。

汽车上的毫米波雷达可以探测到其他目标车辆,通过最小二乘法拟合目标车辆历史点,可以简单地预测目标汽车未来的走向。

后文会推导最小二乘法拟合直线,并通过C语言实现,最后进行简单的验证。对于二次及更高次多项式的拟合,采用类似的方法。

2 公式推导

作为工程应用,推导公式不需要像数学上的那么抽象,只需要针对当前需求推导即可。

首先,需要拟合的方程为一次函数:

y = a x + b y = ax + b y=ax+b

并且已知n个观测点:

( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . ( x n , y n ) (x_{1}, y_{1}),(x_{2}, y_{2}),...(x_{n}, y_{n}) (x1,y1),(x2,y2),...(xn,yn)
则拟合的误差的平方和为:
E ( a , b ) = ∑ i = 1 n ( a x i + b − y i ) 2 E(a,b)=\sum_{i=1}^n\left({{}}a{{x}}{}_{{i}}+b-{{y}}{}_{{i}}\right)^2 E(a,b)=i=1n(axi+byi)2

注意,x和y是已知量,该函数是关于a和b的二元函数。目标是求误差的最小值,因此需要分别对a和b求偏导数:
∂ E ∂ a = ∑ i = 1 n 2 ⋅ ( a x i + b − y i ) ⋅ x i = 2 ∑ i = 1 n ( a x i 2 + b x i − y i x i ) \frac{\partial E}{\partial a}=\sum_{i=1}^n{2\cdot\left(a{{x}}{}_{{i}}+b-{{y}}{}_{{i}}\right)}\cdot{{x}}{}_{{i}}=2\sum_{i=1}^n{\left(a{{x}}_{{i}}^{{2}}+b{{x}}{}_{{i}}-{{y}}{}_{{i}}{{x}}{}_{{i}}\right)} aE=i=1n2(axi+byi)xi=2i=1n(axi2+bxiyixi)   ∂ E ∂ b = ∑ i = 1 n 2 ⋅ ( a x i + b − y i ) = 2 ∑ i = 1 n ( a x i + b − y i ) \:\frac{\partial E}{\partial b}=\sum_{i=1}^n{2\cdot\left(a{{x}}{}_{{i}}+b-{{y}}{}_{{i}}\right)}=2\sum_{i=1}^n\left(a{{x}}{}_{{i}}+b-{{y}}{}_{{i}}\right) bE=i=1n2(axi+byi)=2i=1n(axi+byi)

对偏导数取值为0,可以得到线性方程组:
a ∑ i = 1 n x i 2   + b ∑ i = 1 n x i   = ∑ i = 1 n y i x i a\sum_{i=1}^n{{{x}}_{{i}}^{{2}}}\:+b\sum_{i=1}^n{{x}}{}_{{i}}\:=\sum_{i=1}^n{{y}}{}_{{i}}{{x}}{}_{{i}} ai=1nxi2+bi=1nxi=i=1nyixi a ∑ i = 1 n x i   +   b n   = ∑ i = 1 n y i a\sum_{i=1}^n{{x}}{}_{{i}}\:+\:bn_{{}}\:=\sum_{i=1}^n{{y}}{}_{{i}}{{}} ai=1nxi+bn=i=1nyi

求解方程组可以用克拉默法则:
D =   ∣ ∑ i = 1 n x i 2 ∑ i = 1 n x i ∑ i = 1 n x i n ∣   =   n ∑ i = 1 n x i 2   −   ( ∑ i = 1 n x i ) 2 D=\:\left|\begin{matrix}{\sum_{i=1}^n{{{x}}_{{i}}^{{2}}}{}} & {\sum_{i=1}^n{{{x}}_{{i}}^{{}}}{}} \\ {\sum_{i=1}^n{{{x}}_{{i}}^{{}}}{}} & n\end{matrix}\right|\:=\:n\sum_{i=1}^n{{x}}_{{i}}^{{2}}\:-\:\left(\sum_{i=1}^n{{x}}_{{i}}^{}\right)^2 D= i=1nxi2i=1nxii=1nxin =ni=1nxi2(i=1nxi)2 D a =   ∣ ∑ i = 1 n x i y i ∑ i = 1 n x i ∑ i = 1 n y i n ∣   =   n ∑ i = 1 n x i y i   −   ∑ i = 1 n x i ∑ i = 1 n y i {{D}}{}_{{a}}=\:\left|\begin{matrix}{\sum_{i=1}^n{{{{x{}}{}_{{i}}y}}_{{i}}}{}} & {\sum_{i=1}^n{{{x}}_{{i}}^{{}}}{}} \\ {\sum_{i=1}^n{{y{}}_{{i}}^{{}}}{}} & n\end{matrix}\right|\:=\:n\sum_{i=1}^n{{{x{}}{}_{{i}}y}}_{{i}}^{{}}\:-\:\sum_{i=1}^n{x{}}{}_{{i}}\sum_{i=1}^n{y{}}_{{i}}^{{}} Da= i=1nxiyii=1nyii=1nxin =ni=1nxiyii=1nxii=1nyi D b =   ∣ ∑ i = 1 n x i 2 ∑ i = 1 n x i y i ∑ i = 1 n x i ∑ i = 1 n y i ∣   =   ∑ i = 1 n x i 2   ∑ i = 1 n y i   −   ∑ i = 1 n x i ∑ i = 1 n x i y i {{D}}{}_{{b}}=\:\left|\begin{matrix}{\sum_{i=1}^n{{{x}}_{{i}}^{{2}}}{}} & {\sum_{i=1}^n{{{{x{}}{}_{{i}}y}}_{{i}}^{{}}}{}} \\ {\sum_{i=1}^n{{{x}}_{{i}}^{{}}}{}} & \sum_{i=1}^n{{y}}{}_{{i}}\end{matrix}\right|\:=\:\sum_{i=1}^n{{x}}_{{i}}^{{2}}\:\sum_{i=1}^n{{y}}{}_{{i}}\:-\:\sum_{i=1}^n{{x}}_{{i}}^{{}}\sum_{i=1}^n{{{x{}}{}_{{i}}{y{}}{}_{{i}}}} Db= i=1nxi2i=1nxii=1nxiyii=1nyi =i=1nxi2i=1nyii=1nxii=1nxiyi

可以求得a和b:
a   =   D a D   =   n ∑ x i y i − ∑ x i ∑ y i n ∑ x i 2 − ( ∑ x i ) 2 a\:=\:\frac{{{D}}{}_{{a}}}{D}\:=\:\frac{n\sum{{{x}}{}_{{i}}}{{y}}{}_{{i}}-\sum{{{x}}{}_{{i}}}\sum{{y{}}{}_{{i}}}}{n\sum{{x}}_{{i}}^{{2}}-\left(\sum{{x}}{}_{{i}}\right)^2} a=DDa=nxi2(xi)2nxiyixiyi b   =   D b D   =   ∑ x i 2 ∑ y i − ∑ x i ∑ x i y i n ∑ x i 2 − ( ∑ x i ) 2 b\:=\:\frac{{{D}}{}_{b{}}}{D}\:=\:\frac{\sum{{x}}_{{i}}^{{2}}{{}}{}_{{}}\sum{{y{}}{}_{{i}}}-\sum{{{x}}{}_{{i}}}\sum{{{{x}}{}_{{i}}y{}}{}_{{i}}}}{n\sum{{x}}_{{i}}^{{2}}-\left(\sum{{x}}{}_{{i}}\right)^2} b=DDb=nxi2(xi)2xi2yixixiyi

3 C语言代码实现

1)首先设计头文件polyfit_types.h,用于定义一些基本类型和结构体;

//polyfit_types.h
#ifndef POLYFIT_TYPES_H
#define POLYFIT_TYPES_H

typedef signed char int8;
typedef unsigned char uint8;
typedef short int16;
typedef unsigned short uint16;
typedef int int32;
typedef unsigned int uint32;
typedef float float32;

typedef struct POINT_TAG
{
	float32 x_f32;
	float32 y_f32;
}POINT_TYPE;

typedef struct LINE_TAG
{
	float32 a_f32;
	float32 b_f32;
}LINE_TYPE;

#endif

这里先定义一些基本类型,比如uint8,float32等。接着定义两个结构体:点和直线。点结构体的成员是点的x和y坐标,直线结构体的成员是直线的系数a和b。

2)设计头文件polyfit.h;

//polyfit.h
#ifndef POLYFIT_H
#define POLYFIT_H

//Include
#include "polyfit_types.h"

//Define
#define EPS 0.000001F
#define OK  1U
#define NOK 0U

//Function
uint8 polyfit(const POINT_TYPE* points_vs, const uint8 point_number_u8, LINE_TYPE* line_s, float32* square_error_f32);

#endif

头文件首先包含了type头文件。接着是宏定义EPS表示一个极小值,用于浮点数相等比较。然后就是函数polyfit的声明,函数的前两个参数是输入的点的数组和点的数量,后两个参数表示输出的直线和残差的平方和。

3)设计C文件polyfit.c,是实现算法的核心代码;

//polyfit.c
#include <math.h>
#include <string.h>
#include "polyfit.h"

//Function
uint8 polyfit(const POINT_TYPE* points_vs, const uint8 point_number_u8, LINE_TYPE* line_s, float32* square_error_f32)
{
	//var
	float32 sum_x_f32  = 0.0F;
	float32 sum_xy_f32 = 0.0F;
	float32 sum_x2_f32 = 0.0F;
	float32 sum_y_f32  = 0.0F;

	float32 xi = 0.0F;
	float32 yi = 0.0F;	

	float32 Det_f32   = 0.0F;
	float32 Det_a_f32 = 0.0F;
	float32 Det_b_f32 = 0.0F;

	float32 Err = 0.0F;

	uint8 index_u8;

	//initialize
	*square_error_f32 = 0.0F;
	memset(line_s, 0.0F, sizeof(LINE_TYPE));

	//calculate sum
	for (index_u8 = 0U; index_u8 < point_number_u8; index_u8++)
	{
		xi = points_vs[index_u8].x_f32;
		yi = points_vs[index_u8].y_f32;

		sum_x_f32  += xi;
		sum_xy_f32 += xi * yi;
		sum_x2_f32 += xi * xi;
		sum_y_f32  += yi;
	}

	//calculate determinant
	Det_f32   = point_number_u8 * sum_x2_f32 - sum_x_f32 * sum_x_f32;
	Det_a_f32 = point_number_u8 * sum_xy_f32 - sum_x_f32 * sum_y_f32;
	Det_b_f32 = sum_x2_f32      * sum_y_f32  - sum_x_f32 * sum_xy_f32;

	//determine if Det_f32 is 0
	if (fabsf(Det_f32) < EPS)
	{
		return NOK;
	}

	//calculate coefficient
	line_s->a_f32 = Det_a_f32 / Det_f32;
	line_s->b_f32 = Det_b_f32 / Det_f32;

	//calculate sum of square error
	for (index_u8 = 0U; index_u8 < point_number_u8; index_u8++)
	{
		xi = points_vs[index_u8].x_f32;
		yi = points_vs[index_u8].y_f32;

		Err = yi - (line_s->a_f32 * xi + line_s->b_f32);
		*square_error_f32 += Err * Err;
	}

	return OK;
}

代码中,首先定义局部变量,并初始化输出参数。接着,按照公式计算三个行列式,并判断D是否为0,如果为零就停止计算并返回。最后通过克拉默法则计算系数,以及根据算出的直线计算残差的平方和。

4 测试验证

在主函数里可以简单写一段测试代码,验证一下是否正确。

1)参考某百科,假设输入4个点为(1,6),(2,5),(3,7),(4,10),测试代码如下;

#include <stdio.h>
#include "polyfit.h"

int main()
{
	POINT_TYPE point_vs[4];
	point_vs[0].x_f32 = 1.0F; point_vs[0].y_f32 = 6.0F;
	point_vs[1].x_f32 = 2.0F; point_vs[1].y_f32 = 5.0F;
	point_vs[2].x_f32 = 3.0F; point_vs[2].y_f32 = 7.0F;
	point_vs[3].x_f32 = 4.0F; point_vs[3].y_f32 = 10.0F;

	LINE_TYPE line_s;	
	float32 square_error_f32;
	uint8 retVal;

	retVal = polyfit(point_vs, 4U, &line_s, &square_error_f32);

	printf("retVal = %d , a = %f , b = %f , err = %f \r\n", retVal, line_s.a_f32, line_s.b_f32, square_error_f32);
}

打印出来的结果为:

在这里插入图片描述

这表示拟合的直线方程为:

y = 1.4 x + 3.5 y = 1.4x + 3.5 y=1.4x+3.5

绘图出来的结果是:
在这里插入图片描述

2)如果输入的4个点是(1,6),(1,5),(1,7),(1,10),即四个点在一条垂直于x轴的直线上,这时行列式D=0,就无法得出拟合结果:

#include <stdio.h>
#include "polyfit.h"

int main()
{
	POINT_TYPE point_vs[4];

	point_vs[0].x_f32 = 1.0F; point_vs[0].y_f32 = 6.0F;
	point_vs[1].x_f32 = 1.0F; point_vs[1].y_f32 = 5.0F;
	point_vs[2].x_f32 = 1.0F; point_vs[2].y_f32 = 7.0F;
	point_vs[3].x_f32 = 1.0F; point_vs[3].y_f32 = 10.0F;

	LINE_TYPE line_s;	
	float32 square_error_f32;
	uint8 retVal;

	retVal = polyfit(point_vs, 4U, &line_s, &square_error_f32);

	printf("a = %f , b = %f , err = %f \r\n", line_s.a_f32, line_s.b_f32, square_error_f32);
}

打印出来的结果为:

在这里插入图片描述

5 总结

本文本文研究通过C语言实现最小二乘法拟合直线。在工程应用中,一次和二次多项式的拟合用的比较多。二次多项式拟合可以参考一次的推导过程和编程过程,需要求解三阶行列式求解三个系数。

>>返回个人博客总目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/80956.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode15. 三数之和

这里保证1.元素a不会重复。2.所有解都是有序的。3.b和c元素不重复。所以解不会重复。 class Solution { public:vector<vector<int>> threeSum(vector<int>& nums) {std::vector<std::vector<int>> result;if (nums.size() < 3) return …

提升大数据技能,不再颓废!这6家学习网站是你的利器!

随着国家数字化转型&#xff0c;大数据领域对人才的需求越来越多。大数据主要研究计算机科学和大数据处理技术等相关的知识和技能&#xff0c;从大数据应用的三个主要层面&#xff08;即数据管理、系统开发、海量数据分析与挖掘&#xff09;出发&#xff0c;对实际问题进行分析…

更多openEuler镜像加入AWS Marketplace!

自2023年7月openEuler 22.03 LTS SP1正式登陆AWS Marketplace后&#xff0c;openEuler社区一直持续于在AWS上提供更多版本。 目前&#xff0c;openEuler22.03 LTS SP1 ,SP2两个版本及 x86 arm64两种架构的四个镜像均可通过AWS对外提供&#xff0c;且在亚太及欧洲15个Region开放…

UML图绘制 -- 类图

1.类图的画法 类 整体是个矩形&#xff0c;第一层类名&#xff0c;第二层属性&#xff0c;第三层方法。 &#xff1a;public- : private# : protected空格: 默认的default 对应的类写法。 public class Student {public String name;public Integer age;protected I…

深入理解【二叉树】

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…

java+springboot+mysql银行管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的银行管理系统&#xff0c;系统包含超级管理员、管理员、客户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;客户管理&#xff1b;卡号管理&#xff08;存款、取款、转账&#xff09…

C++系列-内存模型

内存模型 内存模型四个区代码区全局区栈区堆区内存开辟和释放在堆区开辟数组 内存模型四个区 不同区域存放的数据生命周期是不同的&#xff0c;更为灵活。 代码区&#xff1a;存放函数体的二进制代码&#xff0c;操作系统管理。全局区&#xff1a;存放全局变量&#xff0c;常…

【ES5和ES6】数组遍历的各种方法集合

一、ES5的方法 1.for循环 let arr [1, 2, 3] for (let i 0; i < arr.length; i) {console.log(arr[i]) } // 1 // 2 // 32.forEach() 特点&#xff1a; 没有返回值&#xff0c;只是针对每个元素调用func三个参数&#xff1a;item, index, arr &#xff1b;当前项&#…

Gradio部署应用到服务器不能正常访问

用Gradio部署一个基于ChatGLM-6B的应用&#xff0c;发布到团队的服务器上&#xff08;局域网&#xff0c;公网不能访问&#xff09;&#xff0c;我将gradio应用发布到服务器的9001端口 import gradio as gr with gr.Blocks() as demo:......demo.queue().launch(server_port90…

RocketMQ、Dashboard部署以及安全设置

RocketMQ、dashboard部署以及安全设置 一、启动RocketMQ1.1 下载RocketMQ1.2 修改配置文件1.2.1 修改nameServer Jvm内存配置1.2.2 修改broker参数 1.3 启动1.3.1 启动NameServer1.3.2 启动Broker1.3.3 测试是否启动成功1.3.3.1 测试消息发送1.3.3.2 测试消息接收1.3.3.3 Java程…

SparkSQL源码分析系列03-Antlr4分析测试

SparkSQL主要通过Antlr4定义SQL的语法规则&#xff0c;完成SQL词法&#xff0c;语法解析&#xff0c;最后将SQL转化为抽象语法树。所以有必要先了解下Antlr4的工作流程。 ANTLR4是什么&#xff1f; ANTLR 是 ANother Tool for Language Recognition 的缩写&#xff0c;官网&a…

3D沉浸式旅游网站开发案例复盘【Three.js】

Plongez dans Lyon网站终于上线了。 我们与 Danka 团队和 Nico Icecream 共同努力&#xff0c;打造了一个令我们特别自豪的流畅的沉浸式网站。 这个网站是专为 ONLYON Tourism 和会议而建&#xff0c;旨在展示里昂最具标志性的活动场所。观看简短的介绍视频后&#xff0c;用户…

数据结构基础

将节点构建成树 数据的结构逻辑结构集合线性结构树形结构图状结构 存储结构合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如…

vue2.0/vue3.0学习笔记——2022.08.16

vue2&#xff08;查漏补缺&#xff09; 一、vue基础 内置指令&#xff08;查漏补缺&#xff09; 1、v-text 更新元素的textContent 2、v-html 更新元素的innerHtml 3、v-cloak 防止闪现&#xff0c;与css配合: [v-cloak] {dispaly: none} 4、v-once 在初次动态渲染厚&#x…

wxPython使用matplotlib绘制动态曲线

1.思路 我们创建了一个继承自wx.Frame的自定义窗口类MyFrame。在MyFrame的构造函数中&#xff0c;我们创建了一个matplotlib的Figure对象和一个FigureCanvas对象&#xff0c;用于在窗口中显示绘图结果。然后&#xff0c;我们使用numpy生成了一个包含100个点的x轴坐标数组self.…

Java IO流(二)IO模型(BIO|NIO|AIO)

概述 Java IO模型同步阻塞IO&#xff08;BIO&#xff09;、同步非阻塞IO&#xff08;NIO&#xff09;、异步非阻塞IO&#xff08;AIO/NIO2&#xff09;,Java中的BIO、NIO和AIO理解为是Java语言对操作系统的各种IO模型的封装 IO模型 BIO(Blocking I/O) 概述 BIO是一种同步并阻…

Linux实用运维脚本分享

Linux实用运维脚本分享&#x1f343; MySQL备份 目录备份 PING查询 磁盘IO检查 性能相关 进程相关 javadump.sh 常用工具安装 常用lib库安装 系统检查脚本 sed进阶 MySQL备份 #!/bin/bashset -eUSER"backup" PASSWORD"backup" # 数据库数据目录…

C++ Builder 关于TRichEdit的字符颜色标记处理

//积累经验每一天&#xff0c;以后忘记好搜索 void __fastcall TForm2::btn3Click(TObject *Sender) { //初始化验证 mmo->SelStart0; mmo->SelLengthmmo->Text.Length(); mmo->SelAttributes->ColorclBlack; String CGhEd…

如何使用Kali Linux进行渗透测试?

1. 渗透测试简介 渗透测试是通过模拟恶意攻击&#xff0c;评估系统、应用或网络的安全性的过程。Kali Linux为渗透测试人员提供了丰富的工具和资源&#xff0c;用于发现漏洞、弱点和安全风险。 2. 使用Kali Linux进行渗透测试的步骤 以下是使用Kali Linux进行渗透测试的基本…

visual studio 2017 运行的程序关闭后不能再运行?(visual studio建立项目之后退出,如何再次完整打开项目?)

在你储存项目的文件夹里面应该是这样的 里面.vcxproj后缀名的就是原来创建的项目&#xff0c;直接打开这个头文件源文件就会一起出来了&#xff01; 真的管用&#xff0c;亲测有效。