机器学习——K近邻算法

1、K近邻算法介绍

kNN(k-nearest neighbor, KNN)是一个基本的分类和回归算法,在1968年有Cover和Hart提出。

k近邻算法的三个基本要素(模型三要素):k值的选择、距离度量(如:欧式距离)、分类决策规则(如:多数表决majority vote)

  • INPUT: 是实例的特征向量,对应于特征空间的点;
    Output:为实例的类别,可以取多类。
  • k近邻算法给定一个训练数据集,其中实例类别已定
  • 分类时,对新的实例,根据其k个近邻的训练实例的类别,通过多数表决等方式进行预测。
  • k近邻法不具有显式的学习过程。
    • 简单的说,给定一个训练数据集,对新的输入实例,在训练集中找到与该实例最近邻的k个实例,这k个实例的多数属于哪个类,就把该输入实例分为这个类。通常k是不大于20的整数(特殊的,k=1为最近邻)

:已知表格的前四部电影,根据fight镜头和kiss镜头判断一个新的电影所属类别?
机器学习——K近邻算法_第1张图片

  • 欧式距离(Euclidean Distance)计算公式:两个n维向量a(x11,x12,…,x1n)a(x11,x12,…,x1n)与b(x21,x22,…,x2n)b(x21,x22,…,x2n)间
    的欧氏距离:
    d = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d = \sqrt {\sum_{k=1}^n(x_{1k}-x_{2k})^2} d=k=1n(x1kx2k)2
  • 对于本例子n = 2,
    电影1与未知电影距离:20.5;
    电影2与未知电影距离:18.7;
    电影3与未知电影距离:117.4;
    电影4与未知电影距离:118.9;
  • 得到了训练集中所有样本与未知电影的距离,按照距离递增排序,可以找到距离最近的电影,假设k=3, 则三个最靠近的电影依次是电影1、电影2、电影3,而这三部电影类别两个为爱情片,一个为动作片,所以预测该位置电影的所属类别是爱情片。

算法流程L:

(1)收集数据:可以使用任何方法。
(2)准备数据:距离计算所需要的数值,最好是结构化的数据格式。
(3)分析数据:可以使用任何方法。
(4)训练算法:此步骤不适用与k-近邻算法。
(5)测试算法:计算错误率
(6)使用算法:首先需要输入样本数据和结构化的输出结果,
然后运行k-近邻算法判断输入数据分别属于那个分类,
最后应用对计算出的分类执行后续的处理。

1.准备:通过python导入数据
准备数据:对训练数据,用numpy创建数据集和标签

'''
函数功能:生成自定义数据集
'''
def createDataSet():
    group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group,labels
  1. KNN分类操作
  • 算法思想:
  • 对未知类别属性的数据集的每个点 未知类别属性的数据集的每个点依次执行以下操作:
    (1) 计算已知类别数据集中的点与当前点之间的距离;
    (2) 按照距离递增次序 按照距离递增次序排序;
    (3)选取与当前点距离最小的k个点;
    (4)确定前k个点所在类别的出现频率;
    (5)返回前k个点所出现频率最高的类别作为当前点的预测分
'''
函数功能: KNN分类
Iuput:   inx:测试集
         dataSet:已知数据的特征(N*M)
         labels:已知数据的标签或类别(1*M vertor)
         k:K近邻算法中的k
Output: 测试样本最可能所属的标签
'''
def classify0(inx,dataSet,labels,k):
    dataSetSize = dataSet.shape[0]          #shape[0]返回dataSet的行数
    diffMat = np.tile(inx,(dataSetSize,1))-dataSet
    # np.tile(inx,(a,b))函数将inx重复a行,重复b列
    sqDiffMat = diffMat**2      #做差后平方
    sqDistances = sqDiffMat.sum(axis = 1)
    #sum()求和函数,sum(0):每列所有元素相加,sum(1):每行所有元素相加
    distances = sqDistances**0.5  #开平方,求欧式距离
    sortedDisIndicies = distances.argsort()
    #argsort()函数返回的是数组值从小到大的索引值
    classCount={}
    for i in range(k):
        voteIlabel = labels[sortedDisIndicies [i]]
        #取出前k个距离对应的标签
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
        #计算每个类别的样本数。字典get()函数返回指定键的值,如果值不在字典返回默认值0
    sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse = True)
    #reverse 降序排列字典
    #key = operator.itemgetter(1)按照字典的值(value)进行排序
    #key = operator.itemgetter(0)按照字典的键(key)进行排序
    return sortedClassCount[0][0] #返回字典的第一条的key,也即是测试样本所属类别'''

