機器學習--k近鄰演算法探索及糖尿病預測
演算法原理:未標記樣本類別由距離其最近的k個鄰居投票決定。計算待標記的樣本和資料集中每個樣本的距離,取距離最近的k個樣本,待標記樣本所屬類別由這k個距離最近的樣本投票產生。
- 優點:KNN原理簡單,容易實現,結果精度高,無需估計引數,無需訓練模型,可用於分類(投票)和迴歸(平均值),對異常值和噪聲有較高的容忍度;
- 不足:當樣本容量不平衡時,可能導致需預測的樣本中大容量類的樣本佔多數;可解釋性差;計算量大,對記憶體需求較大,每次對未標記樣本分類時都需全部計算。
演算法引數:k
- k值越大,模型偏差越大,對噪聲資料越不敏感,k值很大時可能造成模型欠擬合;
- k值越小,模型方差越大,k值太小時造成過擬合。
weight權重:預設計算距離時都使用相同權重,“uniform”,但實際上,可以針對不同鄰居指定不同距離權重,距離越近,權重越高,“distance”.
a. 用k近鄰演算法進行分類
sklearn.neighbors.KNeighborsClassifer
%matplotlib inline import matplotlib.pyplot as plt import numpy as np # 使用sklearn.datasets.samples_generator中的make_blobs()函式來生成資料集 from sklearn.datasets.samples_generator import make_blobs # 生成60個訓練樣本,這些樣本以centers引數指定中心的周圍 # cluster_std為標準差,用來指定點分佈的鬆散程度 centers = [[-2,2],[2,2],[0,4]] # X為以center為中心的60個樣本shape(60,2),y為中心值類別,根據centers不同分為[0,1,2] X,y = make_blobs(n_samples=60,centers=centers,random_state=0,cluster_std=0.6) # 將資料集用圖表展現 plt.figure(figsize=(8,5),dpi=144) c = np.array(centers) plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap='cool') #畫出樣本 plt.scatter(c[:,0],c[:,1],s=50,marker='^',c='orange') # 畫出中心點 plt.savefig('knn-1.png') # 使用KNeighborsClassifier對演算法進行訓練 from sklearn.neighbors import KNeighborsClassifier k=5 clf=KNeighborsClassifier(n_neighbors=k) # 訓練 clf.fit(X,y) # 對新樣本預測 X_sample=[[0,2]] y_sample=clf.predict(X_sample) # clf.neighbors把樣本週圍5個最近的點取出來,取出來的點是訓練集X裡面的索引 neighbors = clf.kneighbors(X_sample,return_distance=False) # 把新樣本與最近的5個點標記出來 plt.figure(figsize=(8,5),dpi=144) plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap='cool') #訓練樣本 plt.scatter(c[:,0],c[:,1],c='orange',marker='^',s=50) #三個中心點 plt.scatter(X_sample[0][0],X_sample[0][1],s=50,marker='x',c=y_sample[0],cmap='cool') #新樣本 # neighbors為二維資料shape(0,5) for i in neighbors[0]: plt.plot([X[i][0],X_sample[0][0]],[X[i][1],X_sample[0][1]],'k--',linewidth=0.6) #預測點與5個近鄰點連線
b. k近鄰演算法進行迴歸
分類問題的預測值是離散的,k近鄰演算法可在連續區間內對數值進行預測,進行迴歸擬合。
sklearn.neighbors.KNeighborsRegressor
from sklearn.neighbors import KNeighborsRegressor # 生成資料集 n_dots = 40 X=5*np.random.rand(n_dots,1) #shape(40,1) #.ravel()將資料降一維, 與,flatten()的作用都是降一維,不同點是ravel返回的是view,flatten返回的是拷貝 y=np.cos(X).ravel() # 對y新增噪聲 y += 0.2*np.random.rand(n_dots)-0.1 # 訓練模型 k=5 knn=KNeighborsRegressor(n_neighbors=k) knn.fit(X,y) # 新樣本:在X軸指定區間內創造足夠多的樣本點,用模型進行預測,把這些的連線起來就構成預測曲線 # [:,np.newaxis]將一維陣列轉換為二維陣列shape(500,1) T = np.linspace(0,5,500)[:,np.newaxis] y_predict = knn.predict(T) # 擬合曲線對訓練樣本的擬合準確性 knn.score(X,y) # 畫出擬合曲線 plt.figure(figsize=(8,5)) plt.scatter(X,y,label='data',s=50,c='g') #訓練樣本 plt.plot(T,y_predict,label='prediction',c='k',lw=4) #擬合曲線 plt.axis('tight') plt.title('KNeighborsRegressor (k=%i)'%k) plt.savefig('knn=3.png')
c. 例項:糖尿病預測
資料來源於kaggle
目的是對Pima印第安人的糖尿病進行預測
資料集8個特徵:
- Pregnancies: 懷孕次數
- Glucose: 口服葡萄糖耐量試驗中血漿葡萄糖濃度
- BloodPressure: 舒張壓(mm Hg)
- SkinThickness: 三頭肌組織褶厚度(mm)
- Insulin: 2小時血清胰島素(μU/ ml)
- BMI: 體重指數(kg/(身高(m))^ 2)
- Diabetes Pedigree Function: 糖尿病系統功能
- Age: 年齡(歲)
標記值:0 沒有糖尿病,1 有糖尿病
import pandas as pd
data = pd.read_csv('diabetes.csv')
print('dataset shape {}'.format(data.shape))
data.head()
dataset shape (768, 9)
Out[68]:
Pregnancies | Glucose | BloodPressure | SkinThickness | Insulin | BMI | DiabetesPedigreeFunction | Age | Outcome | |
---|---|---|---|---|---|---|---|---|---|
0 | 6 | 148 | 72 | 35 | 0 | 33.6 | 0.627 | 50 | 1 |
1 | 1 | 85 | 66 | 29 | 0 | 26.6 | 0.351 | 31 | 0 |
2 | 8 | 183 | 64 | 0 | 0 | 23.3 | 0.672 | 32 | 1 |
3 | 1 | 89 | 66 | 23 | 94 | 28.1 | 0.167 | 21 | 0 |
4 | 0 | 137 | 40 | 35 | 168 | 43.1 | 2.288 | 33 | 1 |
data.groupby('Outcome').size()
Outcome 0 500 1 268 dtype: int64
1. 使用knn的三種權重對模型進行擬合併比較準確度:
# 分離特徵與目標
X = data.iloc[:,0:8]
Y = data.iloc[:,8]
print('shape of X {};shape of Y {}'.format(X.shape,Y.shape))
# 劃分訓練集與測試集
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,Y,test_size=.2)
# 模型比較,普通的k均值演算法、帶權重的k均值演算法、指定半徑的k均值演算法
from sklearn.neighbors import KNeighborsClassifier,RadiusNeighborsClassifier
# 構造三個模型
models=[]
models.append(('KNN',KNeighborsClassifier(n_neighbors=2)))
models.append(('KNN with weights',KNeighborsClassifier(n_neighbors=2,weights='distance')))
models.append(('Radius Neighbors',RadiusNeighborsClassifier(n_neighbors=2,radius=500.0)))
# 分別訓練3個模型,並計算評分
results=[]
for name,model in models:
model.fit(X_train,y_train)
results.append((name,model.score(X_test,y_test)))
for i in range(len(results)):
print('name: {};score: {}'.format(results[i][0],results[i][1]))
name: KNN;score: 0.7012987012987013 name: KNN with weights;score: 0.6883116883116883 name: Radius Neighbors;score: 0.6233766233766234
- KNN普通演算法:weights='uniform',權重相同;
- KNN權重演算法:weights='distance',距離越近,權重越高;
- RadiusNeighborsClassifer: 限定半徑最近鄰法,以指定半徑內的點投票決定。
2. 交叉驗證
由於訓練集和測試集是隨機分配的,測試結果具有隨機性,不能用於判斷演算法好壞。因此,多次隨機分配訓練集和交叉驗證測試集,然後對結果取平均值再比較:
從sklearn.model_selection裡匯入KFold, cross_val_score
KFold: K折,將資料集分為K份,(K-1)份為組成訓練集,1份組成驗證集,進行訓練驗證,一共訓練K次
cross_val_score: 總共計算K次交叉驗證準確性結果,再取平均值。
from sklearn.model_selection import KFold,cross_val_score
results = []
for name,model in models:
# 10折
kfold = KFold(n_splits=10)
cv_result = cross_val_score(model,X,Y,cv=kfold)
results.append((name,cv_result))
for i in range(len(results)):
print('name: {}; cross val score: {}'.format(results[i][0],results[i][1].mean()))
name: KNN; cross val score: 0.7147641831852358 name: KNN with weights; cross val score: 0.6770505809979495 name: Radius Neighbors; cross val score: 0.6497265892002735
由以上結果,仍然是KNN演算法結果較優,接下來檢視普通KNN演算法對訓練集和驗證集的擬合分數,並進一步畫出學習曲線
3. KNN學習曲線
knn訓練集驗證集擬合情況:
knn = KNeighborsClassifier(n_neighbors=2)
knn.fit(X_train,y_train)
train_score=knn.score(X_train,y_train)
test_score = knn.score(X_test,y_test)
print('train score: {}; test score: {}'.format(train_score,test_score))
train score: 0.8485342019543974; test score: 0.7012987012987013
可以看出,模型對訓練集擬合情況不佳,只有84%準確度,而預測結果的準確性更差,只有70%。
進一步地,檢視學習曲線:
# ShuffleSplit對資料集打亂再分配
from sklearn.model_selection import ShuffleSplit
from common.utiles import plot_learning_curve
cv = ShuffleSplit(n_splits=10,test_size=0.2,random_state=0)
plt.figure(figsize=(10,6))
plot_learning_curve(plt,knn,'Learning Curve for KNN Diabetes',X,Y,ylim=(0.0,1.01),cv=cv)
把訓練樣本數量分成五等分,逐漸增加訓練樣本數:
訓練樣本評分仍然較低,是欠擬合的表現。
# 不同k值訓練結果
nn_score=[]
best_prediction=[-1,-1]
for i in range(1,100):
knn = KNeighborsClassifier(n_neighbors=i,weights='distance')
knn.fit(X_train,y_train)
score = knn.score(X_test,y_test)
nn_score.append(score)
if score > best_prediction[1]:
best_prediction=[i,score]
print(best_prediction)
plt.plot(range(1,100),nn_score)
[10, 0.7532467532467533]
k=10時測試集準確度最高位75%,仍然欠擬合,Knn演算法沒有更好的措施來解決欠擬合問題,只能試著用其他演算法。
4. 特徵選擇及視覺化
用最直觀的方法把k均值演算法不是針對這個資料很好的模型畫出來,但是這個資料有8個特徵,無法在這麼高的維度上畫出,因此,選擇兩個與輸出值關係最大的特徵,在二維平面上畫出輸入值與輸出值的關係:
sklearn.feature_selection裡的SelectKBest可以用來選擇相關性最大的兩個特徵:
from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=2)
X_new = selector.fit_transform(X,Y)
plt.figure(figsize=(8,5),dpi=200)
plt.ylabel('BMI')
plt.xlabel('Glucose')
# 畫出Y==0的陰性樣本,用圓圈表示
plt.scatter(X_new[Y==0][:,0],X_new[Y==0][:,1],c='r',marker='o',s=10)
# 畫出Y==1的陽性樣本,用三角形表示
plt.scatter(X_new[Y==1][:,0],X_new[Y==1][:,1],c='g',marker='^',s=10)
橫座標是血糖值,縱座標是BMI值,在中間資料密集區,陰性樣本和陽性樣本幾乎重疊,可以直觀地看出k-均值演算法在這個糖尿病預測問題上無法達到很好的預測準確性。
參考: