KNN原理-python实现

《机器学习实战》CH02

KNN算法介绍
KNN是一种监督学习算法,通过计算新数据与训练数据特征值之间的距离,然后选取K(K>=1)个距离最近的邻居进行分类判(投票法)或者回归。若K=1,新数据被简单分配给其近邻的类。

KNN算法实现过程
(1)选择一种距离计算方式, 通过数据所有的特征计算新数据与已知类别数据集中的数据点的距离;
(2)按照距离递增次序进行排序,选取与当前距离最小的k个点;
(3)对于离散分类,返回k个点出现频率最多的类别作预测分类;对于回归则返回k个点的加权值作为预测值;

算法关键
(1)数据的所有特征都要做可比较的量化
若是数据特征中存在非数值的类型,必须采取手段将其量化为数值。例如样本特征中包含颜色,可通过将颜色转换为灰度值来实现距离计算。
(2)样本特征要做归一化处理
样本有多个参数,每一个参数都有自己的定义域和取值范围,他们对距离计算的影响不一样,如取值较大的影响力会盖过取值较小的参数。所以样本参数必须做一些scale处理,最简单的方式就是所有特征的数值都采取归一化处置。
(3)需要一个距离函数以计算两个样本之间的距离
距离的定义:欧氏距离、余弦距离、汉明距离、曼哈顿距离等,一般选欧氏距离作为距离度量,但是这是只适用于连续变量。在文本分类这种非连续变量情况下,汉明距离可以用来作为度量。通常情况下,如果运用一些特殊的算法来计算度量的话,K近邻分类精度可显著提高,如运用大边缘最近邻法或者近邻成分分析法。
(4)确定K的值
K值选的太大易引起欠拟合,太小容易过拟合。交叉验证确定K值。

KNN分类
分类算法常采用多数表决决定。一个缺点是出现频率较多的样本将会主导测试点的预测结果。解决这个缺点的方法之一是在进行分类时将K个邻居到测试点的距离考虑进去。若样本到测试点距离d,则选1/d为该邻居的权重,统计k个邻居所有类标签的权重和,值最大的就是新数据点的预测类标签。

KNN回归
KNN回归是取K个邻居类标签值得加权作为新数据点的预测值。

优缺点
(1)KNN算法的优点
1.简单、有效。
2.重新训练的代价较低(类别体系的变化和训练集的变化,在Web环境和电子商务应用中是很常见的)。
3.计算时间和空间线性于训练集的规模(在一些场合不算太大)。
4.由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合。
5.该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量较小的类域采用这种算法比较容易产生误分。
(2)KNN算法缺点
1.KNN算法是懒散学习方法(lazy learning,基本上不学习),一些积极学习的算法要快很多。
2.类别评分不是规格化的(不像概率评分)(???)。
3.输出的可解释性不强,例如决策树的可解释性较强。
4.该算法在分类时有个主要的不足是,当样本不平衡时,如一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新样本时,该样本的K个邻居中大容量类的样本占多数。该算法只计算最近的邻居样本,某一类的样本数量很大,那么或者这类样本并不接近目标样本,或者这类样本很靠近目标样本。无论怎样,数量并不能影响运行结果。可以采用权值的方法(和该样本距离小的邻居权值大)来改进。
5.计算量较大。目前常用的解决方法是事先对已知样本点进行剪辑,事先去除对分类作用不大的样本。

KNN实现

