《ML in Action》笔记(1) —— kNN分类器

  • 本章有三个案例,分别是:
    (1) 根据打斗镜头和接吻镜头数量评估电影类型;
    (2) 根据飞行常客里程数等3个参数预判约会网站的配对效果。
    (3) 利用训练数据集的手写数字识别

  • kNN(k-近邻)算法的实现相对简单:

    1. 计算当前点与训练数据集中每个点的距离
    2. 选取与当前点最近的k个点;
    3. 统计前k个点的类别频率(选举)
    4. 取统计频率最高的那个类别,作为对当前点的分类判别。
  • 以下是代码笔记,原书代码为PYTHON2,经学习消化,改为PYTHON3.5可运行版本。

【kNN.py: part1 —— 引入库文件】

from numpy import *
import operator
from os import listdir
  • 从这第一个最简单的案例起,就严重依赖numpy数组的特性,盖因其便捷的矢量化运算(操作符默认对数组内每一个元素进行批量运算)。

【kNN.py: part2 —— 分类器核心函数classify0()

  • classify0()函数是实现kNN算法的核心代码。输入参数inX是待分类的当前值,dataSet是训练数据集,labels是训练集的分类标签,k是k近邻的取值。函数输出对inX的分类判定结果。
def classify0(inX, dataSet, labels, k):
	# 训练集记录数
    dataSetSize = dataSet.shape[0]  
    # 计算数据与训练集的差值
    diffMat = tile(inX, (dataSetSize,1)) - dataSet 
    # 计算差值的平方
    sqDiffMat = diffMat**2
    # 按记录行求和,得到平方差
    sqDistances = sqDiffMat.sum(axis=1)
    # 开根求得欧式距离 
    distances = sqDistances**0.5  
    # 按距离升序排列
    sortedDistIndicies = distances.argsort() 
    classCount={}       
    # 选取TOP K个近邻样本   
    for i in range(k):  
	    # TOP K对应样本的分类标签
        voteIlabel = labels[sortedDistIndicies[i]]  
        # 针对某个样本分类标签的投票计数
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 
    # 原书代码此处用iteritems(),PYTHON3的DICT已不支持,改用items()和lambda
    #sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    sortedClassCount = sorted(classCount.items(), key=lambda item:item[1], reverse=True) 
    return sortedClassCount[0][0]
  • items()表示取出DICT的键值对(keys()values())。lambda表示取数模式:lambda item:item[1] 按数值排序,lambda item:item[0] 按键值排序,常用于sorted()函数。

【kNN.py: part3 —— 构建案例一的简单训练集】

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

【IPYTHON —— 运行案例一】

# 生成训练集数据
group,labels = kNN.createDataSet()
# 运行分类器,对样本点[0.3,0.4]进行判定
rst = kNN.classify0([0.3,0.4],group,labels,3)

【kNN.py: part4 —— 案例二用到的其他函数】

# 加载包含训练集数据的文本文件
def file2matrix(filename):
	# 打开文件
    fr = open(filename)
    # 读取文件内容(一次读所有行)
    arrayOLines = fr.readlines()
    # 获取记录行数
    numberOfLines = len(arrayOLines)
    # 初始化用于存储训练集数据的数组
    returnMat = zeros((numberOfLines,3))
    classLabelVector = []
    index = 0
    # 遍历行
    for line in arrayOLines:
        # 去除行末回车符
        line = line.strip()
        # 按tab分列
        listFromLine = line.split('\t')
        # 存入该行记录的3个参数。
        returnMat[index,:] = listFromLine[0:3]
        # 将该行记录对应的标签结果存入标签数组
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    # 返回训练集参数数组和标签数组
    return returnMat,classLabelVector
    
# 数据归一化 (x-min)/(max-min)
def autoNorm(dataSet):
    # 按列取最小值
    minVals = dataSet.min(0)    
    # 按列取最大值
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals  
    normDataSet = zeros(shape(dataSet)) 
    # 取记录数
    m = dataSet.shape[0]    
    # 分子 (x-min)
    normDataSet = dataSet - tile(minVals, (m,1))
    # 计算归一化数值
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals

# 算法准确率测试
def datingClassTest():
	# 训练样本比例
    hoRatio = 0.10      
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')       
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]    
    numTestVecs = int(m*hoRatio)
    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)
  • 第一个函数file2matrix主要演示了如何加载文本数据到Numpy数组。其中倒数4行,原代码使用了listFromLine[0:3],Numpy数组的下表是“左方右圆”括号,实际只取出3个数。
  • 第二个函数autoNorm对数据进行了归一化。参数点的“尺度”对距离的计算结果影响很大,从而影响分类器的结果。本案例采用了最简单的一种归一化方法。在实际运用中,归一化的方法应该是与样本数据本身的分布密切相关的。
  • 第三个函数datingClassTest()演示了用训练数据测试分类器准确性的过程。

【kNN.py: part5—— 案例二的实际运用】

# 根据输入的样本参数,输出分类器结果
def classifyPerson():
    resultList = ['not at all','in small doses','in large doses']
    # 以此读取用户输入的三个参数。
    # 原代码此处使用raw_input(),Python3 不再支持,改作input()
    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])
    # 调用分类器核心函数,输出分类结果
    classifierResult = classify0((inArr-\
        minVals)/ranges,normMat,datingLabels,3)
    print("You will probably like this person: ",\
          resultList[classifierResult - 1])

【kNN.py: part6—— 案例三的函数代码】

# 将二进制图形向量转为一维向量
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

# 批量运行手写数字识别的程序函数
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]
        # 提取文件名中包含的标签(数字)
        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)
        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)))
  • 本案例省略了将数字图形转化为二进制向量的过程,算法的模型输入是1024位长的2进制数组。随后可直接运用classify0()函数进行分类。

算法小结

  • 这两个案例调用了同一个kNN函数classify0()。kNN算法不存在一个经训练生成后可以直接“调用”的参数模型,而是每次运行时都要计算当前点与所有点的距离,并在k-近邻中进行分类选举。
  • 距离的定义和数据的归一化方法是与业务和参数本身高度相关的,不同的选择肯定会影响分类器的结果和准确率,后续可以亲自测试一下。

PYTHON小结

  • numPy数组的基本用法
  • 读取文本文件到numPy的方法
  • sorted排序函数的用法。sorted(classCount.items(), key=lambda item:item[1], reverse=True)
posted @ 2017-02-03 00:10  公爵  阅读(331)  评论(0编辑  收藏  举报