机器学习笔记--常见算法(1)--k近邻算法介绍及应用实战

教程:
http://cuijiahua.com/blog/2017/11/ml_1_knn.html

1.KNN简介

K-nearest neighbor
KNN categorizes objects based on the classes of their nearest neighbors in the dataset
KNN predictions assume that objects near each other are similar. Distance metrics, such as Euclidean, city block, cosine, and Chebychev, are used to find the nearest neighbor.
这里写图片描述
KNN算法的思想不难理解,首先,了解一下最近邻:找出与待预测值距离最近的值,该值就是待预测值可能取到的值。k近邻是指:先计算待预测值与所有已给定的值的距离,将这些距离按从小到大排序,取前k个数中出现次数最多的那个值,就是待预测值取到的值。

KNN算法优缺点:
这里写图片描述

举个例子:使用k-近邻算法分类一个电影是爱情片还是动作片
这里写图片描述
上表是我们已有的数据集,即训练样本集。该数据集有两个特征:打斗镜头数和接吻镜头数。除此之外,我们也知道每个电影的所属类型,即分类标签。现给出一部未知分类的电影,根据这两个特征使用k-近邻算法可粗略得出这部电影是什么类型,如,给出打斗镜头数=100,接吻镜头数=5,分别计算(100, 5)与(1, 101), (5, 89), (108, 5), (115, 8)的距离,根据算法可得出这部电影属于动作片

2.距离度量

二维(2个特征)
可使用两点距离计算公式:(欧氏距离在二维空间上的公式)
这里写图片描述
高维(n个特征n>2)
欧氏距离:
这里写图片描述

3.k-近邻算法步骤

Step1:计算已知类别数据集中的点与当前点之间的距离;
Step2:按照距离递增次序排序;
Step3:选取与当前点距离最小的k个点;
Step4:确定前k个点所在类别的出现频率;
Step5:返回前k个点所出现频率最高的类别作为当前点的预测分类。

4.k-近邻算法实战

K-近邻算法的一般流程:
Step1:收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据。一般来讲,数据放在txt文本文件中,按照一定的格式进行存储,便于解析及处理。
Step2:准备数据:使用Python解析、预处理数据。
Step3:分析数据:可以使用很多方法对数据进行分析,例如使用Matplotlib将数据可视化。
Step4:测试算法:计算错误率。
Step5:使用算法:错误率在可接受范围内,就可以运行k-近邻算法进行分类。

4.0 实战背景

约会网站配对效果判定
海伦女士一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人。经过一番总结,她发现自己交往过的人可以进行如下分类:
不喜欢的人
魅力一般的人
极具魅力的人

海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。datingTestSet.txt数据下载:数据集下载
海伦收集的样本数据主要包含以下3种特征:
每年获得的飞行常客里程数
玩视频游戏所消耗时间百分比
每周消费的冰淇淋公升数

4.1 准备数据:数据解析

在将上述特征数据输入到分类器前,必须将待处理的数据的格式改变为分类器可以接收的格式。分类器接收的数据格式:将数据分类两部分,即特征矩阵和对应的分类标签向量

4.2 数据可视化

详见4.7代码。

4.3 准备数据:数据归一化

为什么要使用数据归一化?
举个例子,
这里写图片描述
根据上表中给出的数据,若想计算样本3和样本4之间的距离,可以使用欧拉公式计算,如下:
这里写图片描述
很容易发现,上面方程中数字差值最大的属性(飞行里程数)对计算结果影响最大,而冰激凌公升数对结果影响非常非常小,产生这种现象的原因仅仅是因为飞行常客里程数远大于其他特征值。但海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重影响到计算结果。
在处理这种不同取值范围的特征值时,通常采用的方法是将数值归一化。如将取值范围处理为0到1或者-1到1之间。可用下面的公式进行归一化:
这里写图片描述
其中,min 和max分别是数据集中的最小特征值和最大特征值
实现过程见4.7代码。

4.4 KNN算法实现分类

通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。需要注意的是,10%的测试数据应该是随机选择的,由于海伦提供的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性。
实现过程见代码。

4.5 测试算法性能

详见代码

4.6 应用:根据给出的数据输出结果

详见代码
运行给出的结果:
这里写图片描述

4.7 总代码

#!/usr/bin/env python3 
# -*- coding: UTF-8 -*-
from matplotlib.font_manager import FontProperties
import numpy as np
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import operator


# 1.准备数据:数据解析
"""
函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力

Parameters:
    filename - 文件名
Returns:
    returnMat - 特征矩阵
    classLabelVector - 分类Label向量

"""
def file2matrix(filename):
    # 打开文件
    fr = open(filename)
    # 读取文件所有内容
    arrayOLines = fr.readlines()
    # 得到文件行数
    numberOfLines = len(arrayOLines)
    # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    returnMat = np.zeros((numberOfLines, 3))
    # 返回的分类标签向量
    classLabelVector = []
    # index:行的索引值
    index = 0
    for line in arrayOLines:
        #s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        line = line.strip()
        #使用s.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