相关函数介绍:
shape函数是numpy.core.fromnumeric中的函数,它的功能是查看矩阵或者数组的维数。
tile函数位于python模块 numpy.lib.shape_base中,功能是重复某个数组。比如tile(A,n),功能是将数组A重复n次,构成一个新的数组。
sorted函数:对所有可迭代的对象进行排序操作。
sorted(iterable[, cmp[, key[, reverse]]])
参数说明:

  • iterable – 可迭代对象。E.g., list、tuple、dict、set、str等
  • cmp – 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0。
  • key – 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于
    可迭代对象中,指定可迭代对象中的一个元素来进行排序。
  • reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)

2、实例:改进匹配约会网站

在约会网站上使用k-近邻算法:

  • 收集数据:提供文本。
  • 准备数据:使用python解析文本文件。
  • 分析数据:使用Matplotlib画二维扩散图。
  • 测试算法:使用海伦提供的部分数据作为测试样本。测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误 标记为一个错误。
  • 使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。
    准备数据:
'''
函数功能: 将文本转换为矩阵
Iuput: 文件名字符串
Output: 训练样本矩阵和类标签向量
'''
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    returnMat = np.zeros((numberOfLines,3))#生成一个全0矩阵
    classLabelVertor = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        #strip()函数用于移除字符串头尾指定的字符(默认为空格或换行符)
        listFromLine = line.split('\t')
        # split()函数通过指定分隔符对字符串进行分割并返回一个列表
        returnMat[index,:] = listFromLine[0:3]
        classLabelVertor.append((listFromLine[-1]))
        index += 1
    return returnMat,classLabelVertor

分析数据:

fig = plt.figure()
ax = fig.add_subplot(111)
plt.xlabel('percentage of time spent playing vedio games')
plt.ylabel('liters of ice cream consumed per year')
for i in range(len(datingLabels)):
    if datingLabels[i] == str(1):
        type1 = ax.scatter(datingDataMat[i,1],datingDataMat[i,2],marker='x',s = 10,color = 'blue')
    if datingLabels[i] == str(2):
        type2 = ax.scatter(datingDataMat[i, 1], datingDataMat[i, 2], marker='x', s=20, color='green')
    if datingLabels[i] == str(3):
        type3 = ax.scatter(datingDataMat[i,1],datingDataMat[i,2],marker='x',s = 30,color = 'red')
plt.title('dating statistics')
plt.legend((type1,type2,type3),('do not like','probably like','like'))
plt.show()

分析结果:
机器学习——K近邻算法_第2张图片
3. 归一化
直接对数据使用欧式距离计算,则会使得高数量级的特征对目标变量影
响权重大,但实际上,并非真正如此,对其进行归一化到[0,1)之间,认
为其同等重要,故需要对数据进行归一化。

'''
函数功能:  归一化特征值
Input:  数据特征值
Output: 归一化后的数据特征值
'''
def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(dataSet.shape)
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals,(m,1))
    normDataSet = normDataSet/np.tile(ranges,(m,1))
    return normDataSet,ranges,minVals
  1. 测试
  • 使用错误率来检验我们的分类器的性能!
    • 对于k-近邻算法,我们的主要参数便是三要素:k的选择、距离度量的标准、分类决策的规则。
    • 对于已有的数据,将90%作为训练样本,剩下作为测试。设定k=3,对数据进行归一化各个特征权重一样大使用欧式距离计算
    • 调整训练的数据,调整k值,不使用欧式距离计算相似度,对不同特征采取不同权重等等,都可以使得最终的错误率不一样,为此需要多次实验以得到最好的一组参数使得错误率最小。
'''
函数功能:作为完整程序验证分类器
'''
def datingClassTest():
    hoRatio = 0.10
    datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
    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],3)
        print('the classifier came back with:',classifierResult,', the real answer is:',datingLabels[i])
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    errorrate = errorCount/float(numTestVecs)
    print('the total error rate is:',errorrate)
  1. 算法应用
