必須要看的前言
本文風格:以??簡單易懂??的語言帶你徹底搞懂KNN,了解什么是有監督學習演算法,
認真看完這篇文章,徹底了解KNN、了解監督學習演算法絕對是一樣很簡單的事情,
注:本篇文章非常詳細,同時我也附加了Python代碼,歡迎收藏后慢慢閱讀,
目錄
- 必須要看的前言
- 監督學習演算法
- KNN/K近鄰演算法
- 1 演算法原理
- 1.1 實作程序
- 1.2 距離的確定
- 2 演算法的優缺點
- 3 演算法的變種
- 3.1 變種一
- 3.2 變種二
- 4 Python代碼實作
- 4.1 匯入模塊
- 4.2 構建已經分類好的原始資料集
- 4.3 計算已知類別資料集中的點與當前點之間的距離,
- 4.4 將距離升序排列,然后選取距離最小的k個點,
- 4.5 確定前k個點所在類別的計數,
- 4.6 封裝成函式
- 5 SCIKIT-LEARN演算法庫實作
- 5.1 案例一:紅酒
- 5.2 案例二:乳腺癌
- 6 選擇最優K值
- 6.1 學習曲線
- 7 交叉驗證
- 7.1 泛化能力
- 7.2 K折交叉驗證
- 7.3 帶交叉驗證的學習曲線
- 7.4 是否需要驗證集
- 7.5 其他交叉驗證
- 7.6 避免折數太大
- 8 歸一化
- 8.1 距離類模型歸一化的要求
- 8.2 先分資料集,再做歸一化
- 8.3 通過 python 實作
- 8.4 通過 sklearn 實作
- 9 距離的懲罰
- 結束語
監督學習演算法
本文主要介紹的有監督學習演算法是KNN,后續會接著介紹決策樹、線性回歸等演算法,
KNN/K近鄰演算法
1 演算法原理
首先,第一個也是最主要的問題——KNN是如何對樣本進行分類的呢?
它的本質是通過距離判斷兩個樣本是否相似,如果距離夠近就認為他們足夠相似屬于同一類別,
當然只對比一個樣本是不夠的,誤差會很大,我們需要找到離其最近的 k 個樣本,并將這些樣本稱之為「近鄰」(nearest neighbor),對這 k 個近鄰,查看它們的都屬于何種類別(這些類別我們稱作「標簽」 (labels)),
然后根據“少數服從多數,一點算一票”原則進行判斷,數量最多的的標簽類別就是新樣本的標簽類別,其中涉及到的原理是“越相近越相似”,這也是KNN的基本假設,
1.1 實作程序
假設 X_test 待標記的資料樣本,X_train 為已標記的資料集,
- 遍歷已標記資料集中所有的樣本,計算每個樣本與待標記點的距離,并把距離保存在 Distance 陣列中,
- 對 Distance 陣列進行排序,取距離最近的 k 個點,記為 X_knn,
- 在 X_knn 中統計每個類別的個數,即 class0 在 X_knn 中有幾個樣本,class1 在 X_knn 中有幾個樣本等,
- 待標記樣本的類別,就是在 X_knn 中樣本個數最多的那個類別,
1.2 距離的確定
該演算法的「距離」在二維坐標軸就表示兩點之間的距離,計算距離的公式有很多,
我們常用歐拉公式,即“歐氏距離”,回憶一下,一個平面直角坐標系上,如何計算兩點之間的距離?
應該不難會想起來吧,公式應該大致如下: d i s t a n c e ( A , B ) = ( x A ? x B ) 2 + ( y A ? y B ) 2 distance(A, B)=\sqrt[]{(x_A-x_B)^2+(y_A-y_B)^2} distance(A,B)=(xA??xB?)2+(yA??yB?)2 ?那如果不是在一個平面直角坐標系,而是在立體直角坐標系上,怎么計算呢? d i s t a n c e ( A , B ) = ( x A ? x B ) 2 + ( y A ? y B ) 2 + ( z A ? z B ) 2 distance(A, B)=\sqrt[]{(x_A-x_B)^2+(y_A-y_B)^2+(z_A-z_B)^2} distance(A,B)=(xA??xB?)2+(yA??yB?)2+(zA??zB?)2 ?那如果是n維空間呢? d i s t a n c e ( A , B ) = ( x 1 A ? x 1 B ) 2 + ( x 2 A ? x 2 B ) 2 + ( x 3 A ? x 3 B ) 2 + . . . . . . + ( x n A ? x n B ) 2 = ∑ i = 1 n ( x i A ? x i B ) 2 distance(A, B)=\sqrt[]{(x_{1A}-x_{1B})^2+(x_{2A}-x_{2B})^2+(x_{3A}-x_{3B})^2+......+(x_{nA}-x_{nB})^2}=\sqrt[]{\sum_{i=1}^{n} {(x_{iA}-x_{iB})^2}} distance(A,B)=(x1A??x1B?)2+(x2A??x2B?)2+(x3A??x3B?)2+......+(xnA??xnB?)2 ?=i=1∑n?(xiA??xiB?)2 ?而在我們的機器學習中,坐標軸上的值 x 1 x_1 x1?, x 2 x_2 x2? , x 3 x_3 x3? ,…… x n x_n xn?正是我們樣本資料上的 n 個特征,
2 演算法的優缺點
演算法引數是 k,k 可以理解為標記資料周圍幾個數作為參考物件,引數選擇需要根據資料來決定,
- k 值越大,模型的偏差越大,對噪聲資料越不敏感,
- k 值很大時,可能造成模型欠擬合,
- k 值越小,模型的方差就會越大,
- 但是 k 值太小,容易過擬合,
3 演算法的變種
3.1 變種一
默認情況下,在計算距離時,權重都是相同的,但實際上我們可以針對不同的鄰居指定不同的距,離權重,比如距離越近權重越高,
- 這個可以通過指定演算法的 weights 引數來實作,
3.2 變種二
使用一定半徑內的點取代距離最近的 k 個點,
- 在 scikit-learn 中,RadiusNeighborsClassifier 實作了這種演算法的變種,
- 當資料采樣不均勻時,該演算法變種可以取得更好的性能,
4 Python代碼實作
這里我還是先以上篇文章講到的紅酒分類為例子,待會還會有其他實體,
4.1 匯入模塊
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 解決坐標軸刻度負號亂碼
plt.rcParams['axes.unicode_minus'] = False
# 解決中文亂碼問題
plt.rcParams['font.sans-serif'] = ['Simhei']
plt.style.use('ggplot')
# plt.figure(figsize=(2,3),dpi=720)
4.2 構建已經分類好的原始資料集
首先隨機設定十個樣本點表示十杯酒,這里取了部分樣本點,
為了方便驗證,這里使用 Python 的字典 dict 構建資料集,然后再將其轉化成 DataFrame 格式,
rowdata = {'顏色深度': [14.23,13.2,13.16,14.37,13.24,12.07,12.43,11.79,12.37,12.04],
'酒精濃度': [5.64,4.38,5.68,4.80,4.32,2.76,3.94,3.1,2.12,2.6],
'品種': [0,0,0,0,0,1,1,1,1,1]}
# 0 代表 “黑皮諾”,1 代表 “赤霞珠”
wine_data = pd.DataFrame(rowdata)
wine_data

X = np.array(wine_data.iloc[:,0:2]) #我們把特征(酒的屬性)放在X
y = np.array(wine_data.iloc[:,-1]) #把標簽(酒的類別)放在Y
我們先來畫一下圖,
#探索資料,假如我們給出新資料[12.03,4.1] ,你能猜出這杯紅酒是什么類別么?
new_data = np.array([12.03,4.1])
plt.scatter(X[y==1,0], X[y==1,1], color='red', label='赤霞珠') #畫出標簽y為1 的、關于“赤霞珠”的散點
plt.scatter(X[y==0,0], X[y==0,1], color='purple', label='黑皮諾') #畫出標簽y為0 的、關于“黑皮諾”的散點
plt.scatter(new_data[0],new_data[1], color='yellow') # 新資料點 new_data
plt.xlabel('酒精濃度')
plt.ylabel('顏色深度') plt.legend(loc='lower right') plt.savefig('葡萄酒樣本.png')

講道理,你應該一下就能看出來了,不過,如果是計算機,會這么分辨呢?
4.3 計算已知類別資料集中的點與當前點之間的距離,
我們使用歐式距離公式,計算新資料點 new_data 與現存的 X 資料集每一個點的距離:
from math import sqrt
distance = [sqrt(np.sum((x-new_data)**2)) for x in X ]
distance

