《Machine Learning in Action》| 第2章 k-近邻算法

本文深入讲解kNN算法原理,演示如何使用Python实现电影分类、改进约会网站配对效果及手写数字识别。涵盖数据预处理、特征归一化、算法测试与系统构建全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

准备:使用 Python 导入数据

"""
@函数说明: 创建数据集
"""
def createDataSet():
    # 四组二维特征
    group = np.array([[3,104],[2,100],[101,10],[99,5]])
    # 四组特征的标签
    labels = ['爱情片','爱情片','动作片','动作片']
    return group, labels

"""

实施kNN算法

  • 函数的伪代码如下:
    对未知类别属性的数据集中的每个点依次执行以下操作:
    (1) 计算已知类别数据集中的点与当前点之间的距离;
    (2) 按照距离递增次序排序;
    (3) 选取与当前点距离最小的k个点;
    (4) 确定前k个点所在类别的出现频率;
    (5) 返回前k个点出现频率最高的类别作为当前点的预测分类。
程序清单 2-1 k-近邻算法

"""
@函数说明:kNN分类(k近邻算法)
Parameters:
	inX - 用于分类的数据(测试集)
	dataSet - 用于训练的数据(训练集)
	labels - 类别标签
	k - 选择最近邻居的数目
Returns:
	sortedClassCount[0][0] - 分类结果
"""
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0] # numpy函数shape[0]返回dataSet的行数,4行
    
    # 欧式距离计算
    # 横向复制inX共1次,纵向复制inX共dataSize次
#    print(np.tile(inX, (dataSetSize,1)))
    """
    构造相同行数的测试集test
    [[ 10 120]
     [ 10 120]
     [ 10 120]
     [ 10 120]]
    """
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # 沿横向、纵向复制多维数组
    """
    test:              group:            diffMat:
    [[ 10 120]         [[  3 104]        [[  7  16]
     [ 10 120]   ——     [  2 100]    =    [  8  20]
     [ 10 120]          [101  10]         [-91 110]
     [ 10 120]]         [ 99   5]]        [-89 115]]
    """
#    print(diffMat)
    # 二维特征相减后平方
    sqDiffMat = diffMat ** 2
#    print(sqDiffMat)
    """
    sqDiffMat(每个元素平方):
    [[   49   256]
     [   64   400]
     [ 8281 12100]
     [ 7921 13225]]
    """
    # sum()将所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
#    print("sqDistances:\n",sqDistances)
    """
    sqDistances:
    [  305   464 20381 21146]
    """
    # 开方,计算出距离
    distances = sqDistances ** 0.5
#    print(" distances:\n", distances)
    """
    distances(算出来的最终的欧式距离):
    [ 17.4642492   21.54065923 142.76203977 145.41664279]
    """
    sortedDistIndices = distances.argsort() # 返回distances中元素从小到大排序后的索引值
#    print(sortedDistIndices) # [0 1 2 3]
    classCount = {} # 定义记录类别次数的字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]] # 取出前K个元素的类别,前3个类别为:爱情片 爱情片 动作片
#        print(voteIlabel)
        #dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
		 #计算类别次数
#         print(classCount.get(voteIlabel,0))
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # 没出现过的返回0再+1就是1次,爱情片(在空字典中没出现过,+1作为1次)、爱情片(出现过1次,+1是两次)、动作片(没出现过,+1作为1次)
#        print(classCount[voteIlabel]) # 爱情片 爱情片 动作片, 1 2 1
#        classCount: {'爱情片': 2, '动作片': 1}
    """
    python内置函数:sorted函数
    sorted(iterable<可迭代对象>, cmp<对函数排序>, key<对元素排序>, reverse<True降序,False升序(默认)>)
    sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
    内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
    详解:http://www.runoob.com/python/python-func-sorted.html
    """
    #python3中用items()替换python2中的iteritems()
	#key=operator.itemgetter(1)根据字典的值value进行排序
	#key=operator.itemgetter(0)根据字典的键key进行排序
	#reverse降序排序字典,reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#    print('sortedClassCount:\n',sortedClassCount)
    """
    sortedClassCount:
    [('爱情片', 2), ('动作片', 1)]
    """
    return sortedClassCount[0][0]

示例:使用 k-近邻算法改进约会网站的配对效果

准备数据:从文本文件中解析数据

程序清单 2-2 将文本记录转换为NumPy的解析程序
"""
函数说明:打开并解析文件,对数据进行分类:1代表不喜欢、2代表魅力一般,3代表极具魅力
Parameters:
    filename - 文件名
