KNN
简介:k近邻法(KNN)是一种基本分类与回归方法。目前仅介绍分类部分。k近邻法的输入为实例的特征向量,对应于特征空间的点,输出为实例的类别,可以取多类。k近邻法假设给定一个训练集,其中的实例类别已定。分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻法不具有显示的学习过程。k近邻法实际上利用训练集对特征向量空间进行划分,并作为其分类的“模型”。k值的选择、距离度量、分类决策规则是k近邻法的三大基本要素。本博客主要介绍三大基本要素,以及一个实现的方法,kd树。
模型
k近邻法中,当训练集、距离度量(如欧式距离)、k值及分类决策规则(如多数表决)确定后,对于任何一个新输入的实例,它属于的类是唯一地确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子空间的每个点所属的类。这个和最近邻算法很相似。
距离度量
特征空间中两个点的距离是两个点的相似度的反映。特征空间一般是n维实数向量空间R**n。使用的距离一般是欧式距离,也可以是闵可夫斯基距离、曼哈顿距离、切比雪夫距离。
闵可夫斯基距离
image.png
曼哈顿距离
image.png
欧式距离
image.png
切比雪夫距离
image.png
k值的选择
- k值的选择会对k近邻法的结果产生重大影响。
- 如果k值选择较小,相当于使用较小的邻域中的训练实例进行预测,近似误差会减小,但是会对噪声敏感,- 容易发生过拟合;
- 如果k值选择较大,缺点是近似误差会变大,距离输入点较远的点也会对最后结果产生作用,k值的增大意味着模型的简单。
- 在应用中,k值一般取一个比较下的值。通常采用交叉验证法来选择最优的k值。
分类决策规则
-
分类决策规则一般是采用多数表决,即由输入实例的k个邻近实例中的多数决定输入实例的类别。
image.png
要使得误分类率最小,也就是经验风险最小,就要使得最右边公式最大,所以多数表决规则等价于经验最小化。
k近邻法python样例:
# -*- coding: UTF-8 -*-
"""
=================================================
@Project :ML
@File : knn_base
@Desc :k近邻法基础版本
==================================================
"""
import numpy as np
from collections import Counter
class KnnBase():
def __init__(self,n=4,p=2):
self.n = n # 邻近点个数
self.p = p # 距离度量
def fit(self,trainX,trainY):
self.trainX = trainX
self.trainY = trainY
def predict(self,X):
knn_list = []
for i in range(self.n):
dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
knn_list.append([dist,self.trainY[i]])
for i in range(self.n,len(self.trainX)):
max_index = knn_list.index(max(knn_list,key=lambda x:x[0]))
dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
if dist < knn_list[max_index][0]:
knn_list[max_index] = [dist,self.trainY[i]]
knn = [x[-1] for x in knn_list]
return Counter(knn).most_common()
if __name__ == '__main__':
trainX = np.array([[0,2,3],[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[8,9,10]])
trainY = np.array([1,1,1,1,2,2,2,2])
knn = KnnBase()
knn.fit(trainX,trainY)
X = np.array([4,6,8])
result = knn.predict(X)
print(result)
kd树
简介:kd树是一种对n维空间的实例点进行存储,以便对其进行快速检索的树形结构。kd树是二叉树,构造kd树相当于不断的用垂直于坐标轴的超平面将n维空间进行划分,构成一系列的n维超矩阵区域。
下面的流程图更加清晰的描述了kd树的构建过程:
image.png
kd树python代码如下:(明天补充)
import numpy as np
from collections import Counter
class KnnBase():
def __init__(self,n=4,p=2):
self.n = n # 邻近点个数
self.p = p # 距离度量
def fit(self,trainX,trainY):
self.trainX = trainX
self.trainY = trainY
def predict(self,X):
knn_list = []
for i in range(self.n):
dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
knn_list.append([dist,self.trainY[i]])
for i in range(self.n,len(self.trainX)):
max_index = knn_list.index(max(knn_list,key=lambda x:x[0]))
dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
if dist < knn_list[max_index][0]:
knn_list[max_index] = [dist,self.trainY[i]]
knn = [x[-1] for x in knn_list]
return Counter(knn).most_common()
import time
from collections import Counter
# kd-tree每个结点中主要包含的数据结构如下
class Node:
def __init__(self, data, label, depth=0, lchild=None, rchild=None):
self.data = data
self.depth = depth
self.lchild = lchild
self.rchild = rchild
self.label = label
class KdTree:
def __init__(self, dataSet, label):
self.KdTree = None
self.n = 0
self.nearest = None
self.create(dataSet, label)
# 建立kdtree
def create(self, dataSet, label, depth=0):
if len(dataSet) > 0:
m, n = np.shape(dataSet)
self.n = n
axis = depth % self.n
mid = int(m / 2)
dataSetcopy = sorted(dataSet, key=lambda x: x[axis])
node = Node(dataSetcopy[mid], label[mid], depth)
if depth == 0:
self.KdTree = node
node.lchild = self.create(dataSetcopy[:mid], label, depth+1)
node.rchild = self.create(dataSetcopy[mid+1:], label, depth+1)
return node
return None
# 前序遍历
def preOrder(self, node):
if node is not None:
print(node.depth, node.data)
self.preOrder(node.lchild)
self.preOrder(node.rchild)
# 搜索kdtree的前count个近的点
def search(self, x, count = 1):
nearest = []
for i in range(count):
nearest.append([-1, None])
# 初始化n个点,nearest是按照距离递减的方式
self.nearest = np.array(nearest)
def recurve(node):
if node is not None:
# 计算当前点的维度axis
axis = node.depth % self.n
# 计算测试点和当前点在axis维度上的差
daxis = x[axis] - node.data[axis]
# 如果小于进左子树,大于进右子树
if daxis < 0:
recurve(node.lchild)
else:
recurve(node.rchild)
# 计算预测点x到当前点的距离dist
dist = np.sqrt(np.sum(np.square(x - node.data)))
for i, d in enumerate(self.nearest):
# 如果有比现在最近的n个点更近的点,更新最近的点
if d[0] < 0 or dist < d[0]:
# 插入第i个位置的点
self.nearest = np.insert(self.nearest, i, [dist, node], axis=0)
# 删除最后一个多出来的点
self.nearest = self.nearest[:-1]
break
# 统计距离为-1的个数n
n = list(self.nearest[:, 0]).count(-1)
'''
self.nearest[-n-1, 0]是当前nearest中已经有的最近点中,距离最大的点。
self.nearest[-n-1, 0] > abs(daxis)代表以x为圆心,self.nearest[-n-1, 0]为半径的圆与axis
相交,说明在左右子树里面有比self.nearest[-n-1, 0]更近的点
'''
if self.nearest[-n-1, 0] > abs(daxis):
if daxis < 0:
recurve(node.rchild)
else:
recurve(node.lchild)
recurve(self.KdTree)
# nodeList是最近n个点的
nodeList = self.nearest[:, 1]
# knn是n个点的标签
knn = [node.label for node in nodeList]
return self.nearest[:, 1], Counter(knn).most_common()[0][0]
class KNNKdTree(KnnBase):
def __init__(self, n_neighbors=3, p=2):
super(KNNKdTree, self).__init__(n=n_neighbors, p=p)
def fit(self, X_train, y_train):
self.X_train = np.array(X_train)
self.y_train = np.array(y_train)
self.kdTree = KdTree(self.X_train, self.y_train)
def predict(self, point):
nearest, label = self.kdTree.search(point, self.n)
# print("nearest", [node.data for node in nearest])
return nearest, label
def score(self, X_test, y_test):
right_count = 0
for X, y in zip(X_test, y_test):
_, label = self.predict(X)
if label == y:
right_count += 1
return right_count / len(X_test)
if __name__ == '__main__':
trainX = np.array([[0.8,2,3],[1.9,2.9,3],[2,3,4],[3,4.8,5],[4,5.6,6],[5,6.7,7.9],[6.9,7,8],[8,9,10]])
trainY = np.array([1,1,1,1,2,2,2,2])
knn = KnnBase()
knn.fit(trainX,trainY)
X = np.array([4,6,8])
result = knn.predict(X)
print(result)
kdt = KdTree(trainX,trainY)
print("END")