机器学习实战笔记(一)K近邻算法 曼哈顿距离,欧式距离,明式距离,切比雪夫距离区别

趁着暑假的档期,总结一下机器学习的一些算法,如有表达不清,或者错误的地方,欢迎指导.本篇是基于<机器学习实战>并参考<西瓜书>以及李航的<统计机器学习>这三本书所记的笔记,所有代码均是由python缩写,且有相对详细的中文注释,其中加入了自己的一些想法.

k近邻算法(k-nearest neighbor)也可以叫做KNN,是一种基本的分类和回归方法.输入是训练数据的特征向量,当然,KNN是不具备显式的学习过程.假定给定一个训练数据集,且每个数据都有对应的类别,分类时,对新的数据,根据K个最近的数据类别,通过多数表达等方式进行预测.最简单的说法,找K个离的最近的训练数据,哪种类别占的多,就用哪种方法.

K近邻的三个基本要素:K值的选择、距离度量、以及分类决策规则.

先简单提下一距离度量,这篇文章讲的不错,下面就复制粘贴了,想了解距离度量的直接点下面传送门,这里就简单介绍一下.

曼哈顿距离,欧式距离,明式距离,切比雪夫距离区别

最熟悉的欧式距离:最常见的两点之间或多点之间的距离表示法,又称之为欧几里得度量,它定义于欧几里得空间中,如点 x = (x1,...,xn) 和 y = (y1,...,yn) 之间的距离为:


曼哈顿距离,我们可以定义曼哈顿距离的正式意义为L1-距离或城市区块距离,也就是在欧几里得空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。例如在平面上,坐标(x1, y1)的点P1与坐标(x2, y2)的点P2的曼哈顿距离为:

切比雪夫距离,若二个向量或二个点p 、and q,其座标分别为,则两者之间的切比雪夫距离定义如下:

    这也等于以下Lp度量的极值:,因此切比雪夫距离也称为L∞度量


这里我使用了四个例子,第一个是构建一个最简单的2维特征向量以及对应的实例,然后输入一个测试向量,离谁近,就是分类结果就是谁'第二个例子是机器学习实战里的例子,使用kNN改进约会网站的配对效果,数据是由txt文本的样式给出,前三列分别对应着每年获得的飞行常客里程数、玩视频游戏所消耗时间的百分比、每周消费的冰淇淋公升数,最后一列是标签,如不喜欢约会,大量约会,少量约会,数据如下:

40920    8.326976    0.953952    largeDoses
14488    7.153469    1.673904    smallDoses
26052    1.441871    0.805124    didntLike
75136    13.147394    0.428964    didntLike
38344    1.669788    0.134296    didntLike

72993    10.141740    1.032955    didntLike

这里,我们发现,如果计算距离,第一列每年飞行里程数的特征值很大,会占有很大比重,但是,玩游戏消耗的时间和吃冰淇淋同样是评估约会的重要特征,这时候就需要对特征数据进行标准化,本篇当中我选择的是将数值归一化到0-1之间

而选择的标准化算法为 new = (old-min)/(max-min),其中old为旧的特征值,new为新的特征值,min和max分别对应特征中的最小值和最大值.

第三个例子是关于手写数字识别的,采用的数据也由机器学习实战提供的,只不过这里我的看到的手写数字已经转换成了文本形式,如下对应的是3这个值

00000000000011111111100000000000
00000000000111111111110000000000
00000000001111111111110000000000
00000000001111111111111000000000
00000000001111111111111100000000
00000000001111111111111100000000
00000000001111100001111100000000
00000000000011100001111110000000
00000000000001000011111110000000
00000000000000000111111100000000
00000000000000011111111100000000
00000000000000111111111100000000
00000000000111111111111000000000
00000000001111111111110000000000
00000000001111111111000000000000
00000000000111111111000000000000
00000000000111111111100000000000
00000000000011111111110000000000
00000000000000011111111000000000
00000000000000000111111100000000
00000000000000000011111100000000
00000000000000000001111110000000
00000000011000000000011111000000
00000000111100000000111111000000
00000000111100000001111111000000
00000000111100000011111111000000
00000000011111111111111111000000
00000000011111111111111110000000
00000000011111111111111110000000
00000000001111111111111100000000
00000000000011111111111000000000
00000000000001111111110000000000

第四个例子我采用的mnist数据,数据样本是png单通道图像,我将图像矩阵转换成1*784的特征矩阵,同样,错误仅有0.0334,并没有采取最优化的算法,所以这边计算时间有点长.值得注意的是,我将所有png的路径以txt的文件形式表示,前面是路径,空格后面是标签,样式如下:

