K-近邻算法,KNN
概念
K-Nearest Neighbors(KNN):k个最近的邻居,即每个样本都可以用它最接近的k个邻居来代表。
最近邻 (k-Nearest Neighbors, KNN) 算法是一种分类算法, 1968年由 Cover和 Hart 提出, 应用场景有字符识别、 文本分类、 图像识别等领域。
该算法的思想是: 一个样本与数据集中的k个样本最相似, 如果这k个样本中的大多数属于某一个类别, 则该样本也属于这个类别。
距离度量
L1 (Manhattan) distance
d 1 ( I 1 , I 2 ) = ∑ p ∣ I 1 p − I 2 p ∣ d1(I1,I2)=\sum_{p}|I_1^p-I_2^p| d1(I1,I2)=p∑∣I1p−I2p∣
L2 (Euclidean) distance
d 1 ( I 1 , I 2 ) = ∑ p ( I 1 p − I 2 p ) 2 ) d1(I1,I2)=\sqrt {\sum_{p}(I_1^p -I_2^p)^2)} d1(I1,I2)=p∑(I1p−I2p)2)
选择那个距离是更具具体的数据以及实际意义来定的,在选择时选择效果最好的距离即可
如何选择最佳的K和距离呢
选择最佳的K
第一种想法
只有训练集
在训练数据中找到最佳的K
最坏的情况是K=1,这个在训练集上总会得到完美的表现,但很有可能出现过拟合的现象。
但是在实际中应该使得K尽可能最大,这样对于在训练集中未出现过的数据分类性能更佳,因为我们不是要我们的方法尽可能拟合训练集,而是在训练集以外的未知数据上表现得更好。
第二种想法
将数据分为训练集和测试集
在训练集上训练好分类器,运用在测试集上,找到在测试集中表现最好的超参数。
但实际上别这么做,因为此方法给我们的是预估的一种方法,如果遇到全新的数据无法确定其表现如何。
第三种想法
将数据集划分为训练集验证集和测试集
在训练集上训练好分类器,运用在验证集上,找到表现最好的超参数,最后在测试集上做测试
第四种想法
使用K折交叉验证:
-
将数据分成k份,并进行k次训练。每次训练将1份作为验证集,剩下的k-1份作为训练集,k次训练正好每1份都当了一次验证集。
-
将每次训练的误差做平均得到平均误差。依据平均误差来调节模型的超参数。
-
超参数固定好之后,用完整的数据集来重新训练模型。
对于小数据集是非常有用的,但是在深度学习中并不是经常适用。
距离度量选择
上述图像分别是原始图像遮盖,下移和变蓝。如果用L1和L2来验证,上述几张图像差异不大计算得到L1和L2的结果一样。在差异很小的情况下,说明了在图像中不用L1和L2距离度量图像的相似性。
小结
在图像分类中,我们从图像的训练集开始,必须预测测试集上的标签。
K邻近分类器基于K个最邻近示例来预测标签
距离度量和K是超参数
使用验证集选择超参数
最后只在测试集上运行一次
代码
import numpy as np
from collections import Counter
class KNearestNeighbor(object):
""" a kNN classifier with L2 distance """
def __init__(self):
pass
def train(self, X, y):
self.X_train = X
self.y_train = y
def predict(self, X, k=1, num_loops=0):
if num_loops == 0:
dists = self.compute_distances_no_loops(X)
elif num_loops == 1:
dists = self.compute_distances_one_loop(X)
elif num_loops == 2:
dists = self.compute_distances_two_loops(X)
else:
raise ValueError('Invalid value %d for num_loops' % num_loops)
return self.predict_labels(dists, k=k)
def compute_distances_two_loops(self, X):
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):#测试样本的循环
for j in range(num_train):#训练样本的循环
#dists[i,j]=np.sqrt(np.sum(np.square(self.X_train[j,:]-X[i,:])))
dists[i,j]=np.linalg.norm(X[i]-self.X_train[j])
#np.square是针对每个元素的平方方法
return dists
def compute_distances_one_loop(self, X):
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
#dists[i,:] = np.sqrt(np.sum(np.square(self.X_train-X[i,:]),axis = 1))
dists[i,:]=np.linalg.norm(X[i,:]-self.X_train[:],axis=1)
return dists
def compute_distances_no_loops(self, X):
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
"""
mul1 = np.multiply(np.dot(X,self.X_train.T),-2)
sq1 = np.sum(np.square(X),axis=1,keepdims = True)
sq2 = np.sum(np.square(self.X_train),axis=1)
dists = mul1+sq1+sq2
dists = np.sqrt(dists)
"""
dists += np.sum(np.multiply(X,X),axis=1,keepdims = True).reshape(num_test,1)
dists += np.sum(np.multiply(self.X_train,self.X_train),axis=1,keepdims = True).reshape(1,num_train)
dists += -2*np.dot(X,self.X_train.T)
dists = np.sqrt(dists)
return dists
def predict_labels(self, dists, k=1):
num_test = dists.shape[0]
y_pred = np.zeros(num_test)
for i in range(num_test):
closest_y = []
closest_y = self.y_train[np.argsort(dists[i, :])[:k]].flatten()
c = Counter(closest_y)
y_pred[i]=c.most_common(1)[0][0]
"""
closest_y=self.y_train[np.argsort(dists[i, :])[:k]]
y_pred[i] = np.argmax(np.bincount(closest_y))
"""
return y_pred