C++实现KNN和K-Means

news2024/11/24 0:17:02

 学校机器学习课程的实验课要求实现KNN和K-Means:

 

 (平时没听课)临时去查了一下KNN和K-Means是啥,然后自己用C++写了小例子,想着写都写了那就把代码贴出来吧。

顺便再聊聊自己对于这俩算法的理解。

下面是文心一言的回答。

首先这俩都是分类算法,我们需要根据已经拥有的数据来对新数据进行分类。

KNN是把这个新数据扔到已有的数据里,然后找出K个距离这个新数据最近的以后数据,如果K个数据中大部分都是A类,那么我们就认为这个新数据是A类。

代码中我是自己定义了一个类来作为数据,一共有两个元素,可以把数据看作的二维的坐标点,然后要分类新数据的时候就对所有已有数据点来计算距离,然后挑出距离最近的K个点,按照类型的占比来对新数据的类型进行预测。

代码写得比较冗余,但是应该还算是好理解的(毕竟注释都写出来了)。

#include <iostream>
#include <vector>
#include <random>
#include<chrono>

using namespace std;

//自定义数据类型,共有两个参数
class mydata {
public:
	int val1;
	int val2;
	int type;

	mydata(int val1=0,int val2=0,int type=0):val1(val1),val2(val2),type(type) {
		//cout << "type is" << type << "\tval1 is" << val1 << "\tval2 is" << val2 << endl;
	}
};

vector<vector<mydata>>Data_19(3,vector<mydata>(100));		//3分类,每类100个的数据集
vector<vector<mydata>>Validation_19(3, vector<mydata>(10));	//3分类,每类10个的验证集

void CreateData() {
	default_random_engine e;									//默认的随机数引擎生成无符号整型
	e.seed(chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count());		//设置随机数种子
	uniform_int_distribution<unsigned int> type1_1(0,100);		//类型1的val1的随机数范围	
	uniform_int_distribution<unsigned int> type1_2(100,150);	//类型1的val2的随机数范围	
	uniform_int_distribution<unsigned int> type2_1(30,130);		//类型2的val1的随机数范围	
	uniform_int_distribution<unsigned int> type2_2(130,180);	//类型2的val2的随机数范围	
	uniform_int_distribution<unsigned int> type3_1(50,150);		//类型3的val1的随机数范围	
	uniform_int_distribution<unsigned int> type3_2(160,210);	//类型3的val2的随机数范围	
	for (int i = 0; i < 100; ++i) {
		//随机生成三个类型的数据
		Data_19[0][i] = mydata(type1_1(e),type1_2(e),1);
		Data_19[1][i] = mydata(type2_1(e),type2_2(e),2);
		Data_19[2][i] = mydata(type3_1(e),type3_2(e),3);
		if (i < 10) {
			//随机生成三个类型的验证集
			Validation_19[0][i] = mydata(type1_1(e), type1_2(e), 1);
			Validation_19[1][i] = mydata(type2_1(e), type2_2(e), 2);
			Validation_19[2][i] = mydata(type3_1(e), type3_2(e), 3);
		}
	}
}

int myKNN(int k,int val1,int val2) {
	//定义大小为300,元素为pair(可以存放两个元素)的数组,每个元素存放数据集的数据的类型以及与待预测数据的欧式距离
	vector<pair<int, int>>distance_types(300);
	//初始化数组存放的类型
	for (int i = 0; i < 100; ++i) distance_types[i].first = 1;
	for (int i = 100; i < 200; ++i) distance_types[i].first = 2;
	for (int i = 200; i < 300; ++i) distance_types[i].first = 3;
	for (int i = 0; i < 3; ++i) {
		for (int j = 0; j < 100; ++j) {
			//计算欧式距离 
			distance_types[100 * i + j].second = sqrt(pow(Data_19[i][j].val1-val1,2)+pow(Data_19[i][j].val2-val2,2));
		}
	}
	//按照欧式距离排序
	sort(distance_types.begin(), distance_types.end(), [&](auto& a,auto& b) {
		return a.second < b.second;
	});
	//大小为3的数组,用来记录与待预测数据最近的K个数据是什么类型的
	vector<int>count(3);
	for (int i = 0; i < k; ++i) {
		count[distance_types[i].first - 1]++;
	}
	//将接近待预测数据最多的类型返回
	if (count[0] >= count[1] && count[0] >= count[2]) return 1;
	if (count[1] >= count[0] && count[1] >= count[2]) return 2;
	return 3;
}

void check(int K) {						//KNN算法中的近邻数
	double right = 0, worry = 0;		//用于记录KNN预测的正确数以及错误数
	for (int n = 0; n < 100; ++n) {		//进行一百轮
		CreateData();					//调用生成数据集以及验证集的函数
		for (int i = 0; i < 3; ++i) {
			for (int j = 0; j < 10; ++j) {
				//如果预测成功则增加正确数,反之增加错误数
				if (myKNN(K, Validation_19[i][j].val1, Validation_19[i][j].val2) == Validation_19[i][j].type) ++right;
				else ++worry;
			}
		}
	}
	cout << "K is " << K << "\t\tright count: " << right << "\tworry count: " << worry << "\taccuracy is: " << right / (right + worry) << endl;
}

int main(void) {
	for (int k = 3; k <= 10; ++k) {
		check(k);
	}
	return 0;
}

K-Means的K是指我们需要把已有的数据集划分成K类。

首先我们先初始化K个锚点,随便初始化(当然效果不会好),一个锚点算是单独一个类型,然后计算所有数据点到这K个锚点之间的距离,如果数据点到某个锚点最近,那么就把这个数据点划分为这个锚点的类型。

计算完毕之后,再把每个锚点下的所有数据点取平均值再赋给锚点,这样锚点就被更新到这个类型的数据点集的中心位置了。

接着再重复刚才的过程,直到锚点的坐标不再改变,那么我们就说K-Means收敛了。

完毕之后我们就得到了K个锚点。

来新数据的时候我们只需要计算新数据和这个K个锚点之间的距离,把新数据归类到最近的锚点下就完成了分类。

刚才的KNN是在分类新数据的时候要进行大量计算(计算新数据与所有老数据的距离),而K-Means是在一开始初始化的时候进行大量计算。

代码中没有像KNN那样新建一个类型来表示数据,但是每个数据还是有两个元素,可以看作是二维平面上的坐标点。

#include <iostream>
#include <vector>
#include <random>
#include <chrono>
#include <unordered_map>

using namespace std;

vector<vector<int>>Datas;
int N = 100;										//随机生成数据的数量
int K = 5;											//分成K类
unordered_map<char, vector<vector<int>>> M;			//用于划分数据的类
default_random_engine e;							//默认的随机数引擎生成无符号整型
uniform_int_distribution<int> u(0,100);				//控制随机数生成范围

void CreateDatas_09() {
	//随机生成100个数据	
	for (int i = 0; i < N; ++i) {
		Datas.push_back({ u(e),u(e) });
	}
}

void K_Means_09(int K) {
	vector<vector<int>>K_Sources(K);		//存放K个聚点
	vector<vector<int>>temp_K_Sources(K);	//用于比较是否和上次聚点的坐标一致
	vector<pair<double,char>>cache(K);		//临时存放数据与聚点的距离
	int epoch = 0;
	CreateDatas_09();						//调用函数生成数据集

	//随机初始生成K个聚点
	for (int i = 0; i < K; ++i) K_Sources[i]={ u(e),u(e) };
	while (1) {
		temp_K_Sources = K_Sources;			//保存当前聚点的数据,用于判断是否收敛
		M.clear();
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < K; ++j) {
				//计算欧式距离
				cache[j] = { sqrt(pow(K_Sources[j][0] - Datas[i][0],2) + pow(K_Sources[j][1] - Datas[i][1],2)) ,'A' + j };
			}
			//排序数据与每个聚点的距离
			sort(cache.begin(), cache.end(), [](auto& a,auto& b){
				return a.first < b.first;
			});
			//如果哈希表中没有此类聚点的键,那么添加
			if (M.find(cache[0].second) == M.end()) M[cache[0].second] = vector<vector<int>>(0);
			//分配数据到相应的类
			M[cache[0].second].push_back(Datas[i]);
		}
		cout << "epoch is:" << epoch++ << endl;
		for (int i = 0; i < K; ++i) {
			//重新计算聚点数据
			int x = 0, y = 0;
			//累加所有数据的值
			for (auto& a : M['A' + i]) {
				x += a[0];
				y += a[1];
			}
			//取平均值作为新聚点
			K_Sources[i][0] = x / M['A' + i].size();
			K_Sources[i][1] = y / M['A' + i].size();
			cout << static_cast<char>('A' + i) << "类的聚点:\t" << K_Sources[i][0] << "\t" << K_Sources[i][1] << endl;
		}
		//如果聚点与上次的结果相同,那么收敛,结束迭代
		if (temp_K_Sources == K_Sources) break;
	}
}

int main(void) {
	//设置时间戳保证每次随机的数据都不同
	e.seed(chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count());
	K_Means_09(K);

	cout << "----------------------------------分割线------------------------------------------" << endl;

	//打印分类的结果
	for (int i = 0; i < K; ++i) {
		cout << static_cast<char>('A' + i) << "类包含的点:" << endl;
		for (auto& a : M['A' + 1]) cout << a[0] << '\t' << a[1] << endl;
	}

	return 0;
}

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

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

相关文章

洛谷 P3131 [USACO16JAN] Subsequences Summing to Sevens S

被普及-卡的没思路真是蒟蒻啊233 优化思路 每次都在枚举(a[r]-a[l-1])%70&#xff0c;所以可以认为数组大小对最终答案没有影响&#xff0c;考虑对前缀和数组取模&#xff0c;那么如果有a[r]的值等于a[l-1]的值相等&#xff08;即余数相等&#xff09;&#xff0c;那么两者相减…

米尔AM62x核心板,高配价低,AM335x升级首选

AM335x是TI经典的工业MPU&#xff0c;它引领了一个时代&#xff0c;即工业市场从MCU向MPU演进&#xff0c;帮助产业界从Arm9迅速迁移至高性能Cortex-A8处理器。随着工业4.0的发展&#xff0c;HMI人机交互、工业工控、医疗等领域的应用面临迫切的升级需求&#xff0c;AM62x处理器…

Python 双门双向门禁控制板实时监控源码

本示例使用设备&#xff1a;实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) #python通过缩进来表示代码块&#xff0c;不可以随意更改每行前面的空白&#xff0c;否则程序会运行错误&#xff01;&#xff01;&#xff01;如果缩进不一致&a…

这款IDEA插件真的爱了

IDEA是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序。我们在编写完接口代码后需要进行接口调试等操作&#xff0c;一般需要打开额外的调试工具。 今天给大家介绍一款IDEA插件&#xff1a;Api…

Taro编译警告解决方案:Error: chunk common [mini-css-extract-plugin]

文章目录 1. 背景2. 问题分析3. 解决方案3.1 更新 Taro 版本3.2 更新相关依赖3.3 调整 webpack 配置3.4 检查依赖版本 4. 拓展与分析4.1 拓展4.2 避免不必要的依赖4.3 查阅 Taro GitHub 仓库 5. 总结 &#x1f389;欢迎来到Java学习路线专栏~Taro编译警告解决方案&#xff1a;E…

golang学习笔记——斐波纳契数列

斐波纳契数列 编写一个程序来计算某个数字的斐波纳契数列。 斐波那契数列是一个数字列表&#xff0c;其中每个数字是前两个斐波那契数字之和。 例如&#xff0c;数字 6 的序列是 1,1,2,3,5,8&#xff0c;数字 7 的序列是 1,1,2,3,5,8,13&#xff0c;数字 8 的序列是 1,1,2,3,5…

[每周一更]-(第72期):Docker容器瘦身方式

Docker清理缓存操作 在构建测试的过程中&#xff0c;由于是自家小服务器&#xff0c;资源紧张&#xff0c;发现磁盘一直爆满&#xff0c;删除一些大镜像还是会占满的情况&#xff0c;就想到是不是也是缓存问题。 经过查询确实是build过程中的缓存启发的占用问题。 因此引出以下…

WPF创建自定义控件编译通过但是找不到资源

报错&#xff1a; 原因: 路径写错了&#xff1a; 不是这样&#xff1a; Source"pack://application:,,,/Controls/Styles/xTabControl.xaml" 而是这样&#xff1a; Source"pack://application:,,,/项目名;component/Controls/Styles/xTabControl.xaml …

Objectarx 使用libcurl请求WebApi