mnist/train/5/00000.png 5
mnist/train/0/00001.png 0
mnist/train/4/00002.png 4
mnist/train/1/00003.png 1
mnist/train/9/00004.png 9
mnist/train/2/00005.png 2
mnist/train/1/00006.png 1

四个例子简单介绍后,详细讲解代码实现过程,请看代码的具体注释,test1-test4分别对应这四个列子

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Mon Jul  9 14:19:59 2018

@author: hjxu
"""

import numpy as np
import os
import operator
import scipy
from scipy import misc #这是我用来打开图像的一个库,有兴趣的可以了解一下


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

def classify(inX, dataSet, labels, k): #我们整个数据都在这边处理
    '''
    inX:输入参数
    dataSet:训练样本集
    labels:训练样本标签向量
    K:最近邻参数的个数
    '''

    dataSetSize = dataSet.shape[0]  # shape是array的一个查看数组大小的函数,返回高和宽
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet  # tile是将inX的纵向扩展dataSetSize倍,
                                                        # 横向扩展1倍,为了能直接和dataSet相减
    sqDiffMat = diffMat**2
    sqDistance = sqDiffMat.sum(axis=1)  # axis=1指横向求内和,如果axis=0则纵向求内和,如果没有这个参数,则所有值求和
    distance = sqDistance**0.5
    sortDistanceIndicies = distance.argsort()  # 按照从小到大排序,输出每个值对应的下标
    classCount = {}  # 创建一个字典

    for i in range(k):  # 选择距离最小的k个点
        votelabel = labels[sortDistanceIndicies[i]]
        classCount[votelabel] = classCount.get(votelabel, 0) + 1 # 对每一个字典的key后面对应的int加一

    #下面就需要将字典进行排序,要按照第二列排序
    sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse = True)
    return sortedClassCount[0][0]

def file2matrix(filename): #第二个列子的数据处理,主要是打开txt文件,然后转换成特征向量
    fr = open(filename)#打开文件
    numbersOfLines = len(fr.readlines())#查看有多少行,注意,readlines和readline是有很大区别的
    resMat = np.zeros((numbersOfLines, 3))#生成矩阵
    reslabels = []
    print numbersOfLines
    fr = open(filename)
    index = 0
    for line in fr.readlines():
        line = line.strip()#去掉回车字符
        # print line
        listFromLine = line.split('\t')#按照空格分割
        # print listFromLine[0:3]
        resMat[index, :] = listFromLine[0:3]#给矩阵赋值,前三列对应这特征
        reslabels.append(int(listFromLine[-1])) #最后一列则是标签,注意,这里标签我都转换成了数字
        index += 1
    return resMat, reslabels

def Norm(DataSet): #特征归一化
    minval = DataSet.min(0) ##能返回每列对应的最小值
    maxval = DataSet.max(0)
    ranges = maxval - minval
    normalDataSet = np.zeros(DataSet.shape)  # 新建立一个数组用来存放归一化后的data
    numOfData = DataSet.shape[0]
    normalDataSet = DataSet - np.tile(minval, (numOfData, 1))  # tile是将inX的纵向扩展numOfData倍,
    normalDataSet = normalDataSet/np.tile(ranges, (numOfData, 1))  # 方便直接相减和相除
    return normalDataSet, ranges, minval

def img2vector(filename): #这是第三个例子,需要将手写数字的txt文件转换成列向量
    resVect = np.zeros((1,1024))
    fr = open(filename)
    for i in range(32):  # 按照行展开
        lineStr = fr.readline()
        for j in range(32):
            resVect[0, 32*i+j] = int(lineStr[j])
    return resVect

def png2vector(filename):  #这个是第四个例子,其中folder + filename对应着图片所在的对峙
    folder = '/home/hjxu/PycharmProjects/tf_examples/hjxu_mnist/mnist_img_data/'
    resVect = np.zeros((1,784))
    fr =misc.imread(folder + filename) #读取图像
    for i in range(28):  # 按照行展开
        for j in range(28):
            resVect[0, 28*i+j] = float(1.0 * fr[i, j] / 255) #图像中的值是0-255的,所以归一化可以选择除以255
    return resVect


def mnistClassTest(train_patch, test_patch): #第三个例子的测试,参数分别对应这训练集和测试集所在的文件夹路径
    trainLabels = [] 
    trainFileList = os.listdir(train_patch) #os库的用法,返回当前目录下的子文件
    mTrain = len(trainFileList)#查看有多少子文件
    trainMat = np.zeros((mTrain, 1024)) #建立空的特征向量
    for i in range(mTrain):
        fileName = trainFileList[i] #对应这每个子文件
        labels = int(fileName.split('_')[0]) #因为标签的是按照2_6.txt这种形式命名的,所以在'_'前的数字就是对应的手写数字标签
        trainLabels.append(labels)
        trainMat[i, :] = img2vector(train_patch + '/' + fileName) #转换成特征向量
#测试同上
    testfileList = os.listdir(test_patch)
    m_Test = len(testfileList)
    error = 0
    for i in range(m_Test):
        fileName = testfileList[i]
        testLabel = int(fileName.split('_')[0])
        vectorTest = img2vector(test_patch + '/' + fileName)
        classResult = classify(vectorTest, trainMat, trainLabels, 10)
        print "predict: %d, label: %d" % (classResult, testLabel)
        if(classResult != testLabel):
            error = error + 1
    print "The predicted erroe: %f" % (1.0 * error/(m_Test))


def mnistPngClassTest(train_list, test_list):#对应这两个txt文件所在的路径
    fr = open(train_list)  # 打开文件
    mTrain = len(fr.readlines())  # 查看有多少行,注意,readlines和readline是有很大区别的
    trainMat = np.zeros((mTrain, 784))  # 生成矩阵,由于我的图像是28*28的,所以展开有784列
    trainLabels = []
    fr = open(train_list)#注意,这里需要重新打开一次,具体为啥,我也不是很清楚,应该和文件打开的数据流有关
    i = 0
    for line in fr.readlines():
        line = line.strip()  # 去掉回车字符
        # print line
        listFromLine = line.split(' ')  # 按照空格分割
        filepath = listFromLine[0]
        label = int(listFromLine[1])
        trainMat[i, :] = png2vector(filepath)
        trainLabels.append(label)
        i = i + 1

    #测试
    frtest = open(test_list)
    mTest = len(frtest.readlines())
    print mTest
    error = 0
    frtest = open(test_list)
    for line in frtest.readlines():
        line = line.strip()  # 去掉回车字符
        # print line
        listFromLine = line.split(' ')  # 按照空格分割
        filepath = listFromLine[0]
        testLabel = int(listFromLine[1])

        vectorTest = png2vector(filepath)
        classResult = classify(vectorTest, trainMat, trainLabels, 10)
        print "predict: %d, label: %d" % (classResult, testLabel)
        if (classResult != testLabel):
            error = error + 1
    print "The predicted error: %f" % (1.0 * error / (mTest))




def test1():
    testa = [[0, 0]]
    groups, labels = createDataSet()
    testalabels = classify(testa, groups, labels, 3)
    print "the predicted result of testa is ", testalabels

def test2():
    Ratio = 0.1
    dataMat, labels = file2matrix('./datingTestSet2.txt')
    norMat, _,_ = Norm(dataMat)
    m = norMat.shape[0]
    numOfTest = int(Ratio * m)
    errorCount = 0.0

    for i in range(numOfTest):
        classResult = classify(norMat[i,:],norMat[numOfTest:m,:], labels[numOfTest:m],5)
        print "predict: %d, label: %d" % (classResult,labels[i])
        if(classResult == labels[i]):
            errorCount += 1.0
    print "The predicted erroe: %f" %(1.0 * errorCount/(numOfTest))

def test3():
    train_patch = '/home/hjxu/MLProject/KNN/digits/trainingDigits'
    test_path = '/home/hjxu/MLProject/KNN/digits/testDigits'
    mnistClassTest(train_patch, test_path)

def test4():
    train_patch = '/home/hjxu/PycharmProjects/tf_examples/hjxu_mnist/mnist_img_data/train.txt'
    test_path = '/home/hjxu/PycharmProjects/tf_examples/hjxu_mnist/mnist_img_data/test.txt'
    mnistPngClassTest(train_patch, test_path)

if __name__ == '__main__':
    # test1()
    # test2()
    # test3()
    test4()

本代码中用KNN直接对图像进行手写数据识别所消耗的时间较长,但是准确率还是很高的,错误率仅为0.0334

代码输出如下

predict: 2, label: 2
predict: 3, label: 3
predict: 4, label: 4
predict: 5, label: 5
predict: 6, label: 6

The predicted error: 0.033400

到这里,简单回顾一下knn算法,以及算法的简单实现,有时间会利用比较好的数据结构来实现KNN算法,如K-D树等.


Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/hjxu2016/article/details/80990661