《机器学习实战》分类篇01.K近邻算法

K近邻算法

1.简介

简单来说:k-近邻算法采用测量不同特征值之间的距离方法进行分类。
K近邻算法(k-nearest neighbor, k-NN)是Hart P提出的一种基本分类与回归方法。工作原理:存在一个样本数据集合,也称训练样本集,**并且样本集中每个数据都存在标签,(即知道了样本集中每一个数据与所属分类的对应关系。)**然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
举例:
有人曾经统计过很多电影的打斗镜头和接吻镜头,假如有一部未看的电影,如何确定它是爱情片还是动作片呢?这个问题就能用k-NN来解决。
我们可以根据已知的一些样本电影(打斗镜头和接吻镜头也已知),通过计算未知电影与样本电影的距离,结果按递增排序,确定k值,如果前k部样本电影都是爱情平,那未知电影就是爱情片了。

2.k-近邻算法的优缺点:

  • 优点:精度高、对异常值不敏感、无数据输入假定。
  • 缺点:计算复杂度高、空间复杂度高。
  • 适用数据范围:数值型和标称型。

3.k-近邻算法的一般流程:

  1. 收集数据:可以使用任何方法。
  2. 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
  3. 分析数据:可以使用任何方法。
  4. 训练算法:此步骤不适用于k-近邻算法。
  5. 测试算法:计算错误率。
  6. 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。

4.实战操作1:

4.1准备:使用Python导入数据

PyCharm中新建k近邻算法的project,再在project中新建kNN.py,打开kNN.py,创建一个createDataSet()函数,该函数用于创建数据集和标签。

from numpy import *  # 导入科学计算包NumPy;
import operator      # 导入运算符模块;

def createDataSet():
	# labels包含了每个数据点的标签信息,labels包含的元素个数等于group矩阵行数
	group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
	labels = ['A','A','B','B']
	return group, labels

if __name__ == '__main__':
	group, labels = createDataSet()
	print(group)
	print(labels)

输出结果:

4.2从文本文件中解析数据

继续在kNN.py中创建新函数classify0,主要目的是为每组数据分类。
其伪代码:
对未知类别属性的数据集中的每个点依次执行以下操作:

  1. 计算已知类别数据集中的点与当前点之间的距离
  2. 按照距离递增次序排序
  3. 选取与当前点距离最下的k个点
  4. 确定前k个点所在的类别的出现频率
  5. 返回前k个点出现频率最高的类别作为当前点的预测分类
from numpy import *  # 导入科学计算包NumPy;
import operator      # 导入运算符模块;

def createDataSet():
	# labels包含了每个数据点的标签信息,labels包含的元素个数等于group矩阵行数
	group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
	labels = ['A','A','B','B']
	return group, labels
def classify0(inX, dataSet, labels, k):
	# numpy函数shape[0]返回dataSet的行数
	dataSetSize = dataSet.shape[0]
	# 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
	diffMat = tile(inX, (dataSetSize,1)) - dataSet
	# 二维特征相减后平方
	sqDiffMat = diffMat ** 2
	# sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis = 1)
    # 开方,计算距离
    distances = sqDistances ** 0.5
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndicies = distances.argsort()
    # 定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
    	# 取出前k个元素的类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        # 计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # python3中用items()替换python2中的iteritems()
    # key=operator.itemgetter(1)根据字典的值进行排序
    # key=operator.itemgetter(0)根据字典的键进行排序
    # reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),
                              key = operator.itemgetter(1),reverse = True)
    # 返回次数最多的类别,即所要分类的类别
    return sortedClassCount[0][0]