那現在,我們就已經得到黃點距離其它每個點的距離啦,
4.4 將距離升序排列,然后選取距離最小的k個點,
sort_dist = np.argsort(distance) # 這里是用到了argsort方法,可以回傳資料對應的下標,如果直接用sort方法的話是回傳打亂的資料,我們也不好區分對應是什么類別,
sort_dist
array([6, 7, 1, 4, 5, 9, 2, 8, 3, 0], dtype=int64)
6、7、4為最近的3個“資料點”的索引值,那么這些索引值對應的原資料的標簽是什么?
k = 3
topK = [y[i] for i in sort_dist[:k]]
topK
[1,1,0]
這個時候我們就得到了離黃點最近的三個點對應的類別啦,
4.5 確定前k個點所在類別的計數,
# 在numpy中有mean、median方法可以求平均數和中位數,不過沒有方法直接求眾數,
pd.Series(topK).value_counts().index[0]
1
所以當我們的k取3時,分類結果為1,也就是赤霞珠,大家看一下是不是跟我們人腦分辨的結果是一樣的呢?
4.6 封裝成函式
那為了后續更好的操作,我們可以將上述程序封裝成一個函式,
def KNN(new_data,dataSet,k):
'''
函式功能:KNN分類器
引數說明:
new_data: 需要預測分類的資料集
dataSet: 已知分類標簽的資料集
k: k-近鄰演算法引數,選擇距離最小的k個點
return:
result: 分類結果
'''
from math import sqrt
from collections import Counter
import numpy as np
import pandas as pd
result = []
distance = [sqrt(np.sum((x-new_data)**2)) for x in np.array(dataSet.iloc[:,0:2])]
sort_dist = np.argsort(distance)
topK = [dataSet.iloc[:,-1][i] for i in sort_dist[:k]]
result.append(pd.Series(topK).value_counts().index[0])
return result
# 測驗函式的運行結果
new_data=np.array([12.03,4.1])
k = 3
KNN(new_data,wine_data,k)
[1]
5 SCIKIT-LEARN演算法庫實作
上述程序是我們自己一個個實作的,而在如今呢,人們更習慣用scikit-learn這一Python的第三方模塊,
scikit-learn 自 2007 年發布以來,scikit-learn已經成為 Python 中重要的機器學習庫了,scikit-learn,簡稱 sklearn, 支持了包括分類、回歸、降維和聚類四大機器學習演算法,以及特征提取、資料預處理和模型評估三大模塊, 在工程應用中,用 Python 手寫代碼來從頭實作一個演算法的可能性非常低,這樣不僅耗時耗力,還不一 定能夠寫出構架清晰,穩定性強的模型,更多情況下,是分析采集到的資料,根據資料特征選擇適合的演算法, 在工具包中呼叫演算法,調整演算法的引數,獲取需要的資訊,從而實作演算法效率和效果之間的平衡,而 sklearn, 正是這樣一個可以幫助我們高效實作演算法應用的工具包,
http://scikit-learn.org/stable/index.html
上述鏈接是sklearn的官方網站,里面詳細地介紹了sklearn的使用,感興趣的朋友可以看一看這個,或者看我下面的簡要介紹,

主要設計原則:
1)一致性
所有物件共享一個簡單一致的界面(介面),
- 估算器:fit()方法,基于資料估算引數的任意物件,使用的引數是一個資料集(對應 X, 有監督演算法還需要一個 y),引導估算程序的任意其他引數稱為超引數,必須被設定為實體變數,
- 轉換器:transform()方法,使用估算器轉換資料集,轉換程序依賴于學習引數,可以使用便捷方式: fit_transform(),相當于先 fit()再 transform(),(fit_transform 有時被優化過,速度更快)
- 預測器:predict()方法,使用估算器預測新資料,回傳包含預測結果的資料,還有score()方法:用于度量給定測驗集的預測效果的好壞,(連續 y 使用 R 方,分類 y 使用準確率 accuracy)
2)監控
檢查所有引數,所有估算器的超引數可以通過公共實體變數訪問,所有估算器的學習引數都可以通過有下劃線后綴的公共實體變數訪問,
3)防止類擴散
物件型別固定,資料集被表示為 Numpy 陣列或 Scipy 稀疏矩陣,超參是普通的 Python 字符或數字,
4)合成
現有的構件盡可能重用,可以輕松創建一個流水線 Pipeline,
5)合理默認值
大多數引數提供合理默認值,可以輕松搭建一個基本的作業系統,
5.1 案例一:紅酒
from sklearn.neighbors import KNeighborsClassifier
# 0 代表 “黑皮諾”,1 代表 “赤霞珠”
clf = KNeighborsClassifier(n_neighbors = 3)
clf = clf.fit(wine_data.iloc[:,0:2], wine_data.iloc[:,-1])
result = clf.predict([[12.8,4.1]]) # 回傳預測的標簽
result
array([0])
# 對模型進行一個評估,介面score回傳預測的準確率
score = clf.score([[12.8,4.1]],[0])
score
1.0
clf.predict_proba([[12.8,4.1]])
array([[0.66666667, 0.33333333]])
這里解釋一下,0.66666667是標簽為0的概率,0.33333333是標簽為1的概率,
5.2 案例二:乳腺癌
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
#讀取資料集
data = load_breast_cancer()
#DateFrame格式顯示
X = data.data
y = data.target
name = ['平均半徑','平均紋理','平均周長','平均面積',
'平均光滑度','平均緊湊度','平均凹度',
'平均凹點','平均對稱','平均分形維數',
'半徑誤差','紋理誤差','周長誤差','面積誤差',
'平滑度誤差','緊湊度誤差','凹度誤差',
'凹點誤差','對稱誤差',
'分形維數誤差','最差半徑','最差紋理',
'最差的邊界','最差的區域','最差的平滑度',
'最差的緊湊性','最差的凹陷','最差的凹點',
'最差的對稱性','最差的分形維數','患病否']
data=np.concatenate((X,y.reshape(-1,1)),axis=1)
table=pd.DataFrame(data=data,columns=name)
table.head()

# 劃分訓練集(Xtrain,Ytrain)和測驗集(Xtest,Ytest)
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=420)
# 建立模型&評估模型
clf = KNeighborsClassifier(n_neighbors=2)
clf = clf.fit(Xtrain,Ytrain)
score = clf.score(Xtest,Ytest)
score
0.9210526315789473
如何用上面分類器擬合結果找出離 Xtest 中第 20 行和第 30 行最近的 4 個“點”?
#查找點的K鄰居,回傳每個點的鄰居的與之的距離和索引值,
clf.kneighbors(Xtest[[20,30],:],return_distance=True)
(array([[35.70015941, 42.02374599, 81.82147557, 83.06271326],
[11.81126721, 14.5871725 , 17.4734004 , 18.94892695]]),
array([[112, 221, 303, 263], [268, 162, 42, 134]], dtype=int64))
6 選擇最優K值
相信大家看到這可能會有疑惑說,這個k值到底怎么取比較好,那么接下來,就正式講一講怎么選擇k值吧,
KNN 中的 k 是一個超引數,所謂“超引數”,就是需要人為輸入,演算法不能通過直接計算得出的引數,
KNN 中的 k 代表的是距離需要分類的測驗點 x 最近的 k 個樣本點,如果不輸入這個值,那么演算法中重要部分 “選出 k 個最近鄰” 就無法實作,
從 KNN 的原理中可見,是否能夠確認合適的 k 值對演算法有極大的影響,
如果選擇的 k 值較小,就相當于較小的鄰域中的訓練實體進行預測,這時候只有與輸入實體較近的(相似的)訓練實體才會對預測結果起作用,但缺點是預測結果會對近鄰的實體點非常敏感,如果鄰近的實體點恰好是噪聲,預測就會出錯,
相反地,如果選擇的 k 值較大,就相當于較大的鄰域中的訓練實體進行預測,這時與輸入實體較遠的 (不相似的)訓練實體也會對預測起作用,使預測發生錯誤,因此,超引數 k 的選定是 KNN 的頭號問題,

正如上圖中圈紅的x,取5和取11的話,結果也會完全不一樣,
6.1 學習曲線
那我們怎樣選擇一個最佳的 k 呢?在這里我們要使用機器學習中的神器:引數學習曲線,引數學習曲線是一條以不同的引數取值為橫坐標,不同引數取值下的模型結果為縱坐標的曲線,我們往往選擇模型表現最佳點的引數取值作為這個引數的取值,
# 更換不同的n_neighbors引數的取值,觀察結果的變化
clf = KNeighborsClassifier(n_neighbors=7)
clf = clf.fit(Xtrain,Ytrain) score =
clf.score(Xtest,Ytest)
score
0.9385964912280702
繪制學習曲線:
score = []
krange = range(1,20)
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
clf = clf.fit(Xtrain,Ytrain)
score.append(clf.score(Xtest,Ytest))
plt.plot(krange,score)
plt.show()

究竟上圖中k為多少的時候分數越高?
score.index(max(score))+1
8
但是這個時候會有個問題,如果隨機劃分的資料集變化的的話,得分最高的k值也會發生變化:
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=421)
score = []
krange = range(1,20)
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
clf = clf.fit(Xtrain,Ytrain)
score.append(clf.score(Xtest,Ytest))
plt.plot(krange,score)
plt.show()

score.index(max(score))+1
3
這樣就無法確定最佳的k值了,就無法進行下面的建模作業,怎么辦?
我們接著往下看,
7 交叉驗證
確定了 k 之后,我們還能夠發現一件事:每次運行的時候學習曲線都在變化,模型的效果時好時壞, 這是為什么呢?
實際上,這是由于「訓練集」和「測驗集」的劃分不同造成的,模型每次都使用不同的訓練集進行訓練, 不同的測驗集進行測驗,自然也就會有不同的模型結果,
在業務當中,我們的訓練資料往往是已有的歷史資料,但我們的測驗資料卻是新進入系統的一系列還沒有標簽的未知資料,我們的確追求模型的效果,但我們追求的是模型在未知資料集上的效果,在陌生資料集上表現優秀的能力被稱為泛化能力,即我們追求的是模型的泛化能力,
7.1 泛化能力
我們在進行學習演算法前, 通常會將一個樣本集分成訓練集(training set)和測驗集(testing set),其中訓練集用于模型的學習或訓練,而后測驗集通常用于評估訓練好的模型對于資料的預測性能評估,
**訓練誤差(training error)**代表模型在訓練集上的錯分樣本比率,
**測驗誤差(empirical error)**是模型在測驗集上的錯分樣本比率,
訓練誤差的大小,用來判斷給定問題是不是一個容易學習的問題, 測驗誤差則反映了模型對未知資料的預測能力,測驗誤差小的學習方法具有很好的預測能力,如果得到的訓練集和測驗集的資料沒有交集,通常將此預測能力稱為泛化能力(generalization ability), 我們認為,如果模型在一套訓練集和資料集上表現優秀,那說明不了問題,只有在眾多不同的訓練集和 測驗集上都表現優秀,模型才是一個穩定的模型,模型才具有真正意義上的泛化能力,為此,機器學習領域有發揮神作用的技能:「交叉驗證」,來幫助我們認識模型,
7.2 K折交叉驗證
最常用的交叉驗證是 k 折交叉驗證,我們知道訓練集和測驗集的劃分會干擾模型的結果,因此用交叉驗證 n 次的結果求出的均值,是對模型效果的一個更好的度量,

