这是一篇学习机器学习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算法是怎么实现的:
- 读取这个想要预测的数据,并计算这个数据和所有已知训练集数据的
差异
, 也就是计算当前数据和其他样本点的每一个维度的距离大小(这里的距离就像中学的亮点间的距离公式,因为一个数据就是一个多维的向量,在坐标系里每个数据向量对应一个点,使用点的坐标公式就能得到亮点间的距离) - 找到距离最近的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
写在最后,
博主还在上大学~
希望和大家一起成长,
也希望大家能够关注~
目标是每周最少更新一篇机器学习相关笔记,
正在持续的学习中,
如果喜欢的话可以给我一个免费的赞吗~
这将是对我最大的鼓励,
希望这篇文章可以帮助到你~