if __name__ == '__main__':
	group, labels = createDataSet()
	print(group)
	print(labels)
	print(classify0([0,0],group,labels,3)

输出结果:

5.实战操作2

使用k-近邻算法改进约会网站的配对效果
海伦发现她曾交往过三种类型的人:

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人
    尽管发现上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的分类。她觉得在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人,海伦希望改进这个配对效果,此外她还收集了一些约会网站的数据信息:datingTestSet.txt,总共有1000行样本数据,样本主要包含以下3中特征:
  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗费时间百分比
  • 每周消费的冰淇淋公升数
5.1准备数据:从文本文件中解析数据

在将特征数据输入到分类器前,必须将待处理的数据的格式改变为分类器可以接受的格式。故在kNN.py中继续创建file2matrix()函数,输入为文本名字符串,输出为训练样本矩阵和类标签向量。

from numpy import *  # 导入科学计算包NumPy;

def file2matrix(filename):
    fr = open(filename)  # 打开文本文件
    arrayOLines = fr.readlines()   # 读取文件内容
    numberOfLines = len(arrayOLines)  # 获得文件行数
    returnMat = zeros((numberOfLines,3))  # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    classLabelVector = []   # 返回的分类标签向量
    index = 0
    for line in arrayOLines:
        line = line.strip()   #  .strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        # 使用 .split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t')
        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat,classLabelVector
    
if __name__ == '__main__':
	# datingTestSet.txt数据集放到自己的工程目录下
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    print(datingDataMat)
    print(datingLabels[0:20])  # 只打印前20个datingLbaels

输出结果:

5.2分析数据:使用Matplotib创建散点图

使用Matplotib要先导入:

import matplotlib
import matplotlib.pyplot as plt

把这两行代码加到kNN.py中,然后在main函数中添加:

    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])
    plt.show()

输出结果:

散点图分别表示:“玩视频游戏所耗时间百分比”(横)和“每周所消费的冰淇淋公升数”(纵)

由于上图没有使用样本分类的特征值,比较难看出数据信息,所以采用彩色来标记:

# 把ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])修改成如下
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2],15.0*array(datingLabels),15.0*array(datingLabels))

重新输出结果:

如果能加上属性值说明,散点图就更完整了。完整代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Pluto time: 2019/10/25

from numpy import *
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines

def file2matrix(filename):
    fr = open(filename)  # 打开文本文件
    arrayOLines = fr.readlines()   # 读取文件内容
    numberOfLines = len(arrayOLines)  # 获得文件行数
    returnMat = zeros((numberOfLines,3))  # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    classLabelVector = []   # 返回的分类标签向量
    index = 0
    for line in arrayOLines:
        line = line.strip()   #  .strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        # 使用 .split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t')
        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat,classLabelVector

def showdatas(datingDataMat, datingLabels):
    # 设置汉字格式,simsun.ttc 是Windows10自带的简体,路径一般都是这个
    font = FontProperties(fname = r"c:\Windows\fonts\simsun.ttc", size = 14)
    # 将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)
    # 当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域
    fig, axs = plt.subplots(nrows = 2, ncols = 2,sharex = False, sharey = False, figsize = (13,8))

    numberOfLabels = len(datingLabels)
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')

    # 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5
    axs[0][0].scatter(x = datingDataMat[:,0],y = datingDataMat[:,1],color = LabelsColors,s = 15,alpha = 0.5)
    # 设置标题,x轴label,y轴label
    axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比',FontProperties = font)
    axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数',FontProperties = font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗的时间占比',FontProperties = font)
    plt.setp(axs0_title_text, size = 9, weight = 'bold', color = 'red')
    plt.setp(axs0_xlabel_text, size = 7, weight = 'bold', color = 'black')
    plt.setp(axs0_ylabel_text, size = 7, weight = 'bold', color = 'black')

    axs[0][1].scatter(x = datingDataMat[:,0],y = datingDataMat[:,2],color = LabelsColors,s = 15,alpha = 0.5)
    axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰淇淋公式数',FontProperties = font)
    axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数',FontProperties = font)
    axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰淇淋公式数',FontProperties = font)
    plt.setp(axs1_title_text, size = 9, weight = 'bold', color = 'red')
    plt.setp(axs1_xlabel_text, size = 7, weight = 'bold', color = 'black')
    plt.setp(axs1_ylabel_text, size = 7, weight = 'bold', color = 'black')

    axs[1][0].scatter(x = datingDataMat[:,1],y = datingDataMat[:,2],color = LabelsColors,s = 15,alpha = 0.5)
    axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗的时间占比与每周消费的冰淇淋公式数',FontProperties = font)
    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗的时间占比',FontProperties = font)
    axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰淇淋公式数',FontProperties = font)
    plt.setp(axs2_title_text, size = 9, weight = 'bold', color = 'red')
    plt.setp(axs2_xlabel_text, size = 7, weight = 'bold', color = 'black')
    plt.setp(axs2_ylabel_text, size = 7, weight = 'bold', color = 'black')

    # 设置图例
    didntLike = mlines.Line2D([], [], color='black', marker='.',
                      markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.',
                      markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.',
                      markersize=6, label='largeDoses')
    # 添加图例
    axs[0][0].legend(handles=[didntLike,smallDoses,largeDoses])
    axs[0][1].legend(handles=[didntLike,smallDoses,largeDoses])
    axs[1][0].legend(handles=[didntLike,smallDoses,largeDoses])
    plt.savefig('F:\\PycharmProjects\\Pic\\kNN.png')  # 保存图片并指定dpi
    # 显示图片
    plt.show()

