第三章 k近邻法

第三章 k k k 近邻法


k 近邻法(k-nearest neighbor, k-NN)是一种基本分类与回归方法。

输入: 实例的特征向量,对应于特征空间的点;

输出: 实例的类别,可以去多类。


算法

思想: 给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的 k k k 个实例,这 k k k 个实例的多数属于某个类,就把该输入实例分为这个类。

输入: 训练数据集
T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)}
其中,其中 x i ∈ X = R n x_i \in \mathcal{X}= \mathbf R^n xiX=Rn 为实例的特征向量, y i ∈ Y = { c 1 , c 2 , . . . , c K } y_i \in \mathcal Y =\{c_1,c_2,...,c_K\} yiY={c1,c2,...,cK}为实例的类别, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,实例特征向量 x x x.

输出: 实例 x x x 所属的类 y y y .

  1. 根据给定的距离度量,在训练集 T T T 中找出与 x x x 最近邻的 k k k 个点,涵盖这个点的 x x x 的领域记作 N k ( x ) N_k(x) Nk(x) ;

  2. N k ( x ) N_k(x) Nk(x) 中根据分类决策规则(如多数表决)决定 x x x 的类别 y y y

    y = arg ⁡ max ⁡ c j ∑ x i ∈ N k ( x ) I ( y i = c j ) , i = 1 , 2 , . . . , N ;   j = 1 , 2 , . . . , K y = \arg \max \limits _{c_j} \sum \limits_{x_i \in N_k(x)}I(y_i=c_j),\qquad i=1,2,...,N;\ j=1,2,...,K y=argcjmaxxiNk(x)I(yi=cj),i=1,2,...,N; j=1,2,...,K

    I I I 为指示函数,即当 y i = c j y_i=c_j yi=cj I I I 为 1,否则 I I I 为 0 。

k = 1 k=1 k=1 时,称为最近邻算法


模型

三个基本要素: 距离度量, k k k 值的选择,分类决策规则。

单元(cell): 特征空间中,对每个训练实例点 x i x_i xi ,距离该点比其他点更近的所有点组成的一个区域。

See the source image


距离度量

  1. L p L_p Lp 距离:
    L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p , p ≥ 1 L_p(x_i,x_j)=\left( \sum \limits _{l=1}^n |x_i^{(l)}-x_j^{(l)}|^p \right)^{\frac{1}{p}},\qquad p \geq 1 Lp(xi,xj)=(l=1nxi(l)xj(l)p)p1,p1

  2. p = 2 p = 2 p=2 时,欧氏距离(Euclidean distance):
    L 2 ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 L_2(x_i,x_j)=\left( \sum \limits _{l=1}^n |x_i^{(l)}-x_j^{(l)}|^2 \right)^{\frac{1}{2}} L2(xi,xj)=(l=1nxi(l)xj(l)2)21

  3. p = 1 p = 1 p=1 时,曼哈顿距离(Manhattan distance):
    L 1 ( x i , x j ) = ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ L_1(x_i,x_j)= \sum \limits _{l=1}^n |x_i^{(l)}-x_j^{(l)}| L1(xi,xj)=l=1nxi(l)xj(l)

  4. p = ∞ p = \infty p= 时,为各个坐标距离的最大值:
    L ∞ ( x i , x j ) = max ⁡ l ∣ x i ( l ) − x j ( l ) ∣ L_{\infty}(x_i,x_j)= \max\limits _{l} |x_i^{(l)}-x_j^{(l)}| L(xi,xj)=lmaxxi(l)xj(l)

See the source image


k 值的选择

k k k 值减小意味着整体模型变得复杂,容易发生过拟合, k k k 值增大意味着整体的模型变得简单。

在应用中, k k k 值一般取一个比较小的数值,通常采用交叉验证法来选取最优的 k k k 值。


分类决策规则

多数表决方法(majority vote rule): 由输入实例的 k k k 个邻近的训练实例中的多数类决定输入实例的类。

