C++实现KNN和K-Means

 学校机器学习课程的实验课要求实现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.mfbz.cn/a/157816.html

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

相关文章

如何快速下载mysql的不同版本并启动mysql服务?

如何快速下载mysql的不同版本并启动mysql服务&#xff1f; 下载mysql的安装版本 首先我们要使用到迅雷去下载&#xff0c;因为迅雷下载是很快的。在迅雷里面搜索下面的Mysql Installer安装窗口&#xff0c;如下图&#xff1a; 连接&#xff1a;https://dev.mysql.com/downlo…

如何避免被他人“背刺”?

请公主们、王子们&#xff0c;花点时间看一下&#xff0c;谢谢。 在人与人相处中&#xff0c;难免不会碰上与人合作交往&#xff0c;虽然大多数时候我们是选择熟悉一点的朋友&#xff0c;但是也不能掉以轻心&#xff0c;现实生活中也不是不存在被亲戚朋友“背刺”&#xff0c;…

MySQL主从同步

文章目录 MySQL主从同步概述MySQL主从同步原理MySQL主从同步结构模式MySQL主从同步搭建搭建步骤一主一从实验环境master主机slave1主机验证主从同步 一主多从master主机slave2主机验证主从同步 链式复制&#xff08;主从从&#xff09;slave1主机slave2主机验证链式复制 MySQL主…

SpringBoot2—基础篇

目录 快速上手SpringBoot • SpringBoot入门程序开发 基于Idea创建SpringBoot工程&#xff08;一&#xff09; 基于官网创建SpringBoot工程&#xff08;二&#xff09; 基于阿里云创建SpringBoot工程&#xff08;三&#xff09; 手工创建Maven工程修改为SpringBoot工程&…

GPT-4V新玩法登顶GitHub热榜,随手一画就能生成网页!web开发者:感受到了威胁

西风 发自 凹非寺 量子位 | 公众号 QbitAI 随手一画就能生成网页&#xff01;GPT-4V新玩法登顶GitHub热榜&#xff0c;狂揽3000&#x1f31f;&#xff1a; 现在只要简单画一画&#xff0c;框一框&#xff0c;点击执行&#xff1a; “啪”地一下&#xff0c;一个带有各种“按钮…

Apriori算法

Apriori算法由R. Agrawal和R. Srikant于1994年在数据集中寻找布尔关联规则的频繁项集。该算法的名称是Apriori&#xff0c;因为它使用了频繁项集属性的先验知识。我们应用迭代方法或逐层搜索&#xff0c;其中k-频繁项集用于找到k1个项集。 为了提高频繁项集逐层生成的效率&…

oracle21c安装报错【[INS-32014] 指定的 Oracle 基目录位置XXX无效】

一.问题 [INS-32014] 指定的 Oracle 基目录位置XXX无效 二.解决办法 安装包的文件放置不可以在中文字文件夹下面&#xff0c;改为英文【soft】,就可以成功安装完成了&#xff01;

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

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

mysql统计整个数据库记录条数

SELECTSUM(TABLE_ROWS) FROM(SELECTTABLE_NAME,TABLE_ROWSFROMINFORMATION_SCHEMA.TABLESWHERETABLE_SCHEMA 数据库名&#xff0c;其他不变) t;效果如下&#xff1a;

[pybind11] debug C++代码

首先要有一个项目&#xff0c;我发布在github上了【传送门】 项目的结构如下&#xff1a; 其中src目录下是C代码&#xff0c;test.py是python测试代码。 然后直接开始演示。 1、把项目下载到本地 git clone --recursive https://github.com/immortalmin/pybind11_debug_eg.g…

4月2日-3日·上海 | 3DCC 第二届3D细胞培养与类器官研发峰会携手CGT Asia 重磅来袭

类器官&#xff08;Organoids&#xff09;作为干细胞研究领域最重要的成果之一&#xff0c;在基础医学研究、转化医学及药物研发领域展现出巨大的应用潜力&#xff0c;特别是在精准医疗以及药物安全性和有效性评价等方向凭借其先天优势引起了极大的市场关注&#xff0c;成为各大…

采访仁川市政府:探索《仁川登陆行动》体验及其 NFT 作品集背后的故事!

请简单介绍一下自己 大家好&#xff0c;我是仁川市政府品牌经理崔俊浩&#xff0c;负责《仁川登陆行动》的元宇宙活动。很高兴见到您。 是什么启发了你创作《仁川登陆行动》体验&#xff1f; 《仁川登陆行动》并未得到广泛认可&#xff0c;并且被认为是一项几乎不可能完成的任务…

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…

前端实现页面内容的截图与下载(html2canvas)

今天是一个发文的好日子&#x1f600;~ &#x1f447;&#x1f447;&#x1f447; 一个需求&#xff0c;要截取页面中的内容并截图保存&#xff0c;来看一看我是怎么实现的吧&#xff1a; 这里需要使用到插件--html2canvas 1.安装并引入html2canvas npm install html2canv…

创作者焦点:Temple of Dum-Dum(试炼 3)

《Bomkus 博士的试炼》创作的幕后花絮。 《创作者焦点》系列共分为六部分&#xff0c;重点介绍《Bomkus 博士的试炼》的游戏创作过程及其独特的游戏功能。 Temple of Dum-Dum&#xff1a; Temple of Dum-Dum 是 Bomkus 博士试炼中的第三个挑战&#xff0c;该试炼由六项体验组成…

阎良区公益创投之“小飞机大梦想” 航模DIY主题活动

创造是人类探索迈出的第一步&#xff0c;科学是开启奇妙世界的金钥匙。为进一步提升“未来星”对科技知识的兴趣&#xff0c;培养他们的科学创新精神&#xff0c;11月16日&#xff0c;阎良区社会组织公益创投——“未来星”助力乡村留守儿童成长计划项目在阎良区聚宝小学开展“…

【hive-解决】HiveAccessControlException Permission denied: CREATEFUNCTION

文章目录 一.任务描述二. 解决 一.任务描述 Error while compiling statement: FAILED: HiveAccessControlException Permission denied: Principal [nameroot, typeUSER] does not have following privileges for operation CREATEFUNCTION [ADMIN PRIVILEGE on INPUT, ADMIN…

springboot 数据传输的加密解密方式一: AES(对称加密)

全文目录,一步到位 1.加密/解密方式一: AES1.1 AES加密简介1.2 AES解密简介1.3 AES细致介绍(外链) 2. AES加密解密使用2.1 应用场景2.2 工具包使用方式2.2.0 (关键)生成一个秘钥(16位)2.2.1 AES加密文本2.2.2 AES解密文本2.2.3 AES加密文件(源)2.2.4 AES解密文件(源)2.2.5 文件…

leetcode刷题日记:168. Excel Sheet Column Title(Excel表列名称)

我不知道你看到这一道题目有什么感觉&#xff0c;我先告诉你我有什么感觉&#xff0c;在此之前我再给你写一组有相同模式的数字。 你先告诉我你有什么感觉&#xff0c;有没有感觉&#xff0c;没有感觉的话&#xff0c;那我们就来更深的了解一下&#xff1a; 我们分析最后一…

Echarts -- 实现动态加载series

一、需求说明 1.1具体说明 根据每天的订单,查询出券码(title字段)的核销情况,如下单成功,已核销,取消订单,订单失败, title字段又分大概七八种,最后数据进行整合完毕之后,前端使用Echarts进行堆叠柱状图显示每天数据。 1.2 需求拆解 根据时间范围查询出每天的订单数据后,根据…
最新文章