k最近邻算法 (k-NN)
k最近邻 (k-Nearst Neighbor, k-NN) 算法是一种比较简单易懂的机器学习算法,1968年由Cover和Hart提出,常应用于字符识别、文本分类、图像识别等领域。该算法的思想是:一个样本与数据集中的k
个样本最相似,如果这k
个样本中的大多数属于某一个类别,则该样本也属于这个类别。
在sklearn
库中,k-NN包含在sklearn.neighbors
中,有k最近邻分类KNeighborsClassifier
和k最近邻回归KNeighborsRegressor
。本文以k-NN分类器为例进行学习。
sklearn
库中的k-NN方法有很多超参数,常用的超参数如下:
1. weights
:用于分配权重。基本的最近邻回归使用统一的权重,即本地邻域内的每个邻点对查询点的分类贡献一致。在某些环境下,对邻点加权可能是有利的,使得附近点对于回归所作出的贡献多于远处点。默认为weights = 'uniform'
,表示为所有点分配同等权重。weights = 'distance'
表示分配的权重与查询点距离呈反比。此外,我们还可以自定义一个距离函数用来计算权重。
2. n_neighbors
:邻居个数。
3. p
:p
参数只有在weights = 'distance'
时才有。p
是一个大于或等于1的值。p = 1
表示曼哈顿距离 (Manhattan Distance),p = 2
表示欧式距离 (Euclidean Distance),p = ∞
表示它是各个坐标距离的最大值。
下面介绍一下三种距离计算公式。设特征空间
X
\mathcal{X}
X是
n
n
n维实数向量空间
R
n
R_{n}
Rn,
x
i
,
x
j
∈
X
x_{i},x_{j}\in\mathcal{X}
xi,xj∈X,
x
i
=
(
x
i
(
1
)
,
x
i
(
2
)
,
.
.
.
,
x
i
(
n
)
)
T
x_{i}=(x_{i}^{(1)},x_{i}^{(2)},...,x_{i}^{(n)})^{T}
xi=(xi(1),xi(2),...,xi(n))T,
x
j
=
(
x
j
(
1
)
,
x
j
(
2
)
,
.
.
.
,
x
j
(
n
)
)
T
x_{j}=(x_{j}^{(1)},x_{j}^{(2)},...,x_{j}^{(n)})^{T}
xj=(xj(1),xj(2),...,xj(n))T,
x
i
,
x
j
x_{i},x_{j}
xi,xj的
L
p
L_{p}
Lp距离定义为
L
p
(
x
i
,
x
j
)
=
(
∑
l
=
1
n
∣
x
i
(
l
)
−
x
j
(
l
)
∣
p
)
1
p
.
L_p(x_i,x_j) = (\sum_{l=1}^{n} \; |x_i^{(l)}-x_j^{(l)}|^p)^{\frac{1}{p}}.
Lp(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣p)p1.上式中
p
≥
1
p\geq1
p≥1. 当
p
=
1
p=1
p=1时,称为曼哈顿距离,此时
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
p=2
p=2时,称为欧式距离,此时
L
2
(
x
i
,
x
j
)
=
(
∑
l
=
1
n
∣
x
i
(
l
)
−
x
j
(
l
)
∣
2
)
1
2
.
L_2(x_i,x_j) = (\sum_{l=1}^{n} \; |x_i^{(l)}-x_j^{(l)}|^{2})^{\frac{1}{2}}.
L2(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣2)21.当
p
=
∞
p=\infty
p=∞时,表示各个坐标距离的最大值,此时
L
∞
(
x
i
,
x
j
)
=
m
a
x
l
∣
x
i
(
l
)
−
x
j
(
l
)
∣
.
L_{\infty}(x_i,x_j)= \mathop{max}_l \; |x_i^{(l)}-x_j^{(l)}|.
L∞(xi,xj)=maxl∣xi(l)−xj(l)∣.
接下来简单说明一下k-NN的算法流程。输入训练数据集
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}\subseteq R^{n}
xi∈X⊆Rn为实例的特征向量,
y
i
∈
Y
=
{
c
1
,
c
2
,
.
.
.
,
c
k
}
y_{i}\in\mathcal{Y}=\{c_1,c_2,...,c_k\}
yi∈Y={c1,c2,...,ck}为实例的类别,
i
=
1
,
2
,
.
.
.
,
N
i=1,2,...,N
i=1,2,...,N;实例特征向量
x
x
x.
1. 根据给点的距离度量,在训练集
T
T
T中找出与
x
x
x最近邻的
k
k
k个点,涵盖着
k
k
k个点的领域,记为
N
k
(
x
)
N_k(x)
Nk(x).
2. 在
N
k
(
x
)
N_k(x)
Nk(x)中根据分类决策规则(如多数表决),决定
x
x
x的类别
y
y
y:
y
=
a
r
g
m
a
x
c
j
∑
x
i
∈
N
k
(
x
)
I
(
y
i
=
c
j
)
,
i
=
1
,
2
,
.
.
.
,
N
;
y=arg \mathop{\; max}_{c_j} {\sum}_{x_i \in N_k(x)} \; I(y_i=c_j) , i=1,2,...,N;
y=argmaxcj∑xi∈Nk(x)I(yi=cj),i=1,2,...,N;在上式中,
I
I
I为指示函数,即当
y
i
=
c
j
y_i=c_j
yi=cj时,
I
I
I为
1
1
1,否则
I
I
I为
0
0
0.
最后输出实例
x
x
x所属的类
y
y
y.
k-NN的特殊情况是k=1
的情形,称为最近邻算法。对于输入的实例点(特征向量)
x
x
x,最近邻算法将训练数据集中与
x
x
x最近邻点的类作为
x
x
x的类。
GridSearchCV调参
GridSearchCV
包含在sklearn.model_selection
中。它可以拆分为“GridSearch”和“CV”两个部分,即网格搜索和交叉验证。网格搜索用于选取模型的最优超参数。获取最优超参数的方式可以绘制验证曲线,但是验证曲线只能每次获取一个最优超参数。如果多个超参数有很多排列组合的话,就可以使用网格搜索寻求最优超参数的组合。网格搜索针对超参数组合列表中的每一个组合,实例化给定的模型,进行交叉验证,将平均得分最高的超参数组合作为最佳的选择,返回模型对象。
GridSearchCV(estimator, param_grid, scoring=None, fit_params=None, n_jobs=1, iid=True,
refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', error_score='raise',
return_train_score=True)
以下是GridSearchCV
方法中常用的超参数。
1. estimator
:创建的算法对象。
2. param_grid
:值为字典或者列表,需要最优化的参数的取值。
3. scoring
:准确度评价标准,默认None
,这时需要使用score
函数;或者如scoring='roc_auc'
,根据所选模型不同,评价准则不同。字符串(函数名)或是可调用对象,需要其函数签名,形如scorer(estimator, X, y)
;如果是None
,则使用estimator
的误差估计函数。
4. n_jobs
:并行数,默认为1
,n_jobs = -1
表示跟CPU核数一致。
5. cv
:交叉验证参数,默认None
,使用三折交叉验证。指定fold
数量,默认为3
,也可以是yield
训练或测试数据的生成器。
6. verbose
:日志冗长度。verbose = 0
表示不输出训练过程,verbose = 1
表示偶尔输出,verbose > 1
表示对每个子模型都输出。
GridSearchCV
还内置了一些属性。
1. best_estimator_
:效果最好的分类器。
2. best_score_
:成员提供优化过程期间观察到的最好的评分。
3. best_params_
:描述了已取得最佳结果的参数的组合。
4. best_index_
:对应于最佳候选参数设置的索引(cv_results_
数组的索引)。
优化Titanic生存预测代码
在上一篇博客中,我使用决策树和k-NN两种分类器对处理后的Titanic数据集进行了分类预测,其中k-NN分类器的training set score为0.82,test set score为0.72,还有一部分优化空间。下面用网格搜索和交叉验证进行调参。
# 使用GridSearchCV进行调参
from sklearn.model_selection import GridSearchCV
knn = KNeighborsClassifier()
param_grid = [
{
'weights': ['uniform'],
'n_neighbors': [i for i in range(1, 11)]
},
{
'weights': ['distance'],
'n_neighbors': [i for i in range(1, 11)],
'p': [i for i in range(1, 6)]
}
]
grid_search = GridSearchCV(knn, param_grid, n_jobs=-1, verbose=2)
%%time
grid_search.fit(X_train, y_train)
接下来查看一下GridSearchCV
的属性。
# 最优超参数组合对应的分类器
grid_search.best_estimator_
# 最优超参数组合
grid_search.best_params_
# 最优超参数组合对应的准确率
grid_search.best_score_
最后我们使用最优的分类器模型对测试集进行预测。
knn = grid_search.best_estimator_
y_predict = knn.predict(X_test)
print(y_predict)
print('Training set score: {:.2f}'.format(knn.score(X_train, y_train)))
print('Test set score: {:.2f}'.format(knn.score(X_test, y_test)))
可以看出training set score有了明显的提升(从0.82提升到0.98),但是test set score的提升不是很多(从0.72提升到0.73)。