机器学习--KNN算法学习笔记

#新星杯·14天创作挑战营·第11期#


这是一篇学习机器学习KNN算法的学习笔记,用时2.5h,讲解较为细致,便于理解,整体按照分析题目时思考的逻辑进行写作,通俗易懂,使用c++实现,便于理解底层原理,附有源码和代码分层讲解,并附有数据点,供大家参考

什么是KNN算法?

K最临近算法(K-Nearest-Neighbors)是一种有监督机器学习算法,适用于简单的分类任务,这里以鸢尾花分类的题目为例进行讲解:

  • 鸢尾花分类问题:给定鸢尾花的 花萼长度,花萼宽度,花瓣长度,花瓣宽度 的四种数据,对鸢尾花进行分类,这里鸢尾花的 数据类别 就是有监督机器学习的分类问题中要学习和求解的部分。
  • 数据点样例:

5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica

要解决的问题就是,在给定data.txt时,将鸢尾花数据分为训练集和测试集两部分,使用训练集训练KNN算法的模型,通过训练得到的模型对测试集部分的数据进行预测,观察KNN算法得到的模型的预测结果和实际样本的真实结果之间的差异,得出本模型的准确率

KNN算法的基本思路

我们知道了这样一个机器学习算法就是通过程序实现一种算法时,这个程序称为模型,使用训练数据对模型进行输入,经过算法的运算后,再输入测试数据,就能输出这个测试数据的类型的过程,那么怎么设计这个程序呢?

从我们想要实现的效果入手,假设我们现在已经有了并且处理好了训练集的数据,那么当一个被预测的数据出现时,我们来看KNN算法是怎么实现的:

  1. 读取这个想要预测的数据,并计算这个数据和所有已知训练集数据的 差异 , 也就是计算当前数据和其他样本点的每一个维度的距离大小(这里的距离就像中学的亮点间的距离公式,因为一个数据就是一个多维的向量,在坐标系里每个数据向量对应一个点,使用点的坐标公式就能得到亮点间的距离)
  2. 找到距离最近的k个向量,这k个数据里面出现次数最多的类型就认为是该样本的预测类型

到这里我们就理解了KNN算法的基本思路,下面来考虑怎么使用c++代码实现我们的想法

KNN算法的实现

写代码的第一步当然就是敲好头文件并且优雅地写下main()函数,哈哈哈,那接下来怎么补充程序的内容?

让我们回顾一下,我们想要通过这个程序实现一个算法,读入鸢尾花的有类别数据之后进行处理,并且在之后新读入一个无类别数据时,能够进行预测的效果

怎么处理这些数据?要用文件读取,要有一定的结构来储存,数据量可能不确定,我们可以考虑使用vector进行存储,每一个vector单元中储存一个样本数据,那么可以把鸢尾花的特征放进结构体中

我们先试着完成读取和存储数据的这部分,然后再考虑对数据的进一步操作

#include<iostream>
#include<cstdio>//c++保留的C语言风格的文件读取操作fopen
#include<vector>
#include<cstring>
using namespace std;
struct Iris{
	double a,b,c,d;
	int type;
	Iris(double a, double b, double c, double d, int type)
        : a(a), b(b), c(c), d(d), type(type) {}
};

void readData(vector<Iris>& sample){
	FILE *f=fopen("data.txt","r");
    double a,b,c,d;char ch[20];
    while(fscanf(f,"%lf,%lf,%lf,%lf,%s",&a,&b,&c,&d,ch)==5){
        if(strcmp(ch,"Iris-setosa")==0){
            sample.emplace_back(a,b,c,d,1);//可以直接传参数,不用构造成实际类型后在传递
        }else if(strcmp(ch,"Iris-versicolor")==0){
            sample.emplace_back(a,b,c,d,2);
        }else if(strcmp(ch,"Iris-virginica")==0){
            sample.emplace_back(a,b,c,d,3);
        }
    }
    fclose(f);
    printf("readed successfully!\n");
}
int main(){
	vector<Iris> sample;
	readData(sample);
} 

在读入完成后,我们可以考虑如何对数据进行处理,因为样本数据本身都是完整的,所以这里不需要对数据进行初始化了,那么接下来就是把数据划分成训练集和测试集两部分,并在划分成功之后对数据进行训练

具体的划分方法,我们使用c++内置的用于将容器内部的数据进行随机化的函数shuffle(),这个函数在c++的<algorithm>库中,通过调用一个随机化引擎实现对容器中内容的随机化,这里的default_random_engine()引擎存在于<random>库中

#include<iostream>
```exiting code
#include<algorithm>
#include<random>
using namespace std;
```exiting code

