《机器学习实战》笔记——第2章 K-近邻算法


数据下载:https://pan.baidu.com/s/1D4vkduD8PVGyPDmx5FRCnw


目录

 

一、算法概述

1.1 工作原理

1.2 一般流程

1.3 优点

1.4 缺点

二、示例1-构建第一个分类器

2.1 创建数据集和标签

2.2 构建第一个分类器

三、示例2-应用KNN算法改进约会网站的配对效果

3.1 准备数据:从文本文件中解析数据

3.2 分析数据:使用 Matplotlib 创建散点图

3.3 【改进】准备数据:归一化数值

3.4 测试算法:作为完整程序验证分类器

3.5 使用算法:构建完整可用的系统

 

四、示例3-手写识别系统

五、完整代码


一、算法概述

1.1 工作原理

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

1.2 一般流程

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

1.3 优点

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

1.4 缺点

  • 计算复杂度高
  • 空间复杂度高
  • 无法给出任何数据的基础结构信息,因此我们无法知晓平均实例样本和典型实例样本具有什么特征

二、示例1-构建第一个分类器

2.1 创建数据集和标签

  • 代码
import numpy as np
import operator

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
  • 执行结果

2.2 构建第一个分类器

  • 代码
# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# labels:标签向量
# k:表示用于选择最近邻居的数目
# 注意标签向量的元素数目和矩阵 dataSet 的行数相同
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]

    # 使用欧氏距离公式计算两点间距离
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort()

    # 选择距离最小的 k 个点
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1

    # 排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]
  • 执行结果


此处应注意:

  1. 为防止两个模块互相导入的问题,Python默认所有的模块都只导入一次。如果修改模块代码后,重新导入模块无效。
  2. Python3中已经无法直接使用reload(),可使用的方法可参考:https://blog.csdn.net/xiakexiaohu/article/details/74999108

三、示例2-应用KNN算法改进约会网站的配对效果

3.1 准备数据:从文本文件中解析数据

  • 代码
# 文件记录转换为矩阵
def file2matrix(filename):
    fr = open(filename)

    # 得到文件行数
    arrayOfLines = fr.readlines()
    numOfLines = len(arrayOfLines)

    # 创建返回值的Numpy矩阵
    returnMat = np.zeros((numOfLines, 3))

    # 解析文件数据到列表
    classLabelVector = []
    index = 0
    for line in arrayOfLines:
        # 使用line.strip()截取掉所有的回车字符
        line = line.strip()
        # 使用tab字符将上一步得到的整行数据分割成一个元素列表
        listFromLine = line.split('\t')
        # 选取前3个元素,存储到特征矩阵
        returnMat[index, :] = listFromLine[0: 3]
        # Python 可以使用索引值-1表示列表中的最后一列元素
        # 利用这种负索引,极其方便地将列表的最后一列存储到向量classLabelVector中
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector
  • 执行结果


此处应注意:

原书中为“datingDataMat, datingLabels = KNN.file2matrix('datingTestSet.txt')”

但实应为“datingDataMat, datingLabels = KNN.file2matrix('datingTestSet2.txt')”

此错误是因为《datingTestSet.txt》文件中数据格式为字符串类型,《datingTestSet2.txt》文件中数据格式为 int 类型。


3.2 分析数据:使用 Matplotlib 创建散点图

参考文章:https://blog.csdn.net/jichun4686/article/details/75700448

鉴于上述文章在该模块的阐述极为清晰明了,此处不再赘述。

3.3 【改进】准备数据:归一化数值

在处理不同取值范围的特征值时,通常采用的方法是将数值归一化,如将取值范围处理为 0~1 或 -1~1 之间。

下面公式可以将任意取值范围的特征值转化为 0~1 区间内的值:

newValue = (oldValue - min) / (max - min)

  • 代码
# 约会系统-归一化特征值
def autoNorm(dataSet):
    # min(0)中的参数0可以从列中选取最小值,而不是从行中选取最小值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    # 特征值矩阵中1000 * 3个值,minVals和ranges的值都为1 * 3
    # 故使用numpy库中tile()函数将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    return normDataSet, ranges, minVals
  • 执行结果

上图调用 autoNorm 函数对 datingDataMat 进行归一化处理,下图查看 datingDataMat 。 

3.4 测试算法:作为完整程序验证分类器

  • 代码
