机器学习--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-均值算法在这个糖尿病预测问题上无法达到很好的预测准确性。

参考:

  1. 黄永昌《scikit-learn机器学习》
  2. 刘建平博客:https://www.cnblogs.com/pinard/p/6065607.html

猜你喜欢

转载自blog.csdn.net/huangxiaoyun1900/article/details/82787714