本文始发于个人公众号:TechFlow,原创不易,求个关注

今天的文章给大家分享机器学习领域非常简单的模型——KNN,也就是K Nearest Neighbours算法,翻译过来很简单,就是K最近邻居算法。这是一个经典的无监督学习的算法,原理非常直观,易于理解。

监督与无监督

简单介绍一下监督这个概念,监督是supervised的直译,我个人觉得不太准确,翻译成有标注和无标注可能更加准确。也就是说如果模型在学习的时候,既能够看到样本的特征又可以看到样本的结果,那么就是有监督学习,如果只能看到特征,但是并不能知道这些特征对应的结果,那么就是无监督学习。

之前我们介绍的线性回归和逻辑回归模型就是典型的有监督模型,因为模型在训练的时候知道样本的结果,并且根据我们设计的损失函数朝着贴近样本真实结果的方向“努力”。而今天介绍的KNN算法则是一个经典的无监督学习模型,算法在训练的时候并不知道正确的结果是什么,也因此模型根本没有损失函数这个概念,那么自然整个算法的运行原理也很监督模型大相径庭。

算法概述

其实KNN算法的原理非常简单,简单到只有一句话,就是找到样本的K个邻居,然后这K个邻居出现次数最多的结果就是答案。

但是我们怎么定义邻居,又怎么找到这些邻居呢?

在回答这个问题之前,我们先来看一个例子。

假设现在有这么一个问题,我需要知道全城的用户有哪些用户有车,但是我们只知道用户的家庭地址,那么该怎么办呢?

很显然,我需要在全城做一个调查,也就是对全城市民做一个抽样,抽取一部分做个是否有车的调研。对于剩下的用户呢,我去寻找离他最近的几个邻居,看看他的这几个邻居是否有车。如果离他近的邻居大多数都有车,那么,我可以认为,该用户可能住在富人区,他有车的概率比较大。如果他的邻居都没有车,可能他住在穷人区,他很有可能也没有车。

你可能会说中国不像美国可以划分成穷人区和富人区,往往在一个区域内穷富是杂居的,用这种方法得出的结果准确率肯定不高。

的确存在这个问题,所以我们可以在此基础上做一点优化,很简单,我们只知道用户住在哪里是不够的,我们可能还需要了解用户的收入情况。在寻找他最近的邻居的过程当中,除了要考虑距离上的远近之外,还需要保证收入尽可能接近。

如果能知道和他距离和收入都接近的邻居是否有车,那么大概率可以判断这个用户是否有车。重复这个算法,我就可以通过少量的样本,算出全体样本是否有车的情况。

说到这里,想必你应该能明白,在KNN算法的范畴当中,“邻居”指的不是地理上的邻近关系,而指的是样本空间的接近

我们都知道,对于向量A(a1,a2,a3...an),B(b1,b2,b3...bn)

在机器学习当中,我们通常会把一条样本数据当做向量空间中的一条向量。比如在刚刚的问题当中,用户A,他的样本可能是(120.3213, 30.1312, 10.5),指的是居住地点的经纬度和年收入。也可能是(城东, 泾河花园, 10.5),或者是(城东, 沿河西路, 泾河花园, 10.5)等等。

同样的一条样本,表示成向量就有多种形式。对于不同的问题而言,不同的表示方法拥有不同的效果。在当前问题当中,我们需要计算向量和向量之间的距离,显然,使用第一种经纬度的表示方式更好。

算法原理

通过上面这个例子,其实我们已经把算法的整个运行过程讲解清楚了。所谓的k-邻近算法,其实就是使用距离样本最近的k个样本的结果来代表当前样本的结果。

整个算法的流程如下:

  1. 采集一批有标注结果的样本,设为s
  2. 遍历每一个未知结果的样本
  3. 遍历s,计算s中的每一个样本和的距离
  4. 根据距离进行排序,选出距离最小的k个样本
  5. 选出这k个样本中出现频次最多的类别作为的结果

算法流程不难理解,但是其中有几个注意点:

首先,每一个样本其实指的是样本空间里的一个向量。既然是向量,并且要计算样本之间的距离,那么向量的每一个维度都必须是实数。一般情况下,字符串是无法作为特征计算距离的。

其次,向量距离的计算方法。常规来说,向量之间的距离可以理解成空间中两个点的距离,关于这个距离常规上有几种计算的公式。

第一种叫做欧式公式,就是我们刚才列的也是最常见的欧几里得距离公式:

第二种叫做曼哈顿距离,曼哈顿是纽约的CBD,既然是街区,从一个路口到另一个路口的距离显然是不能从街区中跨越的。所以两个路口的距离,其实是两点的直角连线距离。

假设一个点坐标是(3, 4) ,另一个点的坐标是(5, 1),这两点的距离d = | 3 - 5| + | 4 - 1| = 5

公式为:

在距离计算的方法当中,欧氏距离和曼哈顿距离最常用,除了这两种之外还有切比雪夫距离和闵可夫斯基距离等,一般不太常用,我们不多做赘述,感兴趣的可以自行谷歌。