圖中綠色部分就是我們分離出來的驗證集,可以看到在每一次測驗中驗證集的位置(也就是資料)都會發生改變,K折交叉驗證就是會有K次測驗,
7.3 帶交叉驗證的學習曲線
對于帶交叉驗證的學習曲線,我們需要觀察的就不僅僅是最高的準確率了,而是準確率高且方差還相對較小的點,這樣的點泛化能力才是最強的,在交叉驗證+學習曲線的作用下,我們選出的超引數能夠保證更好的泛化能力,
from sklearn.model_selection import cross_val_score as CVS
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=420)
clf = KNeighborsClassifier(n_neighbors=8)
cvresult = CVS(clf,Xtrain,Ytrain,cv=6) #訓練集對折6次,一共6個預測率輸出
cvresult #每次交叉驗證運行時估算器得分的陣列
array([0.92207792, 0.90789474, 0.97368421, 0.94736842, 0.93333333, 0.92 ])
# 均值:查看模型的平均效果
cvresult.mean()
# 方差:查看模型是否穩定
cvresult.var()
0.934059770638718 0.0004622658270548926
score = []
var = []
krange=range(1,20) #設定不同的k值,從1到19都看看
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
cvresult = CVS(clf,Xtrain,Ytrain,cv=5)
score.append(cvresult.mean()) # 每次交叉驗證回傳的得分陣列,再求陣列均 值
var.append(cvresult.var())
plt.plot(krange,score,color='k')
plt.plot(krange,np.array(score)+np.array(var)*2,c='red',linestyle='--')
plt.plot(krange,np.array(score)-np.array(var)*2,c='red',linestyle='--')

7.4 是否需要驗證集
最標準,最嚴謹的交叉驗證應該有三組資料:訓練集、驗證集和測驗集, 當我們獲取一組資料后:
- 先將資料集分成整體的訓練集和測驗集,
- 然后我們把訓練集放入交叉驗證中,
- 從訓練集中分割更小的訓練集(k-1 份)和驗證集(1 份),
- 回傳的交叉驗證結果其實是驗證集上的結果,
- 使用驗證集尋找最佳引數,確認一個我們認為泛化能力最佳的模型,
- 將這個模型使用在測驗集上,觀察模型的表現,
通常來說,我們認為經過驗證集找出最終引數后的模型的泛化能力是增強了的,因此模型在未知資料(測驗集)上的效果會更好,但尷尬的是,模型經過交叉驗證在驗證集上的調參之后,在測驗集上的結果沒有變好的情況時有發生,
原因其實是:
- 我們自己分的訓練集和測驗集,會影響模型的效果,
- 交叉驗證后的模型的泛化能力增強了,表示它在未知資料集上方差更小,平均水平更高,但卻無法保證它在現在分出來的測驗集上預測能力最強,
- 如此說來,是否有測驗集的存在,其實意義不大了,
如果我們相信交叉驗證的調整結果是增強了模型的泛化能力的,那即便測驗集上的測驗結果并沒有變 好(甚至變壞了),我們也認為模型是成功的, 如果我們不相信交叉驗證的調整結果能夠增強模型的泛化能力,而一定要依賴測驗集來進行判斷,我們完全沒有進行交叉驗證的必要,直接用測驗集上的結果來跑學習曲線就好了, 所以,究竟是否需要驗證集,其實是存在爭議的,在嚴謹的情況下,大家還是使用有驗證集的方式,
7.5 其他交叉驗證
交叉驗證的方法不止“k 折” 一種,分割訓練集和測驗集的方法也不止一種,分門別類的交叉驗證占據了sklearn 中非常長的一章,
所有的交叉驗證都是在分割訓練集和測驗集,只不過側重的方向不同,
- “k 折"就是按順序取訓練集和測驗集,
- ShuffleSplit 就側重于讓測驗集分布在資料的全方位之內,
- StratifiedKFold 則是認為訓練資料和測驗資料必須在每個標簽分類中占有相同的比例,
各類交叉驗證的原理繁瑣,大家在機器學習道路上一定會逐漸遇到更難的交叉驗證,但是萬變不離其宗:本質上交叉驗證是為了解決訓練集和測驗集的劃分對模型帶來的影響,同時檢測模型的泛化能力的,

當然常用的還是K折交叉驗證啦,
另外,交叉驗證的折數不可太大,因為折數越大抽出來的資料集越小,訓練資料所帶的資訊量會越小,模型會越來越不穩定,
7.6 避免折數太大
如果你發現不使用交叉驗證的時候模型表現很好,一使用交叉驗證模型的效果就驟降,
- 一定要查看你的標簽是否有順序,
- 然后就是查看你的資料量是否太小,折數是否太高,
如果將上面例題的代碼中將 cv 將 5 改成 100:

