机器学习基础-k近邻

  • 本章内容主要基于机器学习实战

概述

k—近邻算法采用测量不同特征值之间的距离方法进行分类。
优点精度高,对异常值不敏感,无数据输入假定
缺点计算复杂度高、空间复杂度高
适用数据范围数值型和标称型

简单来说,就是根据给定的事实,判断样本集中各样本与给定事实的差距,来对样本进行分类。

k-近邻算法分类器

分类器可以满足的功能是:输入三类信息的数值,计算出该值与已知数据之间的距离,对该距离进行排序,取排序前k个数据,统计前k个数据中各种标签所占数量(本任务中,标签为海伦的喜欢程度),数量最多的,为分类器给出的结果,即给定数据的判定结果。

# 分类器
def classify0(inx, dataSet, labels, k):
    #
 inx:给定点的坐标。dataSet:点集。labels:点的标签集合。k: 选择近邻点的个数
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inx, (dataSetSize, 1)) - dataSet
    # tile()函数:tile(inx,(x,y));将inx重复x行,y列,返回数组array()类型
    # 此处将给定点与点集横纵坐标相减,得到△x,△y
    sqDiffMat = diffMat ** 2  # 将diffMat各项平方,得到△x^2,△y^2
    sqDistances = sqDiffMat.sum(axis=1)
    # 数组,array()类型sum()函数表示各项相加,axis = 0,表示按列相加,axis = 1,表示按行相加
    # 此处得到△x^2+△y^2
    distances = sqDistances ** 0.5  # 求得点集各点到给定点的距离
    sortedDistIndicies = distances.argsort()
    # shuzu.argsort()numpy库中的函数,用于返回一个数组,数组中存放的是shuzu中元素从小到大排序
    # 的索引值
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
        # 字典对象的get()函数,get(x,y),返回指定键x的值,如果字典中不存在该指定键,返回y
        # 此句统计标签的数目
    sortedClassCount = sorted(classCount.items(),
                              key=operator.itemgetter(1), reverse=True)
    # dic.items()返回字典的键,键值元组数组。
    # key=operator.itemgetter(1)表示比较的元素为每一个元组的第二项,即数量。
    # reverse=True为降序排列,reverse=False为升序排列
    return sortedClassCount[0][0]
    # 返回第一个元组的第一个元素

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

从文本文件中解析数据

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    ### 读取文件中的每一行全部元素存入列表中,每一行组成列表的一个元素
    numberOfLines = len(arrayOLines)
    ### 获取列表长度,即文件的行数
    returnMat = zeros((numberOfLines, 3))
    ### 新建一个全0的数组。zeros()新建的是一个数组,array()类型
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        ### strip()删除字符串头尾指定的字符(默认为空格或换行符),不能删除中间部分的字符
        listFromLine = line.split('\t')  
        ### 对每一行的元素进行分割,得到相应列表。
        returnMat[index, :] = listFromLine[0:3]
        labels = {'largeDoses': 3, 'smallDoses': 2, 'didntLike': 1}
        ### 相较书中新增的字典
        classLabelVector.append(labels[listFromLine[-1]])
        index += 1
        ### 将listFromLine列表中数值按行,将前三项赋值给returnMat列表
    return returnMat, classLabelVector

使用matplotlib创建散点图

import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure()
### 创建图像,采用默认配置
ax = fig.add_subplot(111)
### 规定输出图像的格式,111表示创建1×1的网格,图像位于第一子图
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
### 绘制散点图,用datingDataMat第一列的值定位x轴坐标,第二列的值定为y轴坐标
plt.show()

散点图颜色处理

###其他语句可不变,改变此句
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15*array(datingLabels))
### 新增两项
### 乘15是为了将差异放大
### 前一项为修改点的大小,此处可将datingLabels修改为数组,列表也可以操作,也不要求元素数量与点的数量相同,列表内元素可以循环使用。
### 后一项为修改点的颜色,此处必须将datingLabels修改为数组,或者组元也可,但元素数量必须与绘制的点的数量相同