分类函数为:
f : R n → { c 1 , c 2 , . . . , c K } f:\mathbf R^n \to \{c_1,c_2,...,c_K\} f:Rn{c1,c2,...,cK}
误分类的概率为:
P ( Y ≠ f ( X ) ) = 1 − P ( Y = f ( X ) ) P(Y \neq f(X)) =1-P(Y=f(X)) P(Y̸=f(X))=1P(Y=f(X))
对给定的实例 x ∈ X x \in \mathcal X xX,其最近邻的 k k k 个训练实例点构成集合 N k ( x ) N_k(x) Nk(x)。如果涵盖 N k ( x ) N_k(x) Nk(x) 的区域的类别是 c j c_j cj ,那么误分类率是:
1 k ∑ x i ∈ N k ( x ) I ( y i ≠ c j ) = 1 − 1 k ∑ x i ∈ N k ( x ) I ( y i = c j ) \frac{1}{k} \sum \limits_{x_i \in N_k(x)} I(y_i \neq c_j)=1- \frac{1}{k}\sum \limits_{x_i \in N_k(x)} I(y_i =c_j) k1xiNk(x)I(yi̸=cj)=1k1xiNk(x)I(yi=cj)
要使误分类概率最小即经验风险最小,就要使 ∑ x i ∈ N k ( x ) I ( y i = c j ) \sum \limits_{x_i \in N_k(x)} I(y_i =c_j) xiNk(x)I(yi=cj) 最大。所以多数表决规则等价于经验风险最小化。


kd树

算法:(构造平衡 k d kd kd 树)

输入: k k k 维空间数据集 T = { x 1 , x 2 , . . . , x N } T=\{x_1,x_2,...,x_N\} T={x1,x2,...,xN},其中 x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( k ) ) T ,   i = 1 , 2 , . . . , N x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(k)})^T,\ i =1,2,...,N xi=(xi(1),xi(2),...,xi(k))T, i=1,2,...,N;

输出: k d kd kd

  1. 开始:构造根节点,使根结点对应于包含 T T T k k k 维空间的超矩形区域。

    选择 x ( 1 ) x^{(1)} x(1) 为坐标轴,以 T T T 中所有实例的 x ( 1 ) x^{(1)} x(1) 坐标的中位数为切分点,将根结点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴 x ( 1 ) x^{(1)} x(1) 垂直的超平面实现。

    由根结点生成深度为 1 的左、右子结点:左子结点对应坐标 x ( 1 ) x^{(1)} x(1) 小于切分点的子区域,右子结点对应于坐标 x ( 1 ) x^{(1)} x(1) 大于切分点的子区域。

  2. 重复:对深度为 j j j 的结点,选择 x ( l ) x^{(l)} x(l) 为切分的坐标轴, l = j ( m o d    k ) + 1 l=j(\mod k)+1 l=j(modk)+1 , 以该结点的区域中的所有实例的点 x ( l ) x^{(l)} x(l) 坐标的中位数为切分点,将该结点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴 x ( l ) x^{(l)} x(l) 垂直的超平面实现。

    由该结点生成深度为 j + 1 j+1 j+1 的左、右子结点:左子结点对应坐标 x ( l ) x^{(l)} x(l) 小于切分点的子区域,右子结点对应于坐标 x ( l ) x^{(l)} x(l) 大于切分点的子区域。

    将落在切分超平面上的实例点保存在该结点。

  3. 知道两个区域没有实例存在时停止。从而形成 k d kd kd 树的区域划分。

See the source image

See the source image


算法:(用 k d kd kd 树的最近邻搜索)

输入:已构造的 k d kd kd 树,目标点 x x x.