折數過大:
- 運算效率變慢,
- 預測率方差變大,難以保證在新的資料集達到預期預測率,
8 歸一化
8.1 距離類模型歸一化的要求
什么是歸一化?我們把 X 放到資料框中來看一眼,你是否觀察到,每個特征的均值差異很大?有的特征數值很大,有的特征數值很小,這種現象在機器學習中被稱為"量綱不統一",KNN 是距離類模型,歐氏距離的計算公式中存在著特征上的平方和: d i s t a n c e ( A , B ) = ( x 1 A ? x 1 B ) 2 + ( x 2 A ? x 2 B ) 2 + ( x 3 A ? x 3 B ) 2 + . . . . . . + ( x n A ? x n B ) 2 = ∑ i = 1 n ( x i A ? x i B ) 2 distance(A, B)=\sqrt[]{(x_{1A}-x_{1B})^2+(x_{2A}-x_{2B})^2+(x_{3A}-x_{3B})^2+......+(x_{nA}-x_{nB})^2}=\sqrt[]{\sum_{i=1}^{n} {(x_{iA}-x_{iB})^2}} distance(A,B)=(x1A??x1B?)2+(x2A??x2B?)2+(x3A??x3B?)2+......+(xnA??xnB?)2 ?=i=1∑n?(xiA??xiB?)2 ?如果某個特征 的取值非常大,其他特征的取值和它比起來就不算什么,那么距離的大小很大程度都會由這個 來決定,其他的特征之間的距離可能就無法對d(A,B)的大小產生什么影響,這種現象會讓KNN這樣的距離類模型的效果大打折扣,
然而在實際分析情景當中,絕大多數資料集都會存在各特征值量綱不同的情況,此時若要使用 KNN 分類器,則需要先對資料集進行歸一化處理,即是將所有的資料壓縮都同一個范圍內,
當資料(x)按照最小值中心化后,再按極差(最大值-最小值)縮放,資料移動了最小值個單位,并且會被收斂到[0,1]之間,而這個程序,就稱作資料歸一化(Normalization,又稱 Min-Max Scaling), x ? = x ? m i n ( x ) m a x ( x ) ? m i n ( x ) x^*=\frac {x-min(x)} {max(x)-min(x)} x?=max(x)?min(x)x?min(x)?
8.2 先分資料集,再做歸一化
直接在全資料集 X 上進行了歸一化,然后放入交叉驗證繪制學習曲線,這種做法是錯誤的,
真正正確的方式是,先分訓練集和測驗集,再歸一化!
為什么?想想看歸一化的處理手段,我們是使用資料中的最小值和極差在對資料進行壓縮處理,如果我們在全資料集上進行歸一化,那最小值和極差的選取是會參考測驗集中的資料的狀況的,因此,當我們歸一化后,無論我們如何分割資料,都會由一部分測驗集的資訊被“泄露”給訓練集,這會使得我們的模型效果被高估,
在現實業務中,我們只知道訓練集的資料,不了解測驗集究竟會長什么樣,所以我們要利用訓練集上的最小值和極差來歸一化測驗集,
8.3 通過 python 實作
data = [[-1,2],[-0.5,6],[0,10],[1,18]]
data=pd.DataFrame(data)
(data-np.min(data,axis=0))/(np.max(data,axis=0)-np.min(data,axis=0))

8.4 通過 sklearn 實作
同樣的,slearn也封裝了Min-Max Scaling方法,具體操作如下:
from sklearn.preprocessing import MinMaxScaler as mms
Xtrain,Xtest,Ytrain,Ytest=train_test_split(X,y,test_size=0.2,random_state=420)
#歸一化
MMS_01=mms().fit(Xtrain) #求訓練集最大/小值
MMS_02=mms().fit(Xtest) #求測驗集最大/小值
#轉換
X_train=MMS_01.transform(Xtrain)
X_test =MMS_02.transform(Xtest)
score=[]
var=[]
for i in range(1,20):
clf=KNeighborsClassifier(n_neighbors=i)
cvresult=CVS(clf,X_train,Ytrain,cv=5) # 交叉驗證的每次得分
score.append(cvresult.mean())
var.append(cvresult.var())
plt.plot(krange,score,color="k")
plt.plot(krange,np.array(score)+np.array(var)*2,c="red",linestyle="--")
plt.plot(krange,np.array(score)-np.array(var)*2,c="red",linestyle="--")
plt.show()

score.index(max(score))+1
8
最終的到 k 最優值為 8,無論 random_state 取什么值,最優 k 值不會相差太多,
把經過交叉驗證、歸一化處理之后,我們得到最優 k 為 8,放在歸一化后的訓練集重新建模,然后在歸一化后的測驗集中查看結果分數:
clf=KNeighborsClassifier(n_neighbors=6,weights='distance').fit(X_train,Ytrain)
score=clf.score(X_test,Ytest)
score
0.956140350877193
大家可以嘗試著更改random_state,然后再根據學習曲線圖更換k值,最終查看分數是否同95.6%有較大出入,
9 距離的懲罰
最近鄰點距離遠近修正在對未知分類程序中, “一點一票” 的規則是 KNN 模型優化的一個重要步驟, 也就是說,對于原始分類模型而言,在選取最近的 k 個元素之后,將參考這些點的所屬類別,并對其進行簡單計數,而在計數的程序中這些點 “一點一票” ,這些點每個點對分類目標點的分類程序中影響效力相同,
但這實際上是不公平的,就算是最近鄰的 k 個點,每個點的分類目標點的距離仍然有遠近之別,而近的點往往和目標分類點有更大的可能性屬于同一類別( 該假設也是 KNN 分類模型的基本假設) ,
關于懲罰因子的選取有很多種方法, 最常用的就是根據每個最近鄰 𝑥= 距離的不同對其作加權, 加權方法為設定 權重,該權重計算公式為 w i = 1 d ( x ′ , x i ) w_i=\frac{1}{d(x^{'},x_i)} wi?=d(x′,xi?)1?這里需要注意的是,關于模型的優化方法只是在理論上而言進行優化會提升模型判別效力,但實際應用程序中最終能否發揮作用,本質上還是取決于優化方法和實際資料情況的契合程度,如果資料本身存在大量例外值點,則采用距離遠近作為懲罰因子則會有較好的效果,反之則不然,
因此在實際我們進行模型優化的程序當中,是否起到優化效果還是要以最終模型運行結果為準,在sklearn中,我們可以通過引數 weights 來控制是否適用距離作為懲罰因子,
for i in range(1,20):
clf=KNeighborsClassifier(n_neighbors=i,weights='distance')
cvresult=CVS(clf,X_train,Ytrain,cv=5) # 交叉驗證的每次得分
score.append(cvresult.mean())
var.append(cvresult.var())
plt.plot(krange,score,color="k")
plt.plot(krange,np.array(score)+np.array(var)*2,c="red",linestyle="--")
plt.plot(krange,np.array(score)-np.array(var)*2,c="red",linestyle="--")
plt.show()

score.index(max(score))+1
6
clf=KNeighborsClassifier(n_neighbors=6,weights='distance').fit(X_train,Ytrain)
score=clf.score(X_test,Ytest)
score
0.9473684210526315
結束語
感謝你能看到這里,我會在后續繼續更新機器學習系列的文章,內容風格依舊會是易懂但有用,希望能得到你的點贊支持,感謝!
機器學習系列往期回顧
?? 開始學習機器學習之前你必須要了解的知識有哪些?機器學習系列入門篇
💚 統計學習方法第二版 李航
往期內容回顧
🖤 我和關注我的前1000個粉絲“合影”啦!收集前1000個粉絲進行了一系列資料分析,識訓滿滿
?? 分享一個超nice的資料分析實戰案例 ? “手把手”教學,收藏等于學會
💙 資料分析必須掌握的RFM模型是什么?一文搞懂如何利用RFM對用戶進行分類【附實戰講解】
💚 MySQL必須掌握的技能有哪些?超細長文帶你掌握MySQL【建議收藏】
💜 Hive必須了解的技能有哪些?萬字博客帶你掌握Hive??【建議收藏】
🧡 一文帶你了解Hive【詳細介紹】Hive與傳統資料庫有什么區別?
推薦關注的專欄
👨?👩?👦?👦 資料分析:分享資料分析實戰專案和常用技能整理
CSDN@報告,今天也有好好學習
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298589.html
標籤:AI
