【小白啃书】统计学习方法(李航第二版)代码实现 (C++) 之 2.K近邻(1)

2023-11-21 12:20

本文主要是介绍【小白啃书】统计学习方法(李航第二版)代码实现 (C++) 之 2.K近邻(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【统计学习方法(C++)】 K近邻(1)遍历法

  • K近邻
    • 写在前面(可以不看)
    • 算法原理
    • 训练
    • 判断标签值
      • 计算距离
      • 根据距离排序
      • 统计标签数量
      • 将标签赋给待分类样本
    • 调用这个函数
    • 运行结果
    • 一些说明

本文仅梳理总结自己在学习过程中的一些理解和思路,水平有限,理解粗鄙浅薄且不一定正确。文章所有观点均不保证绝对正确,请酌情参考。如果各位朋友发现任何错误请及时告诉我,大家一起讨论共同提高。
(不要问我为什么用C++写机器学习,问就是导师要求的)
希望我不鸽,咕咕

相关内容
0.导入数据
1.感知机

K近邻

写在前面(可以不看)

上一篇刚刚说过面向对象的思维不强的问题,写本次的程序的时候就切切实实地深受其害了。上课的时候老师曾经做过这样一个比方,一个对象就仿佛一个完整的人,有鼻子有眼睛有手,能说话能吃饭能跳舞。面向对象的方法要求我们在代码中,饭吃进嘴里,嘴连着喉管,把饭送进肠胃,而不是直接打开这个人的肠胃把食物塞进去,吃个饭都要拎着肠子到处乱跑。这次的代码让我切实地体会到了这种“拎着肠子满街乱跑”的感觉,无法拆分成独立的函数,更将某些部分无法移植到其他代码中使用,整个代码像一团乱码搅在一起竟然也实现了功能,就也还挺“鹅妹子嘤”的。
在本文中,我会把原本的代码贴上来,而在KNN(2)中则会放上修改过后的代码,以便让大家直观地感受一些两者之间的区别,也许会对大家更好地理解“面向对象”这一概念有些许帮助。

算法原理

网上总结太多了,书上也讲的详细,不多赘述。简而言之就是:

  • 我离哪个(或k个)样本最近,我的标签就跟谁一样

当距离最近的k个样本标签不同的时候,通常选择少数服从多数的方法确定最后的标签。

训练

很显然,K近邻算法中不涉及训练,k为超参数,需要不断实验寻找效果最好的k值(所谓调参)

判断标签值

步骤如下

  • 计算与每个样本的的距离
  • 按距离排序
  • 统计与待定样本点最近的K个样本的标签数量
  • 最多的标签即视为待定样本点的标签

计算距离

计算距离使用的为欧氏距离,其计算公式为

d = sqrt( (x1-x2)2+(y1-y2)2 )

for (auto iter : Sample_feature){for (int i = 0; i < feature_num; i++){dis += pow((it_test.first[i] - iter.second[i]), 2);}dis = sqrt(dis);distance.insert(map<int, double>::value_type(iter.first, dis));}

这段代码中用到的pow(平方)函数和sqrt(开方)函数需要包括头文件cmath

#include<cmath>

根据距离排序

map一般会默认按照键值进行排序,而我们这里需要的确是按照值的大小进行排序,以便筛选出距离代求的样本点最近的K个样本。直接对map的value进行相对来说复杂,一般常用的方法是将map放入vector中,利用vector的sort函数进行排序。
将map中的内容放入vector

for (map<int, double>::iterator it = distance.begin(); it != distance.end(); it++){vec_distance.push_back(pair<int, double>(it->first, it->second));}

sort函数的参数有三个,sort(begin, end, storFun),分别为排序的起始位,终止位和排序方式。第三个参数缺省时默认从大到小排列,其他特殊的排序方式需要单独构建排序函数进行说明。我们这里的排序方式为按照vector的second项进行排序。

bool storFun(pair<int, double> a, pair<int, double> b)
{return a.second < b.second;
}

在此基础上,排序只需要一行代码就可以实现

		sort(vec_distance.begin(), vec_distance.end(), storFun); //从大到小排序

统计标签数量

遍历前k项并统计其标签。特别的,map可以通过键值直接索引,当所查找的键值在map中不存在时还会自动增加此键值,这就给我们的统计带来了方便。我们不需要先得知总共出现了哪些标签值,只需要一行代码就可以完成标签的计数。

			map_label_freq[label]++;

当程序读取到标签值时,会将map中对应的计数结果(value)加一,若map中没有这个标签,则会添加这个标签为新的键值。

将标签赋给待分类样本

通过遍历计数结果map来找到出现次数最多的标签,完成样本的分类。

for (auto it_map : map_label_freq){if (it_map.second>max_freq){max_freq = it_map.second;label = it_map.first;}}

调用这个函数

可以看到,我并没有写输出结果的代码(因为想偷懒),所以在KNN函数的最后我打了一个断点以便查看运行结果。

因为前面讲过的原因,整个代码中除了读取数据只有KNN一个功能函数,各种数据纠缠在一起,极度混乱:<

运行结果

在这里插入图片描述
最后的数字1为分类的正确率(虽然数据集是我自己写的在学习过程中这个数字并没有什么意义)

一些说明

为了方便大家看这个代码有多屎,我把这个代码完整复制在这里,如果对这一部分不感兴趣这篇文章阅读到这里就结束了。
结构更加清晰的程序我会在(2)中继续贴出来(如果我写得出来的话)

typedef string TLabel;
typedef double TFeature;
ifstream fin;
ofstream fout;bool storFun(pair<int, double> a, pair<int, double> b){……}int data_read(map<vector<TFeature>, TLabel> &Sample, string data_add, int &sample_num){……}void Sample_data_read(map<int, vector<TFeature>> &Sample_feature, map<int, TLabel>&Sample_label, map<vector<TFeature>, TLabel> &Sample, string data_add, int &sample_num){……}void KNN(int k)
{string data_add = ("F:\\learning ML\\KNN\\data.txt");string test_add = ("F:\\learning ML\\KNN\\test.txt");int feature_num = 0;int sample_num = 0;int test_sample_num = 0;double accuracy = 0;map<vector<TFeature>, TLabel> Sample;map<vector<TFeature>, TLabel> Test_Sample;map<int, vector<TFeature>> Sample_feature;map<int, TLabel>Sample_label;Sample_data_read(Sample_feature, Sample_label, Sample, data_add, sample_num);feature_num = data_read(Test_Sample, test_add, test_sample_num);//计算距离for (auto it_test : Test_Sample){double dis = 0;int index = 0;map<int, double> distance;vector<pair<int, double>> vec_distance;map<TLabel, int> map_label_freq;vector<pair<TLabel, int>>vec_label_freq;for (auto iter : Sample_feature){for (int i = 0; i < feature_num; i++){dis += pow((it_test.first[i] - iter.second[i]), 2);}dis = sqrt(dis);distance.insert(map<int, double>::value_type(iter.first, dis));}for (map<int, double>::iterator it = distance.begin(); it != distance.end(); it++){vec_distance.push_back(pair<int, double>(it->first, it->second));}sort(vec_distance.begin(), vec_distance.end(), storFun); //从大到小排序TLabel label;//统计分类for (int i = 0; i < k; i++){index = vec_distance[i].first;label = Sample_label[index];map_label_freq[label]++;}int max_freq = 0;for (auto it_map : map_label_freq){if (it_map.second>max_freq){max_freq = it_map.second;label = it_map.first;}}cout << "The test data belongs to the " << label << " label" << endl;if (label == it_test.second){accuracy++;}}accuracy = accuracy / test_sample_num;cout << accuracy << endl;system("pause");
}int main()
{int k;cout << "please input the k value : " << endl;cin >> k;KNN(k);}

源码和用到的数据集我打包放在KNN(1)
在这里插入图片描述

最后,错误及有待改进之处,希望各位大佬不吝赐教。

这篇关于【小白啃书】统计学习方法(李航第二版)代码实现 (C++) 之 2.K近邻(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/402306

相关文章

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

一文详解Git中分支本地和远程删除的方法

《一文详解Git中分支本地和远程删除的方法》在使用Git进行版本控制的过程中,我们会创建多个分支来进行不同功能的开发,这就容易涉及到如何正确地删除本地分支和远程分支,下面我们就来看看相关的实现方法吧... 目录技术背景实现步骤删除本地分支删除远程www.chinasem.cn分支同步删除信息到其他机器示例步骤