KNN(K-NearestNeighbor)
一.K近邻算法的基本概念,原理
K近邻算法是一种基本分类和回归方法
即给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例,这K个实例的多数属于某个类,就把该输入实例分类到这个类中,(类似于现实生活中少数服从多数的意思)
二.特征归一化
-
距离的度量
L p ( x i , x j ) = ( ∑ = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p L_p(x_i,x_j)=(\sum_{=1}^n|x_i^{(l)}-x_j^{(l)}|^p)^\frac{1}{p} Lp(xi,xj)=(=1∑n∣xi(l)−xj(l)∣p)p1- 当p=2时,为欧氏距离(Euclidean distance),即
L 2 ( x i , y i ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 L_2(x_i,y_i)=(\sum_{l=1}^n|x_i^{(l)}-x_j^{(l)}|^2)^{\frac{1}{2}} L2(xi,yi)=(l=1∑n∣xi(l)−xj(l)∣2)21
(公式中的l表示的应该是表示第几个样本)
- 当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_{l=1}^n|x_i^{(l)}-x_j^{(l)}| L1(xi,xj)=l=1∑n∣xi(l)−xj(l)∣
其中当p=2的时候,就是我们常见的欧式距离,一般都用欧氏距离来衡量我们高维空间中两点的距离
-
特征归一化的必要性
eg:用一个人的身高(cm)和脚码大小来作为特征值,类别为男性、女性,首先有5个训练样本,如下:
A [(179,42),男] B [(178,43),男] C [(165,36)女] D [(177,42),男] E [(160,35),女]
通过以上样本可以得出,***第一维身高特征是第二维脚码特征的4倍左右,那么在进行距离度量的时候,我们就会偏向于第一维特征,***这样就会造成两个特征不是等价重要,最终可能会导致距离计算错误,从而导致预测错误。
举例证明:
给个预测样本F (167,43),来预测是男性还是女性,采取k=3
用欧式距离分别算出F离训练样本的欧式距离,然后选组最近的3个,以少数服从多数的原则选取预测结果,计算如下:
A F = ( 167 − 179 ) 2 + ( 43 − 42 ) 2 = 145 B F = ( 167 − 178 ) 2 + ( 43 − 43 ) 2 = 121 C F = ( 167 − 165 ) 2 + ( 43 − 36 ) 2 = 53 D F = ( 167 − 177 ) 2 + ( 43 − 42 ) 2 = 101 E F = ( 167 − 160 ) 2 + ( 43 − 35 ) 2 = 103 AF = \sqrt{(167-179)^2+(43-42)^2} = \sqrt{145} \\ BF = \sqrt{(167-178)^2+(43-43)^2} = \sqrt{121} \\ CF = \sqrt{(167-165)^2+(43-36)^2} = \sqrt{53} \\ DF = \sqrt{(167-177)^2+(43-42)^2} = \sqrt{101} \\ EF = \sqrt{(167-160)^2+(43-35)^2} = \sqrt{103} \\ AF=(167−179)2+(43−42)2=145BF=(167−178)2+(43−43)2=121CF=(167−165)2+(43−36)2=53DF=(167−177)2+(43−42)2=101EF=(167−160)2+(43−35)2=103
由计算结果得出,最近的三个样本分别是C、D、E,那么可以得到预测结果为女性但是,一个女性的脚是43码的概率远远小于男性,但预测结果却是女性。这是因为各个特征的量纲不同,在这里导致了身高的重要性远远大于脚码了,这是不客观的。所以,我们应该在这里让每个特征同等重要,这也是需要归一化的原因!!!
-
去量纲,方法如下:
-
线性归一化(Min-Max-Normalization):一般情况下Min=0,Max=1
x n e w = x − m i n ( x ) m a x ( x ) − m i n ( x ) x_{new}=\frac{x-min(x)}{max(x)-min(x)} xnew=max(x)−min(x)x−min(x) -
标准差标准化(Z-Score-Normalization):其中mean(x)为平均值,std(x)为标准差
x n e w = x − m e a n ( x ) s t d ( x ) x_{new}=\frac{x-mean(x)}{std(x)} xnew=std(x)x−mean(x)
-
三.底层算法
class KNN:
def __init__(self,x_train,y_train,n_neighbors = 3,p = 2):
self.n = n_neighbors
self.p = p
self.x_train = x_train
self.y_train = y_train
def predict(self, X):
#X:要预测的样本点的特征
knn_list = []
#K个邻居,【(X和x_train第一个样本点的距离,y_train:第一个样本点的标签)】
for i in range(self.n):
dist = np.linalg.norm(X - self.x_train[i],ord = self.p)
#dist :返回X和样本点的距离
knn_list.append((dist,y_train[i]))
#将结果存入knn_list
for i in range(self.n,len(self.x_train)):
#从第四(k)个点开始迭代
max_index = knn_list.index(max(knn_list , key = lambda x:x[0]))
#返回原本knn_list的最大dist的索引
dist = np.linalg.norm(X - self.x_train[i],ord = self.p)
#计算X和第4(k)个点的距离
if knn_list[max_index][0]>dist:
knn_list[max_index] = (dist,self.y_train[i])
#如果原本的点的距离大于新点的距离,那么,我们就把新点的距离和标签,替换到knn_list里面。
knn = [k[-1] for k in knn_list]
#knn 返回,原本knn_list里的所有标签
count_pairs = Counter(knn)
max_count = sorted(count_pairs.items() ,key = lambda x:x[1])[-1][0]
#计算所有标签的频数,并返回频数最大的标签
return max_count
def score(self,x_test,y_test):
#记录预测正确的样本数/总xtest的样本个数=准确率
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)
四.KNN类的方法说明
-
fit():训练函数,接收参数有两个,训练集特征X和训练集标签y
-
predict():预测函数,输入参数为测试集特征X
-
predict_prob:也是预测函数,只不过输出的是基于概率的预测
-
score():打分函数,接收参数有三个
-
X:接收输入的数组类型测试样本,一般是二维样本,每一行是一个样本,每一列是一个属性
-
y:X这些预测样本的真实标签,一维数组或者二维数组
-
samples_weight=None,是一个和X第一位一样长的个样本对准确率影响的权重,一般默认为None,输出为一个float型数,表示准确率,一般不用写。
内部计算是按照predict()函数计算的结果记性计算的。其实该函数并不是KNeighborsClassifier这个类的方法,而是 它的父类KNeighborsMixin继承下来的方法
-
五.分类
测试
# 导入划分数据集的包
from sklearn.model_selection import train_test_split
# 划分数据集:分别为特征-(训练集、测试集),标签-(训练集、测试集),测试集占比为n
xtrain,xtest,ytrain,ytest = train_test_split(X,y,test_size=n)
# X -> 拿来进行训练的特征量,
# y -> 根据X得出的标签
调包KNN(K近邻)
# 导入sklearn中的KNN类
from sklearn.neighbors import KNeighborsClassifier
# 实例化KNN
knn= KNeighborsClassifier(
n_neighbors=5,
weights='uniform',
algorithm='auto',
leaf_size=30,
p=2,
metric='minkowski',
metric_params=None,
n_jobs=None,
)
"""
参数解释
n_neighbors:指定以最近的几个最近邻样本具有投票权,默认参数为5
weights:每个拥有投票权的样本是按什么比重投票,'uniform'表示等比重,'distance'表示按距离反比
algorithm:即内部采用什么算法实现
'ball_tree':球树、'kd_tree':kd数、'brute':暴力搜索、'auto':自动根据数据的类型 和结构选择合适的算法。
left_size:给出kd_tree或者ball_tree叶节点规模
p:p=2为欧氏距离,p=1为曼哈顿距离,默认为2
metric:距离度量对象,即怎样度量距离,默认为闵氏距离
metric_params:距离度量函数的额外关键字参数,一般不用管
n_jobs:指并行计算的线程数量,默认为1表示一个线程,为-1表示cpu的内核数,如果不是很追求速度的话就不用管
"""
# 将KNN用fit方法传入参数
knn.fit(xtrain,ytrain)
# 用训练集预测准确率
knn.score(xtest,ytest)
# 训练集预测结果
y_hat = knn.predict(xtest)
# 取出预测错误的点
error = xtest[(ytest != y_hat)]
六.回归
测试在上面已经写过,所以这里直接调包进行回归
# 调包
from sklearn.neighbors import KNeighborsRegressor
# 在这里可以直接将KNN实例化后使用fit方法
knn = KNeighborsRegressor.fit(xtrain,ytrain)
# data 表示你需要预测的数据
knn.predict([data])
knn.score(xtest,ytest)
# 计算均方误差、平均绝对误差回归损失
from sklearn.metrics import mean_squared_error,mean_absolute_error
mse = mean_squared_error(ytest,y_pred=knn.predict(xtest))
mae = mean_absolute_error(ytest,y_pred=knn.predict(xtest))