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
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的 KFold 和 cross_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