机器学习实战:K近邻算法--学习笔记

一、KNN的工作原理

  1. 假设有一个带有标签的样本数据集(训练样本集),其中包含每条数据与所属分类的对应关系。
  2. 输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
    1) 计算新数据与样本数据集中每条数据的距离。
    2) 对求得的所有距离进行排序(从小到大,越小表示越相似)。
    3) 取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
  3. 求 k 个数据中出现次数最多的分类标签作为新数据的分类。

二、实现代码笔记

1.下面是实现对一个点的KNN分类(python 3)
# coding=utf-8
import operator
from numpy import *


def createDataSet():
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels


def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    # 矩阵有一个shape属性,是一个(行,列)形式的元组
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  
    # 输入的点到每个点的横纵坐标差
    # tile是把矩阵重复多次
    sqDiffMat = diffMat ** 2 
    # 横纵坐标差的平方
    sqDistances = sqDiffMat.sum(axis=1)  
    # axis=0, 表示列。axis=1, 表示行。
    distances = sqDistances ** 0.5  
    # 开方
    sortedDistIndicies = distances.argsort()  
    # argsort函数返回的是数组值从小到大的索引值
    classCount = {}  
    # 保存A,B出现次数的字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]  
        # 获取索引值对应的是A还是B
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  
        # 在字典中保存A,B出现的次数
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 按照A,B出现的次数排序
    return sortedClassCount[0][0]  # 返回A,B出现最多的那个


group, labels = createDataSet()
answer = classify0([0, 1.2], group, labels, 3)
print(answer)
  • Numpy 中的tile函数
    • 其功能是重复某个数组。比如tile(A,n),功能是将数组A重复n次,构成一个新的数组
    • 具体可参考:python tile函数用法
  • Numpy中的sum函数
    • 方法针对元组,列表进行求和计算。无参时,所有全加;axis=0,按列相加;axis=1,按行相加
    • 具体可参考:python中的sum函数
  • Numpy中的argsort( )函数
  • get函数的作用
    • 以classCount.get(voteIlabel,0)为例:
    • classCount.get(voteIlabel,0)返回字典classCount中voteIlabel元素对应的值,若无,则进行初始化该元素对应的值为0
    • 具体可参考:【Python】get()函数作用
  • Numpy中的sorted
    • 用于对元祖,依据特定的键值进行排序,参数中reverse = True 降序 , reverse = False 升序
    • 具体可参考:python sorted
2.用KNN算法改进约会网站匹配效果(python3)
# coding=utf-8
import operator
from numpy import *
import matplotlib
import matplotlib.pyplot as plt


def classify0(inX, dataSet, labels, k):
    """
    :param inX: 样本点
    :param dataSet: 初始样本集合
    :param labels: 样本集合对应的标签集合
    :param k: 选取的k
    :return: kNN分类结果
    """
    dataSetSize = dataSet.shape[0]
    # 矩阵有一个shape属性,是一个(行,列)形式的元组
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 输入的点到每个点的横纵坐标差
    # tile是把矩阵重复多次
    sqDiffMat = diffMat ** 2  # 横纵坐标差的平方
    sqDistances = sqDiffMat.sum(axis=1)  # axis=0, 表示列。axis=1, 表示行。
    distances = sqDistances ** 0.5  # 开方
    sortedDistIndicies = distances.argsort()  # argsort函数返回的是数组值从小到大的索引值
    classCount = {}  # 保存A,B出现次数的字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]  # 获取索引值对应的是A还是B
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # 在字典中保存A,B出现的次数
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 按照A,B出现的次数排序
    # sorted函数sorted(iterable, cmp=None, key=None, reverse=False)
    '''
    iterable:是可迭代类型;
    cmp:用于比较的函数,比较什么由key决定;
    key:用列表元素的某个属性或函数进行作为关键字,有默认值,迭代集合中的一项;
        operator.itemgetter(1)表示用第2个数据项排序
    reverse:排序规则. reverse = True  降序 或者 reverse = False 升序,有默认值。
    '''
    return sortedClassCount[0][0]  # 返回A,B出现最多的那个



def file2matrix(filename):
    """
    :param filename: 文件名称
    :return: 文件中的数据和标签
    """
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    # 获取文件的行数
    returnMat = zeros((numberOfLines, 3))
    # 创建返回的NumPy矩阵,二维矩阵
    # zeros函数功能是创建给定类型的矩阵,并初始化为0
    classLabelVector = []
    # 创建返回的标签
    index = 0  # index
    for line in arrayOLines:
        # 循环每列
        line = line.strip()
        # 去除每行回车字符
        listFromLine = line.split('\t')
        # 分割
        returnMat[index, :] = listFromLine[0:3]
        # 把数据的前三列都放到要返回的矩阵中,3这个索引是不包括的
        classLabelVector.append(int(listFromLine[-1]))
        # 把数据的每列最后一个元素转换成整数放到标签list里
        index += 1
    # index自增
    return returnMat, classLabelVector


def autoNorm(dataSet):
    """
    :param dataSet: 数据集
    :return: 归一化结果
    """
    minVals = dataSet.min(0)  # 0代表列
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))  # 创建了行列数与dataSet一致的全0矩阵
    m = dataSet.shape[0]  # 行数
    normDataSet = dataSet - tile(minVals, (m, 1))  # 每个元素都减去该列最小值
    normDataSet = normDataSet / tile(ranges, (m, 1))  # 具体数值的除,归一化;不是矩阵相除
    return normDataSet, ranges, minVals


