本章将学习KNN算法,KNN又称K近邻(k-nearest neighbor,k-NN)。该算法即可以解决分类问题,又可以解决回归问题,只不过解决回归问题是在分类问题基础上提出的,所以本文只讨论分类算法。
KNN算法的核心就是近朱者赤近墨者黑。对于给定的数据训练集,其中类别已经分好,对于一个新的数据样本点,根据其最近的 k k k个最近邻来决定新样本点的类别,决定方法就是进行投票,通过多数表决的方式进行预测。
如果结合图形看起来会更清晰:
像这个图一样,我们需要通过已有的三种样本点的数据来判断空心圆所属的种类,在KNN算法中,我们需要对紫色圆内的点的种类进行一个投票表决,显然在空心圆周围,有红色样本点4个,绿色样本点3个,黑色样本点2个,红色样本点出现的次数最多,于是将空心圆归为红色样本点。
KNN的分类原理就是上述这样,我们可以发现,KNN算法是不具有显式的学习过程,是一种惰性算法,即我们不需要先通过训练集数据学习一个模型,而是直接计算待测样本点与训练集的距离,从而直接预测。
其实在初步了解KNN算法之后,我们就能知道,KNN算法是没有太多数学模型的,其主要的思想就在于K值的选取和计算距离的方式。
在KNN分类算法中,K值的选取可以说是最重要的,合适的K值可以使得模型更优秀。
当K值较小时,就相当于使用较小的领域来对样本点进行预测,也就说只有与预测实例相近的点才起到作用,如果该点是噪声点,那么很容易导致模型预测错误。这也说明K值小,模型容易过拟合。
当K值较大时,相当于搜索的领域较大,距离预测点很远的样本都会起作用,这样也可能导致预测错误,就相当于欠拟合。
所以K值的选取非常重要,一般在确定K值时使用的方法就是交叉验证。遍历可能取值的K值,计算当K取不同值时的误差大小,取最小误差时的K值作为最优K值。
除了确定K值这一重要工作以外,计算距离的方式也是非常重要的。何为计算距离的方式,我们前面说了,要根据预测点最近的K个样本点来进行投票表决,但是如何知道哪K个点距离预测点最近呢?我在举例中是通过画图,在图上观察得到的。但是在真实计算过程中,可不能通过观察得到,我们需要找到一个能够反映距离远近的度量,也就是距离计算方式。
假设给定两个样本点 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)、 ( x 2 , y 2 ) (x_2,y_2) (x2,y2),那么如何计算这两个样本点之间的距离呢?
最常用的计算距离方式就是欧式距离:
D = ∑ l = 1 N ( x 1 ( l ) − x 2 ( l ) ) 2 D=\sqrt {\sum\limits_{l=1}^N({x^{(l)}_1-x^{(l)}_2})^2} D=l=1∑N(x1(l)−x2(l))2
其中 N N N表示特征的维数,除了欧式距离以外,还有其他的距离公式例如曼哈顿距离和切比雪夫距离等等,一般来说,我们最常用的距离公式就是欧式距离。在确定好距离的计算公式之后,我们就可以试着编写KNN算法的程序了。
知道了如何选取K值和如何计算样本之间的距离后,我们就可以编写代码,这次使用的任然是Matlab,使用什么语言不重要,主要是能够理解算法的本质,Matlab相比于Python有很多便捷的函数可以使用。
在分类前,我们需要对数据进行归一化处理,因为不同特征之间的度量肯定是不一样的,就比如一个人的身高和体重,身高可以达到180cm,体重60kg,如果只看数值的话180>60,但是体重和身高之间是没法进行比较的,所以我们不能直接带入欧式距离计算公式,需要对不同量纲的数据进行归一化,将数据都缩放在0-1之间,这样后续计算欧式距离时就可以进行比较了。
处理完数据之后,我们需要了解KNN算法的结构,对于一个新的输入样本点,我们需要计算已知所有的样本点与新的样本点之间距离,然后取距离最近的K个样本点为”邻居“,再从邻居中进行投票决定,将新样本点归为票数最多的类别,输出该类别,即KNN算法结束。
下面来看代码:
主函数
clc,clear
tic
% 读取数据
XlRange = 'A2:D68';
data_old = xlsread('多分类水果数据',XlRange);
labels = xlsread('多分类水果数据','E2:E60');
% 数据预处理
data_new = zeros(size(data_old));
for i=1:size(data_old,2) % 遍历每一行
value_max = max(data_old(:,i));
value_min = min(data_old(:,i));
for j =1:size(data_old,1)
data_new(j,i) = (data_old(j,i) - value_min)/(value_max - value_min);
end
end
% 划分测试集与训练集
data_train = data_new(1:59,:);
data_test = data_new(60:67,:);
K = 7;
% 这里的model等于2表示计算KNN算法的方式不同,最后结果都是一样的
model = 2;
% 主函数部分
for i =1:size(data_test,1)% 遍历测试集所有数据
label = KNN(data_test(i,:),data_train,labels,K,model);
disp(['第',num2str(i),'个测试集数据的类别为:',num2str(label)])
end
toc
KNN分类函数
function [label] = KNN(test,train,labels,K,model)
% KNN函数部分
% 计算输入数据距离与样本点的距离
[n,~] = size(train);
dist = zeros(n,1);
for i =1:n
dist(i,:) = norm(train(i,:)-test);
end
% 对距离进行排序
[y,I] = sort(dist,1);
K = min(K,length(y));
class_sort = labels(I); % 得到对应排序的标签
% 1
if model == 1
class = class_sort(1:K); % 获取前K个的标签
% class_num = length(unique(class)); % 获得前K个标签种类的个数
class_ma = zeros(max(class),1); % 提前创造一个矩阵存储后续标签出现的个数
for i =1:length(class) % 遍历前K个标签
temp = class(i);
class_ma(temp) = class_ma(temp) + 1;
end
% 找到出现次数最多的标签
[~,label] = max(class_ma);
else % 直接用众数计算函数
label=mode(class_sort(1:K));
end
end
KNN算法与常规的机器学习算法相比非常特殊,由于其没有显式的学习过程,所以就不存在过多的参数,影响结果的只有一个参数 k k k,而且算法的思想非常简单易懂。
KNN算法的优点
KNN算法的缺点