机器学习原理与实战 | K近邻算法实践

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

KNN算法中,其算法参数是K,参数选择需要根据数据来决定。K值越大,模型的偏差越大,对噪声数据越不敏感,当K值很大时,可能造成模型欠拟合;K值越小,模型的方差就会越大,当K值太小,就会造成模型过拟合。

K-近邻算法有一些变种,其中之一就是可以增加邻居的权重。默认情况下,在计算距离时,都是使用相同的权重。实际上,我们可以针对不同的邻居指定不同的权重,如距离越近权重越高。这个可以通过指定算法的weights参数来实现。

另外一个变种是,使用一定半径内的点取代距离最近的K个点。在scikit-learn里,RadiusNeighborsClassifier类实现了这个算法的变种。当数据采样不均匀时,该算法变种可以取得更好的性能。

1. KNN实现分类

# from sklearn.datasets.samples_generator import make_blobs
from sklearn.datasets import make_blobs

# 根据中心点随机生成数据
centers = [[-2, 2], [2, 2], [0, 4]]
# centers = [[-2, 0], [2, 0], [0, 4], [0,2]]
X, y = make_blobs(n_samples=100, centers=centers, random_state=0, cluster_std=0.40)

# 画出数据
plt.figure(figsize=(12,8))
c = np.array(centers)
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, cmap='cool');         # 画出样本
plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='orange');   # 画出中心点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mzYKWcW3-1631534420316)(output_3_0.png)]

使用KNeighborsClassifier来对算法进行训练,下面进行单样本预测

from sklearn.neighbors import KNeighborsClassifier
# 模型训练
k = 5
clf = KNeighborsClassifier(n_neighbors=k)
clf.fit(X, y)

# 进行预测
X_sample = [0, 2]
X_sample = np.array(X_sample).reshape(1, -1)
# 根据两个x坐标预测一个y坐标
y_sample = clf.predict(X_sample)
# 使用kneighbors()方法,把这个样本周围距离最近的5个点取出来。取出来的点是训练样本X里的索引,从0开始计算。
neighbors = clf.kneighbors(X_sample, return_distance=False)

# 其中,这里输出的1表示一个样本,如果是多个样本,则为n,neighbors里面就会存储n个样本距离最近的前k个点
X_sample.shape, X.shape, y.shape, y_sample.shape, neighbors.shape
((1, 2), (100, 2), (100,), (1,), (1, 5))
# 画出示意图
plt.figure(figsize=(12, 8))
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, cmap='cool')    # 样本
plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='k')   # 中心点
plt.scatter(X_sample[0][0], X_sample[0][1], marker="x", 
            s=100, cmap='cool')    # 待预测的点

for i in neighbors[0]:
    # 预测点与距离最近的 5 个样本的连线。其中第一个列表是存放x坐标,第二个列表存放y的坐标
    plt.plot([X[i][0], X_sample[0][0]], [X[i][1], X_sample[0][1]], 
             'k--', linewidth=0.6)

在这里插入图片描述

多样本预测,上诉图中只使用了一个样本进行可视化,但是一般来说需要处理多个样本,这里使用同样的方法来可视化结果

# 这里的k设置为5
X_sample = np.random.randint(-2,4,[5, 2])
neighbors = clf.kneighbors(X_sample, return_distance=False)

X_sample.shape, neighbors.shape
((5, 2), (5, 5))
# 画出示意图
plt.figure(figsize=(12, 8))
plt.scatter(X[:, 0], X[:, 1], c=y, s=100, cmap='cool')    # 样本
plt.scatter(c[:, 0], c[:, 1], s=100, marker='^', c='k')   # 中心点
plt.scatter(X_sample[:,0], X_sample[:,1], marker="x", s=100, cmap='cool')    # 待预测的点

for i,neighbors_data in enumerate(neighbors):
    # 预测点与距离最近的 5 个样本的连线。其中第一个列表是存放x坐标,第二个列表存放y的坐标
    for t in neighbors_data:
        plt.plot([X[t][0], X_sample[i][0]], [X[t][1], X_sample[i][1]], 'k--', linewidth=0.6)

在这里插入图片描述

如上图所示,直接根据最近的k个值来决定样本的类别

2. KNN实现回归

# 生成训练样本
n_dots = 40
X = 5 * np.random.rand(n_dots, 1)
y = np.cos(X).ravel()

# 添加一些噪声
y += 0.2 * np.random.rand(n_dots) - 0.1

# 显示图像
plt.scatter(X,y,s=20)
<matplotlib.collections.PathCollection at 0x1db687e7888>

在这里插入图片描述

# 训练模型
from sklearn.neighbors import KNeighborsRegressor

k = 5
# 回归训练
knn = KNeighborsRegressor(k)
knn.fit(X, y)

# 生成足够密集的点并进行预测
T = np.linspace(0, 5, 500).reshape(-1,1)
# 得到密集区间内的预测点
y_pred = knn.predict(T)

# 使用score()方法计算拟合曲线对训练样本的拟合准确性
# knn.score(X, y)
# out: 0.9790114894878551

# 再把这些预测点连起来,构成一条拟合曲线
plt.plot(T,y_pred,c='k')
# 显示原散点图图像
plt.scatter(X,y,s=20,c='r')
<matplotlib.collections.PathCollection at 0x1db6894cdc8>

在这里插入图片描述

3. 示例:KNN实现糖尿病预测

加载数据集

import pandas as pd

data = pd.read_csv('E:/学习/机器学习/b站资料/scikit-learn机器学习/code/datasets/pima-indians-diabetes/diabetes.csv')
data.head(6)
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
5 5 116 74 0 0 25.6 0.201 30 0
# 可以进一步观察数据集里的阳性和阴性样本的个数:
data.groupby("Outcome").size()
Outcome
0    500
1    268
dtype: int64

把8个特征值分离出来,作为训练数据集,把Outcome列分离出来作为目标值。然后,把数据集划分为训练数据集和测试数据集。

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=0.2)

X_train.shape, X_test.shape, Y_train.shape, Y_test.shape
shape of X (768, 8); shape of Y (768,)





((614, 8), (154, 8), (614,), (154,))

分别使用普通的KNN算法、带权重的KNN算法和指定半径的KNN算法对数据集进行拟合并计算评分:

from sklearn.neighbors import KNeighborsClassifier, RadiusNeighborsClassifier