def datingClassTest():
    """
    测试算法的函数
    :return:
    """
    hoRatio = 0.10  # hold out 10%
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')  # load data setfrom file
    normMat, ranges, minVals = autoNorm(datingDataMat)  # 归一化
    m = normMat.shape[0]  # 行数
    numTestVecs = int(m * hoRatio)  # 抽出的行数
    print("numTestVecs=", numTestVecs)
    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: %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)))
    print(errorCount)


def classifyPerson():
    """
    用户输入点作为测试点
    :return: 无
    """
    resultList = ['not at all', 'in small doses', 'in large doses']
    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')  # load data setfrom file
    normMat, ranges, minVals = autoNorm(datingDataMat)  # 归一化
    inArr = array([ffMiles, percentTats, iceCream])  # 把用户输出的点当做要求点
    classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)  # 用kNN做分类
    print("you will probably like this person:", resultList[classifierResult - 1])  # 转换成真名


if __name__ == '__main__':
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    fig = plt.figure()
    ax = fig.add_subplot(111) 
    ax.scatter(datingDataMat[:, 0], datingDataMat[:, 1], 15.0* array(datingLabels), 15.0 * array(datingLabels))#后面第一个参数表示大小,第二个参数表示颜色
    #plt.show()
    datingClassTest()
    classifyPerson()
3.用KNN实现手写数字识别(python 3)
import os
import operator
from numpy import *

def classify0(inX, dataSet, labels, k):
    """
    :param inX: 样本点
    :param dataSet: 初始样本集合
    :param labels: 样本集合对应的标签集合
    :param k: 选取的k
    :return: kNN分类结果
    """
    dataSetSize = dataSet.shape[0]
    # 矩阵有一个shape属性,是一个(行,列)形式的元组
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 输入的点到每个点的横纵坐标差
    # tile是把矩阵重复多次
    sqDiffMat = diffMat ** 2  # 横纵坐标差的平方
    sqDistances = sqDiffMat.sum(axis=1)  # axis=0, 表示列。axis=1, 表示行。
    distances = sqDistances ** 0.5  # 开方
    sortedDistIndicies = distances.argsort()  # argsort函数返回的是数组值从小到大的索引值
    classCount = {}  # 保存A,B出现次数的字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]  # 获取索引值对应的是A还是B
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # 在字典中保存A,B出现的次数
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 按照A,B出现的次数排序
    # sorted函数sorted(iterable, cmp=None, key=None, reverse=False)
    '''
    iterable:是可迭代类型;
    cmp:用于比较的函数,比较什么由key决定;
    key:用列表元素的某个属性或函数进行作为关键字,有默认值,迭代集合中的一项;
        operator.itemgetter(1)表示用第2个数据项排序
    reverse:排序规则. reverse = True  降序 或者 reverse = False 升序,有默认值。
    '''
    return sortedClassCount[0][0]  # 返回A,B出现最多的那个


def img2vector(filename):
    returnVect = zeros((1, 1024))  # 每个文件一行结果,1024个0
    fr = open(filename)  # 打开文件
    for i in range(32):  # 遍历32行
        lineStr = fr.readline()  # 读行
        for j in range(32):  # 读每个字符
            returnVect[0, 32 * i + j] = int(lineStr[j])  # 把字符放到结果中
    return returnVect


def handwritingClassTest():
    hwLabels = []  # 保存标签
    trainingFileList = os.listdir('trainingDigits')  # 加载训练集
    m = len(trainingFileList)  # 训练集文件个数
    trainingMat = zeros((m, 1024))  # 训练集数据矩阵raw_input is not defined
    for i in range(m):  # 遍历文件
        fileNameStr = trainingFileList[i]  # 训练集文件名
        fileStr = fileNameStr.split('.')[0]  # 去掉文件名结尾的.txt
        classNumStr = int(fileStr.split('_')[0])  # 把文件名分割之后,获得前半部分,即这个文件表示的字符标签
        hwLabels.append(classNumStr)  # 把文件表示的字符标签放到标签list中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
    # 把每个文件中的字符画转成行向量
    testFileList = os.listdir('testDigits')  # 得到测试集所有文件目录
    errorCount = 0.0  # 错误率
    mTest = len(testFileList)  # 测试集长度
    for i in range(mTest):  # 遍历测试集
        fileNameStr = testFileList[i]  # 测试集文件名
        fileStr = fileNameStr.split('.')[0]  # 去掉文件名结尾的.txt
        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)))

if __name__ == '__main__':
    handwritingClassTest()

程序运行的结果为:

the total number of errors is: 10
the total error rate is: 0.010571
  • 其中运行代码发现,运行时间较久:
    • 因为训练集中有10个数字,每个数字有200个,测试集中的每个数字均和训练集中的每个数字做距离运算,则进行了2000次距离运算;每次运算是两个1*1024的向量之间的欧式距离计算;
    • 另外有900个测试数据,则同样的操作需要进行900次,耗费了较多的时间,效率低下

三、参考资料

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值