void divideData(vector<Iris> &sample,vector<Iris> &train,vector<Iris> &test,double &trainRate){
	shuffle(sample.begin(),sample.end(),default_random_engine(time(0)));
	double trainNum=sample.size()*trainRate;
	for(int i=0;i<sample.size();i++){
		if(i<trainNum){
			train.emplace_back(sample[i]);
		}else{
			test.emplace_back(sample[i]);
		}
	}
	printf("divided successfully!\n");
}
int main(){
	vector<Iris> sample;
	readData(sample);
	vector<Iris> train;
	vector<Iris> test;
	double trainRate=0.7;
	divideData(sample,train,test,trainRate);
}

现在已经对数据成功进行了划分,接下来就是训练数据并实现预测的过程了

#incldue<iostream>
```exiting code
#include<cmath>
using namespace std;
```exiting code
double distance(Iris &x,Iris &y){
	double d1=x.a-y.a;
    double d2=x.b-y.b;
    double d3=x.c-y.c;
    double d4=x.d-y.d;
    return sqrt(d1*d1+d2*d2+d3*d3+d4*d4);
}
double KNN(vector<Iris> &train,vector<Iris> &test,int k){
	double correct=0;
    int n=0;
	for(auto &t:test){
		vector<pair<double,int>> dis;
		int idx=0;
		for(auto &x:train){
			dis.emplace_back(distance(t,x),idx);
			idx++;
		}
		sort(dis.begin(),dis.end());//sort默认按照pair的第一个元素升序排序
		int cnt[4]={0};
		for(int i=0;i<k;i++){
			cnt[train[dis[i].second].type]++;
		}
        int maxx=-1;
        int predict;
        for(int i=1;i<=3;i++){
            if(cnt[i]>maxx){
                maxx=cnt[i];
                predict=i;
            }
        }
        if(predict==t.type)correct++;
        printf("id.%d predict to be %d\n",++n,predict);
	}
    return correct/test.size();

}
int main(){
    vector<Iris> sample;
	readData(sample);
    vector<Iris> train;
    vector<Iris> test;
    double trainRate=0.7;
	divideData(sample,train,test,trainRate);
    int k=4;//k的取值关系到模型结果的准确率
	double correctRate=KNN(train,test,k);
    printf("the correct rate is %.3lf%%",correctRate*100);
} 

到这里就已经完整实现了KNN算法了,可喜可贺 \ qwq /
下面附上完整代码和完整样本数据供大家参考
由上面几个步骤完整组成了最终的求解代码

#include<iostream>
#include<cstdio>//c++保留的C语言风格的文件读取操作fopen
#include<vector>
#include<cstring>
#include<algorithm>
#include<random>
#include<cmath>
using namespace std;
struct Iris{
	double a,b,c,d;
	int type;
    Iris(double a, double b, double c, double d, int type)
        : a(a), b(b), c(c), d(d), type(type) {}
};

void readData(vector<Iris>& sample){
	FILE *f=fopen("data.txt","r");
    if(f==nullptr){
        printf("reading failed...");
        return;
    }
    double a,b,c,d;char ch[20];
    while(fscanf(f,"%lf,%lf,%lf,%lf,%s",&a,&b,&c,&d,ch)==5){
        if(strcmp(ch,"Iris-setosa")==0){
            sample.emplace_back(a,b,c,d,1);//可以直接传参数,不用构造成实际类型后在传递
        }else if(strcmp(ch,"Iris-versicolor")==0){
            sample.emplace_back(a,b,c,d,2);
        }else if(strcmp(ch,"Iris-virginica")==0){
            sample.emplace_back(a,b,c,d,3);
        }
    }
    fclose(f);
    printf("readed successfully!\n");
}

