上上一章已经学习了感知机模型、策略和算法,感知机对于分类任务有着其优点,但是该模型是在具有强假设的条件下——训练数据集必须是线性可分的,但是如果数据集是呈现无规则的分布,那么此时如果要做分类任务,还可以考虑k近邻(KNN),这是一种基本的分类和回归方法,既可以做简单的二分类也可以做复杂的多分类任务,还可以做回归任务。
KNN模型
KNN模型实际上对应于对特征空间的划分,虽然没有具体的数学抽象语言描述,但是仍然存在其三要素:距离度量、K值的选择、分类决策规则。
距离度量
\[设特征空间\chi是n维实数向量空间R^n,x_i,x_j \in \chi,x_i=(x_i^{(1)},x_i^{(2)},x_i^{(3)}...,x_i^{(n)})^T,\\ x_j=(x_j^{(1)},x_j^{(2)},x_j^{(3)}...,x_j^{(n)})^T,x_i,x_j的距离可定义为:\\ L_P(x_i,x_j)=(\sum^n_{l=1}|x_i^{(l)}-x_j^{(l)}|^p)^{\frac{1}{p}}\\ 一般地,当p=1时,L_1(x_i,x_j)=(\sum^n_{l=1}|x_i^{(l)}-x_j^{(l)}|),称为曼哈顿距离;\\ 当p=2时,L_2(x_i,x_j)=(\sum^n_{l=1}|x_i^{(l)}-x_j^{(l)}|^2)^{\frac{1}{2}},其实形式上也是L2范数,称为欧式距离,平常使用的比较多;\\ 当p=\infty,它是各个坐标距离的最大值,即为:L_{\infty}(x_i,x_j)=max|x_i^{(l)}-x_j^{(l)}| \]
K值的选择
除了距离度量外,还有K值的选择对KNN算法的结果也会产生重大影响。
- 如果选择较小的k值,就相当于用较小的领域中的训练实例进行预测,“学习”的近似误差会减小,只有与输入实例较近的实例才会对预测结果起到作用,但缺点就是学习的估计误差就会增大,预测结果就会近邻的实例点非常敏感;
- 如果选择较大的值,学习的误差估计会减小,但是与此同时,近似误差就会增大,这时会出现对于距离比较远的实例点起不到预测作用,使得预测结果错误。
分类决策规则
KNN中的决策规则通常就是“投票选举”——少数服从多数的方式。
如果损失函数是0-1损失函数,那么分类函数就是:
\[f:R^n \rightarrow {\{c_1,c_2,...,c_k}\} \]
对于相邻k个训练实例点构成集合N,误分类率是:
\[\frac{1}{k}\sum_{x_i\in N_k(x)}I(y_i \neq c_j )=1-\frac{1}{k}\sum_{x_i\in N_k(x)}I(y_i = c_j ) \]
要使误分类率最小,那么就是要求正确的概率最大,所以少数服从多数的规则正好可以满足经验风险最小化。
KNN算法
算法描述
\[输入:训练数据集:T=\{(x_1,y_1),(x_2,y_2),(x_3,y_3)...,(x_N,y_N)\},其中x_i \in \chi \subseteq R^n为实例的特征向量,\\ y_i \in y=\{c_1,c_2,...,c_K\}为实例得类别,i=1,2,...,;实例特征向量x;\\ 输出:实例x所属的类别y。\\ (1)根据给定的距离向量,在训练集T中找出与x最近邻的k个点,包含k个点的x的领域记作N_k(x);\\ (2)在N_k(x)中根据分类决策规则决定x的类别y:y=argmax\sum_{x_i\in N_k(x)}I(y_i = c_j ),i,j=1,2,..,N。 \]
实现KNN时,主要是考虑的问题时如何对训练数据进行快速K近邻搜索,如果特征空间的维数大或者训练数据容量大时,那么数据存储就是一个大问题。KNN最简单的实现方法是线性扫描,这时当数据集很大,计算就非常地耗时。为了提高这种搜索效率,使用特殊地结构进行存储训练数据——kd树(kd tree)。kd树是一种对k维空间中的点进行存储以便于对其进行快速搜索的树形数据结构。实质上,kd树是一种二叉树,表示对k维空间的一个划分。
代码实现
自编程实现
class KNN:
"""
使用自编程实现KNN算法
@author cecilia
"""
def __init__(self,X_train,y_train,k=3):
# 所需参数初始化
self.k=k # 所取k值
self.X_train=X_train
self.y_train=y_train
def predict(self,X_new):
# 计算欧氏距离
dist_list=[(np.linalg.norm(X_new-self.X_train[i],ord=2),self.y_train[i])
for i in range(self.X_train.shape[0])]
#[(d0,-1),(d1,1)...]
# 对所有距离进行排序
dist_list.sort(key=lambda x: x[0])
# 取前k个最小距离对应的类别(也就是y值)
y_list=[dist_list[i][-1] for i in range(self.k)]
# [-1,1,1,-1...]
# 对上述k个点的分类进行统计
y_count=Counter(y_list).most_common()
# [(-1, 3), (1, 2)]
return y_count[0][0]
def main():
# 初始化数据
X_train=np.array([[5,4],
[9,6],
[4,7],
[2,3],
[8,1],
[7,2]])
y_train=np.array([1,1,1,-1,-1,-1])
# 测试数据
X_new = np.array([[5, 3]])
# 不同的k(取奇数)对分类结果的影响
for k in range(1,6,2):
#构建KNN实例
clf=KNN(X_train,y_train,k=k)
#对测试数据进行分类预测
y_predict=clf.predict(X_new)
print("k={},class label is:{}".format(k,y_predict))
Sklearn库
from sklearn.neighbors import KNeighborsClassifier
def sklearn_knn():
"""
使用sklearn库实现KNN算法
@author cecilia
"""
X_train=np.array([[5,4],
[9,6],
[4,7],
[2,3],
[8,1],
[7,2]])
y_train=np.array([1,1,1,-1,-1,-1])
# 待预测数据
X_new = np.array([[5, 3]])
# 不同k值对结果的影响
for k in range(1,6,2):
# 构建实例
clf = KNeighborsClassifier(n_neighbors=k,n_jobs=-1)
# 选择合适算法
clf.fit(X_train, y_train)
# print(clf.kneighbors(X_new))
# 预测
y_predict=clf.predict(X_new)
#print(clf.predict_proba(X_new))
print("accuracy:{:.0%}".format(clf.score([[5,3]],[[1]])))
print("k={},label lcass is:{}".format(k,y_predict))
结果显示:
思考
KNN算法模型的复杂度主要是体现在哪儿?什么情况下会造成过拟合?
k临近算法的模型复杂度体现在k值上;k值较小容易造成过拟合,k值较大容易造成欠拟合。