代码实现

下面就到了我们紧张刺激的代码实现环节,KNN的原理不算难,只要能理解,稍微思考一下我想大部分同学应该都能写出来。所以之前阿里的校招经常用KNN作为笔试题,考察一下同学的代码能力以及对基础模型的理解情况。所以实现是一方面,将代码写漂亮,实现完美又是另一方面。

仔细想一下当我们的数据有了之后,KNN主体就只有一个函数,我们先来看一下不使用任何库函数进行实现的代码:

def classify(vector, dataSet, labels, k):
dis = []
for i in range(len(dataSet)):
data = dataSet[i]
d = calculate_distance(vector, data)
dis.append(d)
dis_index = sorted(enumerate(dis), key=lambda x: x[1])
label_map = {}
for i in range(k):
label = labels[dis_index[i][0]]
if label in label_map:
label_map[label] += 1
else:
label_map[label] = 1
maxi = 0
label = None
for i in label_map:
if label_map[i] > maxi:
maxi = label_map[i]
label = i

return label

其中calculate_distance是计算两个向量距离的函数,实现也很简单,就是套用一下上面刚才提到的距离公式,基本没有难度:

def calculate_distance(vectorA, vectorB):
d = 0
for i in range(len(vectorA)):
d += (vectorA[i] - vectorB[i]) * (vectorA[i] - vectorB[i])
return math.sqrt(d)

但是显然这是没有必要的,我们多做了很多无用功。灵活地使用库函数,可以将代码缩减到不可思议的地步:

import random

import numpy as np
from collections import Counter

def classify(x, dataset, labels, K):
x, dataset = np.array(x), np.array(dataset)
# 通过numpy计算距离,numpy有广播机制
# 会自动将x和dataset当中的每一行计算距离
dis = np.sqrt(np.sum((x - dataset) ** 2, axis=1))
# 按照距离排序,返回结果对应的下标
topKIdices = np.argsort(dis)[:K]
labels = np.array(labels)
# 使用Counter进行计数,返回数量最多的
counter = Counter(labels[topKIdices])
return counter.most_common(1)[0][0]

不知道大家看到有没有觉得有点不可思议,我们一个for循环都没有用到就实现了KNN算法,只有短短5行。其中Numpy是我们做机器学习非常常用的包,由于Numpy有非常多的API可以非常方便地进行计算,所以我们会在之后单独开一个Numpy专题。关于Counter等库函数的用法,在之前介绍collections的文章当中介绍过,如果有遗忘的同学可以在公众号回复collections获取文章。

最后,我们创造一个简单的sample来验证一下:

def create_data_set():
dataset = np.array([[0.5, 0], [0, 0.5], [1.5, 1], [1, 1.5]])
labels = ['A', 'A', 'B', 'B']
return dataset, labels

运行程序得到的结果是A。

优化

到这里我们还没有结束,还有一个问题值得讨论。在我们刚才叙述算法流程的过程当中,有一个关键点被我们忽略了。

我们的样本由特征构成,我们对特征向量计算距离。问题是这些特征并不是一个维度的,还用上面的例子。我们为了判断一个用户是否有车,用到了三个特征,分别是他家的经度、纬度和年收入。注意,经纬度和年收入并不是一个量纲下的变量,从数学上我们当然可以对它们当做是一个量纲来计算,但是这样显然是不准确的。最主要的问题是,不同量纲的特征波动的幅度可能完全不同。

这一点应该不难理解,对于经纬度而言,取值范围假设是[0, 360],但是年收入的浮动范围可能是上千万。显然如果我们直接来计算距离的话,那么主要的权重就落在了年收入上,这个模型就发生了倾斜,这显然是我们不想看到的,因为会影响模型最终的效果。为了解决这个问题,我们需要将这些量纲归一化,消除量纲带来的影响。这也是KNN关键的优化。

归一化的方式很多,比较常用的有两种。一种是Standardization,又称为Z-score normalization,归一化之后的数据服从正态分布,它的公式如下:

这里的分别对应样本的均值和方差,归一化之后的结果在[-1, 1]之间。

另一种归一化的方法叫做Min-max Scaling,它是根据样本的最大最小值进行的缩放。公式如下:

这样缩放之后的结果在[0, 1]之间,最大值时取1,最小值时取0,这也是最常用的归一化的方法之一。通过归一化之后,我们可以将不同量纲下的变量缩放到同一个取值范围当中,从而将特征拉到平等的维度,这样模型学习的效果更佳均匀,不容易被其中某一个或者某几个特征带偏。

今天的文章就是这些,KNN是非常基础的机器学习算法,相信对大家而言应该并不难。如果觉得有所收获,请顺手点个关注或者转发吧,你们的举手之劳对我来说很重要。