void divideData(vector<Iris> &sample,vector<Iris> &train,vector<Iris> &test,double &trainRate){
	shuffle(sample.begin(),sample.end(),default_random_engine(time(0)));
	double trainNum=sample.size()*trainRate;
	for(int i=0;i<sample.size();i++){
		if(i<trainNum){
			train.emplace_back(sample[i]);
		}else{
			test.emplace_back(sample[i]);
		}
	}
	printf("divided successfully!\n");
}
double distance(Iris &x,Iris &y){
	double d1=x.a-y.a;
    double d2=x.b-y.b;
    double d3=x.c-y.c;
    double d4=x.d-y.d;
    return sqrt(d1*d1+d2*d2+d3*d3+d4*d4);
}
double KNN(vector<Iris> &train,vector<Iris> &test,int k){
	double correct=0;
    int n=0;
	for(auto &t:test){
		vector<pair<double,int>> dis;
		int idx=0;
		for(auto &x:train){
			dis.emplace_back(distance(t,x),idx);
			idx++;
		}
		sort(dis.begin(),dis.end());//sort默认按照pair的第一个元素升序排序
		int cnt[4]={0};
		for(int i=0;i<k;i++){
			cnt[train[dis[i].second].type]++;
		}
        int maxx=-1;
        int predict;
        for(int i=1;i<=3;i++){
            if(cnt[i]>maxx){
                maxx=cnt[i];
                predict=i;
            }
        }
        if(predict==t.type)correct++;
        printf("id.%d predict to be %d\n",++n,predict);
	}
    return correct/test.size();

}
int main(){
    vector<Iris> sample;
	readData(sample);
    vector<Iris> train;
    vector<Iris> test;
    double trainRate=0.7;
	divideData(sample,train,test,trainRate);
    int k=4;//k的取值关系到模型结果的准确率
	double correctRate=KNN(train,test,k);
    printf("the correct rate is %.3lf%%",correctRate*100);
} 
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa
5.4,3.9,1.7,0.4,Iris-setosa
4.6,3.4,1.4,0.3,Iris-setosa
5.0,3.4,1.5,0.2,Iris-setosa
4.4,2.9,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.4,3.7,1.5,0.2,Iris-setosa
4.8,3.4,1.6,0.2,Iris-setosa
4.8,3.0,1.4,0.1,Iris-setosa
4.3,3.0,1.1,0.1,Iris-setosa
5.8,4.0,1.2,0.2,Iris-setosa
5.7,4.4,1.5,0.4,Iris-setosa
5.4,3.9,1.3,0.4,Iris-setosa
5.1,3.5,1.4,0.3,Iris-setosa
5.7,3.8,1.7,0.3,Iris-setosa
5.1,3.8,1.5,0.3,Iris-setosa
5.4,3.4,1.7,0.2,Iris-setosa
5.1,3.7,1.5,0.4,Iris-setosa
4.6,3.6,1.0,0.2,Iris-setosa
5.1,3.3,1.7,0.5,Iris-setosa
4.8,3.4,1.9,0.2,Iris-setosa
5.0,3.0,1.6,0.2,Iris-setosa
5.0,3.4,1.6,0.4,Iris-setosa
5.2,3.5,1.5,0.2,Iris-setosa
5.2,3.4,1.4,0.2,Iris-setosa
4.7,3.2,1.6,0.2,Iris-setosa
4.8,3.1,1.6,0.2,Iris-setosa
5.4,3.4,1.5,0.4,Iris-setosa
5.2,4.1,1.5,0.1,Iris-setosa
5.5,4.2,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.0,3.2,1.2,0.2,Iris-setosa
5.5,3.5,1.3,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
4.4,3.0,1.3,0.2,Iris-setosa
5.1,3.4,1.5,0.2,Iris-setosa
5.0,3.5,1.3,0.3,Iris-setosa
4.5,2.3,1.3,0.3,Iris-setosa
4.4,3.2,1.3,0.2,Iris-setosa
5.0,3.5,1.6,0.6,Iris-setosa
5.1,3.8,1.9,0.4,Iris-setosa
4.8,3.0,1.4,0.3,Iris-setosa
5.1,3.8,1.6,0.2,Iris-setosa
4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5.0,3.3,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4.0,1.3,Iris-versicolor
6.5,2.8,4.6,1.5,Iris-versicolor
5.7,2.8,4.5,1.3,Iris-versicolor
6.3,3.3,4.7,1.6,Iris-versicolor
4.9,2.4,3.3,1.0,Iris-versicolor
6.6,2.9,4.6,1.3,Iris-versicolor
5.2,2.7,3.9,1.4,Iris-versicolor
5.0,2.0,3.5,1.0,Iris-versicolor
5.9,3.0,4.2,1.5,Iris-versicolor
6.0,2.2,4.0,1.0,Iris-versicolor
6.1,2.9,4.7,1.4,Iris-versicolor
5.6,2.9,3.6,1.3,Iris-versicolor
6.7,3.1,4.4,1.4,Iris-versicolor
5.6,3.0,4.5,1.5,Iris-versicolor
5.8,2.7,4.1,1.0,Iris-versicolor
6.2,2.2,4.5,1.5,Iris-versicolor
5.6,2.5,3.9,1.1,Iris-versicolor
5.9,3.2,4.8,1.8,Iris-versicolor
6.1,2.8,4.0,1.3,Iris-versicolor
6.3,2.5,4.9,1.5,Iris-versicolor
6.1,2.8,4.7,1.2,Iris-versicolor
6.4,2.9,4.3,1.3,Iris-versicolor
6.6,3.0,4.4,1.4,Iris-versicolor
6.8,2.8,4.8,1.4,Iris-versicolor
6.7,3.0,5.0,1.7,Iris-versicolor
6.0,2.9,4.5,1.5,Iris-versicolor
5.7,2.6,3.5,1.0,Iris-versicolor
5.5,2.4,3.8,1.1,Iris-versicolor
5.5,2.4,3.7,1.0,Iris-versicolor
5.8,2.7,3.9,1.2,Iris-versicolor
6.0,2.7,5.1,1.6,Iris-versicolor
5.4,3.0,4.5,1.5,Iris-versicolor
6.0,3.4,4.5,1.6,Iris-versicolor
6.7,3.1,4.7,1.5,Iris-versicolor
6.3,2.3,4.4,1.3,Iris-versicolor
5.6,3.0,4.1,1.3,Iris-versicolor
5.5,2.5,4.0,1.3,Iris-versicolor
5.5,2.6,4.4,1.2,Iris-versicolor
6.1,3.0,4.6,1.4,Iris-versicolor
5.8,2.6,4.0,1.2,Iris-versicolor
5.0,2.3,3.3,1.0,Iris-versicolor
5.6,2.7,4.2,1.3,Iris-versicolor
5.7,3.0,4.2,1.2,Iris-versicolor
5.7,2.9,4.2,1.3,Iris-versicolor
6.2,2.9,4.3,1.3,Iris-versicolor
5.1,2.5,3.0,1.1,Iris-versicolor
5.7,2.8,4.1,1.3,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
7.1,3.0,5.9,2.1,Iris-virginica
6.3,2.9,5.6,1.8,Iris-virginica
6.5,3.0,5.8,2.2,Iris-virginica
7.6,3.0,6.6,2.1,Iris-virginica
4.9,2.5,4.5,1.7,Iris-virginica
7.3,2.9,6.3,1.8,Iris-virginica
6.7,2.5,5.8,1.8,Iris-virginica
7.2,3.6,6.1,2.5,Iris-virginica
6.5,3.2,5.1,2.0,Iris-virginica
6.4,2.7,5.3,1.9,Iris-virginica
6.8,3.0,5.5,2.1,Iris-virginica
5.7,2.5,5.0,2.0,Iris-virginica
5.8,2.8,5.1,2.4,Iris-virginica
6.4,3.2,5.3,2.3,Iris-virginica
6.5,3.0,5.5,1.8,Iris-virginica
7.7,3.8,6.7,2.2,Iris-virginica
7.7,2.6,6.9,2.3,Iris-virginica
6.0,2.2,5.0,1.5,Iris-virginica
6.9,3.2,5.7,2.3,Iris-virginica
5.6,2.8,4.9,2.0,Iris-virginica
7.7,2.8,6.7,2.0,Iris-virginica
6.3,2.7,4.9,1.8,Iris-virginica
6.7,3.3,5.7,2.1,Iris-virginica
7.2,3.2,6.0,1.8,Iris-virginica
6.2,2.8,4.8,1.8,Iris-virginica
6.1,3.0,4.9,1.8,Iris-virginica
6.4,2.8,5.6,2.1,Iris-virginica
7.2,3.0,5.8,1.6,Iris-virginica
7.4,2.8,6.1,1.9,Iris-virginica
7.9,3.8,6.4,2.0,Iris-virginica
6.4,2.8,5.6,2.2,Iris-virginica
6.3,2.8,5.1,1.5,Iris-virginica
6.1,2.6,5.6,1.4,Iris-virginica
7.7,3.0,6.1,2.3,Iris-virginica
6.3,3.4,5.6,2.4,Iris-virginica
6.4,3.1,5.5,1.8,Iris-virginica
6.0,3.0,4.8,1.8,Iris-virginica
6.9,3.1,5.4,2.1,Iris-virginica
6.7,3.1,5.6,2.4,Iris-virginica
6.9,3.1,5.1,2.3,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
6.8,3.2,5.9,2.3,Iris-virginica
6.7,3.3,5.7,2.5,Iris-virginica
6.7,3.0,5.2,2.3,Iris-virginica
6.3,2.5,5.0,1.9,Iris-virginica
6.5,3.0,5.2,2.0,Iris-virginica
6.2,3.4,5.4,2.3,Iris-virginica
5.9,3.0,5.1,1.8,Iris-virginica

写在最后,
博主还在上大学~
希望和大家一起成长,
也希望大家能够关注~
目标是每周最少更新一篇机器学习相关笔记,
正在持续的学习中,
如果喜欢的话可以给我一个免费的赞吗~
这将是对我最大的鼓励,
希望这篇文章可以帮助到你~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值