if __name__ == '__main__':
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    print(datingDataMat)
    print(datingLabels[0:20])
    showdatas(datingDataMat,datingLabels)

输出结果:

看图分析:从左上角更能清晰地展示这两个特征更容易区分数据点地从属地类别,也能反映出海伦是更喜欢那种每年飞行里程数不能太多,但又有时间玩游戏的人,间接反映出海伦不喜欢那种工作狂或不上进的游戏爱好者,而是喜欢那种热爱工作又玩游戏(有休闲时间)的人。(个人见解)

5.3准备数据:归一化数值

给出下表四组样本,如果想要计算样本3和4之间的距离,欧拉公式:
( 0 − 67 ) 2 + ( 20000 − 32000 ) 2 + ( 1.1 − 0.1 ) 2 \sqrt (0-67)^2+(20000-32000)^2+(1.1-0.1)^2 ( 067)2+(2000032000)2+(1.10.1)2

样本玩游戏所消耗时间百分比每年获得的飞行常用里程数每周消费的冰淇淋公升数样本分类
10.84000.51
212134000.93
30200001.12
467320000.12

很容易发现如此求距离,每年获得的飞行常用里程数对计算结果影响最大,故,在处理这种不同取值范围的特征值时,通常采用的方法是将数值归一化如将数值范围处理为0到1或-1到1之间,下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

newValue = (oldValue - min) / (max - min)

其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做,在kNN.py文件中编写名为autoNorm的函数,用该函数自动将数据归一化。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Pluto time: 2019/10/25

from numpy import *
import operator

def file2matrix(filename):
    fr = open(filename)  # 打开文本文件
    arrayOLines = fr.readlines()   # 读取文件内容
    numberOfLines = len(arrayOLines)  # 获得文件行数
    returnMat = zeros((numberOfLines,3))  # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    classLabelVector = []   # 返回的分类标签向量
    index = 0
    for line in arrayOLines:
        line = line.strip()   #  .strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        # 使用 .split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t')
        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat,classLabelVector

def autoNorm(dataSet):
    # 获得最小、最大值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 最小到最大值的范围
    ranges = maxVals - minVals
    # shape(dataSet)返回dataSet的矩阵行列数
    normDataSet = zeros(shape(dataSet))
    # 返回dataSet的行数
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1))
    # 除以最大和最小值的差,得到归一化数据
    normDataSet = normDataSet / tile(ranges,(m,1))
    # 返回归一化数据结果,数据范围,最小值
    return normDataSet,ranges,minVals

if __name__ == '__main__':
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    normDataSet,ranges,minVals = autoNorm(datingDataMat)
    print(normDataSet)
    print(ranges)
    print(minVals)

输出结果:

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

机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。注意:10%是随机选择的。
完整测试算法代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Pluto time: 2019/10/25

from numpy import *
import operator

def classify0(inX, dataSet, labels, k):
    # numpy 函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = tile(inX,(dataSetSize,1)) - dataSet
    sqDiffMat = diffMat ** 2   # 二维特征相减后平方
    # sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances ** 0.5  # 开方,计算距离
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndicies = distances.argsort()
    # 定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素的类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        # 计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # python3中用items()替换python2中的iteritems()
    # key=operator.itemgetter(1)根据字典的值进行排序
    # key=operator.itemgetter(0)根据字典的键进行排序
    # reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),
                              key = operator.itemgetter(1),reverse = True)
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)  # 打开文本文件
    arrayOLines = fr.readlines()   # 读取文件内容
    numberOfLines = len(arrayOLines)  # 获得文件行数
    returnMat = zeros((numberOfLines,3))  # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    classLabelVector = []   # 返回的分类标签向量
    index = 0
    for line in arrayOLines:
        line = line.strip()   #  .strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        # 使用 .split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t')
        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat,classLabelVector

