上上一章已經學習了感知機模型、策略和算法,感知機對於分類任務有着其優點,但是該模型是在具有強假設的條件下——訓練數據集必須是線性可分的,但是如果數據集是呈現無規則的分布,那么此時如果要做分類任務,還可以考慮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值較大容易造成欠擬合。