c++ 实现 梯度下降线性回归模型

理论与python实现部分

3.1. 线性回归 — 动手学深度学习 2.0.0 documentation

c++代码

没能力实现反向传播求梯度,只能自己手动算导数了

#include <bits/stdc++.h>
#include <time.h>
using namespace std;

//y_hat = X * W + b
// linreg 函数:线性回归预测  
// 参数:  
//   double** X: 输入数据的二维数组,其中 X[i][j] 表示第 i 个样本的第 j 个特征  
//   double* W: 权重向量,W[j] 表示第 j 个特征的权重  
//   double b: 偏置项  
//   int batch_size: 批量大小,即一次处理的样本数量  
//   int lenw: 权重向量的长度,即特征的数量  
// 返回值:  
//   double* y_hat: 预测值的数组,长度为 batch_size  
double* linreg(double** X, double* W, double b, int batch_size, int lenw)
{
	// 分配内存来存储预测值  
	double* y_hat = new double[batch_size];
	// 遍历每一个样本  
	for (int i=0; i<batch_size; i++)
	{
		double sum=0;  // 求和计算 X * W 
		// 遍历每一个特征  
		for (int j=0; j<lenw; j++)
		{
			// 累加该样本的每个特征与对应权重的乘积
			sum += X[i][j]*W[j];
		}
		// 加上偏置项得到最终的预测值  
		y_hat[i] = sum+b;
	}
	// 返回预测值的数组
	return y_hat;
}

// squared_loss 函数:计算平方损失  
// 参数:  
//   double* y_hat: 预测值的数组  
//   double* y: 实际值的数组  
//   int len: 预测值和实际值的长度(应相同)  
// 返回值:  
//   double* l: 平方损失的数组,长度为 len  
double* squared_loss(double* y_hat, double* y, int len)
{
	// 分配内存来存储平方损失  
	double* l = new double[len];
	// 遍历每一个预测值与实际值
	for (int i=0; i<len; i++)
	{
		// 计算平方损失 (y_hat[i]-y[i])^2 的一半(常用在损失函数中)
		l[i] = 0.5 * (y_hat[i]-y[i]) * (y_hat[i]-y[i]);
	}
	// 返回平方损失的数组 
	return l;
}

// sum 函数:计算数组的和  
// 参数:  
//   double* l: 需要求和的数组  
//   int len: 数组的长度  
// 返回值:  
//   double ans: 数组的和 
double sum(double* l, int len)
{
	double ans=0; // 初始化和为0  
	for (int i=0; i<len; i++)
	{
		ans += l[i]; // 累加数组中的每一个元素  
	}
	return ans; // 返回数组的和  
}

// sgd 函数:使用随机梯度下降(Stochastic Gradient Descent)算法更新权重和偏置项  
// 参数:  
//   double** X: 输入数据的二维数组,其中 X[i][j] 表示第 i 个样本的第 j 个特征  
//   double* y: 实际值的数组,与输入数据一一对应  
//   double* W: 权重向量,W[j] 表示第 j 个特征的权重  
//   double &b: 偏置项的引用,以便在函数内部修改其值  
//   int lenw: 权重向量的长度,即特征的数量  
//   double lr: 学习率(Learning Rate),用于控制权重更新的步长  
//   int batch_size: 批量大小,即一次处理的样本数量  
// 返回值:  
//   无返回值,但会直接修改 W 和 b 的值  
/*
y_hat = X * W + b
loss = 0.5 * (y_hat - y) * (y_hat - y)
d(loss)/d(y_hat) = y_hat - y
d(y_hat)/d(W) = X
d(y_hat)/d(b) = 1
∴ d(loss) / d(W) = d(loss)/d(y_hat) * d(y_hat)/d(W) = (y_hat - y) * X
   d(loss) / d(b) = d(loss)/d(y_hat) * d(y_hat)/d(b) = (y_hat - y) * 1
*/ 
void sgd(double** X, double* y, double* W, double &b, int lenw, double lr, int batch_size)
{
	// 调用 linreg 函数获取预测值  
	double* y_hat = linreg(X, W, b, batch_size, lenw);
	// 计算每个权重的梯度 
	for (int i=0; i<lenw; i++)
	{
		double grad=0;// 初始化当前权重的梯度为0  
		for (int j=0; j<batch_size; j++)
		{
			// 计算梯度:每个样本的梯度为该样本的特征值与预测误差的乘积
			grad += X[j][i]*(y_hat[j]-y[j]);
		}
		// 更新权重:使用学习率乘以平均梯度(梯度之和 除以batch_size),并从当前权重中减去  
		W[i] = W[i] - lr * grad / batch_size;
	}
	// 计算偏置项的梯度  
	double grad=0; // 初始化偏置项的梯度为0  
	for (int j=0; j<batch_size; j++)
	{
		// 计算梯度和:所有样本的预测误差之和 
		grad += (y_hat[j]-y[j]);
	}
	// 更新偏置项:使用学习率乘以平均梯度(除以batch_size),并从当前偏置项中减去  
	b = b - lr * grad / batch_size;
	
	// 释放预测值数组的内存
	delete[] y_hat;
}

