kNN

这是《机器学习实战》第二单元k近邻算法的学习笔记,用的是python3实现代码,

对于k近邻这个算法,英文名是k-Nearest Neighbors,即k个最近的邻居,这里的近指的是距离近,所以我们要先计算距离,假如是计算(x_{1}, y_{1})到(x_{2}, y_{2})的距离,我们用欧氏距离公式\sqrt{(x_{1}-x_{2})^2+(y_{1}-y_{2})^2}即可求得距离。求完距离之后对距离进行排序,离自己最近的k个坐标,分别对他们的标记(label)进行统计投票,得到票数最多那个即为输入向量的分类(class)。

下面是代码实现,

在此之前我们先准备数据,

我们准备四组打好标的二维向量,

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

准备好数据之后,开始写分类器,分类器大致可以分为三部分内容,

  1. 计算距离,所有的数据距离自己(输入向量inX)的距离
  2. 对距离进行排序,选择距离最近的k个点,并统计他们的label
  3. 得到票数最多的那个label
def classify0(inX, dataSet, labels, k):
    '''
    params:
        inX: 输入需要分类的数据
        dataSet: 已经打好标的数据
        labels: 标记
        k: 需要选取的最近邻居的数量
    '''
    dataSetSize = dataSet.shape[0] # data.shape可返回行和列,[0]即为行
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet # 差矩阵,tile()可以理解为复制,里边的参数分别为向下向下复制,这里将复制完的矩阵和原矩阵相减
    sqDiffMat = diffMat ** 2 # 将差矩阵的每个值平方(不是矩阵乘矩阵)
    sqDistances = sqDiffMat.sum(axis=1) # 将平方之后的值按行相加,.sum(axis=0)为按列相加
    distances = sqDistances ** 0.5 # 将和再开方得到距离
    sortedDistIndicies = distances.argsort() # 对距离进行排序,但得到的是排好的数据在原列表中的索引
    classCount = {} # 分类统计
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] # 由于是原列表中的索引,所以标记还是原来的顺序,没有被打乱,这样就很容易的得到label
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 默认为0,如果get不到就是0,否则在其基础上加一
    sortedClassCount = sorted(classCount.items(), # 对classCount进行排序
                              key=operator.itemgetter(1),
                              reverse=True)
    return sortedClassCount[0][0] # 得到票数最多的那个

做好之后进行简单的测试,我们需要测试的是[0,0]这个点的分类是什么,

分类结果为B,和预想结果没有差别,

接下来书中介绍到对约会数据进行测试,先是从文件中将数据解析出来,文件中数据格式是这样的,

前三列分别代表飞行里程数,玩游戏所占百分比,和每周消耗的冰淇淋数,最后一列为标记,1-3分别代表不喜欢的人,魅力一般的人和极具魅力的人,最后我们输入一组数据,来预测这个人是什么样的人,我们先从文件解析数据,

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines() # 得到所有的行
    numberOfLines = len(arrayOLines) # 得到行数
    returnMat = zeros((numberOfLines, 3)) # 初始化行数为文件行数,列数为3的0矩阵
    classLabelVector = [] # 标记向量
    index = 0
    for line in arrayOLines:
        line = line.strip() # 去除前后空格
        listFromLine = line.split('\t') # 以制表符为间隔分割
        returnMat[index,:] = listFromLine[0:3] # 将前三个数据给returnMat返回向量
        classLabelVector.append(listFromLine[-1]) # 将每行最后一个元素压入标记向量
        index += 1
    return returnMat, classLabelVector

得到数据之后,由于数据还没有达到最终能用的程度,比如飞行公里数,打游戏的时间,这些是具体的时间,并不是我们想要的,我们想要的是归一化后的数值,最小的为0,最大的为1,newValue = (oldValue - minVal) / (maxValue - minValue),下面的函数为归一化的过程,

def autoNorm(dataSet):
    '''
    dataSet: 需要归一化的数据
    '''
    minVals = dataSet.min(0) # min(0)得到每列的最小值,min(1)得到每行的最小值
    maxVals = dataSet.max(0) # max(0)得到每行的最大值,max(1)得到每行的最小值
    ranges = maxVals - minVals # 差
    m = dataSet.shape[0] # 行数
    normDataSet = dataSet - tile(minVals, (m, 1)) # (oldValue - minVal)
    normDataSet = normDataSet / tile(ranges, (m, 1)) # (oldValue - minValue) / (maxValue - minValue)
    return normDataSet, ranges, minVals

分类器还是用之前的分类器,原理也一样,都是算距离,现在从数据集中拿出10%的数据来测试,计算错误率,

def datingClassTest():
    hoRation = 0.10 # 拿出10%的数据做测试
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # 从文件里加载数据
    normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化数据
    m = normMat.shape[0] # 得到行数
    numTestVecs = int(m * hoRation) # 需要测试的数量
    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" % (int(classifierResult), int(datingLabels[i])))
        # 不同的记录到errorCount
        if(classifierResult != datingLabels[i]):
            errorCount += 1.0
    # 得出错误率
    print("The total error rate is: %f" % (errorCount/float(numTestVecs)))

可得结果为,

现在我们随机输入一组数据来看这个人是不是有魅力的人,

需要注意的是python2里的raw_input()在python3里已改成input()作为输入,输入的数据不是归一化的数据,所以需要处理成分类器需要的数据, (oldValue - minValue) / (maxValue-minValue),

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 cunsumed per year?")) # 消耗的冰淇淋数量
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # 从文件里加载数据
    normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化数据
    inArr = array([ffMiles, percentTats, iceCream]) # 输入向量
    classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3) # 分类,但在分类之前要处理一下输入矩阵
    print("You will probably like this person: ", resultList[int(classifierResult) - 1]) # 得到结果

这就是预测约会概率的例子,下面是测试手写的字的例子,

手写的字肯定有一些偏差,怎么通过k近邻来测试这是不是某个数字呢,将字迹转换为0和1组成的矩阵来表示字迹,每个文件都是由32*32的矩阵组成,其具体格式如下,

我们先准备数据,将数据从文件中解析出来,解析成1行1024列大小的数据,这样每个文件占一行,总共就有文件个数行,

def img2vector(filename):
    returnVect = zeros((1, 1024)) # 初始化1行,1024列的图像向量
    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

这只是一个文件的解析,

下面将trainingDigits文件夹下所有的文件都解析出来,并对testDigits文件夹里的数字进行测试,得到错误率,

def handwritingClassTest():
    hwLabels = [] # 标记向量
    trainingFileList = listdir('trainingDigits') # 训练数字
    m = len(trainingFileList) # 训练文件的长度
    trainingMat = zeros((m, 1024)) # 初始化m * 1024的矩阵
    for i in range(m):
        fileNameStr = trainingFileList[i] # 0_0.txt
        fileStr = fileNameStr.split('.')[0] # 0_0
        classNumStr = int(fileStr.split('_')[0]) # 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" % (int(classifierResult), classNumStr)) # 打印预测结果和原有结果
        if(int(classifierResult) != classNumStr):
            errorCount += 1.0
    print("The total error count is: %d" % errorCount) # 总的错误数
    print("The total error rate is: %f" % (errorCount/float(mTest))) # 总的错误率

最重要在于分类,然后就是求距离找距离最近的k个距离,然后进行投票,最多的就预测为那个值了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值