机器学习实战—— K-邻近算法(KNN)

Posted JS_MY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机器学习实战—— K-邻近算法(KNN)相关的知识,希望对你有一定的参考价值。

K-邻近算法

K-邻近算法概述

KNN采用测量不同特征值之间的距离方法进行分类。

  • 优点:精度高、对异常值不敏感、无数据输入假定

  • 缺点:计算复杂度高、空间复杂度高

  • 适用数据范围:数值型和标称型

  • 工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

手写KNN算法

  1. 创建数据集并观察特征

    import numpy as np
    from matplotlib import pyplot as plt
    
    def createDataSet():
        group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
        label = ['A','A','B','B']
        return group,label
           
    ## 创建数据集
    group,label = createDataSet()
    
    ## 绘制四个点
    plt.scatter(group[0][0], group[0][1],c='b',label='A')
    plt.scatter(group[1][0], group[1][1],c='b')
    plt.scatter(group[2][0], group[2][1],c='r',label='B')
    plt.scatter(group[3][0], group[3][1],c='r')
    
    ## 绘制X轴,Y轴
    plt.xlabel('X')
    plt.ylabel('Y')
    
    ## 添加图例
    plt.legend()
    
    ## 添加标题
    plt.title("KNN-TEST")
    
    ## 图片展示
    plt.show()
    

  1. 实施KNN算法
import numpy as np
import operator

## 创建数据集
def createDataSet():
    group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    label = ['A','A','B','B']
    return group,label

## 构建分类器
def classify0(inX,dataSet,labels,k):
    '''
    Parameters
    ----------
    inX : 输入向量
    dataSet : 训练样本集
    labels : 标签向量
    k : 选择最近邻居的数目
    -------
    '''
    # 求数据集中一共多少组数据
    dataSet_size = dataSet.shape[0]
    ## tile函数表示将inX在行方向上重复dataSet_size次,在列方向上重复1次
    # 求输入向量与数据集中点横纵坐标之差
    diffMat = np.tile(inX,(dataSet_size,1))-dataSet 
    # 求输入向量与数据集中点横纵坐标之差的平方
    sqDiffMat = diffMat ** 2
    # 求横坐标之差平方与纵坐标之差平方和
    sqDistances = sqDiffMat.sum(axis=1)
    # 求距离
    distances = sqDistances**0.5
    ## argsort函数返回一个列表,为原列表从小到大排序后各元素的索引
    ## 比方说第一个元素为原列表中第二小的元素,argsort函数在新列表中1位置写1
    ## 第三小,就在该位置写2,以此类推
    # 求distances数组中元素从小到大排序后各元素的索引
    sortedDistIndices = distances.argsort()
    classCount = {}
    for i in range(k):
        # 依次寻找最邻近点的标签
        voteIlabel = labels[sortedDistIndices[i]]
        # 在字典中寻找有无标签,没有就添加,有就+1
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 将字典拆解为列表
    # 遍历classCount字典的键值对,并按value进行排序,升序排列
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]
 
group,label = createDataSet()
category = classify0([-1,-1], group, label, 3)
print(category)    

输出结果为B,表示分类器将[-1,-1]这个点分类到了B类别

示例:使用K-邻近算法改进约会网站的配对效果

import numpy as np
import operator

## 将文件内容转为numpy
def file2matrix(filename):
    '''
    Parameters
    ----------
    filename : 数据文件名
    -------
    '''
    # 将标签映射为数字
    love_dictionary = {'largeDoses':3, 'smallDoses':2, 'didntLike':1}
    # 打开文件
    fr = open(filename)
    # 读取每一行
    arrayOLines = fr.readlines()
    # 计算行数
    numberOfLines = len(arrayOLines)    
    # 创建零初始矩阵      
    returnMat = np.zeros((numberOfLines, 3))    
    classLabelVector = []                       
    index = 0
    for line in arrayOLines:
        # 截取掉所有的回车字符
        line = line.strip()
        # 将截取后的每行数据分割成一个列表
        listFromLine = line.split('\\t')
        # 取前三个特征,存储到特征矩阵中
        returnMat[index, :] = listFromLine[0:3]
        # 如果标签是数字就添加数字,如果不是数字,将映射的数字添加到列表中
        if(listFromLine[-1].isdigit()):
            classLabelVector.append(int(listFromLine[-1]))
        else:
            classLabelVector.append(love_dictionary.get(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector

## 归一化特征值
def autoNorm(dataSet):
    '''
    Parameters
    ----------
    dataSet : 数据集
    -------
    '''
    # 返回数据集中每一列的最小值,minVals是一个列表
    minVals = dataSet.min(0)
    # 返回数据集中每一列的最大值,maxVals是一个列表
    maxVals = dataSet.max(0)
    # 求每一列特征的变化范围,为归一化做准备
    ranges = maxVals - minVals
    # 创建与数据集相同size的零矩阵
    normDataSet = np.zeros(np.shape(dataSet))
    # 计算数据集的行数
    m = dataSet.shape[0]
    ## 线性函数归一化,等比例缩放原始数据
    # 将minVals扩展到与数据集相同size,并与数据集做减法
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))  
    return normDataSet, ranges, minVals

