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) * Xd(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 yfreopen("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.mzph.cn/pingmian/16631.shtml

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

相关文章

无经验求职者的福音:AI生成简历的便捷之道

第一步你需要先给自己写个简历&#xff0c;简历就是你求职时的一张脸&#xff0c;“漂亮”程度与否那可大了去了。一份漂亮的简历不仅内容满满当当突出重点&#xff0c;而且排版清晰亮眼&#xff0c;能让hr一下子捕捉到重点。 来看看一份漂亮的简历长啥样↓ 工作经历、个人能力…

Go语言

Go语言 Go语言全称Golanguage&#xff0c;Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译并发型语言。于2009年首次发布 官网 特点 简单易学&#xff1a;Go语言语法简洁明了&#x…

【C++】Vector的简易模拟与探索

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

04Django项目基本运行逻辑及模板资源套用

对应视频链接点击直达 Django项目用户管理及模板资源 对应视频链接点击直达1.基本运行逻辑Django的基本运行路线&#xff1a;视图views.py中的 纯操作、数据返回、页面渲染 2.模版套用1.寻找一个好的模版2.模板部署--修改适配联动 OVER&#xff0c;不会有人不会吧不会的加Q1394…

Java 类加载过程和双亲委派模型

Java 类加载过程概述 在 Java 中&#xff0c;类装载器把一个类装入 Java 虚拟机中&#xff0c;要经过三个步骤来完成&#xff1a;装载、链接和初始化&#xff0c;其中链接又可以分成校验、准备、解析 Java类加载过程分为如下步骤&#xff1a; 1.装载&#xff08; 加载&#xf…

Python编程-后端开发之Django5应用请求处理与模板基础

Python编程-后端开发之Django5应用请求处理与模板基础 最近写项目&#xff0c;刚好用到了Django&#xff0c;现在差不多闲下来&#xff0c;个人觉得单体项目来讲django确实舒服&#xff0c;故写此总结 模板语法了解即可&#xff0c;用到了再看&#xff0c;毕竟分离已经是主流操…

LeetCode300:最长递增子序列

题目描述 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的 子序列 代码…

react 函数组件 开发模式默认被渲染两次

这是 React 刻意为之&#xff0c;函数式组件应当遵从函数式编程风格&#xff0c;每次执行应该是无副作用的(no sideEffect)&#xff0c;在 dev 下多次渲染组件&#xff0c;是为了防止开发者写出有问题的代码。 用 React 写函数组件&#xff0c;如何避免重复渲染&#xff1f; -…

Java学习【面向对象综合练习——实现图书管理系统】

Java学习【面向对象综合练习——实现图书管理系统】 前期效果图书的创建用户的创建操作的实现完善操作显示图书查找图书新增图书借阅图书归还图书删除图书 前期效果 用户分为普通用户和管理员&#xff0c;登录进系统之后可以对图书进行一系列操作&#xff0c;此时我们要明白&am…

斐讯N1刷OpenWRT并安装内网穿透服务实现远程管理旁路由

文章目录 前言1. 制作刷机固件U盘1.1 制作刷机U盘需要准备以下软件&#xff1a;1.2 制作步骤 2. N1盒子降级与U盘启动2.1 N1盒子降级2.2 N1盒子U盘启动设置2.3 使用U盘刷入OpenWRT2.4 OpenWRT后台IP地址修改2.5 设置旁路由&无线上网 3. 安装cpolar内网穿透3.1 下载公钥3.2 …

时空数据治理白皮书(2024)

来源&#xff1a;泰伯智库&#xff1a; 近期历史回顾&#xff1a;

企业微信修改主体花了大几千的踩坑经验,家人们避雷

企业微信变更主体有什么作用&#xff1f;如果原有的公司注销了&#xff0c;或者要更换一家公司主体来运营企业微信&#xff0c;那么就可以进行变更主体&#xff0c;变更主体后才可以保留原来企业微信上的所有用户&#xff0c;否则就只能重新申请重新积累用户了。企业微信变更主…

运维专题.Docker功能权限(Capabilities)管理和查看

运维专题 Docker功能权限&#xff08;Capabilities&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:htt…

MedSegDiff: Medical Image Segmentation with Diffusion Probabilistic Model 论文总结

题目&#xff1a;MedSegDiff: Medical Image Segmentation&#xff08;图像分割&#xff09;with Diffusion Probabilistic Model&#xff08;扩散概率模型&#xff09; 论文&#xff08;MIDL会议&#xff09;&#xff1a;MedSegDiff: Medical Image Segmentation with Diffusi…

勇于创新,勤于探索 —— 我的创作纪念日

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

纯CSS丝滑边框线条动画

在这个网站&#xff08;minimal-portfolio-swart.vercel.app&#xff09;发现一个不错的交互效果&#xff0c;用户体验效果很不错。如封面图所示&#xff0c;这个卡片上有一根白色的线条围绕着卡片移动&#xff0c;且在线条的卡片内部跟随这一块模糊阴影&#xff0c;特别是在线…

关于Nginx热部署的细节分析

文章目录 前言一、环境准备二、热部署步骤总结 前言 Nginx由于其高并发、高性能、可扩展性好、高可靠性、热部署、BSD许可证等优势被广泛使用&#xff0c;本人主要针对热部署的部分展开说明热部署的具体步骤以及步骤背后发生的具体事情。 本次热部署采用的Nginx版本号为&…

高通 Android 12/13冻结屏幕

冻结屏幕很多第一次听到以为是Android一种异常现象&#xff0c;实则不然&#xff0c;就是防止用户在做一些非法操作导致问题防止安全漏洞问题。 1、主要通过用户行为比如禁止下拉状态栏和按键以及onTouch事件拦截等&#xff0c;不知道请看这篇文章&#xff08;Touch事件传递流…

GitHub打不开的解决方案

1、打开https://sites.ipaddress.com/github.com/找到DNS Resource Records&#xff0c;复制github的ip地址&#xff0c;先保存起来&#xff1a; 140.82.112.32、打开https://sites.ipaddress.com/fastly.net/找到DNS Resource Records&#xff0c;复制其中一个ip地址&#xf…

基于Nacos实现Sentinel规则持久化

基于Nacos实现Sentinel规则持久化 一、Sentinel使用痛点二、解决方案2.1 保存本地文件2.2 保存数据库2.3 保存到Nacos 三、规则持久化到Nacos3.1 Nacos服务端修改配置3.2 Sentinel控制台修改配置3.3 Nacos数据源整合到Sentinel中 一、Sentinel使用痛点 SpringCloudAlibaba帮我…