def datingClassTest():
    hoRatio = 0.10
    # 使用file2matrix()和autoNorm()函数从文件中读取数据并将其转换为归一化特征值
    datingDataMat, datingLabels = file2matrix('testSet.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 计算测试向量的数量
    # 此步决定了normMat向量中哪些数据用于测试、哪些数据用于分类器的训练样本
    m = normMat.shape[0]
    numTestVecs = int(m * hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        # 将这两部分数据输入到原始KNN分类器函数classify0()
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
        # 计算错误率
        if (classifierResult != datingLabels[i]):
            errorCount += 1.0
    # 输出结果
    print("the total error rate is :%f" % (errorCount / float(numTestVecs)))
  • 执行结果

3.5 使用算法:构建完整可用的系统

  • 代码
def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    # python3 中使用 input 替代了 row_input
    percentTats = float(input("percentage of time spent playing video 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[classifierResult - 1])
  • 执行结果

 

四、示例3-手写识别系统

  • 代码
# 手写识别系统-准备数据(图像格式转换为分类器使用的List格式)
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

# 手写识别系统-测试代码
def handWritingClassTest():
    hwLabels = []

    # 获取目录内容
    trainingFileList = listdir('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("trainingDigits/%s" % fileNameStr)

    testFileList = listdir('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('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        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)))

  • 执行结果


注意:此处若涉及路径输入,应注意转义字符。

其次在上述动图中,我们可以很直观地发现,加载数据集需要花费较长的时间、然后函数开始依次测试每个文件,此过程耗费的时间依赖于机器速度。

同时正如书中所说:

实际使用这个算法时,算法的执行效率并不高。因为算法需要为每个测试向量做2000次距离计算,每个距离计算包括了 1024个维度浮点运算,总计要执行900次。此外,我们还需要为测试向量准备2MB的存储空间。


五、完整代码

from os import listdir
import numpy as np
import operator

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


# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# labels:标签向量
# k:表示用于选择最近邻居的数目
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]

    # 使用欧氏距离公式计算两点间距离
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort()

    # 选择距离最小的k个点
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1

    # 排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

# 文件记录转换为矩阵
def file2matrix(filename):
    fr = open(filename)

    # 得到文件行数
    arrayOfLines = fr.readlines()
    numOfLines = len(arrayOfLines)

    # 创建返回值的Numpy矩阵
    returnMat = np.zeros((numOfLines, 3))

    # 解析文件数据到列表
    classLabelVector = []
    index = 0
    for line in arrayOfLines:
        # 使用line.strip()截取掉所有的回车字符
        line = line.strip()
        # 使用tab字符将上一步得到的整行数据分割成一个元素列表
        listFromLine = line.split('\t')
        # 选取前3个元素,存储到特征矩阵
        returnMat[index, :] = listFromLine[0: 3]
        # Python 可以使用索引值-1表示列表中的最后一列元素
        # 利用这种负索引,极其方便地将列表的最后一列存储到向量classLabelVector中
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLabelVector

# 约会系统-归一化特征值
def autoNorm(dataSet):
    # min(0)中的参数0可以从列中选取最小值,而不是从行中选取最小值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    # 特征值矩阵中1000 * 3个值,minVals和ranges的值都为1 * 3
    # 故使用numpy库中tile()函数将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

# 约会系统-计算错误率
def datingClassTest():
    hoRatio = 0.10
    # 使用file2matrix()和autoNorm()函数从文件中读取数据并将其转换为归一化特征值
    datingDataMat, datingLabels = file2matrix('testSet.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 计算测试向量的数量
    # 此步决定了normMat向量中哪些数据用于测试、哪些数据用于分类器的训练样本
    m = normMat.shape[0]
    numTestVecs = int(m * hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        # 将这两部分数据输入到原始KNN分类器函数classify0()
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
        # 计算错误率
        if (classifierResult != datingLabels[i]):
            errorCount += 1.0
    # 输出结果
    print("the total error rate is :%f" % (errorCount / float(numTestVecs)))


# 约会系统-交互
def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    # python3 中使用 input 替代了 row_input
    percentTats = float(input("percentage of time spent playing video 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[classifierResult - 1])


# 手写识别系统-准备数据(图像格式转换为分类器使用的List格式)
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

# 手写识别系统-测试代码
def handWritingClassTest():
    hwLabels = []

    # 获取目录内容
    trainingFileList = listdir('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("trainingDigits/%s" % fileNameStr)

    testFileList = listdir('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('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        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)))





猜你喜欢

转载自blog.csdn.net/clevercaiquebrightme/article/details/81664671