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

news2024/12/24 0:00:29

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

>>返回个人博客总目录

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无线液位传感器VS有线液位传感器,优点在哪里?

无线技术在催生新行业诞生的同时&#xff0c;也在不断促使着很多传统设备做出新的改变&#xff0c;包括在工业领域中常用到的液位传感器。 无线液位传感器与有线液位传感器相比&#xff0c;最大的优点就在于使用方便。 在传输上做到无线&#xff1a;无线液位传感器可以选择两…

Acwing C++

756. 蛇形矩阵 题解&#xff1a; 蛇形矩阵走法&#xff1a;右 -> 下 ->左 ->上 坐标变化&#xff1a;(x2,y2) (x1,y1) (dx[d] dy[d]) d步数变化&#xff1a;d (d 1)%4 dx[4],dy[4] 分别用来存放xy偏移量&#xff0c;d初始值为0&#xff0c;在两种情况下会1&#…

OC调用Swift编写的framework

一、前言 随着swift趋向稳定&#xff0c;越来越多的公司都开始用swift来编写苹果相关的业务了&#xff0c;关于swift的利弊这里就不多说了。这里详细介绍OC调用swift编写的framework库的步骤 二、制作framework 1、新建项目&#xff0c;选择framework 2、填写framework的名称…

SpringBoot统⼀功能处理

前言&#x1f36d; ❤️❤️❤️SSM专栏更新中&#xff0c;各位大佬觉得写得不错&#xff0c;支持一下&#xff0c;感谢了&#xff01;❤️❤️❤️ Spring Spring MVC MyBatis_冷兮雪的博客-CSDN博客 本章是讲Spring Boot 统⼀功能处理模块&#xff0c;也是 AOP 的实战环节&…

[国产MCU]-W801开发实例-开发环境搭建

W801开发环境搭建 文章目录 W801开发环境搭建1、W801芯片介绍2、W801芯片特性3、W801芯片结构4、开发环境搭建1、W801芯片介绍 W801芯片是联盛德微电子推出的一款高性价比物联网芯片。 W801 芯片是一款安全 IoT Wi-Fi/蓝牙 双模 SoC芯片。芯片提供丰富的数字功能接口。支持2.…

YOLOV7改进:加入RCS-OSA模块,提升检测速度

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:RCS-OSA模块更加轻量化,有效提升检…

开源了一套基于springboot+vue+uniapp的商城,包含分类、sku、商户管理、分销、会员、适合企业或个人二次开发

RuoYi-Mall-JAVA商城-电商系统简介 开源了一套基于若依框架&#xff0c;SringBoot2MybatisPlusSpringSecurityjwtredisVueUniapp的前后端分离的商城系统&#xff0c; 包含分类、sku、商户管理、分销、会员、适合企业或个人二次开发。 前端采用Vue、Element UI&#xff08;ant…

Ubuntu一直卡死的问题(20.04)

Ubuntu一直卡死的问题&#xff08;18.04&#xff09;_ubuntu频繁死机_Mr.Yi的博客-CSDN博客 我自己的解决方法: 1、首先强制关机重启后&#xff0c;直接打开命令行查看磁盘的使用&#xff1a; df -h发现/dev/loop都沾满了&#xff0c;我们能需要做的就是把他们清理干净 sud…

自动驾驶港口车辆故障及事故处理机制

1、传感器故障&#xff1a; &#xff08;1&#xff09;单一传感器数据异常处理。自动驾驶电动平板传感方案为冗余设置&#xff0c;有其他传感器能够覆盖故障传感器观测区域&#xff0c;感知/定位模块将数据异常情况发给到规划决策模块&#xff0c;由“大脑”向中控平台上报故障…

分布式 - 服务器Nginx:一小时入门系列之负载均衡

文章目录 1. 负载均衡2. 负载均衡策略1. 轮询策略2. 最小连接策略3. IP 哈希策略4. 哈希策略5. 加权轮询策略 1. 负载均衡 跨多个应用程序实例的负载平衡是一种常用技术&#xff0c;用于优化资源利用率、最大化吞吐量、减少延迟和确保容错配置。‎使用 nginx 作为非常有效的HT…

关于Power Query中一些忽略的细节

Power Query中一些忽略的细节 重新认识Power Query查询的引用----提高数据加载效率透视逆透视----一对“好朋友”神奇的拼接----实现很多意想不到的操作 重新认识Power Query 关于它的定义&#xff0c;这里不再赘述&#xff0c;主要说一些新的理解。 Power Query 可以理解就是一…

一个简单的协议定制

目录 补充概念&#xff1a;三次握手&#xff0c;四次挥手 再谈协议 网络版计算器 准备工作 makefile log.hpp calServer.hpp calServer.cc calClient.hpp calClient.cc 服务端 新建文件与接口 Protocol.hpp 1.0服务端的一个流程 1.1创建一个回调方法 1.2保证你…

【vue3+xlxs+xlsx-style-vite】vue3项目中使用xlsx插件实现Excel表格的导出和解析,已实现

在vue3项目中使用xlsx插件实现Excel表格的导出和解析 1、xlsx插件包官方 xlsx插件包官方 2、FileReader官方文档&#xff1a;FileReader官方文档 安装xlsx和xlsx-style-vite、file-saver npm install xlsx npm install xlsx-style-vite npm install file-saverpackage.json中查…

【C语言】小游戏-扫雷(清屏+递归展开+标记)

大家好&#xff0c;我是深鱼~ 目录 一、游戏介绍 二、文件分装 三、代码实现步骤 1.制作简易游戏菜单 2. 初始化棋盘(11*11) 3.打印棋盘(9*9) 4.布置雷 5.计算(x,y)周围8个坐标的和 6.排查雷 <1>清屏后打印棋盘 <2>递归展开 <3>标记雷 四、完整代…

Open_MV学习笔记1:开发环境获取

稍微学点计算机视觉相关吧&#xff0c;从今天开始浅浅地学习一下Open_MV&#xff0c;以及回忆一下Python编程相关&#xff0c;Open_mv编程需要用到Python&#xff0c;因此设俩个专栏&#xff1a;Open_mv专栏与Python的专栏&#xff0c;大家可以与我一起&#xff0c;在俩者之间跳…

电脑-C盘结构

一 缓存文件 winR 输入%temp% 就会进入到电脑缓存目录 这里面的东西都可以删除 主要目录在User/xxx/AppData\Local\Temp 二 临时文件 C盘右键&#xff0c;详细信息 三 桌面文件 文件类型 program data表示是游戏存档/系统/软件的配置文件 drivers文件表示驱动程序文件 s…

js this变量

js this变量 有个比较特殊的箭头函数没有自己的this&#xff0c;而是继承了外部作用域的this

VBA技术资料MF43:VBA_Excel中自动填充

【分享成果&#xff0c;随喜正能量】以时寝息&#xff0c;当愿众生&#xff0c;身得安隐&#xff0c;心无动乱。愿我们都能&#xff0c;梦见幸福&#xff01;在踉跄中前进&#xff0c;在跌倒后跃进&#xff0c;逐渐强大.。 我给VBA的定义&#xff1a;VBA是个人小型自动化处理的…

吃鸡绝地求生游戏找不到msvcp140.dll缺失打不开怎么办?

msvcp140.dll是Microsoft Visual C Redistributable的一部分&#xff0c;它是一个重要的动态链接库文件&#xff0c;包含了许多用于运行依赖于Visual C的应用程序所需的函数和类。当运行依赖于Visual C的应用程序时&#xff0c;系统会自动加载和使用msvcp140.dll文件。当电脑系…