Return:
    returnMat - 特征矩阵
    classLabelVector - 分类Label向量
"""
def file2matrix(filename):
    loveDict = {'didntLike':1,'smallDoses':2,'largeDoses':3}
    fr = open(filename) # 打开文件
    arrayOlines = fr.readlines() # 逐行读取
#    print(arrayOlines)
    '''
    arrayOlines:
    ['40920\t8.326976\t0.953952\tlargeDoses\n', '14488\t7.153469\t1.673904\tsmallDoses\n',...,'43757\t7.882601\t1.332446\tlargeDoses\n']
    '''
    numberOfLines = len(arrayOlines) # 得到文件行数,共1000行数据
#    print(numberOfLines)
    returnMat = np.zeros( (numberOfLines,3) ) # 初始化特征矩阵,解析完成的数据:numberOfLines行3列
    classLabelVector = [] # 初始化分类标签向量
    index = 0 # 行的索引值
    for line in arrayOlines:
        # s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        line = line.strip() # 删去字符串首尾部空字符
#        print(line)
        '''
        如最后两行line:
        48111   9.134528        0.728045        largeDoses
        43757   7.882601        1.332446        largeDoses
        '''
        # 使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t') # 按'\t'对字符串进行分割,listFromLine是列表
#        print(listFromLine) # 分割成列表
        '''
        如最后两行listFromLine(列表):
        ['48111', '9.134528', '0.728045', 'largeDoses']
        ['43757', '7.882601', '1.332446', 'largeDoses']
        '''
        returnMat[index,:] = listFromLine[0:3] # 将数据前三列(特征)一行一行赋值给returnMat
        index += 1
        # 根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        # 读取的listFromLine的最后一列为类别标签
        if listFromLine[-1].isdigit(): # 如果listFromLine最后一列是数字,数据集datingTestSet.txt
            classLabelVector.append(int(listFromLine[-1])) # 直接赋值给classLabelVector
        else: # 如果listFromLine最后一列不是数字,而是字符串,数据集datingTestSet2.txt
            classLabelVector.append(loveDict.get(listFromLine[-1]))       
    return returnMat, classLabelVector # 返回的类别标签向量是1,2,3
    '''
    returnMat:                                           classLabelVector:
    [[4.0920000e+04 8.3269760e+00 9.5395200e-01]         [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2,
    [1.4488000e+04 7.1534690e+00 1.6739040e+00]           1, 1, 1, 1, 1, 2, 3, 2, 1, 2, 3, 2, 3, 
    [2.6052000e+04 1.4418710e+00 8.0512400e-01]           ...
    ...                                                   2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 
    [2.6575000e+04 1.0650102e+01 8.6662700e-01]           2, 2, 2, 1, 3, 3, 3]
    [4.8111000e+04 9.1345280e+00 7.2804500e-01]
    [4.3757000e+04 7.8826010e+00 1.3324460e+00]]
    '''

准备数据:归一化数值

程序清单 2-3 归一化特征值

"""
函数说明:特征归一化函数,对数据进行归一化
Parameters:
    dataSet - 特征矩阵
Returns:
    normDataSet - 归一化后的特征矩阵
    ranges - 数据范围
    minVals - 数据最小值
"""
def autoNorm(dataSet):
    minVals = dataSet.min(0) # 获取数据每一列的最小值和最大值,返回一维列表,min(0) axis=0每一列,axis=1每一行
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals # 最大值和最小值的范围
#    print('minVals:\n',minVals) # [0.       0.       0.001156]
#    print('maxVals:\n',maxVals) # [9.1273000e+04 2.0919349e+01 1.6955170e+00]
#    print('ranges:\n',ranges) # [9.1273000e+04 2.0919349e+01 1.6943610e+00] 
    normDataSet = np.zeros(np.shape(dataSet)) # 初始化归一化特征矩阵,np.shape(dataSet)返回dataSet的矩阵行列数
#    print(dataSet.shape) # (1000, 3)
    m = dataSet.shape[0] # dataSet的行数,共1000行
    '''
    下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
    newValue = (oldValue - min) / (max - min)
    其中min和max分别是数据集中的最小特征值和最大特征值。
    '''
    normDataSet = dataSet - np.tile(minVals, (m, 1)) # 原始值减去最小值
    normDataSet = normDataSet / np.tile(ranges, (m, 1)) # 再除以最大值和最小值的差,得到归一化数据,normDataSet值被限定在[0, 1]之间
    return normDataSet, ranges, minVals # 返回归一化数据结果,数据范围,最小值
    '''
    normDataSet:
    [[0.44832535 0.39805139 0.56233353]
    [0.15873259 0.34195467 0.98724416]
    [0.28542943 0.06892523 0.47449629]
    ...
    [0.29115949 0.50910294 0.51079493]
    [0.52711097 0.43665451 0.4290048 ]
    [0.47940793 0.3768091  0.78571804]]
    ranges:
    [9.1273000e+04 2.0919349e+01 1.6943610e+00]
    minVals:
    [0.       0.       0.001156]
    '''

测试算法:作为完整程序验证分类器

程序清单 2-4 分类器针对约会网站的测试代码
"""
函数说明:分类器测试函数    
"""
def datingClassTest():
    hoRatio = 0.10 # 整个数据集的10%用来测试,即拿100个样本作为测试集
    datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 导入数据集
    normMat, ranges, minVals = autoNorm(datingDataMat) # 所有特征归一化,返回归一化特征矩阵,数据范围,最小值
#    print(normMat.shape) # (1000, 3)
    m = normMat.shape[0] # 样本个数,normMat的行数,共1000个样本
    numTestVecs = int(m * hoRatio) # 测试样本个数,100个
    errorCount = 0.0 # 分类错误计数
#    print('测试集第一条数据:\n',normMat[0,:], '------------------------------','\n训练集:\n',normMat[numTestVecs:m,:])
    '''
    测试集第一条数据:
    [0.44832535 0.39805139 0.56233353] 
    ----------------------------------
    训练集:
    [[0.46457331 0.53983597 0.12206372]       
    [0.36871802 0.31502142 0.79791792]
    [0.10047878 0.2252441  0.11391374]
    ...
    [0.29115949 0.50910294 0.51079493]
    [0.52711097 0.43665451 0.4290048 ]
    [0.47940793 0.3768091  0.78571804]]
    '''
    for i in range(numTestVecs):
        # 对测试集的每行数据进行分类测试
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 3)  # 取前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集,datingLabels[numTestVecs:m]为训练集标签
        print("分类结果:%s, 真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率:%g%%" % (errorCount / float(numTestVecs)*100)) # 打印错误率
    print("错误个数:",errorCount)

使用算法:构建完整可用系统

程序清单 2-5 约会网站预测函数
"""
函数说明:通过用户输入一个人的三维特征,进行分类输出
"""
def classifyPerson():
    resultList = ['一点也不喜欢','有点喜欢','非常喜欢']
    # 用户输入特征
    precentTats = float(input("玩视频游戏所消耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰淇淋公升数:"))
    datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 打开并处理数据集
    normMat, ranges, minVals = autoNorm(datingDataMat) # 训练集归一化
#    print('normMat:\n',normMat)
    inArr = np.array([ffMiles, precentTats, iceCream]) # 测试集,生成Numpy数组
#    print('inArr:\n',inArr)
    norminArr = (inArr - minVals) / ranges # 测试集归一化
#    print('norminArr:\n',norminArr)
    '''
    normMat:
    [[0.44832535 0.39805139 0.56233353]
    [0.15873259 0.34195467 0.98724416]
    [0.28542943 0.06892523 0.47449629]
    ...
    [0.29115949 0.50910294 0.51079493]
    [0.52711097 0.43665451 0.4290048 ]
    [0.47940793 0.3768091  0.78571804]]
    inArr:
    [1.34e+05 5.00e+01 9.00e-01]
    norminArr:
    [1.4681231  2.39013174 0.53049144]
    '''
    classifierResult = classify0(norminArr, normMat, datingLabels, 3) # 进行kNN分类
    print("你可能%s这个人" % (resultList[classifierResult-1])) # 打印分类结果,classifierResult-1,数组下标从0起

示例:手写识别系统

准备数据:将图像转换为测试向量

"""
函数说明:将32*32的二进制图像转换成1*1024的向量
Parameters:
    filename - 文件名(路径)