def autoNorm(dataSet):
    # 获得最小、最大值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 最小到最大值的范围
    ranges = maxVals - minVals
    # shape(dataSet)返回dataSet的矩阵行列数
    normDataSet = zeros(shape(dataSet))
    # 返回dataSet的行数
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1))
    # 除以最大和最小值的差,得到归一化数据
    normDataSet = normDataSet / tile(ranges,(m,1))
    # 返回归一化数据结果,数据范围,最小值
    return normDataSet,ranges,minVals

def datingClassTest():
    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    hoRatio = 0.10  # 取所有数据的百分之十
    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    normMat,ranges,minVals = autoNorm(datingDataMat)
    # 获得normMat的行数
    m = normMat.shape[0]
    #百分之十的测试数据的个数
    numTestVecs = int(m * hoRatio)
    errorCount = 0.0

    for i in range(numTestVecs):
        #前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],4)
        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)))

if __name__ == '__main__':
    datingClassTest()

输出结果:

分析:分类器处理约会数据集的错误率是4%,我们可以改变函数datingClassTest()函数内变量hoRotio和变量k的值,检测错误率是否随着变量值的变化而增加?依赖于分类算法、数据集和程序设置,分类器的输出结果可能有很大不同。(试了下k=3,5,6都是5%错误率,而k=7是4%错误率)

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

5.4已经对分类器测试过了,现在给海伦一小段程序,运行程序后,可以输入一个人的信息,程序会给出她对对方喜欢程度的预测值。
完整程序:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Pluto time: 2019/10/25

from numpy import *
import operator

def classify0(inX, dataSet, labels, k):
    # numpy 函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = tile(inX,(dataSetSize,1)) - dataSet
    sqDiffMat = diffMat ** 2   # 二维特征相减后平方
    # sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances ** 0.5  # 开方,计算距离
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndicies = distances.argsort()
    # 定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素的类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        # 计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    # python3中用items()替换python2中的iteritems()
    # key=operator.itemgetter(1)根据字典的值进行排序
    # key=operator.itemgetter(0)根据字典的键进行排序
    # reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),
                              key = operator.itemgetter(1),reverse = True)
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)  # 打开文本文件
    arrayOLines = fr.readlines()   # 读取文件内容
    numberOfLines = len(arrayOLines)  # 获得文件行数
    returnMat = zeros((numberOfLines,3))  # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    classLabelVector = []   # 返回的分类标签向量
    index = 0
    for line in arrayOLines:
        line = line.strip()   #  .strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        # 使用 .split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片
        listFromLine = line.split('\t')
        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat,classLabelVector

def autoNorm(dataSet):
    # 获得最小、最大值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 最小到最大值的范围
    ranges = maxVals - minVals
    # shape(dataSet)返回dataSet的矩阵行列数
    normDataSet = zeros(shape(dataSet))
    # 返回dataSet的行数
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1))
    # 除以最大和最小值的差,得到归一化数据
    normDataSet = normDataSet / tile(ranges,(m,1))
    # 返回归一化数据结果,数据范围,最小值
    return normDataSet,ranges,minVals

def classifyPerson():
    # 输出结果
    resultList = ['一点也不喜欢','有点喜欢','非常喜欢']
    # 三维特征用户输入
    precentTats = float(input("玩视频游戏所耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰淇淋公升数:"))
    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    normMat,ranges,minVals = autoNorm(datingDataMat)
    # 生成NumPy数组,测试集
    inArr = array([precentTats,ffMiles,iceCream])
    # 测试集归一化
    norminArr = (inArr - minVals) / ranges
    # 返回分类结果
    classifierResult = classify0(norminArr,normMat,datingLabels,3)
    print("你可能%s这个人" % (resultList[classifierResult - 1]))

if __name__ == '__main__':
    classifyPerson()

输出结果:

运行小程序后,依次输入[15,50000,0.5],预测结果“你可能一点也不喜欢这个人”

参考文献:

  1. 《机器学习实战》-k近邻算法
  2. 优秀大神:https://blog.csdn.net/c406495762
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZPILOTE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值