'''
函数功能:约会网站预测
'''
def classifyPerson():
    resultList = ['not at all', 'in small does', 'in large does']
    percentTats = float(input('percentage of time spent playing vedio games:'))
    ffMiles = float(input('frequent flier miles earned per year:'))
    iceCream = float(input('liters of ice cream consumed per year:'))
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = np.array([ffMiles,percentTats,iceCream])
    classifierResult = classify0((inArr - minVals)/ranges,normMat,datingLabels,3)
    print('you will probably like this person:',resultList[int(classifierResult)-1])

3、手写数字识别

  1. 准备数据
    图像格式化处理为一个向量,把一个32X32的二进制图像矩阵通过
    img2vector()函数转换为1X1024的向量:
'''
函数功能:把一个32X32的二进制图像矩阵通过转换为1X1024的向量
'''
def img2vector(filename):
    returnVect = np.zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        linestr = fr.readline()
        for j in range(32):
            returnVect[0, 32*i+j] = int(linestr[j])
    return returnVect
  1. 测试
'''
函数功能:手写数字识别系统
'''
def handwritingClassTest():
    hwLabels = []
    trainingFileList = os.listdir('digits/trainingDigits')
    m = len(trainingFileList)
    trainingMat = np.zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        trainingMat[i,:] = img2vector('digits/trainingDigits/%s' % fileNameStr)
    testFileList = os.listdir('digits/testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
        classifierResult = knn.classify0(vectorUnderTest,trainingMat,hwLabels,3)
        print('the classifier came back with:',classifierResult)
        print('the real answer is:',classNumStr)
        if classifierResult != classNumStr:
            errorCount +=1.0
    print('\n the total number of error is:',errorCount)
    print('\n the total error rate is:',(errorCount/float(mTest)))

4、总结

  • K近邻是分类数据最简单最有效的算法
  • 必须保存全部数据集,如果数据集很大,必须使用大量存储空间
  • 必须对数据集中每个数据计算距离值,非常耗时
  • 无法给出任何数据的基础结构信息,无法知晓平均实例样本和典型实例样本具有什么样的特征下一章:概率测量方法处理分类问题
  • k近邻不具有显式的学习过程,实际上利用训练数据集对特征空间进行划分

k值的选择:

  • 选择较小的k值:用较小的邻域中的训练实例进行预测,“学习”的近似误差会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。缺点是:学习的估计误差增大,也就是说预测结果会对近邻的实例点非常敏感(如果恰好是噪声,则预测出错)k值减小,整体模型变得复杂 整体模型变得复杂,容易发生过拟合。
  • 选择较大的K值:用较大邻域中的训练实例进行预测,减少学习的估计误差,但缺点是学习的近似误差会增大与输入实例较远的(不相似)的训练实例也会对预测起作用 的训练实例也会对预测起作用,从而预测错误。K值增大、模型变得简单K=N时,模型过于简单,忽略大量有用信息,不可取。
  • 通常K取比较小的数值,会通过交叉验证法来选取最优k值
  • 过拟合:如果一味追求提高训练数据预测能力,所选择模型的复杂度往往比真模型要高!出现过拟合(over-fitting),对已知数据预测的很好,但对未知数据预测很差的现象。
  • 交叉验证法:重复使用数据,把给定数据进行 把给定数据进行切分,将切
    分数据集组合为训练集和测试集,在此基础上反复进行训练、测试和模型选择 测试和模型选择。

5、使用Scikit-learn进行k近邻算法运算

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

if __name__ == "__main__":
    iris = datasets.load_iris()
    x = iris.data[:,[1,2]]
    print(x)
    y = iris.target
    x_train,x_test,y_train,y_test = train_test_split(x,y,test_size = 0.3,random_state = 0)

    # 训练数据和测试数据进行标准化
    sc = StandardScaler()
    sc.fit(x_train)
    x_train_std = sc.transform(x_train)
    x_test_std = sc.transform(x_test)

    # 建立一个k近邻模型对象
    knn_classifier = KNeighborsClassifier(6)
    # 输入训练数据进行学习建模
    knn_classifier.fit(x_train, y_train)
    # 对测试数据进行预测
    y_predict = knn_classifier.predict(x_test)
    # score(x,y[,sample_weight])返回给定测试数据和标签的平均准确值
    scores = knn_classifier.score(x_test,y_test)
    print(scores)

你可能感兴趣的:(机器学习)