1. 程式人生 > >機器學習--k近鄰演算法探索及糖尿病預測

機器學習--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-均值演算法在這個糖尿病預測問題上無法達到很好的預測準確性。

參考: