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

news2024/11/20 7:23:50

理论与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的训练效果对比

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

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

相关文章

9 个适用于小型企业的顶级API管理解决方案

应用程序接口管理解决方案可帮助各种规模的企业开发、部署和管理其应用程序接口&#xff0c;并实现收入最大化。 建立 API 的组织和开发人员可能会被整个 API 生命周期中需要完成的大量任务压得喘不过气来。从规划和构建到部署、维护和货币化&#xff1b;这是一项具有挑战性的工…

【计算机网络原理】对传输层TCP协议的重点知识的总结

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

7.从0做一个vue键盘组件

文章目录 1. 从0做一个键盘组件1.1. 最终效果1.2. 分析1.3. 实现1.4. 如何引用 1. 从0做一个键盘组件 首先是why的问题&#xff1a;为什么需要做键盘组件&#xff1f; 我们目前可知的场景&#xff1a; 在新增账单的时候&#xff0c;需要用到键盘在比如从账单列表页&#xff…

2024年 电工杯 (B题)大学生数学建模挑战赛 | 大学生平衡膳食食谱的优化设计 | 数学建模完整代码解析

DeepVisionary 每日深度学习前沿科技推送&顶会论文&数学建模与科技信息前沿资讯分享&#xff0c;与你一起了解前沿科技知识&#xff01; 本次DeepVisionary带来的是电工杯的详细解读&#xff1a; 完整内容可以在文章末尾全文免费领取&阅读&#xff01; 问题1&…

【Python自动化测试】:Unittest单元测试与HTMLTestRunner自动生成测试用例的好帮手

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 &#x1f525;前言&#x1f680;unittest编写测试用例&#x1f680;unittest测…

49 序列化和反序列化

本章重点 理解应用层的作用&#xff0c;初识http协议 理解传输层的作用&#xff0c;深入理解tcp的各项特性和机制 对整个tcp/ip协议有系统的理解 对tcp/ip协议体系下的其他重要协议和技术有一定的了解 学会使用一些网络问题的工具和方法 目录 1.应用层 2.协议概念 3. 网络计…

awesome-ai4s 现已开源!超全 AI for Science 学术论文与数据资源汇总,持续更新ing

2018 年中国科学院院士鄂维南提出「AI for Science」概念&#xff0c;强调利用 AI 学习科学原理、创造科学模型来解决实际问题。同年&#xff0c;AlphaFold 崭露头角&#xff0c;从 43 种蛋白质中准确预测出了 25 种蛋白质结构。2021 年&#xff0c;AlphaFold 2 开源并预测了 9…

缓存降级

当Redis缓存出现问题或者无法正常工作时,需要有一种应对措施,避免直接访问数据库而导致整个系统瘫痪。缓存降级就是这样一种机制。 主要的缓存降级策略包括: 本地缓存降级 当Redis缓存不可用时,可以先尝试使用本地进程内缓存,如Guava Cache或Caffeine等。这样可以减少对Redis…

OpenLayers中实现对ImageStatic图层的扩展以支持平铺WrapX功能

地图平铺技术概述 地图平铺&#xff08;Tiling&#xff09;是一种将大尺寸地图数据分割成小块&#xff08;瓦片&#xff09;的技术&#xff0c;这在地图服务中非常常见。它使得地图数据能高效加载和展示&#xff0c;尤其适合网络环境。通过仅加载当前视图窗口所需的地图瓦片&a…

Qt官方示例---opengl

文件相对路径&#xff1a;Examples\Qt-5.9.1\opengl 2dpainting cube computegles31 contextinfo hellogl2 hellowindow paintedwindow qopenglwidget qopenglwindow textures threadedqopenglwidget

Rabbitmq 搭建使用案例 [附源码]

Rabbitmq 搭建使用案例 文章目录 RabbitMQ搭建docker 代码golang生产者消费者 可视化消费进度 RabbitMQ搭建 docker docker run -d --hostname rabbitmq --name rabbitmq -e RABBITMQ_DEFAULT_USERadmin -e RABBITMQ_DEFAULT_PASSadmin -e RABBITMQ_DEFAULT_VHOSTmy_vhost -e…

重组蛋白表达系统优缺点对比|卡梅德生物

重组蛋白是现代生物技术中不可或缺的一部分&#xff0c;它们广泛应用于药物开发、研究工具和工业酶的生产。根据目标蛋白的特性和所需的修饰&#xff0c;可以选择不同的表达系统。下文罗列一下四个主要蛋白表达系统的优缺点&#xff1a; 1. 原核表达系统&#xff08;如大肠杆菌…

【QT实战】汇总导航

✨Welcome 大家好&#xff0c;欢迎来到瑾芳玉洁的博客&#xff01; &#x1f611;励志开源分享诗和代码&#xff0c;三餐却无汤&#xff0c;顿顿都被噎。 &#x1f62d;有幸结识那个值得被认真、被珍惜、被捧在手掌心的女孩&#xff0c;不出意外被敷衍、被唾弃、被埋在了垃圾堆…

深度学习之基于Tensorflow卷积神经网络(CNN)实现猫狗识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在人工智能和深度学习的热潮中&#xff0c;图像识别是一个备受关注的领域。猫狗识别作为图像识…

Milvus的内存索引

简介&#xff1a; 这篇文章主要介绍milvus支持的各种内存索引&#xff0c;以及它们最适用的场景&#xff0c;还有用户为了获得更好的搜索性能可以配置的参数。 索引是有效组织数据的过程&#xff0c;它的主要角色是在大的数据集中显著的加速耗时的查询从而有效的进行相似搜索…

【制作100个unity游戏之28】花半天时间用unity复刻童年4399经典小游戏《黄金矿工》(附带项目源码)

最终效果 文章目录 最终效果前言素材模拟绳子钩子来回摆动发射回收钩子方法发射钩子回收钩子勾取物品随机生成物品其他源码完结 前言 在游戏发展史上&#xff0c;有些游戏以其简单而耐玩的特性&#xff0c;深深地烙印在了玩家的记忆中。《黄金矿工》就是其中之一&#xff0c;它…

SpringBootWeb 篇-深入了解 Mybatis 删除、新增、更新、查询的基础操作与 SQL 预编译解决 SQL 注入问题

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Mybatis 的基础操作 2.0 基础操作 - 环境准备 3.0 基础操作 - 删除操作 3.1 SQL 预编译 3.2 SQL 预编译的优势 3.3 参数占位符 4.0 基础操作 - 新增 4.1 主键返回…

每周节省7800万工时!ChatGPT等成美国降本增效利器

5月23日&#xff0c;全球最大教育、商业出版社之一的Pearson plc在官网发布了&#xff0c;ChatGPT等生成式AI如何帮助人们提升工作效率节省时间的深度研究报告。 该报告一共分析了美国、英国、澳大利亚、巴西和印度5个国家。到2026年&#xff0c;美国节省的时间最多&#xff0…

面试准备-项目【面试准备】

面试准备-项目【面试准备】 面试准备自我介绍&#xff1a;项目介绍&#xff1a; 论坛项目功能总结简介数据库表设计注册功能登录功能显示登录信息功能发布帖子评论私信点赞功能关注功能通知搜索网站数据统计热帖排行缓存 论坛项目技术总结Http的无状态cookie和session的区别为什…