int main()
{
	// 设定真实的权重和偏置项,用于比较训练结果  
	const double true_w[] = {3.3, -2.4}, true_b = 11.4;
	// 设定权重向量的长度  
	const int lenw=2;
	// 初始化权重和偏置项  
	double w[lenw] = {0, 0}; // 理论上应该可以为任意值
	double b=0.0;
	// 设定样本数量 
	int sample_num=2000;
	// 分配二维数组内存用于存储特征数据  
	double** X = new double*[sample_num];
	for (int i=0; i<sample_num; i++) X[i] = new double[lenw];
	// 分配一维数组内存用于存储实际标签  
	double y[sample_num];
	// 打开数据文件datas.txt并读取特征数据和标签  
	// 单行数据格式 x1 x2 x3 ... xn y
	freopen("datas.txt", "r", stdin);
	for (int i=0; i<sample_num; i++)
	{
		for (int j=0; j<lenw; j++)
		{
			scanf("%lf", &X[i][j]); // 读取特征值 
		}
		scanf("%lf", &y[i]); // 读取标签  
	}
	// 关闭数据文件,并重新打开标准输入  
	freopen("CON", "r", stdin);
	// 设定学习率、迭代次数和批量大小 
	double lr = 0.03;
    int num_epochs = 800; 
    // 将数据集分成50个批次 
    int batch_size = sample_num/50;
    double* (*net) (double**, double*, double, int, int);
    double* (*loss) (double*, double*, int);
    net = linreg;// 函数指针,没啥实际用处
    loss = squared_loss;
    time_t st=clock();
    // 迭代训练模型  
    for (int epoch=0; epoch < num_epochs; epoch++)
    {
    	// 计算当前批次的预测值  
    	double* y_hat = net(X, w, b, batch_size, lenw);
    	// 计算当前批次的损失  
    	double loss1 = sum(loss(y_hat, y, batch_size), batch_size);
    	// 使用随机梯度下降更新权重和偏置项  
    	// 如果不需要输出查看训练过程的损失,每次迭代其实只需要sgd函数就可以完成训练(即参数 w 和 b 的更新) 
    	sgd(X, y, w, b, lenw, lr, batch_size);
    	
    	// 计算整个数据集的预测值  
    	double* y1_hat = linreg(X, w, b, sample_num, lenw);
    	// 计算整个数据集的损失 
    	double loss2 = sum(loss(y1_hat, y, sample_num), sample_num);
    	// 每50个迭代周期打印一次训练损失
    	if (epoch%50 == 0)
    		printf("in epoch %d, train loss is %lf\n", epoch+1, loss2/sample_num);
    	// 释放当前批次和整个数据集预测值的内存  
    	
    	delete[] y_hat;
		delete[] y1_hat; 
	}
	
	// 设定测试样本数量
	int test_num=30;
	// 分配二维数组内存用于存储测试数据  
	double** test_X = new double*[test_num];
	for (int i=0; i<test_num; i++) test_X[i] = new double[lenw];
	// 分配一维数组内存用于存储测试标签  
	double test_y[test_num];
	// 打开测试数据文件tests.txt并读取测试数据和标签 
	freopen("tests.txt", "r", stdin);
	for (int i=0; i<test_num; i++)
	{
		for (int j=0; j<lenw; j++)
		{
			scanf("%lf", &test_X[i][j]); // 读取测试特征值  
		}
		scanf("%lf", &test_y[i]); // 读取测试标签  
	}
	// 关闭测试数据文件,并重新打开标准输入  
	freopen("CON", "r", stdin);
	// 计算测试集的预测值
	double* test_y_hat = linreg(test_X, w, b, test_num, lenw);
	// 计算测试数据的损失值  
	double loss2 = sum(squared_loss(test_y_hat, test_y, test_num), test_num);
	// 计算测试数据的平均损失值  
	double loss_mean = loss2 / test_num;
	
	printf("in test, loss is %lf\n", loss_mean);
	printf("w is ");
	for (int i=0; i<lenw; i++) printf("%lf%c", w[i], i==(lenw-1)?'\n':' ');
	printf("b=%lf\n", b);
	
	printf("true_w is {3.3, -2.4}, true_b is 11.4\n");
	time_t ed=clock();
	printf(" %d epoch, time %d ms\n", num_epochs, ed-st);
	
	// 注意:这里还需要释放test_X数组的内存,以及之前分配的X数组的内存  
	// 释放test_X数组的内存  
	for (int i = 0; i < test_num; i++) {  
	    delete[] test_X[i];  
	}  
	delete[] test_X;  
	  
	// 释放X数组的内存(注意:这部分代码应在前面的循环之后添加)  
	for (int i = 0; i < sample_num; i++) {  
	    delete[] X[i];  
	}  
	delete[] X;
}