机器学习基础——简单易懂的K邻近算法,根据邻居“找自己”的更多相关文章

  1. 机器学习算法及代码实现–K邻近算法

    机器学习算法及代码实现–K邻近算法 1.K邻近算法 将标注好类别的训练样本映射到X(选取的特征数)维的坐标系之中,同样将测试样本映射到X维的坐标系之中,选取距离该测试样本欧氏距离(两点间距离公式)最近 ...

  2. 《机器学习实战》学习笔记一K邻近算法

     一. K邻近算法思想:存在一个样本数据集合,称为训练样本集,并且每个数据都存在标签,即我们知道样本集中每一数据(这里的数据是一组数据,可以是n维向量)与所属分类的对应关系.输入没有标签的新数据后,将 ...

  3. <机器学习实战>读书笔记--k邻近算法KNN

    k邻近算法的伪代码: 对未知类别属性的数据集中的每个点一次执行以下操作: (1)计算已知类别数据集中的点与当前点之间的距离: (2)按照距离递增次序排列 (3)选取与当前点距离最小的k个点 (4)确定 ...

  4. [机器学习实战] k邻近算法

    1. k邻近算法原理: 存在一个样本数据集,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系.输入没有标签的新数据后,将新数据的每个特征与样本集中数据对 ...

  5. 监督学习——K邻近算法及数字识别实践

    1. KNN 算法 K-近邻(k-Nearest Neighbor,KNN)是分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一.该方法的思路是:如果一个样本在特征空间中的k个最相似( ...

  6. k邻近算法(KNN)实例

    一 k近邻算法原理 k近邻算法是一种基本分类和回归方法. 原理:K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例,这K个实例的多数属于某个类,就把该输入实 ...

  7. kaggle赛题Digit Recognizer:利用TensorFlow搭建神经网络(附上K邻近算法模型预测)

    一.前言 kaggle上有传统的手写数字识别mnist的赛题,通过分类算法,将图片数据进行识别.mnist数据集里面,包含了42000张手写数字0到9的图片,每张图片为28*28=784的像素,所以整 ...

  8. Python实现kNN(k邻近算法)

    Python实现kNN(k邻近算法) 运行环境 Pyhton3 numpy科学计算模块 计算过程 st=>start: 开始 op1=>operation: 读入数据 op2=>op ...

  9. 机器学习 Python实践-K近邻算法

    机器学习K近邻算法的实现主要是参考<机器学习实战>这本书. 一.K近邻(KNN)算法 K最近邻(k-Nearest Neighbour,KNN)分类算法,理解的思路是:如果一个样本在特征空 ...

随机推荐

  1. numpy中的ndarray与pandas中的series、dataframe的转换

    一个ndarray是一个多维同类数据容器.每一个数组有一个dtype属性,用来描述数组的数据类型. Series是一种一维数组型对象,包含了一个值序列,并且包含了数据标签----索引(index). ...

  2. VSAN磁盘扩容与收缩

    删除闪存盘后,整个磁盘组都会被删除 假如一个磁盘组里面只有一块HDD盘,删除此HDD盘,磁盘组也只接被删除 可以整体删除整个磁盘组

  3. PP图|QQ图|正态性检验|K-S检验|S-W检验|

    应用统计学: 物理条件一致时,有理由认为方差是一致的.配对检验可排除物理影响,使方差变小,但是自由度降低了,即样本数变小.二项分布均值假设检验的模型要依据前面的假设条件: PP图统计图要看中间的贴近情 ...

  4. openssl内存分配,查看内存泄露

    openssl内存分配 用户在使用内存时,容易犯的错误就是内存泄露.当用户调用内存分配和释放函数时,查找内存泄露比较麻烦.openssl提供了内置的内存分配/释放函数.如果用户完全调用openssl的 ...

  5. 记一笔MyBatis的坑

    记一笔MyBatis的坑 1.sql查询concat()连接函数xml运行查询乱码 ) , ), char)'%') bll FROM fact_dkxx sq 由于连接的字符串中包含数字与百分比字符 ...

  6. ES6下的Function.bind方法

    在JavaScript的使用中,this的指向问题始终是一个难点.不同的调用方式,会使this指向不同的对象.而使用call,apply,bind等方式,可改变this的指向,完成一些令人惊叹的黑魔法 ...

  7. 吴裕雄--天生自然HTML学习笔记:HTML <div> 和<span>

    HTML <div> 和<span> HTML 可以通过 <div> 和 <span>将元素组合起来. HTML 区块元素 大多数 HTML 元素被定义 ...

  8. get请求直接通过浏览器发请求传数组或者list到后台

    原文链接: http://blog.csdn.net/qq_27093465/article/details/76160419 感谢原作者 例如: http://localhost:27001/tes ...

  9. 机器学习算法的基本知识(使用Python和R代码)

    本篇文章是原文的译文,然后自己对其中做了一些修改和添加内容(随机森林和降维算法).文章简洁地介绍了机器学习的主要算法和一些伪代码,对于初学者有很大帮助,是一篇不错的总结文章,后期可以通过文中提到的算法 ...

  10. Swift 浅谈Struct与Class

    讨论Struct与Class之前,我们先来看一个概念:Value Type(值类型),Reference Type(引用类型): 1. 值类型的变量直接包含他们的数据,对于值类型都有他们自己的数据副本 ...