在这里插入图片描述

对文件数据进行归一化

在对数据进行分类过程中,因为有些数据值很大,在求距离时,所占比重较大,但对用户来说,这些数值的重要程度相同,此时就需要对数据进行归一化操作,采用百分比的形式:
N U M n e w = ( N U M o l d − N U M m i n ) ( N U M m a x − N U M m i n ) NUM_{new} = {(NUM_{old}-NUM_{min})\over(NUM_{max}-NUM_{min})} NUMnew=(NUMmaxNUMmin)(NUMoldNUMmin)

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    ### min(0)求每一列的最小值,min(1)返回每一行的最小值
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))  
    ### 新建一个同dataSet规模相同的零数组
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m, 1))
    normDataSet = normDataSet / tile(ranges, (m, 1))
    ### 相应元素做除法
    ### 矩阵除法用函数linalg.solve(matA,matB)
    return normDataSet, ranges, minVals

测试代码

我们已经拥有了1000组数据,其中记录了海伦希望知道的三类信息对喜欢程度的影响。为了验证分类器的性能,需要对分类器进行测试。
选取10%的数据用作测试,测试结果与测试数据不符的即为分类器失败的数据,由此可以判断分类器的准确度。

# 分类器针对约会网站的测试代码
def datingClassTest():
    hoRatio = 0.10  # 选择10%的数据进行测试
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    # datingLabels 记录的是不喜欢的人,一般喜欢的人,特别喜欢的人,三类
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)  # numTestVecs为用做数据测试的数据的数量
    errorCount = 0.0  # 记录测试失败的数量
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],
                                     datingLabels[numTestVecs:m], 3)
        # 以数据集中第i行的数据为标准,对训练集后90%条进行分类
        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']
    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('datingTestSet.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierReault = classify0((inArr-minVals)/ranges, normMat, datingLabels, 3)
    print('You will probably like this person: ',
          resultList[classifierReault - 1])

示例:手写识别系统

此处给出的示例是手写识别系统的简化版本,不多做记录。
提供了的文件,是需要识别的数字,已经使用图形处理软件,处理成32×32的黑白图像,全部由0和1构成,如图:在这里插入图片描述

准备数据:将图像转换为测试向量

所谓将图像转换成向量,就是想32×32的0,1数字全部存入一行数组中,构成[x_1,x_2,···,x_1024]的向量。

# 准备数据,将图像转化为测试向量
# 此处将txt文件中的32×32的数字转换成1024的向量,即将所有行放至一行里
def img2vector(filename):
    returnVect = 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

测试算法

在程序中导入listdir(在代码前,写入from os import listdir)
listdir(path):获取该目录下的所有文件,返回列表

def handwritingClassTest():
    hwLabels = []  # 存储训练集向量对应的标签的列表
    trainingFileList = listdir('trainingDigits')
    # 获取该文件夹中所有文件,返回列表
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))  # 用于存储训练集每组数据转化成的向量的数组
    for i in range(m):  # 得到所有训练集的向量的数组,及该向量对应标签的列表
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        # 获取的文件名为:文件名.txt将文件名与格式分开,此句获取文件名
        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)
        # 得到向量
        classfierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print('the classifier came back with: %d, the real answer is: %d'
              % (classfierResult, classNumStr))
        if classfierResult != classNumStr:
            errorCount += 1.0
    print('\n the total number of errors is: %d' % errorCount)
    print('\n the total error rate is: %f' % (errorCount/float(mTest)))

总结

k-近邻算法是分类数据最简单有效的算法。
缺陷:
1、但是对于给定一条需要分类的信息,想要有效对其进行分类,就必须需要大量的训练集,且需要保存。如果训练集很大,就必须使用大量的存储空间。而且必须对训练集中的每个数据计算距离,这在使用时可能很耗时。
2、另一个缺陷,没看懂啥意思,先不写了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>