训练效果

和pytorch的训练效果对比

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

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

相关文章

Unity射击游戏开发教程:(24)创造不同的敌人

在这篇文章中,我们将讨论添加一个可以承受多次攻击的新敌人和一些动画来使事情变得栩栩如生。敌人没有任何移动或射击行为。这将有助于增强未来敌人的力量。 我们将声明一个 int 来存储敌人可以承受的攻击数量,并将其设置为 3。

深度学习之基于Matlab卷积神经网络(CNN)手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手写数字识别是计算机视觉领域的一个重要问题&#xff0c;也是深度学习应用的一个典型场景。卷…

基于消息中间件的异步通信机制在系统解耦中的优化与实现

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

Docker Compose快速入门

本教程旨在通过指导您开发基本Python web应用程序来介绍Docker Compose的基本概念。 使用Flask框架&#xff0c;该应用程序在Redis中提供了一个命中计数器&#xff0c;提供了如何在web开发场景中应用Docker Compose的实际示例。 即使您不熟悉Python&#xff0c;这里演示的概念也…

Llama模型家族之使用 Supervised Fine-Tuning(SFT)微调预训练Llama 3 语言模型(一) LLaMA-Factory简介

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

以色列人Andi Gutmans开发的php zend

虽然目前php语言不行了【相关的文章前几年已经有人发过】&#xff0c;但这不是重点&#xff0c;重点是zend引擎的东西具有极大的技术价值&#xff0c;负责zend引擎实现的大佬都现在差不多都是40&#xff0c;50岁左右了&#xff0c;从1997&#xff0c;1998&#xff0c;2000到202…

Java基础之进制转换和位运算专题

什么是进制&#xff1f; 是数学中的一个概念&#xff0c;就是数据“逢几进位”。 例如&#xff1a;生活中用的计数方法 ---- 十进制。十进制就是数字逢十就要进一位。 例如&#xff1a;一个星期有7天&#xff0c;就是逢七进一&#xff1b;一个月有30天就是逢30进一&#xff1b;…

基于单片机和蓝牙控制的智能小车设计

摘要 &#xff1a; 本文设计了一种以智能手机为平台控制小车的控制系统&#xff0c;该系统以蓝牙为通信模块&#xff0c;手机通过蓝牙发送信号给小 车上的蓝牙模块&#xff0c;从而驱动电机实现小车各种运动&#xff0c;提供了一种无线遥控小车的新思路。设计了该系统的硬件与软…

思维导图-VPN

浏览器集成了受信任的机构的证书

python+selenium - UI自动框架之封装查找元素

单一的元素定位方法不能满足所有元素的定位&#xff0c;可以根据每个元素的特点来找到合适的方法&#xff0c;可以参考下图的方法&#xff1a; elementFind.py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_con…