## 构建分类器
def classify0(inX,dataSet,labels,k):
    '''
    Parameters
    ----------
    inX : 输入向量
    dataSet : 训练样本集
    labels : 标签向量
    k : 选择最近邻居的数目
    -------
    '''
    # 求数据集中一共多少组数据
    dataSet_size = dataSet.shape[0]
    ## tile函数表示将inX在行方向上重复dataSet_size次,在列方向上重复1次
    # 求输入向量与数据集中点横纵坐标之差
    diffMat = np.tile(inX,(dataSet_size,1))-dataSet 
    # 求输入向量与数据集中点横纵坐标之差的平方
    sqDiffMat = diffMat ** 2
    # 求横坐标之差平方与纵坐标之差平方和
    sqDistances = sqDiffMat.sum(axis=1)
    # 求距离
    distances = sqDistances**0.5
    ## argsort函数返回一个列表,为原列表从小到大排序后各元素的索引
    ## 比方说第一个元素为原列表中第二小的元素,argsort函数在新列表中1位置写1
    ## 第三小,就在该位置写2,以此类推
    # 求distances数组中元素从小到大排序后各元素的索引
    sortedDistIndices = distances.argsort()
    classCount = {}
    for i in range(k):
        # 依次寻找最邻近点的标签
        voteIlabel = labels[sortedDistIndices[i]]
        # 在字典中寻找有无标签,没有就添加,有就+1
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 将字典拆解为列表
    # 遍历classCount字典的键值对,并按value进行排序,升序排列
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

## 测试分类器
def datingClassTest():
    # 定义测试集所占数据集的比例
    hoRatio = 0.30
    # 将文本记录转换为Numpy    
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') 
    # 归一化特征值      
    normMat, ranges, minVals = autoNorm(datingDataMat)
    print(normMat.shape)
    # 计算数据集行数
    m = normMat.shape[0]
    # 获取测试集行数
    numTestVecs = int(m*hoRatio)
    print(numTestVecs)
    # 定义初始的误差个数为0
    errorCount = 0.0
    for i in range(numTestVecs):
        # normMat[i,:]表示遍历测试集中每一行数据
        # normMat[numTestVecs:m,:]表示训练集中的数据
        # datingLabels[numTestVecs:m]表示训练集中的标签
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        # 如果分类结果与预计的标签不相同,则初始误差+1
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
    print(errorCount)


if __name__ == '__main__':
    datingClassTest()


示例:手写识别系统

import operator
import numpy as np
from os import listdir

## 准备数据:将图像转换为测试向量
def img2vector(filename):
    # 32*32像素的图片,创建1*1024的零矩阵
    returnVect = np.zeros((1, 1024))
    # 打开文件
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            # 将32*32像素图片展开成1*1024
            returnVect[0, 32*i+j] = int(lineStr[j])
    return returnVect

## 构建分类器
def classify0(inX,dataSet,labels,k):
    '''
    Parameters
    ----------
    inX : 输入向量
    dataSet : 训练样本集
    labels : 标签向量
    k : 选择最近邻居的数目
    -------
    '''
    # 求数据集中一共多少组数据
    dataSet_size = dataSet.shape[0]
    ## tile函数表示将inX在行方向上重复dataSet_size次,在列方向上重复1次
    # 求输入向量与数据集中点横纵坐标之差
    diffMat = np.tile(inX,(dataSet_size,1))-dataSet 
    # 求输入向量与数据集中点横纵坐标之差的平方
    sqDiffMat = diffMat ** 2
    # 求横坐标之差平方与纵坐标之差平方和
    sqDistances = sqDiffMat.sum(axis=1)
    # 求距离
    distances = sqDistances**0.5
    ## argsort函数返回一个列表,为原列表从小到大排序后各元素的索引
    ## 比方说第一个元素为原列表中第二小的元素,argsort函数在新列表中1位置写1
    ## 第三小,就在该位置写2,以此类推
    # 求distances数组中元素从小到大排序后各元素的索引
    sortedDistIndices = distances.argsort()
    classCount = {}
    for i in range(k):
        # 依次寻找最邻近点的标签
        voteIlabel = labels[sortedDistIndices[i]]
        # 在字典中寻找有无标签,没有就添加,有就+1
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # 将字典拆解为列表
    # 遍历classCount字典的键值对,并按value进行排序,升序排列
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

## 手写数字识别系统测试
def handwritingClassTest():
    hwLabels = []
    # 获取训练集中的文件
    trainingFileList = listdir('trainingDigits')
    # 计算测试集中文件的数量
    m = len(trainingFileList)
    # 构建m*1024的零矩阵
    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('trainingDigits/%s' % fileNameStr)
    
    # 获取测试集中的文件
    testFileList = listdir('testDigits')
    # 定义初试误差为0.0        
    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('testDigits/%s' % fileNameStr)
        # 对其进行KNN分类
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        # 只要有一个分类错误就在初始误差上+1
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\\nthe total number of errors is: %d" % errorCount)
    print("\\nthe total error rate is: %f" % (errorCount/float(mTest)))

handwritingClassTest()

关于两个示例的数据集以及整个工程文件,我放在百度网盘中
链接:https://pan.baidu.com/s/1Ndfw89xPGgzwGLV08AA8OQ
提取码:ua7c
说明:以上代码主体均出自于《机器学习实战》一书,博主根据自己的理解添加了大部分注释,浅显易懂,适合小白入门。
欢迎大家一起评论交流~

以上是关于机器学习实战—— K-邻近算法(KNN)的主要内容,如果未能解决你的问题,请参考以下文章

机器学习算法手把手实战:KNN预测城市空气质量

机器学习——KNN K-邻近算法

机器学习(06)——K近附算法实战

机器学习01-kNN邻近算法

数学建模MATLAB应用实战系列(109)-KNN分类(附MATLAB代码)

机器学习01-kNN邻近算法