因为开发cad需要请求服务器的数据&#xff0c;再次之前我在服务器搭设了webapi用户传递数据&#xff0c;所以安装了libcurl在objectarx中使用数据。 Open VS2012 x64 Native Tools Command Prompt补充地址&#xff1a; 我在此将相关的引用配置图片&#xff0c;cad里面的应用和…

CI/CD --git版本控制系统

目录 一、git简介 二、git使用 三、github远程代码仓库 一、git简介 Git特点&#xff1a; 速度简单的设计对非线性开发模式的强力支持&#xff08;允许成千上万个并行开发的分支&#xff09;完全分布式有能力高效管理类似 Linux 内核一样的超大规模项目&#xff08;速度和数…

【Mysql】学习笔记

目录 基本操作登录指令&#xff1a;启动、关闭、重启mysql指令&#xff08;适用于centos7&#xff09;&#xff1a;查看mysql运行状态&#xff1a;删除和创建表 修改密码&#xff08;ubuntu18.04可行&#xff0c;其余版本行不行不知道&#xff09;3 使用MYSQL了解数据库和表 4 …

VR智慧景区:VR赋能文旅产业,激活消费潜能

随着国家数字化战略的不断深入实施&#xff0c;文旅产业数字化转型的步伐也在逐渐加快&#xff0c;以VR技术赋能文旅产业&#xff0c;让文旅景区线上线下双渠道融合&#xff0c;进一步呈现文化底蕴、激活消费潜能。 VR智慧景区以沉浸式、互动式、科技感的方式&#xff0c;将景区…

std::copy代替memcpy

在工作中&#xff0c;经常会有c/c的混合使用。但看到memcpy总是感觉不太安全&#xff0c;c中有一个替代品std::copy&#xff0c;用起来还不错&#xff0c;而且std::copy不会有效率上的损失&#xff0c;放心用吧。迭代器的方式还安全些。 将int数组转换为vector int inputArr[…

springboot+vue+element简单实现教学课程申报管理系统

目录 一、项目预览 二、项目效果图及说明 1.项目说明 1.登录 2.欢迎页 3.教师管理 4.课程申报 ​5.管理员管理 三、代码实现 1.后端项目结构图 2.数据库表脚本 3.路由配置 四、总结 一、项目预览 在线预览&#xff1a;点击访问其他项目访问&#xff1a;点击访问后端实…

恐怖地牢资产来袭!

我们将为您带来 VoxEdit 短片大赛的首场比赛&#xff01;在 6 天内创建可用资产。 主题&#xff1a;设计与恐怖和地牢相关的资产。这些资产必须非常实用&#xff0c;不需要动画。(如果你愿意&#xff0c;你也可以制作动画&#xff09;。 发挥你恐怖的一面&#xff0c;创造出适…

鸿蒙APP外包开发需要注意的问题

在进行鸿蒙&#xff08;HarmonyOS&#xff09;应用开发时&#xff0c;开发者需要注意一些重要的问题&#xff0c;以确保应用的质量、性能和用户体验。以下是一些鸿蒙APP开发中需要特别关注的问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软…

【智能家居】4、智能家居框架设计和代码文件工程建立

目录 一、智能家居项目框架 二、智能家居工厂模式示意 三、代码文件工程建立 SourceInsight创建新工程步骤 一、智能家居项目框架 二、智能家居工厂模式示意 三、代码文件工程建立 创建一个名为si的文件夹用于保存SourceInsight生成的文件信息&#xff0c;然后在SourceInsig…

卡码网语言基础课 | 13. 链表的基础操作Ⅰ

目录 一、 回顾 二、 指针 2.1 声明指针 2.2 指针获取地址 2.3 解引用 2.4 指针与数组的关系 2.5 指针的算术操作及访问 2.6 空指针值 三、 链表 3.1 概念 3.2 虚拟头节点 3.3 定义链表节点 3.4 结构体的成员变量 3.5 初始化结构体 3.6 完整结构体代码 四、 链…

TikTok与媒体素养:如何辨别虚假信息?

在当今数字时代&#xff0c;社交媒体平台如TikTok已经成为信息传播和社交互动的主要渠道之一。然而&#xff0c;随之而来的是虚假信息的泛滥&#xff0c;这对用户的媒体素养提出了严峻的挑战。本文将探讨TikTok平台上虚假信息的现象&#xff0c;以及如何提高媒体素养&#xff0…