汇舟问卷:海外问卷项目适合工作室做吗?

这个项目适合工作室操作&#xff0c;国外问卷调查主要是利用填写问卷来赚取奖励。只要完成得越多&#xff0c;挣得也就越多。 这个项目的本质就是在线上进行简单的工作&#xff0c;只不过结算方式是以美元计算。 即使一份问卷只值1美元&#xff0c;但这也意味着收入达到了7元…

鸿蒙ArkUI-X跨平台技术:【SDK结构介绍】

ArkUI-X SDK目录结构介绍 简介 本文档配套ArkUI-X&#xff0c;将OpenHarmony ArkUI开发框架扩展到不同的OS平台&#xff0c;比如Android和iOS平台&#xff0c;让开发者基于ArkUI&#xff0c;可复用大部分的应用代码&#xff08;UI以及主要应用逻辑&#xff09;并可以部署到相…

ngnix 入门 二,docker启动nginx, 安装ssl 证书,使用配置文件,映射后端服务 ,提供给前端项目访问

搭建生产环境真不是人做的事&#xff0c;特别是对于一知半解的人。仅以此文献给各位技术人 说一下背景&#xff1a;项目前后端分离&#xff0c;前端 vue3 、小程序端 &#xff0c;后端 go 提供服务。 微信小程序需要使用 https 请求。 这就必须让我们想到nginx 了 想要达到的…

代码随想录算法训练营第二天| 977.有序数组的平方 、209.长度最小的子数组、 59.螺旋矩阵II

977. 有序数组的平方 题目链接&#xff1a;977. 有序数组的平方 文档讲解&#xff1a;代码随想录 状态&#xff1a;so easy 刚开始看到题目第一反应就是平方之后进行排序&#xff0c;数据量在 1 0 4 10^4 104&#xff0c;可以使用O(nlogn)的排序。但是更好的方式是使用双指针&a…

ArrayList与LinkedList

内存 内存缓存 预先将数据写到容器等数据存储单元中&#xff0c;就是软件内存缓存。 内存缓存淘汰机制 FIFO&#xff08;First in ,First Out&#xff09;&#xff08;先进先出&#xff09; LFU (Least Frequently Used) (频繁的最后淘汰) LRU(Least Recently Used) &#…

Socket同步通讯

目录 引言 1. 建立连接 2. 数据传输 3. 同步机制 4. 处理延迟 5. 安全性 6、一对一Socket同步通讯 客户端 代码分析 服务端 代码分析 7、服务端操作 1、首先我们先运行客户端代码 2、服务端点击Connect连接客户端 3、服务端输入信息传输到客户端 4、断开连接 引…

【笔记】软件架构师要点记录(1)

【笔记】软件架构师要点记录 20240517 20240517 连续性&#xff1a;恢复能力&#xff1b;可用性&#xff1a;保持稳定态的时长 增量开发模式&#xff1a;在增量开发中&#xff0c;每个增量都有明确的范围和功能&#xff0c;并按照特定的功能顺序完成。增量之间的范围划分在开发…

Flask CORS: 解决跨域资源共享问题的利器

文章目录 安装和启用 CORS配置 CORS拓展 在本文中&#xff0c;我们介绍了如何使用 Flask-CORS 扩展来解决跨域问题。Flask-CORS 是一个方便的工具&#xff0c;可以帮助我们轻松地实现跨域资源共享支持。 安装和启用 CORS 要开始使用 Flask-CORS&#xff0c;我们需要先安装它。…

腹部多器官分割的眼动引导双路径网络

文章目录 标题摘要方法实验结果 标题 摘要 这项研究提出了一种新的方法&#xff0c;名为眼动引导双路径网络&#xff08;Eye-Guided Dual-Path Network&#xff0c;EG-DPN&#xff09;&#xff0c;用于腹部多器官分割。这项工作的主要目标是提高医学影像分析中的多器官分割准…

express.js--token中间件验证及token解析(三)

主要作用 访问路由接口时&#xff0c;哪些需要校验token 通过token解析身份信息&#xff0c;就可以知道是哪个人 框架基本搭建express.js--基本用法及路由模块化(一)-CSDN博客 如何生成tokenexpress.js--生成token(二)-CSDN博客 middleware/index.js const jwt require(…