K 近邻算法(KNN)是一种基本分类与回归方法,其思想非常简单 —— KNN 对新样本进行预测的方法是:根据其k个最近邻的训练实例类别,通过多数表决等方式进行预测。从 KNN 算法的基本思想可以看出,KNN 算法本身不具有显示的学习过程,仅仅是将训练数据中与待预测样本最邻近 k 个点的占多数的类作为待预测样本的类。
由此可得出 KNN 三要素:
1. 距离度量方法
为了找到最近邻的k个训练实例,我们需要对这一条件进行度量。特征空间中两个实例点的距离是两个实例点相似程度的反应。
K近邻模型的特征空间一般是 n n n 维实数向量空间 R n R^n Rn,使用的距离是欧氏距离,但也可以是其他距离。
机器学习中常见的距离度量方式有:
1.1 欧氏距离定义
欧几里得距离是我们最为熟悉也最容易理解的一种距离度量方式。在欧几里得空间中,点 [公式] 和 [公式] 之间的欧氏距离为:
它是一个纯数值,体现数值上的绝对差异。
1.2 余弦距离定义
首先引入余弦相似性的定义:
两个向量间的余弦值可以通过使用欧几里得点积公式求出:
a ⋅ b = ∣ ∣ a ∣ ∣ ∣ ∣ b ∣ ∣ c o s θ a \cdot b = ||a|| ||b|| cos \theta a⋅b=∣∣a∣∣∣∣b∣∣cosθ
给定两个属性向量 A 和 B,其余弦相似性由点积和向量长度给出:
可以看出余弦相似性的取值范围是: [ − 1 , 1 ] [-1,1] [−1,1]。即余弦相似性可能取到负值,但距离通常不可能取负数,所以余弦距离定义为:
余弦距离的取值范围是: [ 0 , 2 ] [0,2] [0,2],非负。
注意,余弦距离并不是一个严格定义的距离,虽然满足正定性和对称性但不满足三角不等式,相关示例解释参考Hulu机器学习问题与解答系列 | 第五弹:余弦距离。
1.3 余弦距离与欧氏距离
从上图可以看出,欧氏距离衡量的是空间各点的绝对距离,跟各个点所在的位置坐标直接相关;而余弦距离衡量的是空间向量的夹角,更加体现在方向上的差异。
1.4 曼哈顿距离
我们可以定义曼哈顿距离的正式意义为L1-距离或城市区块距离,也就是在欧几里得空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。
例如在平面上,坐标 ( x 1 , x 2 ) (x_1,x_2) (x1,x2) 的点 P 1 P_1 P1 与坐标 ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 的点 P 2 P_2 P2 的曼哈顿距离为:
1.5 曼哈顿与欧几里得距离
下图中红、蓝与黄线分别表示所有曼哈顿距离都拥有一样长度(12),而绿线表示欧几里得距离有 6 × 2 ≈ 8.48 6 \times \sqrt{2} \approx 8.48 6×2≈8.48 的长度:
2. K 值的选择
k 值的选择会对 KNN 算法的结果产生重大影响:
3. 分类决策规则
k 近邻算法中的分类决策规则一般是多数表决:由输入实例的k个邻近的训练实例中的多数类决定输入实例的类。
多数表决规则等价于经验风险最小化,具体解释可参考机器学习算法 | K近邻算法 (KNN) 与近邻搜索算法 K-D 树 (K-D Tree)。
优缺点总结
优点
缺点
KNN 算法应用领域比较广泛,在文本分类、模式识别、聚类分析,多分类领域中处处有 KNN 算法的身影。
参考文章
1. KD-Tree简介
kd树(k-dimensional树的简称),是一种分割k维数据空间的数据结构,主要应用于多维空间关键数据的近邻查找(Nearest Neighbor)和近似最近邻查找(Approximate Nearest Neighbor)。
其实KDTree就是二叉查找树(Binary Search Tree,BST)的变种。二叉查找树的性质如下:
1)若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值;
2)若它的右子树不为空,则右子树上所有结点的值均大于它的根结点的值;
3)它的左、右子树也分别为二叉排序树;
如果我们要处理的对象集合是一个K维空间中的数据集,我们首先需要确定是: 怎样将一个K维数据划分到左子树或右子树?
和构造1维BST树类似,只不过对于Kd树,在当前节点的比较并不是通过对K维数据进行整体的比较,而是选择某一个维度d,然后比较两个K维数据在该维度 d上的大小关系,即每次选择一个维度d来对K维数据进行划分,相当于用一个垂直于该维度d的超平面将K维数据空间一分为二,平面一边的所有K维数据 在d维度上的值小于平面另一边的所有K维数据对应维度上的值。也就是说,我们每选择一个维度进行如上的划分,就会将K维数据空间划分为两个部分,如果我 们继续分别对这两个子K维空间进行如上的划分,又会得到新的子空间,对新的子空间又继续划分,重复以上过程直到每个子空间都不能再划分为止。
以上就是构造 Kd-Tree的过程,上述过程中涉及到两个重要的问题:1)每次对子空间的划分时,怎样确定在哪个维度上进行划分;2)在某个维度上进行划分时,怎样确保建立的树尽量地平衡。树越平衡代表着分割得越平均,搜索的时间也就越少。
1.1 在哪个维度上进行划分?
一种选取轴点的策略是median of the most spread dimension pivoting strategy,统计样本在每个维度上的数据方差,挑选出对应方差最大值的那个维度。数据方差大说明沿该坐标轴方向上数据点分散的比较开(后面会发现,选择方差大的维度主要是为了减少回溯时的代价,减少子树的访问)。这个方向上,进行数据分割可以获得最好的平衡。
1.2 怎样确保建立的树尽量地平衡?
给定一个数组,怎样才能得到两个子数组,这两个数组包含的元素 个数差不多且其中一个子数组中的元素值都小于另一个子数组呢?方法很简单,找到数组中的中值(即中位数,median),然后将数组中所有元素与中值进行 比较,就可以得到上述两个子数组。同样,在维度d上进行划分时,划分点(pivot)就选择该维度d上所有数据的中值,这样得到的两个子集合数据个数就基本相同了。
2. KD-Tree构建
1)在K维数据集合中选择具有最大方差的维度k,然后在该维度上选择中值m为pivot对该数据集合进行划分,得到两个子集合;同时创建一个树结点node,用于存储;
2)对两个子集合重复步骤1的过程,直至所有子集合都不能再划分为止;
可以看出,KD-Tree的构建是一个递归的过程。
KD-Tree构建示例图:
具体解释可参考机器学习算法 | K近邻算法 (KNN) 与近邻搜索算法 K-D 树 (K-D Tree),通俗易懂。
3. KD-Tree的最近邻查找
1)将查询数据Q从根结点开始,按照Q与各个结点的比较结果向下访问Kd-Tree,直至达到叶子结点。
2)进行回溯操作,该操作是为了找到离Q更近的“最近邻点”。即判断未被访问过的分支里是否还有离Q更近的点,它们之间的距离小于dis。
注:判断未被访问过的树分支中是否还有离Q更近的点,就是判断"Q与未被访问的树分支的距离|Q(k) - m|“是否小于"Q到当前的最近邻点nearest的距离dis”。从几何空间上来看,就是判断以Q为中心,以dis为半径超球面是否与未被访问的树分支代表的超矩形相交。
总结
因为 k-d 树是二叉搜索树的变种,所以在 k-d 树上进行搜索与在二叉搜索树上搜索的过程是一致的,故 k-d 树上搜索的平均时间也是: O ( l o g n ) O(logn) O(logn) 。
Kd树在维度较小时(比如20、30),算法的查找效率很高,然而当数据维度增大(例如:K≥100),查找效率会随着维度的增加而迅速下降。假设数据集的维数为D,一般来说要求数据的规模N满足N>>2的D次方,才能达到高效的搜索。
另外,推荐阅读knn基础与优化1–kd-tree这篇文章的相关介绍,表达更加简洁。
参考文章
Ball Tree Algorithm (球树算法),用超平面Circle(2D)或Sphere(3D)将所有的数据点分解到两个簇(cluster),这个平面经常被称为超平面(hypersphere),而每个簇表示树的两个节点。.
Ball tree algorithm* 的实现过程:
另外,推荐阅读文章Ball Tree对Ball Tree的介绍,以及文章knn基础与优化2–ball tree、LSH对Ball Tree的总结。
参考文章
参考文章
1. 背景介绍
推荐系统中,在使用Embedding进行召回的时候,由于直接进行for循环计算的时间复杂度巨高,因此需要采用一些技术进行快速计算,其中主要有两种解决方案。解决方案如下:
Faiss向量检索库
局部敏感哈希技术(可以参考书籍 深度学习与推荐系统 王喆)
其中Faiss是一个搜索工具库,Faiss中也有包含LSH(局部敏感哈希)技术进行搜索。
2. Faiss技术介绍
Faiss是FaceBook的AI团队开源的一套用于做稠密向量聚类和相似性搜索的软件库,它包含在任意大小向量上的搜索算法,也支持评估和参数调节。一些非常有用的算法也可以在GPU上使用,支持c++和python调用。
Faiss工具包可以使用在推荐系统的向量召回部分,在做向量召回的时候要么是u2u,u2i或者i2i,这里的u和i分别指user和item。我们知道在实际场景中user和item数量是海量的,我们最容易想到的基于向量相似度的召回就是使用两层循环遍历user列表或者item列表计算两个向量的相似度,但是这样做在面对海量数据是不切实际的,faiss就是用来加速计算某个查询向量最相似的Topk个索引向量。
Faiss所有软件库中对应的搜索技术都有与之对应的索引建立方法,其中PQ(乘积量化)就是其中的一个方法,该方法可以加快搜索并且减少空间。
3. Faiss原理介绍
Faiss包含多种相似度检索方法,它认为向量可以表达距离,可以通过整数来确定距离。向量距离可以通过L2(欧氏距离)和点积确定,同时也支持余弦相似度。它主要是通过向量压缩进行计算,而不是通过使用原型向量进行比较,这种方法虽然降低精度,但是可以极大缩小存储空间以及检索速度,可以达到近似检索。
Faiss本质是:使用PCA、K-means、PQ等算法对数据进行操作,对数据进行分群,每一个群都有一个Index,根据要查找数据的与每个Index距离大小,定位要查找的那个群,也就是缩小了数据查找范围,进而加速。
参考文章
dim, measure = 64, faiss.METRIC_L2
param = 'LSH'
index = faiss.index_factory(dim, param, measure)
print(index.is_trained) # 此时输出为True
index.add(xb)
参考文章