一、机器学习实战之K-近邻算法

以下代码来自《机器学习实战》,对约会数据,手写数字数据,用KNN进行分类,我在每行代码后添加了详细的注释,以便理解

算法概述:给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类

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

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

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

from numpy import *
import operator
import matplotlib
import matplotlib.pyplot as plt
from os import listdir

 分类器主函数,通过输入测试特征向量,训练特征矩阵,训练标签向量,k值,输出分类结果

# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# lables:标签向量
# k:选择最近邻的数目
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 = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] # 取第i个距离对应的标签值
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # get方法表示访问字典的Key为voteIlabel的值,若没有这个key,值设为0,若有这个Key,则值+1
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse=True) # .items()返回一个列表,operator.itemgetter(1)针对列表中每一个数据的1位置的数字进行降序
    return sortedClassCount[0][0] # 返回排名数第一的第一个数据也就是标签

把数据从文件里取出来,转换成训练矩阵,和训练标签向量

# 此数据有3 个特征值 , 1个label
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] # 取列表的0:3 按index 覆盖全来的全0 矩阵
        classLabelVector.append(int(listFromLine[-1])) # 取最后一个数据,代表的是分类
        index += 1
    return returnMat, classLabelVector

有的特征数值太大,归一化特征值到0-1之间

# 归一化特征值 newValue = (oldValue - min) / (max - min)
def autoNorm(dataSet):
    minVals = dataSet.min(0) # 取出每列的最小值 , 结果是一个行向量
    maxVals = dataSet.max(0) # 取出每列的最大值, 结果是一个行向量
    ranges = maxVals - minVals # 得到每列最大值减去最小值的一个范围矩阵,结果是一个行向量
    normDataSet = zeros(shape(dataSet)) # 构造和训练集数据一样的全0 矩阵
    m = dataSet.shape[0] # m 是dataSet的行数
    normDataSet = dataSet - tile(minVals, (m, 1)) # 把最小值行向量,扩展成和训练集数据行数一样的矩阵,再拿训练集数据减去这个最小值扩展矩阵
    normDataSet = normDataSet/tile(ranges, (m, 1)) # 拿上面的结果矩阵,除以把范围矩阵扩展成和训练集数据行数一样的扩展矩阵
    return normDataSet, ranges, minVals

分类过程主函数,打印出结果

def datingClassTest():
    hoRatio = 0.10 # 表示把百分之多少的数据作为测试数据
    datingDataMat, datingLabels = file2matrix("./datingTestSet2.txt") # 把原始数据变成矩阵形式
    normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化数据
    m = normMat.shape[0] 
    numTestVecs = int(m * hoRatio) # 代表多少条数据是测试集
    errorCount = 0.0
    for i in range(numTestVecs): # i表示第几个测试数据,numTestVecs:m 表示训练集数据,k=3
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 4) 
        print("分类器返回结果是:%d, 真实结果是:%d"%(classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("总的错误率为:%f"%(errorCount / float(numTestVecs)))

结果还不错,4%的错误率

定义一个接收输入,预测输入数据分类的函数

def classifyPerson():
    resultList = ['根本不','有一点','非常']
    percentTats = float(input("玩游戏所耗时间百分比是多少?"))
    ffMiles = float(input("每年获取的飞机常客里程数是多少?"))
    iceCream = float(input("每周消费的冰淇淋公升数是多少?"))
    
    datingDataMat, datingLabels = file2matrix("./datingTestSet2.txt")
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
    print("你可能"+resultList[classifierResult - 1]+'喜欢这个人')

手写识别系统

定义一个把图像转换为向量的函数

#将图像转化为向量, 32*32像素的黑白图像
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)) # 构建一个m*1024的全0向量
    for i in range(m): # 对每一行训练数据
        fileNameStr = trainingFileList[i] # 得到数据的文件名
        fileStr = fileNameStr.split('.')[0] 
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr) # 从文件名中取出对应的是哪个数字,添加到标签列表里 (文件名如:0_13.txt 代表手写数字0的第13个样本)
        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) # 往分类器中输入参数:该测试向量,训练矩阵,训练矩阵对应的分类,k值,得到分类器返回分类
        print("分类器返回结果是:%d, 真实结果是:%d"%(classifierResult, classNumStr))
        if classifierResult != classNumStr:
            errorCount += 1.0
    print("\n错误分类的个数:%d"%errorCount)
    print("\n总的错误率:%f"%(errorCount / float(mTest)))

结果超棒,只有1.2%的错误率!

那么接下来,我们自己找个数据集应用一下吧!

在UCI机器学习库里随便找了个关于鲍鱼的数据 http://archive.ics.uci.edu/ml/datasets/Abalone

把刚才的file2matrix 和 datingClassTest函数简单改一改

def file2matrix_new(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    returnMat = zeros((numberOfLines, 8))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFromLine = line.split(',') 
        returnMat[index, :] = listFromLine[1:] 
        classLabelVector.append(listFromLine[0])
        index += 1
    return returnMat, classLabelVector

def abaloneClassTest():
    hoRatio = 0.10 # 表示把百分之多少的数据作为测试数据
    abaloneDataMat, abaloneLabels = file2matrix_new("./abalone.data") # 把原始数据变成矩阵形式
    normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化数据
    m = normMat.shape[0] 
    numTestVecs = int(m * hoRatio) # 代表多少条数据是测试集
    errorCount = 0.0
    for i in range(numTestVecs): # i表示第几个测试数据,numTestVecs:m 表示训练集数据,k=3
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], abaloneLabels[numTestVecs:m], 3) 
        print("分类器返回结果是:%s, 真实结果是:%s"%(classifierResult, abaloneLabels[i]))
        if classifierResult != abaloneLabels[i]:
            errorCount += 1.0
    print("总的错误率为:%f"%(errorCount / float(numTestVecs)))

错误率超过50%。。。笋干爆炸,如果错误率70%那就说明你反过来选就是30%了,而50%左右基本等于随机选择。。。

看来还得继续往后学习啊

猜你喜欢

转载自blog.csdn.net/HHHHHHHHHHan/article/details/88798317