输出: x x x 的最近邻。

  1. k d kd kd 树中找出包含目标点 x x x 的叶结点:从根结点出发,递归地向下访问 k d kd kd 树。若目标点 x x x 当前维的坐标小于切分点的坐标,则移动到做子结点,否则移动到右子结点。知道子结点为叶结点为止。

  2. 以此叶结点为“当前最近点”。

  3. 递归地向上回退,在每个结点进行一下操作:

    a). 如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”。

    b).当前最近点一定存在于该结点一个子结点对应的区域。检查该子结点的父结点的另一子结点对应的区域是否有更近的点。具体地,检查另一子结点对应的区域是否与以目标点为球心、以目标点与“当前最近点”间的距离为半径的超球体相交。

    如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点,接着,递归地进行最近邻搜索。

    如果不想交,向上回退。

  4. 当回退到根结点时,搜索结束。最后的“当前最近点”即为 x x x 的最近邻点。

See the source image

贴上代码及注释,数据集为irisdata,运行环境:python3.6
该代码为麦子学院:深度学习基础出品。

# example of KNN implemented from Scratch in Python

import csv
import random
import math
import operator


def loadDataset(filename, split, trainingSet=[], testSet=[]):
	with open(filename, 'r') as csvfile:  # 将 'rb' 改写为'r',不然会报错
		lines = csv.reader(csvfile)		# 将数据按行读进
		dataset = list(lines)			# 转换为 list
		for x in range(len(dataset)-1):	# 将数据分为训练集和测试集
			for y in range(4):
				dataset[x][y] = float(dataset[x][y])	# 将数据转换为浮点型
			if random.random() < split:  # 注意缩进问题
				trainingSet.append(dataset[x])
			else:
				testSet.append(dataset[x])

                
def euclideanDistance(instance1, instance2, length):	# 计算两点之间的欧氏距离
	distance = 0
	for x in range(length):  # 计算每一维的距离的平方和
		distance += pow((instance1[x] - instance2[x]), 2)
	return (math.sqrt(distance))


def getNeighbors(trainingSet, testInstance, k):			# 测试集的数据与训练集数据做 k-最近邻 计算
	distances = []
	length = len(testInstance) - 1						# 
	for x in range(len(trainingSet)):
		dist = euclideanDistance(testInstance, trainingSet[x], length)	# 将测试数据与所有训练集数据做欧氏距离计算
		distances.append((trainingSet[x], dist))
	distances.sort(key=operator.itemgetter(1))							# 将距离进行排序
	neighbors = []				
	for x in range(k):			# 返回最近的k个邻居
		neighbors.append(distances[x][0])
	return (neighbors)


def getResponse(neighbors):	# 投票表决
	classVotes = {}
	for x in range(len(neighbors)):
		response = neighbors[x][-1]
		if response in classVotes:
			classVotes[response] += 1
		else:
			classVotes[response] = 1
	# 将每类的投票个数按从大到小排序,reverse=True 按降序排列
	sortedVotes = sorted(classVotes.items(), key=operator.itemgetter(1), reverse=True) # dict.iteritems --> items 
	return (sortedVotes[0][0]) # 返回第一个分类,即投票最多的


def getAccuracy(testSet, predictions):	# 计算精确度
	correct = 0
	for x in range(len(testSet)):
		if testSet[x][-1] == predictions[x]:
			correct += 1
	return ((correct/float(len(testSet))) * 100)

def main():
	# prepare data
	trainingSet = []
	testSet = []
	split = 0.67
	loadDataset(r'irisdata.txt', split, trainingSet, testSet)
	print("Train set: \n" + repr(len(trainingSet)))
	print("Test set: \n" + repr(len(testSet)))
	# generate predictions
	predictions = []
	k = 3
	for x in range(len(testSet)):
		neighbors = getNeighbors(trainingSet, testSet[x], k)
		result = getResponse(neighbors)
		predictions.append(result)
		print('> predicted=' + repr(result) + ',actual=' + repr(testSet[x][-1]))
	accuracy = getAccuracy(testSet, predictions)
	print('Accuracy:' + repr(accuracy) + '%')

main()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值