# 2.数据分析:可视化数据
"""
函数说明:可视化数据

Parameters:
    datingDataMat - 特征矩阵
    datingLabels - 分类Label
Returns:
    无
"""

def showdatas(datingDataMat, datingLabels):
    #将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=.5)
    #设置标题,x轴label,y轴label
    axs0_title_text = axs[0][0].set_title(u'Frequent flyer miles/Time spent playing video games')
    axs0_xlabel_text = axs[0][0].set_xlabel(u'Frequent flyer miles')
    axs0_ylabel_text = axs[0][0].set_ylabel(u'Time spent playing video games')
    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')

    #画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
    axs[0][1].scatter(x=datingDataMat[:,0], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5)
    #设置标题,x轴label,y轴label
    axs1_title_text = axs[0][1].set_title(u'Frequent flyer miles/Weekly consumption of ice cream liters')
    axs1_xlabel_text = axs[0][1].set_xlabel(u'Frequent flyer miles')
    axs1_ylabel_text = axs[0][1].set_ylabel(u'Weekly consumption of ice cream liters')
    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')

    #画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
    axs[1][0].scatter(x=datingDataMat[:,1], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5)
    #设置标题,x轴label,y轴label
    axs2_title_text = axs[1][0].set_title(u'Time spent playing video games/Weekly consumption of ice cream liters')
    axs2_xlabel_text = axs[1][0].set_xlabel(u'Time spent playing video games')
    axs2_ylabel_text = axs[1][0].set_ylabel(u'Weekly consumption of ice cream liters')

    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.show()

# 3.数据处理:数据归一化
"""
函数说明:对数据进行归一化

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

# 4. kNN算法实现分类

"""
函数说明:kNN算法,分类器

Parameters:
    inX - 用于分类的数据(测试集)
    dataSet - 用于训练的数据(训练集)
    labes - 分类标签
    k - kNN算法参数,选择距离最小的k个点
Returns:
    sortedClassCount[0][0] - 分类结果
"""

def classify(inX, dataSet, labels, k):
    #numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二维特征相减后平方
    sqDiffMat = diffMat**2
    #sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #开方,计算出距离
    distances = sqDistances**0.5
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    #定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        #取出前k个元素的类别
        voteIlabel = labels[sortedDistIndices[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]

# 5.测试算法性能
"""
函数说明:分类器测试函数

Parameters:
    无
Returns:
    normDataSet - 归一化后的特征矩阵
    ranges - 数据范围
    minVals - 数据最小值
"""
def datingClassTest(filename):
    #将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix(filename)
    #取所有数据的百分之十
    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 = classify(normMat[i,:], normMat[numTestVecs:m,:],
            datingLabels[numTestVecs:m], 4)
        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率:%f%%" %(errorCount/float(numTestVecs)*100))


# 6.应用:根据给出的数据输出结果

"""
函数说明:通过输入一个人的三维特征,进行分类输出

Parameters:
    无
Returns:
    无
"""
def classifyPerson(filename):
    #输出结果
    resultList = ['讨厌','有些喜欢','非常喜欢']
    #三维特征用户输入
    precentTats = float(input("Please input time spent playing video games:"))
    ffMiles = float(input("Please input frequent flyer miles:"))
    iceCream = float(input("Please input weekly consumption of ice cream liters:"))
    #打开并处理数据
    datingDataMat, datingLabels = file2matrix(filename)
    #训练集归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #生成NumPy数组,测试集
    inArr = np.array([ffMiles, precentTats, iceCream])
    #测试集归一化
    norminArr = (inArr - minVals) / ranges
    #返回分类结果
    classifierResult = classify(norminArr, normMat, datingLabels, 3)
    #打印结果
    print("你可能%s这个人" % (resultList[classifierResult-1]))



"""
函数说明:main函数

Parameters:
    无
Returns:
    无
"""

if __name__ == '__main__':
    #打开的文件名
    filename = "knnDataSet.txt"
    #打开并处理数据
    datingDataMat, datingLabels = file2matrix(filename)
    # 显示特征向量
    print(datingDataMat)
    # 显示类别
    print(datingLabels)
    # 数据可视化
    # showdatas(datingDataMat, datingLabels)
    # 显示归一化结果
    normDataSet, ranges, minVals = autoNorm(datingDataMat)
    print('normDataSet =', normDataSet)
    print('ranges =', ranges)
    print('minVals =', minVals)
    # 测试算法性能
    datingClassTest(filename)
    # 使用算法
    classifyPerson(filename)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值