Returns:
    returnVect - 返回的二进制图像的1*1024向量
"""
def img2vector(filename):
    returnVect = np.zeros((1, 1024)) # 创建1*1024的零向量,存储图片像素的向量维度是1*1024
    fr = open(filename) # 打开文件
    for i in range(32): # 按行读取
        lineStr = fr.readline() # 读一行数据
        for j in range(32): # 每一行的前32个数据依次添加到returnVect中
            returnVect[0, 32*i+j] = int(lineStr[j]) # 图片尺寸是32*32,将其依次放入向量returnVect
    return returnVect # 返回转换后的1*1024向量

测试算法:使用 k-近邻算法识别手写数字

程序清单 2-6 手写数字识别系统的测试代码
"""
函数说明:手写数字分类测试
"""
def handwritingClassTest():
    hwLabels = [] # 测试集Labels
    trainingFileList = listdir(r'D:\dataset\inaction\kNN\trainingDigits') # 导入训练集,listdir() 返回指定文件夹名字的列表
    m = len(trainingFileList) # 文件夹下文件的个数,1934个文件
#    print(m)
    trainingMat = np.zeros((m, 1024)) # 初始化训练矩阵
    for i in range(m):
        fileNameStr = trainingFileList[i] # 获得每个文件的名字,如 0_0.txt,9_99.txt
#        print(fileNameStr)
        fileStr = fileNameStr.split('.')[0] # 去掉 .txt , 剩下0_0,9_99
#        print(fileStr)
        classNumStr = int(fileStr.split('_')[0]) # 按下划线‘_’划分‘0_0’,取第一个元素为类别标签
        hwLabels.append(classNumStr) # 将获得的类别添加到hwLabels中
#        print(hwLabels)
        trainingMat[i,:] = img2vector(r'D:\dataset\inaction\kNN\trainingDigits\%s' % fileNameStr) # 将每一个文件的1*1024数据存储到trainingMat矩阵中
    testFileList = listdir(r'D:\dataset\inaction\kNN\testDigits') # 测试样本,返回testDigits目录下的文件名
    errorCount = 0.0 # 分类错误计数
    mTest = len(testFileList) # 测试样本的个数
    for i in range(mTest):
        fileNameStr = testFileList[i] # 获得每个文件的名字,如 0_0.txt
        fileStr = fileNameStr.split('.')[0] # 去掉 .txt , 剩下0_0
        classNumStr = int(fileStr.split('_')[0]) # 按下划线‘_’划分‘0_0’,取第一个元素为类别标签
        vectorUnderTest = img2vector(r'D:\dataset\inaction\kNN\testDigits\%s' % fileNameStr) # 获得测试集的1*1024向量,用于训练
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 调用kNN分类,获得预测结果
        print("分类返回结果:%d, 真实结果:%d" % (classifierResult, classNumStr)) # 打印分类结果
        if classifierResult != classNumStr:
            errorCount += 1.0 # 分类错误个数计数
    print("错误总数:%d\n错误率:%f%%" % (errorCount, errorCount / float(mTest) * 100)) #打印错误个数及错误率
        

附所有代码:

# -*- coding: utf-8 -*-
"""
Created on Tue Oct  9 09:56:29 2018

