本来是准备周末加班两天的,然后,临时突然其他事情又取消了。
顺便看了下csdn,看到一篇介绍KNN的,因为我现在做的也是属于机器学习方向,那自然也要了解一些这部分。
KNN是什么?
KNN可以说是最简单的分类算法之一,同时,它也是最常用的分类算法之一,注意KNN算法是有监督学习中的分类算法,它看起来和另一个机器学习算法Kmeans有点像(Kmeans是无监督学习算法),但却是有本质区别的。那么什么是KNN算法呢,接下来我们就来介绍介绍吧。
KNN算法介绍
KNN的全称是K Nearest Neighbors,意思是K个最近的邻居,从这个名字我们就能看出一些KNN算法的蛛丝马迹了。K个最近邻居,毫无疑问,K的取值肯定是至关重要的。那么最近的邻居又是怎么回事呢?其实啊,KNN的原理就是当预测一个新的值x的时候,根据它距离最近的K个点是什么类别来判断x属于哪个类别。听起来有点绕,还是看看图吧。
图中绿色的点就是我们要预测的那个点,假设K=3。那么KNN算法就会找到与它距离最近的三个点(这里用圆圈把它圈起来了),看看哪种类别多一些,比如这个例子中是蓝色三角形多一些,新来的绿色点就归类到蓝三角了。
但是,当K=5的时候,判定就变成不一样了。这次变成红圆多一些,所以新来的绿点被归类成红圆。从这个例子中,我们就能看得出K的取值是很重要的。
明白了大概原理后,我们就来说一说细节的东西吧,主要有两个,K值的选取和点距离的计算。
距离计算
要度量空间中点距离的话,有好几种度量方式,比如常见的曼哈顿距离计算,欧式距离计算等等。不过通常KNN算法中使用的是欧式距离,这里只是简单说一下,拿二维平面为例,,二维空间两个点的欧式距离计算公式如下:
这个高中应该就有接触到的了,其实就是计算(x1,y1)和(x2,y2)的距离。拓展到多维空间,则公式变成这样:
这样我们就明白了如何计算距离,KNN算法最简单粗暴的就是将预测点与所有点距离进行计算,然后保存并排序,选出前面K个值看看哪些类别比较多。但其实也可以通过一些数据结构来辅助,比如最大堆,这里就不多做介绍,有兴趣可以百度最大堆相关数据结构的知识。
K值选择
通过上面那张图我们知道K的取值比较重要,那么该如何确定K取多少值好呢?答案是通过交叉验证(将样本数据按照一定比例,拆分出训练用的数据和验证用的数据,比如6:4拆分出部分训练数据和验证数据),从选取一个较小的K值开始,不断增加K的值,然后计算验证集合的方差,最终找到一个比较合适的K值。
通过交叉验证计算方差后你大致会得到下面这样的图:
这个图其实很好理解,当你增大k的时候,一般错误率会先降低,因为有周围更多的样本可以借鉴了,分类效果会变好。但注意,和K-means不一样,当K值更大的时候,错误率会更高。这也很好理解,比如说你一共就35个样本,当你K增大到30的时候,KNN基本上就没意义了。
所以选择K点的时候可以选择一个较大的临界K点,当它继续增大或减小的时候,错误率都会上升,比如图中的K=10。具体如何得出K最佳值的代码,下一节的代码实例中会介绍。
下面这个例程是使用KNN的方法来做的一个机器学习例程
首先这个例子是用c实现的,里面的代码大家都可以看得明白,而且这个东西确实是机器学习相关的,如果想了解什么是机器学习,可以从这个入手去学习。
完整的c语言代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <stdbool.h>/*一个手写数字的结构体*/
typedef struct {int pixel[1024];int number;
} SampleData;/*一个有label的距离结构体*/
typedef struct {float distance;int number;
} CalculationData;/*文件路径+名称*/
static const char* TrainingFilePath = "./my-digit-training.txt";
static const char* TestingFilePath = "./my-digit-testing.txt";
static const char* CalculationFilePath = "./my-digit-predict.txt";/*每个数据集的数字个数*/
#define TRAINING_SIZE 943
#define TRAINING_COUNT 10
#define TEST_SIZE 196
#define CALCULATION_SIZE 9/*求距离*/
float CalculateDistance(SampleData sample_01, SampleData sample_02) {int i, square_sum=0.0;for(i=0; i<1024; i++) {square_sum += pow(sample_01.pixel[i]-sample_02.pixel[i], 2.0);}return sqrtf(square_sum);
}/*把文件的數據加載到內存中*/
int DataLoading(SampleData *pSampleDate_t, FILE *fp, int *pNumber) {int index=0;for(index = 0; index<1024; index++) {if(!fscanf(fp, "%d", &pSampleDate_t->pixel[index])) {printf("FILE already read finish.\n");return -true;}}fscanf(fp, "%d", &pSampleDate_t->number);*pNumber = pSampleDate_t->number;return true;
}/*显示一个Digit 结构体*/
void ShowSampleDate(SampleData SampleData_t) {int i, j, id;for(i=0;i<32;i++) {for(j=0;j<32;j++) {printf("%d", SampleData_t.pixel[i*32+j]);}printf("\n");}printf(" %d \n", SampleData_t.number);
}/*交换字符串两项*/
void IndexExchange(CalculationData *in, int index1, int index2) {CalculationData tmp = (CalculationData)in[index1];in[index1] = in[index2];in[index2] = tmp;
}/*选择排序*/
void SelectSort(CalculationData *in, int length) {int i, j, min;int N = length;for(i=0; i<N-1; i++) {min = i;for(j=i+1;j<N;j++) {if(in[j].distance<in[min].distance) min = j;}IndexExchange(in,i,min);}
}/*利用训练数据预测一个数据digit*/
int DataForecast(int K, SampleData in, SampleData *train, int nt)
{int i, it;CalculationData distance[nt];/*求取输入digit与训练数据的距离*/for(it=0; it<nt; it++) {distance[it].distance = CalculateDistance(in, train[it]);distance[it].number = train[it].number;}/*给计算的距离排序(选择排序)*/int predict = 0;SelectSort(distance, nt);for(i=0; i<K; i++) {predict += distance[i].number;}return (int)(predict/K);
}/*用测试数据集进行测试*/
void KnnDatasetClassification(int K)
{printf(".KnnDatasetClassification.\n");int i;FILE *fp;/*读入训练数据*/int trainLabels[TRAINING_SIZE];int trainCount[TRAINING_COUNT] = {0};SampleData *pSampleTrain_t = (SampleData*)malloc(TRAINING_SIZE*sizeof(SampleData));fp = fopen(TrainingFilePath,"r");if (fp < 0) {printf("..Open %s Error.\n", TrainingFilePath);exit(0);}printf("..load training digits.\n");for(i=0; i<TRAINING_SIZE; i++) {DataLoading(&pSampleTrain_t[i], fp, &trainLabels[i]);trainCount[pSampleTrain_t[i].number] ++;}fclose(fp);printf("..Done.\n");/*读入测试数据*/int testLabels[TEST_SIZE];int testCount[TRAINING_COUNT] = {0};SampleData *pSampleDataTest_t = (SampleData*)malloc(TEST_SIZE*sizeof(SampleData));fp = fopen(TestingFilePath,"r");if (fp < 0) {printf("..Open %s Error.\n", TestingFilePath);exit(0);}printf("..load testing digits.\n");for(i=0;i<TEST_SIZE;i++) {DataLoading(&pSampleDataTest_t[i], fp, &testLabels[i]);testCount[pSampleDataTest_t[i].number] ++;}fclose(fp);printf("..Done.\n");/*求测试数据与训练数据之间的距离*/printf("..Cal CalculationData begin.\n");CalculationData Distance2Train[TRAINING_SIZE];int CorrectCount[TRAINING_COUNT] = {0};int itrain, itest, predict;for(itest=0; itest < TEST_SIZE; itest++) {predict = DataForecast(K, pSampleTrain_t[itest], pSampleTrain_t, TRAINING_SIZE);//printf("%d-%d\n",predict, Dtest[itest].number);/*给预测准确的进行计数*/if(predict == pSampleDataTest_t[itest].number) {CorrectCount[predict] ++;}}/*输出测试数据的准确率*/printf(" Correct radio: \n\n");for(i=0;i<10;i++) {printf("%d: ( %2d / %2d ) = %.2f%%\n",i,CorrectCount[i],testCount[i],(float)(CorrectCount[i]*1.0/testCount[i]*100));}
}
/*预测数据*/
void KnnPredict(int K) {int i;FILE *fp = NULL;/*读入训练数据*/int trainLabels[TRAINING_SIZE];int trainCount[TRAINING_COUNT] = {0};SampleData *Dtrain = (SampleData*)malloc(TRAINING_SIZE*sizeof(SampleData));fp = fopen(TrainingFilePath,"r");printf("..load training digits.\n");for(i=0; i<TRAINING_SIZE; i++) {DataLoading(&Dtrain[i], fp, &trainLabels[i]);trainCount[Dtrain[i].number] ++;}fclose(fp);printf("..Done.\n");/*读入需要预测的数据*/int predictLabels[CALCULATION_SIZE];int predictCount[TRAINING_COUNT] = {0};SampleData *Dpredict = (SampleData*)malloc(CALCULATION_SIZE*sizeof(SampleData));fp = fopen(CalculationFilePath,"r");printf("..load predict digits.\n");for(i=0;i<CALCULATION_SIZE;i++) {DataLoading(&Dpredict[i], fp, &predictLabels[i]);predictCount[Dpredict[i].number] ++;}fclose(fp);printf("..Done.\n");/*求输入数据与训练数据之间的距离*/printf("..Cal CalculationData begin.\n");CalculationData Distance2Train[TRAINING_SIZE];int itrain, ipredict, predict;for(ipredict=0; ipredict<CALCULATION_SIZE; ipredict++) {predict = DataForecast(K, Dpredict[ipredict], Dtrain, TRAINING_SIZE);printf("%d\n",predict);}
}int main(int argc, char** argv)
{int K = 1;/*对已知数据进行测试,统计预测的正确率*/KnnDatasetClassification(K);/*对位置数据进行预测*/KnnPredict(K);return 1;
}
代码输出如下:
.KnnDatasetClassification.
..load training digits.
..Done.
..load testing digits.
..Done.
..Cal CalculationData begin.Correct radio: 0: ( 1 / 20 ) = 5.00%
1: ( 2 / 20 ) = 10.00%
2: ( 4 / 25 ) = 16.00%
3: ( 0 / 18 ) = 0.00%
4: ( 1 / 25 ) = 4.00%
5: ( 3 / 16 ) = 18.75%
6: ( 1 / 16 ) = 6.25%
7: ( 3 / 19 ) = 15.79%
8: ( 3 / 17 ) = 17.65%
9: ( 2 / 20 ) = 10.00%
..load training digits.
..Done.
..load predict digits.
..Done.
..Cal CalculationData begin.
5
2
1
8
2
9
9
1
5
百分比的输出是说明对每个不同数字的识别准确率。
最后的输出是输入单个例子的时候得到的结果,这个结果是和输入的样本
完全一致的。
代码里面会用到三个文件,一个是训练的文件,一个是测试文件,一个是
单例的测试文件。
完整的代码下载链接:
链接: https://pan.baidu.com/s/13OpeHEVD4zWCz60XjRC6pA?pwd=2hf9
提取码: 2hf9
文章内容来自:
https://blog.csdn.net/weixin_45014385/article/details/123618841
https://rtoax.blog.csdn.net/article/details/80309077