《Machine Learning in Action》 之 kNN算法学习笔记:


k-NearestNeighbor(k近邻)算法是基于实例的一种学习算法。

基于实例的学习算法只是简单的把训练样例存储起来,将泛化的工作推迟到分类新的实例时。

基于实例的学习好处:

可以为不同的待分类查询实例建立不同的目标函数逼近。当目标函数很复杂但却可以用不太复杂的局部逼近描述时,这样做有显著的优势

基于实例的学习不足:

1.分类新实例的开销可能很大

2.当检索相似的训练样例时,他们一般考虑实例的所有属性,如果目标概念仅依赖与很多属性中的几个时,那么真正最相似的实例之间很可能相去甚远(特别对kNN,可能产生维度灾难)


kNN算法:

假定所有的实例对应于n维空间R中的点。一个实例的最近邻是通过标准欧式距离定义的。他是通过计算距离待分类样例x的k个训练样例中最普遍的目标值,返回其中最普遍的目标值作为x的估计值

优点:精度高、对异常值不敏感、无数据输入假定

缺点:计算复杂度高、空间复杂度高

适用数据范围:数值型和标称型


kNN的改进:

对k个近邻的贡献加权,根据他们相对查询点x的距离,将较大的权值赋给较近的近邻,权值一般采用1/(d平方) 其中d是x的近邻y与x的距离



最原始的分类器的算法:

def classify0(intX, dataSet, labels, k):
    dateSetSize = dataSet.shape[0]
    diffMat = tile(intX, (dateSetSize, 1)) - dataSet;
    sqDiffMat = diffMat ** 2;
    sqDistance = sqDiffMat.sum(axis=1)
    distance = sqDistance ** 0.5;
    sortedDistIndicies = distance.argsort();
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]];
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True);
    return sortedClassCount[0][0]

初次看有很多函数不懂,写在这里做个记录

第1行是dataSet.shape是获得dataSet这一个数组的行数跟列数,返回的是一个元组,【0】即获得行数

第2行tile函数是将intX这一个输入向量扩展为同dataSet具有相同的行数(这个函数还不是太懂,后面参数是(dateSetSize, 1)即把他扩展成dataSetSize行,列数不变,别的参数就不太明白了)

第5行的axis=1是指将sqDiffMat数组的每行元素相加,默认(axis=None)是把所有的元素相加,axis=0则是把每列元素相加,numpy中0轴表示列,1轴表示行

第7行的distance.argsort()函数,是指将distance按从小到达排序,返回排序后的元素在未排序的数组中的下标

第13行的sorted函数中,classCount.iteritems()函数返回字典classCount键值对的可迭代对象,operator.itemgetter(1)是通过operator模块的itemgetter定义了获取下标为1的元素


从文本文件解析数据

将32*32的二进制图像转换成1*1024的向量

def p_w_picpath2Vector(filename):
    returnVector = zeros((1, 1024))
    fr = file(filename)
    flag = True;
    for i in range(32):
        lineStr = fr.readline();
        for j in range(32):
            returnVector[0, 32 * i + j] = int(lineStr[j]);
    return returnVector;

第1行,构建了一个1*1024,初值都为0的数组


归一化特征值

def autoNorm(dataSet):
    minValue = dataSet.min(0);
    maxValue = dataSet.max(0);
    range = maxValue - minValue;
    normDataSet = zeros(shape(dataSet));
    m = dataSet.shape[0];
    normDataSet = dataSet - tile(minValue, (m, 1));
    normDataSet = normDataSet / (tile(range, (m, 1)))
    return normDataSet, range, minValue;

第1行参数0代表是返回每列的最小值,若为1则是返回每行的最小值,默认返回整个数组最小值

第2行同理

第5行的shape(dataSet)等价与dataSet.shape(),返回包含dataSet的行数和列数的元组


手写数字识别系统的测试代码

取k=3

dirName = "/home/quincy/python/MachineLearing/machinelearninginaction/Ch02/trainingDigits";
hwLabels = []
trainingFileList = os.listdir(dirName);
m = len(trainingFileList);
trainingMat = numpy.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, :] = kNN.p_w_picpath2Vector('%s/%s' % (dirName, fileNameStr));
testFileList = os.listdir("/home/quincy/python/MachineLearing/machinelearninginaction/Ch02/testDigits");
errorCount = 0;
mTest = len(testFileList);
for i in range(mTest):
    fileNameStr = testFileList[i];
    fileStr = fileNameStr.split(".")[0];
    classNumStr = int(fileStr.split("_")[0]);
    vectorUnderTest = kNN.p_w_picpath2Vector(
        "/home/quincy/python/MachineLearing/machinelearninginaction/Ch02/testDigits/%s" % fileNameStr);
    classifierResult = kNN.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 "the total number of errors is:%d" % errorCount;
print "the total error rate is: %g" % ( errorCount / float(mTest));

第4行的listdir()函数是os模块中用于返回指定目录中全部文件的方法

自己测了下 随着k的增加,错误率如下

k=3 错误率=0.0137421

k=4 错误率=0.0147992

k=5 错误率=0.0190275

k=6 错误率=0.0200846

k=7 错误率=0.0221987


将分类器自己改进成按距离加权

def classify0(intX, dataSet, labels, k):
    dateSetSize = dataSet.shape[0]
    diffMat = tile(intX, (dateSetSize, 1)) - dataSet;
    sqDiffMat = diffMat ** 2;
    sqDistance = sqDiffMat.sum(axis=1)
    distance = sqDistance ** 0.5;
    sortedDistIndicies = distance.argsort();
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]];
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1/distance[i]** 2
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True);
    return sortedClassCount[0][0]

改变在第11、12行

改后的k值和错误率如下:

k=3 错误率=0.0137421

k=4 错误率=0.0137421

k=5 错误率=0.0169133

k=6 错误率=0.0211416

k=7 错误率=0.0221987

k=8 错误率=0.02537

k=9 错误率=0.02537

k=10 错误率=0.0232558