@author: YAOTIANLONG
"""
# ------示例1: kNN电影分类 -------------------------------------
import numpy as np
import operator # 导入运算符模块,sorted排序使用,key=operator.itemgetter(1)
from os import listdir # os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表
"""
@函数说明: 创建数据集
"""
def createDataSet():
    # 四组二维特征
    group = np.array([[3,104],[2,100],[101,10],[99,5]])
    # 四组特征的标签
    labels = ['爱情片','爱情片','动作片','动作片']
    return group, labels

"""
@函数说明:kNN分类(k近邻算法)
Parameters:
	inX - 用于分类的数据(测试集)
	dataSet - 用于训练的数据(训练集)
	labels - 类别标签
	k - 选择最近邻居的数目
Returns:
	sortedClassCount[0][0] - 分类结果
"""
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0] # numpy函数shape[0]返回dataSet的行数,4行
    
    # 欧式距离计算
    # 横向复制inX共1次,纵向复制inX共dataSize次
#    print(np.tile(inX, (dataSetSize,1)))
    """
    构造相同行数的测试集test
    [[ 10 120]
     [ 10 120]
     [ 10 120]
     [ 10 120]]
    """
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # 沿横向、纵向复制多维数组
    """
    test:              group:            diffMat:
    [[ 10 120]         [[  3 104]        [[  7  16]
     [ 10 120]   ——     [  2 100]    =    [  8  20]
     [ 10 120]          [101  10]         [-91 110]
     [ 10 120]]         [ 99   5]]        [-89 115]]
    """
#    print(diffMat)
    # 二维特征相减后平方
    sqDiffMat = diffMat ** 2
#    print(sqDiffMat)
    """
    sqDiffMat(每个元素平方):
    [[   49   256]
     [   64   400]
     [ 8281 12100]
     [ 7921 13225]]
    """
    # sum()将所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
#    print("sqDistances:\n",sqDistances)
    """
    sqDistances:
    [  305   464 20381 21146]
    """
    # 开方,计算出距离
    distances = sqDistances ** 0.5
#    print(" distances:\n", distances)
    """
    distances(算出来的最终的欧式距离):
    [ 17.4642492   21.54065923 142.76203977 145.41664279]
    """
    sortedDistIndices = distances.argsort() # 返回distances中元素从小到大排序后的索引值
#    print(sortedDistIndices) # [0 1 2 3]
    classCount = {} # 定义记录类别次数的字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]] # 取出前K个元素的类别,前3个类别为:爱情片 爱情片 动作片
#        print(voteIlabel)
        #dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
		 #计算类别次数
#         print(classCount.get(voteIlabel,0))
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # 没出现过的返回0再+1就是1次,爱情片(在空字典中没出现过,+1作为1次)、爱情片(出现过1次,+1是两次)、动作片(没出现过,+1作为1次)
#        print(classCount[voteIlabel]) # 爱情片 爱情片 动作片, 1 2 1
#        classCount: {'爱情片': 2, '动作片': 1}
    """
    python内置函数:sorted函数
    sorted(iterable<可迭代对象>, cmp<对函数排序>, key<对元素排序>, reverse<True降序,False升序(默认)>)
    sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
    内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
    详解:http://www.runoob.com/python/python-func-sorted.html
    """
    #python3中用items()替换python2中的iteritems()
	#key=operator.itemgetter(1)根据字典的值value进行排序
	#key=operator.itemgetter(0)根据字典的键key进行排序
	#reverse降序排序字典,reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#    print('sortedClassCount:\n',sortedClassCount)
    """
    sortedClassCount:
    [('爱情片', 2), ('动作片', 1)]
    """
    return sortedClassCount[0][0]
"""
函数说明:测试kNN电影分类
"""
def test0():
    group, labels = createDataSet() # 创建数据集
    print('group:\n',group) # 打印数据集
    print('labels:\n',labels)
    test = [101,20] # 测试集
    test_class = classify0(test, group, labels, 3) # kNN分类
    print(test_class)
    print('"',test,'kNN classify',test_class,'"') # 打印分类结果

# ------示例2: 使用kNN算法改进约会网站的配对效果 -------------------------------------
"""
函数说明:打开并解析文件,对数据进行分类:1代表不喜欢、2代表魅力一般,3代表极具魅力
Parameters:
    filename - 文件名
Return:
    returnMat - 特征矩阵
    classLabelVector - 分类Label向量
