机器学习之K-近邻算法

简单k-近邻算法

1、k-近邻法简介

k近邻法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一种基本分类与回归方法。它的工作原理是:存在一个样本数据集合,也称作为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

2、距离量度

我们已经知道K-近邻算法是根据特征进行比较,然后提取样本集中特征最相似数据(即最近邻)的分类标签。那接下来要解决的就是如何比较的问题。

图1-1
根据上图,若直接根据人眼来看,我们可以很简单的就知道红点标记的电影属于动作片,因为他距离两个动作片的距离最近。在K-近邻算法里,我们利用两点距离公式来计算距离
在这里插入图片描述
但两点距离公式仅能用于二维空间(即只有x、y坐标),因此两点距离公式仅能满足我们一部分需求,所以我们还需要一个更适用的公式来解决我们的距离问题——欧氏距离
在这里插入图片描述
有了公式之后我们开始计算距离,通过计算,我们可以得到如下结果:

  • (101,20)->动作片(108,5)的距离约为16.55
  • (101,20)->动作片(115,8)的距离约为18.44
  • (101,20)->爱情片(5,89)的距离约为118.22
  • (101,20)->爱情片(1,101)的距离约为128.69

通过计算可知,红色圆点标记的电影到动作片 (108,5)的距离最近,为16.55。如果算法直接根据这个结果,判断该红色圆点标记的电影为动作片,这个算法就是最近邻算法,而非k-近邻算法。那么k-近邻算法是什么呢?k-近邻算法步骤如下:

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

假设我们设置参数k=3,则根据上面计算结果我们排序后选择前三个数据:动作片(108,5)距离16.55,动作片(115,8)距离18.44,爱情片(5,89)距离118.22,在这前三个数据中,动作片占比为三分二,爱情片占比三分之一,因此我们判断红色标记的电影为动作片。

3、python代码实现

准备工作:

 - 安装numpy库(Conda:conda install numpy     Pip: pip install numpy)
 - 安装matplotlib库(Conda: conda install matplotlib  PIP:pip install matplotlib)

3-1 电影分类案例

  • 电影共有两个类别:爱情片、动作片(没有爱情动作片)
  • 二维特征:[打斗次数,接吻次数]
  • 样本数据创建:group = np.array([[1,101],[5,89],[108,5],[115,8]])
  • 测试数据:test = [101,20]
# -*- coding: UTF-8 -*-
import numpy as np
import operator

 
def createDataSet():
    #四组二维特征
    group = np.array([[1,101],[5,89],[108,5],[115,8]])
    #四组特征的标签
    labels = ['爱情片','爱情片','动作片','动作片']
    return group, labels
 
"""
函数说明:kNN算法,分类器
 
Parameters:
    inX - 用于分类的数据(测试集)
    dataSet - 用于训练的数据(训练集)
    labes - 分类标签
    k - kNN算法参数,选择距离最小的k个点
Returns:
    sortedClassCount[0][0] - 分类结果
 
"""
def classify0(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]
 
if __name__ == '__main__':
    #创建数据集
    group, labels = createDataSet()
    #测试集
    test = [101,20]
    #kNN分类
    test_class = classify0(test, group, labels, 3)
    #打印分类结果
    print(test_class)

输出结果:动作片

3-2 海伦约会案例

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

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人

海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。datingTestSet.txt数据下载: 数据集下载

海伦收集的样本数据主要包含以下3种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所消耗时间百分比
  • 每周消费的冰淇淋公升数

分析数据

40920 8.326976 0.953952 largeDoses
14488 7.153469 1.673904 smallDoses
26052 1.441871 0.805124 didntLike
75136 13.147394 0.428964 didntLike
38344 1.669788 0.134296 didntLike
72993 10.141740 1.032955 didntLike
35948 6.830792 1.213192 largeDoses
42666 13.276369 0.543880 largeDoses
67497 8.631577 0.749278 didntLike
35483 12.273169 1.508053 largeDoses
50242 3.723498 0.831917 didntLike
63275 8.385879 1.669485 didntLike
5569 4.875435 0.728658 smallDoses
51052 4.680098 0.625224 didntLike
77372 15.299570 0.331351 didntLike

上面数据为数据集中的一部分,可以发现第一列数据(每年获得的飞行常客里程数)数值太大,会有什么问题出现?
在这里插入图片描述
我们很容易发现,上面方程中数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于表2.1中其他两个特征-玩视频游戏所耗时间占比和每周消费冰淇淋公斤数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。但海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。

在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

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

代码如下:

import numpy as np
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import operator as op

'''
函数说明: 打开并解析文件,对数据进行分类: 1、didntLike 2、smallDoses 3、largeDoses

Parameters:
    filename - 文件名

Returns:
    returnMat - 特征矩阵
    classLabelVector - 分类标签向量
'''
def file2matrix(filename):
    #打开文件
    fr = open(filename)
    #读取文件所有内容
    arrayOfLines = fr.readlines()
    #获取文件行数
    numberOfLines = len(arrayOfLines)

    #创建矩阵
    returnMat = np.zeros((numberOfLines,3))
    #创建分类标签向量
    classLabelVector = []

    #读取内容到矩阵以及标签向量中
    #定义行索引
    index = 0
    for line in arrayOfLines:
        line = line.strip()
        #根据\t对字符串进行分割
        listOfLine = line.split('\t')
        returnMat[index,:] = listOfLine[0:3]
        if listOfLine[-1] == "didntLike":
            classLabelVector.append(1)
        elif listOfLine[-1] == "smallDoses":
            classLabelVector.append(2)
        elif listOfLine[-1] == "largeDoses":
            classLabelVector.append(3)
        
        #行索引加1
        index += 1

    return returnMat, classLabelVector

'''
函数说明:KNN算法

Parameters:
    test - 测试数据集
    dataSet - 特征矩阵
    labels - 标签向量
    k - KNN算法参数

returns:
    sortedClassCount[0][0] - 分类结果
'''

def classify(test, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    #相减
    diffMat = np.tile(test, 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值