第4章 KNeighbors(糖尿病预测)

K近邻算法的核心思想是,未标记样本的类别由距离其最近的k个邻接投票决定。

K近邻算法的原理是,计算待标记的数据样本和数据集中每个样本的距离,取距离最近的k个样本,待标记样本所属的类别,即由上述的核心思想得到。

优点:准确性高,对异常值和噪声有较高的容忍度
缺点:计算量较大,对内存的需求也较大。

k值越大,模型的偏差越大,对噪声数据越不敏感,k很大时,可能造成欠拟合;k值越小,模型的方差就会越大,当k值太小,就会造成过拟合。

对 Pima 印第安人的糖尿病进行预测

数据来源kaggle.com,网址为:https://www.kaggle.com/uciml/pima-indians-diabetes-database

读取数据:

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 加载数据
data = pd.read_csv(r'C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\pima-indians-diabetes\diabetes.csv')
print('dataset shape {}'.format(data.shape))
data.head()

dataset shape (768, 9)
在这里插入图片描述
8个数值型特征:

  • 怀孕次数
  • 口服葡萄糖耐量试验中血浆葡萄糖浓度
  • 舒张压(mm Hg)
  • 三头肌组织褶厚度(mm)
  • 2小时血清胰岛素(μU/ ml)
  • 体重指数(kg/(身高(m))^ 2)
  • 糖尿病系统功能
  • 年龄(岁)

可见为二分类!

观察数据集中男性和女性样本的个数:

data.groupby("Outcome").size()

Outcome
0 500
1 268
dtype: int64

Pandas中loc和iloc函数用法详解

loc:通过行/列索引 “Index” 中的具体值来取行/列数据(如取"Index"为"A"的行)

iloc:通过行/列号来取行/列数据(如取第二行的数据)

将特征和标签(列)分离:

X = data.iloc[:, 0:8]
Y = data.iloc[:, 8]
print('shape of X {}; shape of Y {}'.format(X.shape, Y.shape))

shape of X (768, 8); shape of Y (768,)

将数据及分成训练集和测试集:

from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

构造3个模型进行预测准确性对比:

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)))
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.7662337662337663
name: KNN with weights; score: 0.7337662337662337
name: Radius Neighbors; score: 0.6883116883116883

由此可见普通的K-均值算法性能最好,但这个结果是不准确的,因为训练样本和测试样本是随机分配的,结果也是随机的。

因此我们需要多次随机分配训练数据集和交叉验证数据集,然后求模型准确性评分的平均值。利用scikit-learn.model_selection的 KFoldcross_val_score() 函数即可。

n_split = 10 即将原始数据集分成9份训练集和1份测试集,并计算10次取平均值。

from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score

results = []
for name, model in models:
    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

此时结果才相对准确,仍然是普通的K-均值算法性能最好。

因此采用普通的K-均值算法训练数据集,并对测试集进行预测(此时没有使用k折交叉验证):

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.8208469055374593; test score: 0.7662337662337663

结果非常糟糕,训练样本的拟合情况不佳,评分才0.82,说明算法模型太简单,无法很好地拟合训练样本;且预测准确性也只有76%。

画出学习曲线证实结论:

from sklearn.model_selection import ShuffleSplit
from common.utils import plot_learning_curve
#数据集里有个common文件夹,里面有一个utils.py文件

knn = KNeighborsClassifier(n_neighbors=2)
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
plt.figure(figsize=(10, 6))
plot_learning_curve(plt, knn, "Learn Curve for KNN Diabetes", 
                    X, Y, ylim=(0.0, 1.01), cv=cv);

在这里插入图片描述
从图中可以看出,训练样本评分较低,且测试样本与训练样本距离较大,这是典型的欠拟合现象。而k-均值算法没有更好的措施来解决欠拟合问题。

可试试逻辑回归、支持向量机。

通过特征选取和数据可视化来分析k-均值算法在此类问题的缺陷:

sklearn.feature_selection.SelectKBest

把与糖尿病相关性最大的两个特征放在X_new变量里,同时输出前5个数据样本。

from sklearn.feature_selection import SelectKBest

selector = SelectKBest(k=2)
X_new = selector.fit_transform(X, Y)
X_new[0:5]

array([[148. , 33.6],
[ 85. , 26.6],
[183. , 23.3],
[ 89. , 28.1],
[137. , 43.1]])

对比之前的数据图即可知道这两个特征是Glucose(血糖浓度)和BMI(身体质量指数)。

如果只使用这两个特征进行训练和预测:

results = []
for name, model in models:
    kfold = KFold(n_splits=10)
    cv_result = cross_val_score(model, X_new, 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.725205058099795
name: KNN with weights; cross val score: 0.6900375939849623
name: Radius Neighbors; cross val score: 0.6510252904989747

发现还是普通k-均值模型准确率较高。

现在分析这两个相关性最高的特征:

# 画出数据
plt.figure(figsize=(10, 6))
plt.ylabel("BMI")
plt.xlabel("Glucose")
plt.scatter(X_new[Y==0][:, 0], X_new[Y==0][:, 1], c='r', s=20, marker='o');         # 画出样本
plt.scatter(X_new[Y==1][:, 0], X_new[Y==1][:, 1], c='g', s=20, marker='^');         # 画出样本

在这里插入图片描述

可见在中间数据集密集的区域,男性与女性样本几乎重叠在一起了,若有一个待预测的样本在中间密集区域,此时就很难判断到底是男性居多还是女性居多。

拓展:
K-D Tree
Ball Tree

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值