"""
def file2matrix(filename):
    loveDict = {'didntLike':1,'smallDoses':2,'largeDoses':3}
    fr = open(filename) # 打开文件
    arrayOlines = fr.readlines() # 逐行读取
#    print(arrayOlines)
    '''
    arrayOlines:
    ['40920\t8.326976\t0.953952\tlargeDoses\n', '14488\t7.153469\t1.673904\tsmallDoses\n',...,'43757\t7.882601\t1.332446\tlargeDoses\n']
    '''
    numberOfLines = len(arrayOlines) # 得到文件行数,共1000行数据
#    print(numberOfLines)
    returnMat = np.zeros( (numberOfLines,3) ) # 初始化特征矩阵,解析完成的数据:numberOfLines行3列
    classLabelVector = [] # 初始化分类标签向量
    index = 0 # 行的索引值
    for line in arrayOlines:
        # s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        line = line.strip() # 删去字符串首尾部空字符
#        print(line)
        '''
        如最后两行line:
        48111   9.134528        0.728045        largeDoses
        43757   7.882601        1.332446        largeDoses
        '''
        # 使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t') # 按'\t'对字符串进行分割,listFromLine是列表
#        print(listFromLine) # 分割成列表
        '''
        如最后两行listFromLine(列表):
        ['48111', '9.134528', '0.728045', 'largeDoses']
        ['43757', '7.882601', '1.332446', 'largeDoses']
        '''
        returnMat[index,:] = listFromLine[0:3] # 将数据前三列(特征)一行一行赋值给returnMat
        index += 1
        # 根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        # 读取的listFromLine的最后一列为类别标签
        if listFromLine[-1].isdigit(): # 如果listFromLine最后一列是数字,数据集datingTestSet.txt
            classLabelVector.append(int(listFromLine[-1])) # 直接赋值给classLabelVector
        else: # 如果listFromLine最后一列不是数字,而是字符串,数据集datingTestSet2.txt
            classLabelVector.append(loveDict.get(listFromLine[-1]))       
    return returnMat, classLabelVector # 返回的类别标签向量是1,2,3
    '''
    returnMat:                                           classLabelVector:
    [[4.0920000e+04 8.3269760e+00 9.5395200e-01]         [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2,
    [1.4488000e+04 7.1534690e+00 1.6739040e+00]           1, 1, 1, 1, 1, 2, 3, 2, 1, 2, 3, 2, 3, 
    [2.6052000e+04 1.4418710e+00 8.0512400e-01]           ...
    ...                                                   2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 
    [2.6575000e+04 1.0650102e+01 8.6662700e-01]           2, 2, 2, 1, 3, 3, 3]
    [4.8111000e+04 9.1345280e+00 7.2804500e-01]
    [4.3757000e+04 7.8826010e+00 1.3324460e+00]]
    '''

"""
函数说明:特征归一化函数,对数据进行归一化
Parameters:
    dataSet - 特征矩阵
Returns:
    normDataSet - 归一化后的特征矩阵
    ranges - 数据范围
    minVals - 数据最小值
"""
def autoNorm(dataSet):
    minVals = dataSet.min(0) # 获取数据每一列的最小值和最大值,返回一维列表,min(0) axis=0每一列,axis=1每一行
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals # 最大值和最小值的范围
#    print('minVals:\n',minVals) # [0.       0.       0.001156]
#    print('maxVals:\n',maxVals) # [9.1273000e+04 2.0919349e+01 1.6955170e+00]
#    print('ranges:\n',ranges) # [9.1273000e+04 2.0919349e+01 1.6943610e+00] 
    normDataSet = np.zeros(np.shape(dataSet)) # 初始化归一化特征矩阵,np.shape(dataSet)返回dataSet的矩阵行列数
#    print(dataSet.shape) # (1000, 3)
    m = dataSet.shape[0] # dataSet的行数,共1000行
    '''
    下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
    newValue = (oldValue - min) / (max - min)
    其中min和max分别是数据集中的最小特征值和最大特征值。
    '''
    normDataSet = dataSet - np.tile(minVals, (m, 1)) # 原始值减去最小值
    normDataSet = normDataSet / np.tile(ranges, (m, 1)) # 再除以最大值和最小值的差,得到归一化数据,normDataSet值被限定在[0, 1]之间
    return normDataSet, ranges, minVals # 返回归一化数据结果,数据范围,最小值
    '''
    normDataSet:
    [[0.44832535 0.39805139 0.56233353]
    [0.15873259 0.34195467 0.98724416]
    [0.28542943 0.06892523 0.47449629]
    ...
    [0.29115949 0.50910294 0.51079493]
    [0.52711097 0.43665451 0.4290048 ]
    [0.47940793 0.3768091  0.78571804]]
    ranges:
    [9.1273000e+04 2.0919349e+01 1.6943610e+00]
    minVals:
    [0.       0.       0.001156]
    '''

"""
函数说明:分类器测试函数    
"""
def datingClassTest():
    hoRatio = 0.10 # 整个数据集的10%用来测试,即拿100个样本作为测试集
    datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 导入数据集
    normMat, ranges, minVals = autoNorm(datingDataMat) # 所有特征归一化,返回归一化特征矩阵,数据范围,最小值
#    print(normMat.shape) # (1000, 3)
    m = normMat.shape[0] # 样本个数,normMat的行数,共1000个样本
    numTestVecs = int(m * hoRatio) # 测试样本个数,100个
    errorCount = 0.0 # 分类错误计数
#    print('测试集第一条数据:\n',normMat[0,:], '------------------------------','\n训练集:\n',normMat[numTestVecs:m,:])
    '''
    测试集第一条数据:
    [0.44832535 0.39805139 0.56233353] 
    ----------------------------------
    训练集:
    [[0.46457331 0.53983597 0.12206372]       
    [0.36871802 0.31502142 0.79791792]
    [0.10047878 0.2252441  0.11391374]
    ...
    [0.29115949 0.50910294 0.51079493]
    [0.52711097 0.43665451 0.4290048 ]
    [0.47940793 0.3768091  0.78571804]]
    '''
    for i in range(numTestVecs):
        # 对测试集的每行数据进行分类测试
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 3)  # 取前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集,datingLabels[numTestVecs:m]为训练集标签
        print("分类结果:%s, 真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率:%g%%" % (errorCount / float(numTestVecs)*100)) # 打印错误率
    print("错误个数:",errorCount)

"""
函数说明:通过用户输入一个人的三维特征,进行分类输出
"""
def classifyPerson():
    resultList = ['一点也不喜欢','有点喜欢','非常喜欢']
    # 用户输入特征
    precentTats = float(input("玩视频游戏所消耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰淇淋公升数:"))
    datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 打开并处理数据集
    normMat, ranges, minVals = autoNorm(datingDataMat) # 训练集归一化
#    print('normMat:\n',normMat)
    inArr = np.array([ffMiles, precentTats, iceCream]) # 测试集,生成Numpy数组
#    print('inArr:\n',inArr)
    norminArr = (inArr - minVals) / ranges # 测试集归一化
#    print('norminArr:\n',norminArr)
    '''
    normMat:
    [[0.44832535 0.39805139 0.56233353]
    [0.15873259 0.34195467 0.98724416]
    [0.28542943 0.06892523 0.47449629]
    ...
    [0.29115949 0.50910294 0.51079493]
    [0.52711097 0.43665451 0.4290048 ]
    [0.47940793 0.3768091  0.78571804]]
    inArr:
    [1.34e+05 5.00e+01 9.00e-01]
    norminArr:
    [1.4681231  2.39013174 0.53049144]
    '''
    classifierResult = classify0(norminArr, normMat, datingLabels, 3) # 进行kNN分类
    print("你可能%s这个人" % (resultList[classifierResult-1])) # 打印分类结果,classifierResult-1,数组下标从0起

"""
函数说明:测试约会网站配对
"""
def test1():
    datingDataMat, datingLabels = file2matrix(r'D:\dataset\inaction\kNN\datingTestSet2.txt') # 打开数据
    print('datingDataMat:\n',datingDataMat) # 打印特征矩阵
    print('datingLabels:\n',datingLabels) # 打印类别标签
    normDataSet, ranges, minVals = autoNorm(datingDataMat) # 测试归一化特征函数
    print('normDataSet:\n',normDataSet) # 打印归一化特征矩阵
    print('ranges:\n',ranges) # 打印数据范围
    print('minVals:\n',minVals) # 打印数据最小值
    datingClassTest() # 测试分类器
    classifyPerson() # 测试完整可用系统
    
# ------示例3: 手写识别系统 -------------------------------------
"""
函数说明:将32*32的二进制图像转换成1*1024的向量
Parameters:
    filename - 文件名(路径)