# 三种模型的构建:k值均为2
k = 5
models = []
models.append(("KNN", KNeighborsClassifier(n_neighbors=k)))
models.append(("KNN with weights", KNeighborsClassifier(n_neighbors=k, weights="distance")))
models.append(("Radius Neighbors", RadiusNeighborsClassifier(n_neighbors=k, 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.7077922077922078
name: KNN with weights; score: 0.7142857142857143
name: Radius Neighbors; score: 0.6493506493506493

怎么样更准确地对比算法准确性呢?一个方法是,多次随机分配训练数据集和交叉验证数据集,然后求模型准确性评分的平均值。scikit-learn提供了KFold和cross_val_score()函数来处理这种问题:

  • KFold把数据集分成10份,其中1份会作为交叉验证数据集来计算模型准确性,剩余的9份作为训练数据集。
  • cross_val_score()函数总共计算出10次不同训练数据集和交叉验证数据集组合得到的模型准确性评分,最后求平均值。
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score

results = []
for name, model in models:
    # 把数据集分成10份,其中1份会作为交叉验证数据集来计算模型准确性,剩余的9份作为训练数据集。
    kfold = KFold(n_splits=10)
    # 函数总共计算出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.7265550239234451
name: KNN with weights; cross val score: 0.7265550239234451
name: Radius Neighbors; cross val score: 0.6497265892002735

可以查看一下result的结果:

results
[('KNN',
  array([0.63636364, 0.83116883, 0.7012987 , 0.63636364, 0.71428571,
         0.75324675, 0.74025974, 0.80519481, 0.68421053, 0.76315789])),
 ('KNN with weights',
  array([0.64935065, 0.83116883, 0.68831169, 0.63636364, 0.71428571,
         0.75324675, 0.75324675, 0.79220779, 0.68421053, 0.76315789])),
 ('Radius Neighbors',
  array([0.5974026 , 0.71428571, 0.54545455, 0.5974026 , 0.64935065,
         0.61038961, 0.81818182, 0.67532468, 0.68421053, 0.60526316]))]

可以查看一下models的结果:

models
[('KNN', KNeighborsClassifier()),
 ('KNN with weights', KNeighborsClassifier(weights='distance')),
 ('Radius Neighbors', RadiusNeighborsClassifier(radius=500.0))]

对比了不同的模型效果之后,可以进行训练与结果分析

knn = KNeighborsClassifier(n_neighbors=4)

# 训练
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.8110749185667753; test score: 0.7142857142857143

从这个输出中可以看到两个问题:

  • 一是对训练样本的拟合情况不佳,这说明算法模型太简单了,无法很好地拟合训练样本
  • 二是模型的准确性欠佳,不到72%的预测准确性

画出学习曲线证实结论

from sklearn.model_selection import ShuffleSplit
from utils import plot_learning_curve

knn = KNeighborsClassifier(n_neighbors=2)
# 多次计算使其平滑
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
plt.figure(figsize=(10, 6))
# 这里划分了5次,所以对应五个点
plot_learning_curve(plt, knn, "Learn Curve for KNN Diabetes", 
                    X, Y, ylim=(0.0, 1.01), cv=cv);

在这里插入图片描述

从图中可以看出来,训练样本评分较低,且测试样本与训练样本距离较大,这是典型的欠拟合现象。KNN算法没有更好的措施来解决欠拟合问题,什么KNN算法不是针对这一问题的好模型?下面进行特征选择与可视化出来

特征选择及数据可视化

8个特征,无法在这么高的维度里画出数据,并直观地观察。一个解决办法是特征选择,即只选择2个与输出值相关性最大的特征,这样就可以在二维平面上画出输入特征值与输出值的关系了。scikit-learn在sklearn.feature_selection包里提供了丰富的特征选择方法。可以使用SelectKBest来选择相关性最大的两个特征:

from sklearn.feature_selection import SelectKBest

selector = SelectKBest(k=2)
X_new = selector.fit_transform(X, Y)
X_new[:5]
array([[148. ,  33.6],
       [ 85. ,  26.6],
       [183. ,  23.3],
       [ 89. ,  28.1],
       [137. ,  43.1]])

只使用这2个相关性最高的特征的话,对比不同的KNN模型

results = []
for name, model in models:
    kfold = KFold(n_splits=10)
    # 这里使用了X_new来进行训练
    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.7369104579630894
name: KNN with weights; cross val score: 0.7199419002050581
name: Radius Neighbors; cross val score: 0.6510252904989747

还是普通的KNN模型准确性较高, 实验的结果比使用8个特征还要好

现在我们只有2个特征,可以很方便地在二维坐标上画出所有的训练样本,观察这些数据的分布情况:

plt.figure(figsize=(14, 10))
plt.ylabel("BMI")
plt.xlabel("Glucose")
plt.scatter(X_new[Y==0][:,0],X_new[Y==0][:,1],s=20,c='r',marker='x')
plt.scatter(X_new[Y==1][:,0],X_new[Y==1][:,1],s=20,c='b',marker='x')
plt.title("Data distribution")
Text(0.5, 1.0, 'Data distribution')

在这里插入图片描述

从图中可以看出,在中间数据集密集的区域,阳性样本和阴性样本几乎重叠在一起了,所以比较难以检测

Guess you like

Origin blog.csdn.net/weixin_44751294/article/details/120274205