import numpy as np
import operator
import matplotlib
import matplotlib.pyplot as plt
from os import listdir
def Create_DataSet():
    group = np.array([[1.0, 1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group,labels

函数Create_DataSet创建一个数据集,坐标轴左下角分类为B,右上角分类为A。

下面函数classify0,计算向量inX与数据集中各点的距离,计算n_estimators个近邻中label频率最高的分类号并返回作为向量inX的分类号。

def classify0(inX, dataSet, labels, n_estimators=3):
    dataSetSize = dataSet.shape[0]
    #print 'in classify0,dataSetSize = \n',dataSetSize
    #转变向量inx格式为datasize行,1列;并计算与dataset元素距离
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet

    #计算欧氏距离((x0-x1)^2 + (y0-y1)^2 )^(1/2)
    sqDiffMat = diffMat**2 #diffMat每个元素取平方
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5

    #排序,将值从小到大排列,返回索引
    sortedDistIndicies = distances.argsort()    
    #print 'in classify0,sortedDistIndicies:\n',sortedDistIndicies

    #求与距离最近的k个点的label统计
    classCount={}          
    for i in range(n_estimators):
        voteIlabel = labels[sortedDistIndicies[i]] #获取对应label号
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1

    #对字典排序,按value值降序排列
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    #print 'sortedClassCount[0][0]:\n',sortedClassCount[0][0]
    return sortedClassCount[0][0]

dataSet.shape()函数用于获取矩阵dataSet的大小,shape[0]返回对应行数,shape[1]返回对应列数。
因为需要对每列属性做距离运算,所以需要将输入inX转换为和dataSet相同行数和列数的矩阵,因为inX列数与dataSet中每个元素列数相同,所以需要将其行数进行扩展,np.tile(inX, (dataSetSize,1))将inX行数拓展为dataSetSize行,1表示纵向复制1次,即列不变。
距离公式采用欧式距离计算,得到的距离值为一维列表,分别对应dataSet中每个元素和inX的距离。distances.argsort() 将距离按从小到大排列,并返回索引。例如distance = [0.1,0.5,0.3],distance.argsort()返回[1,3,2] 。返回索引是为了找到对应的label值,并进行统计。
下面for循环用于建立字典并统计前n_estimators个label的个数。key对应label,key_value对应个数。
operator.itemgetter函数,operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号,即需要获取的数据在对象中的序号;例如a = [1,2,3] ,定义函数b=operator.itemgetter(1),获取对象的第1个域的值,则 b(a)=2;若定义函数b,获取对象的第1个域和第0个的值b=operator.itemgetter(1,0),则b(a)=(2, 1) 。注意operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值;
sorted函数:Python内置的排序函数sorted可以对list或者iterator进行排序;第一个参数iterable指定要排序的list或者iterable,第二个参数指定排序时进行比较的函数,可以指定一个函数或者lambda函数。例如students为类对象的list,每个成员有三个域,用sorted进行比较时可以自己定cmp函数,例如这里要通过比较第三个数据成员来排序,students = [(‘john’, ‘A’, 15), (‘jane’, ‘B’, 12), (‘dave’, ‘B’, 10)],sorted(students, key=lambda student : student[2]),key为函数,指定取待排序元素的哪一项进行排序,key指定的lambda函数功能是去元素student的第三个域(student[2]),因此sorted排序时会以students所有元素的第三个域来进行排序;也可以这么写sorted(students, key=operator.itemgetter(2)) ,sorted函数也可以进行多级排序,例如要根据第二个域和第三个域进行排序;sorted(students, key=operator.itemgetter(1,2))即先跟句第二个域排序,再根据第三个域排序;第三个参数reverse是一个bool变量,表示升序还是降序排列,默认为false升序排列,定义为True时将按降序排列。
此处sort函数用于对字典进行排序。按key_value降序排列,即对应label个数从大到小排列。返回值为列表,列表元素为元组,元组第一个元素对应label,第二个元素对应label个数。sortedClassCount[0][0]即返回label次数最多的类标号,为inX的label。

下面测试一个简单的向量:

group,labels = Create_DataSet()
sortedClassCount = classify0([0,0.5],group,labels,3)

输出为
sortedClassCount:[(‘B’, 2), (‘A’, 1)]
sortedClassCount[0][0]:
B

下面函数file2matrix用于从txt中读取原始数据并转化为矩阵。
test.txt格式为
40920 8.326976 0.953952 largeDoses
14488 7.153469 1.673904 smallDoses
26052 1.441871 0.805124 didntLike
75136 13.147394 0.428964 didntLike
……
最后一列为label,值为largeDoses、smallDoses或didntLike。每行元素用\t隔开。转换后label分别对应3、2、1。
转换函数如下:

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines()) 
    print 'in file2matrix,numberOfLines:\n',numberOfLines

    returnMat = np.zeros((numberOfLines,3))
    classLabelVector = []  
    fr = open(filename)
    index = 0
    for line in fr.readlines(): #遍历每一行
        line = line.strip() #strip用于删除字符,此处删除空白字符,回车
        listFromLine = line.split('\t') #获取每行的元素列表,元素用\t分开
        returnMat[index,:] = listFromLine[0:3]#取前3个元素,对应属性集
        if(listFromLine[-1] == 'largeDoses'):#有什么有效的方法 将属性值和类标号分开,相互对应
            classLabelVector.append(3)  
        elif(listFromLine[-1] == 'smallDoses'): 
            classLabelVector.append(2)
        elif(listFromLine[-1] == 'didntLike'):
            classLabelVector.append(1)  
        elif(listFromLine[-1] == 3):
            classLabelVector.append(3)
        elif(listFromLine[-1] == 2):
            classLabelVector.append(2)
        elif(listFromLine[-1] == 1):
            classLabelVector.append(1)
        index += 1
    #print 'returnMat = ',returnMat
    #print 'classLabelVector = ',classLabelVector
    return returnMat,classLabelVector #得到属性集和类标号

首先打开文件并获取行数,建立一个相同大小的空矩阵,用于存储转换后的属性集,并新建一个一维列表,用于存放类标号。fr.readlines()读取所有行,得到一个行列表,列表元素为每行内容;readline只读取1行,获取该行元素的列表。
上述函数即返回属性集矩阵和类标号列表。

因为属性值差距较大,为了减少值太大的属性对值小的属性的影响,分类之前还需要进行归一化。归一化方程为(datain-min_val) / (max_val - min_val),输出值都介于0-1。

def autoNorm(dataSet):
    minVals = dataSet.min(0) #获取每列最大值与最小值,(0)指定列,而不是行
    print 'in autoNorm,minVals:',minVals
    maxVals = dataSet.max(0)
    print 'in autoNorm,maxVals:',maxVals
    ranges = maxVals - minVals
    print 'in autoNorm,ranges:',ranges
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0] #获取行数
    #特征值矩阵为1000x3,minVals值为1x3,使用tile函数扩展为相同大小的矩阵
    #np.tile(minVals, (m,1))矩阵minval,横向复制m次,纵向复制1次
    normDataSet = dataSet - np.tile(minVals, (m,1)) # (data - minval)/(maxval - minval)
    normDataSet = normDataSet/np.tile(ranges, (m,1))   #element wise divide
    print 'in autoNorm,normDataSet = ',normDataSet
    return normDataSet, ranges, minVals

返回归一化以后的属性集。即可进行距离运算并分类。

下面函数即对文件中所有输入的行向量属性进行分类

def datingClassTest(n_estimators=3):
    hoRatio = 0.50
    #(1)读取文件
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    #(2)归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult =  classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],n_estimators=n_estimators)
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print "in datingClassTest,the total error rate is: %f" % (errorCount/float(numTestVecs))
    print 'in datingClassTest,errorCount:',errorCount

将测试文件分为数据集和用于测试的向量2部分。前一半用于测试,后一半作为数据集,并定义errorCount用于统计出错个数。经过归一化以后的数据集和验证通过for循环计算分类结果,并与实际结果进行对比,得到总出错数和出错率。
执行该函数,结果显示:
in datingClassTest,the total error rate is: 0.064000
in datingClassTest,errorCount: 32.0

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: knn算法(k-近邻算法)是一种分类和回归的算法,被广泛应用于模式识别、数据挖掘、图形识别等领域。其原理是根据数据本身的特征,将测试集中的每一个样本按照特征相似度进行分类。其中,k的值表示选择多少个最相似的邻居作为判断依据,通常采用欧氏距离来计算相似度。 在knn算法的实现过程中,需要先将数据集分为训练集和测试集。接着,通过计算测试集中每一个样本与训练集中每一个样本的欧氏距离(或曼哈顿距离等),选择距离最近的k个邻居。最后,采用“多数表决”的方式选择样本类别,并将该类别赋给测试集中的样本。 在Python中,可以使用scikit-learn库实现knn算法。以下是一个简单的代码示例: from sklearn.neighbors import KNeighborsClassifier # 创建训练集和测试集 train_x = [[0], [1], [2], [3]] train_y = [0, 0, 1, 1] test_x = [[1.5]] # 创建knn分类器(k=2) knn = KNeighborsClassifier(n_neighbors=2) # 拟合模型 knn.fit(train_x, train_y) # 进行预测 print(knn.predict(test_x)) 以上代码中,第一行引用了scikit-learn库下的KNeighborsClassifier类,用于创建一个knn分类器。接着,分别创建了训练集和测试集,并针对训练集中的两类样本对应标签进行了标记。接下来,创建k值为2的knn分类器,并使用fit()方法对训练集进行拟合。最后,通过predict()方法进行实际的预测,并输出测试样本的分类结果。 总体来说,knn算法是一种简单易用的分类和回归算法,具有可解释性强、不受算法实现形式的特点,同时能够适应各种数据类型和特征。在Python中,采用scikit-learn库实现knn算法也非常方便。 ### 回答2: KNN算法是一种基于实例的学习方法,通过计算样本之间的距离来确定新样本的类别。KNN算法是一种简单而有效的分类方法,尤其适用于小数据集。算法原理是基于这样一种思想:样本空间中的每个样本都可以用它最近的K个邻居来代表。其中K是一个正整数,是预定的参数。当K=1时,为最近邻分类算法,即只考虑最近的一个邻居。 具体实现步骤: 1.读入数据集,并将其分为训练集和测试集。 2.对数据集进行归一化处理。 3.对每个测试实例,计算其与训练集中所有实例之间的距离。 4.按照距离的大小降序排列。 5.选取前K个距离最小的实例,得到它们所属的类别中出现次数最多的那个类别作为该测试实例的预测类别。 6.计算预测结果与实际结果的差异。 在Python实现KNN算法需要用到一些基本的库:Numpy和Scikit-learn。具体步骤如下: 1.导入Numpy库。 2.导入数据集并将其分为训练集和测试集。 3.使用Scikit-learn库中的MinMaxScaler函数进行数据归一化处理。 4.使用Scikit-learn库中的KNeighborsClassifier函数进行训练,设定参数k和metric。 5.使用Scikit-learn库中的predict函数进行预测,得到预测结果。 6.计算预测结果与实际结果的差异,得到预测准确率。 KNN算法的优点是简单易懂,精度高;缺点是计算复杂度高,对数据的大小敏感。当数据维度较高时,其计算复杂度会变得极高,而且KNN算法对数据的距离非常敏感,如果数据特征选取不当,会导致预测精度大幅下降。因此,在使用KNN算法的时候需要注意数据特征的选取和K值的选择。 ### 回答3: K近邻(k-NN)算法是最简单的基于实例的学习算法之一,它的主要思想是使用距离度量来对特征空间中的样本进行分类。KNN算法中的K代表选择邻居的数量,邻居是指在训练集中与测试数据距离最近的样本点。KNN算法的基本步骤如下: 1. 计算测试数据与所有训练数据之间的距离。 2. 根据距离度量,对距离最近的K个样本进行投票。 3. 根据投票结果,决定测试数据属于哪一类别。 KNN算法的优点是简单易用,能够处理多分类和回归问题;缺点是计算量大,对训练数据敏感,需要进行归一化处理,并需要选择合适的距离度量和K值。 Python实现KNN算法需要使用Scikit-learn或Numpy等相关库。下面给出一个简单的Python代码实现,该代码实现了一个基于欧氏距离的KNN分类器: ``` import numpy as np from sklearn.neighbors import KNeighborsClassifier # 生成训练数据 X_train = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) y_train = np.array([0, 0, 1, 1]) # 创建KNN分类器,选择K=3 clf = KNeighborsClassifier(n_neighbors=3) # 训练分类器 clf.fit(X_train, y_train) # 测试数据,预测其所属类别 X_test = np.array([[2, 3], [4, 5], [6, 7]]) y_test = clf.predict(X_test) print(y_test) ``` 该代码中,通过Numpy库生成了一个4个样本点的训练数据集,其中前两个样本属于类别0,后两个样本属于类别1。同时,也生成了3个测试数据点。然后使用Scikit-learn库中的KNN分类器,在训练数据上训练模型,选择K=3。最后,对测试数据进行分类,并输出分类结果。 以上就是KNN算法的基本原理Python实现,希望对读者有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值