Returns:
    returnVect - 返回的二进制图像的1*1024向量
"""
def img2vector(filename):
    returnVect = np.zeros((1, 1024)) # 创建1*1024的零向量,存储图片像素的向量维度是1*1024
    fr = open(filename) # 打开文件
    for i in range(32): # 按行读取
        lineStr = fr.readline() # 读一行数据
        for j in range(32): # 每一行的前32个数据依次添加到returnVect中
            returnVect[0, 32*i+j] = int(lineStr[j]) # 图片尺寸是32*32,将其依次放入向量returnVect
    return returnVect # 返回转换后的1*1024向量

"""
函数说明:手写数字分类测试
"""
def handwritingClassTest():
    hwLabels = [] # 测试集Labels
    trainingFileList = listdir(r'D:\dataset\inaction\kNN\trainingDigits') # 导入训练集,listdir() 返回指定文件夹名字的列表
    m = len(trainingFileList) # 文件夹下文件的个数,1934个文件
#    print(m)
    trainingMat = np.zeros((m, 1024)) # 初始化训练矩阵
    for i in range(m):
        fileNameStr = trainingFileList[i] # 获得每个文件的名字,如 0_0.txt,9_99.txt
#        print(fileNameStr)
        fileStr = fileNameStr.split('.')[0] # 去掉 .txt , 剩下0_0,9_99
#        print(fileStr)
        classNumStr = int(fileStr.split('_')[0]) # 按下划线‘_’划分‘0_0’,取第一个元素为类别标签
        hwLabels.append(classNumStr) # 将获得的类别添加到hwLabels中
#        print(hwLabels)
        trainingMat[i,:] = img2vector(r'D:\dataset\inaction\kNN\trainingDigits\%s' % fileNameStr) # 将每一个文件的1*1024数据存储到trainingMat矩阵中
    testFileList = listdir(r'D:\dataset\inaction\kNN\testDigits') # 测试样本,返回testDigits目录下的文件名
    errorCount = 0.0 # 分类错误计数
    mTest = len(testFileList) # 测试样本的个数
    for i in range(mTest):
        fileNameStr = testFileList[i] # 获得每个文件的名字,如 0_0.txt
        fileStr = fileNameStr.split('.')[0] # 去掉 .txt , 剩下0_0
        classNumStr = int(fileStr.split('_')[0]) # 按下划线‘_’划分‘0_0’,取第一个元素为类别标签
        vectorUnderTest = img2vector(r'D:\dataset\inaction\kNN\testDigits\%s' % fileNameStr) # 获得测试集的1*1024向量,用于训练
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 调用kNN分类,获得预测结果
        print("分类返回结果:%d, 真实结果:%d" % (classifierResult, classNumStr)) # 打印分类结果
        if classifierResult != classNumStr:
            errorCount += 1.0 # 分类错误个数计数
    print("错误总数:%d\n错误率:%f%%" % (errorCount, errorCount / float(mTest) * 100)) #打印错误个数及错误率
    
"""
函数说明:测试手写识别系统
"""
def test2():
    testVector = img2vector(r'D:\dataset\inaction\kNN\testDigits\0_13.txt')
    print(testVector[0,0:31])
    print(testVector[0,32:63])
    handwritingClassTest()
 
#-----------主函数---------------------------------------------    
if __name__ == '__main__':
    test0() # 测试kNN电影分类
    test1() # 测试约会网站配对
    test2() # 测试手写识别系统

附所有执行结果:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值