机器学习算法:K近邻(k-nearest neighbors)


1、KNN的介绍

kNN(k-nearest neighbors),中文翻译K近邻。我们常常听到一个故事:如果要了解一个人的经济水平,只需要知道他最好的5个朋友的经济能力, 对他的这五个人的经济水平求平均就是这个人的经济水平。这句话里面就包含着kNN的算法思想。
在这里插入图片描述
示例 :如上图,绿色圆要被决定赋予哪个类,是红色三角形还是蓝色四方形?如果K=3,由于红色三角形所占比例为2/3,绿色圆将被赋予红色三角形那个类,如果K=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。
1.KNN建立过程

a 给定测试样本,计算它与训练集中的每一个样本的距离;
b 找出距离近期的K个训练样本。作为测试样本的近邻;
c 依据这K个近邻归属的类别来确定样本的类别。
2. 类别的判定
①投票决定,少数服从多数。取类别最多的为测试样本类别。
②加权投票法,依据计算得出距离的远近,对近邻的投票进行加权,距离越近则权重越大,设定权重为距离平方的倒数。

2、KNN的应用

KNN虽然很简单,但是人们常说"大道至简",一句"物以类聚,人以群分"就能揭开其面纱,看似简单的KNN即能做分类又能做回归, 还能用来做数据预处理的缺失值填充。由于KNN模型具有很好的解释性,一般情况下对于简单的机器学习问题,我们可以使用KNN作为 Baseline,对于每一个预测结果,我们可以很好的进行解释。推荐系统的中,也有着KNN的影子。例如文章推荐系统中, 对于一个用户A,我们可以把和A最相近的k个用户,浏览过的文章推送给A。

机器学习领域中,数据往往很重要,有句话叫做:“数据决定任务的上限, 模型的目标是无限接近这个上限”。 可以看到好的数据非常重要,但是由于各种原因,我们得到的数据是有缺失的,如果我们能够很好的填充这些缺失值, 就能够得到更好的数据,以至于训练出来更鲁棒的模型。接下来我们就来看看KNN如果做分类,怎么做回归以及怎么填充空值。

3、KNN实现二维数据分类

# Step1: 库函数导入 # Step2: 数据导入 # Step3: 模型训练&可视化 # Step4: 原理简析
# Step1: 库函数导入 
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.neighbors import KNeighborsClassifier
from sklearn import datasets
# Step2: 数据导入
iris = datasets.load_iris()
print('iris的数据类型:\n{}'.format(type(iris)))
X = iris.data[:, :2]
print('X的维度:\n{}'.format(X.shape))
y = iris.target
print('y的维度:\n{}'.format(y.shape))

iris的数据类型:
<class ‘sklearn.utils.Bunch’>
X的维度:
(150, 2)
y的维度:
(150,)

# Step3: 模型训练&可视化
k_list = [1, 3, 5, 8, 10, 15]
h = .02
# 创建不同颜色的画布
cmap_light = ListedColormap(['orange', 'cyan', 'cornflowerblue'])
cmap_bold = ListedColormap(['darkorange', 'c', 'darkblue'])

plt.figure(figsize=(15,14))
# 根据不同的k值进行可视化
for ind,k in enumerate(k_list):
    clf = KNeighborsClassifier(k)
    clf.fit(X, y)
    # 画出决策边界
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    # 根据边界填充颜色
    Z = Z.reshape(xx.shape)

    plt.subplot(321+ind)  
    plt.pcolormesh(xx, yy, Z, cmap=cmap_light)
    # 数据点可视化到画布
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold,
                edgecolor='k', s=20)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.title("3-Class classification (k = %i)"% k)

plt.show()

在这里插入图片描述
Step4: 原理简析 如果选择较小的K值,就相当于用较小的领域中的训练实例进行预测,例如当k=1的时候,在分界点位置的数据很容易受到局部的影响, 图中蓝色的部分中还有部分绿色块,主要是数据太局部敏感。当k=15的时候,不同的数据基本根据颜色分开,当时进行预测的时候, 会直接落到对应的区域,模型相对更加鲁棒。

4、KNN实现鸢尾花数据分类

#Step1: 库函数导入  #Step2: 数据导入&分析 #Step3: 模型训练 #Step4:模型预测&可视化
#Step1: 库函数导入
import numpy as np
# 加载莺尾花数据集
from sklearn import datasets
# 导入KNN分类器
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
#Step2: 数据导入&分析
# 导入莺尾花数据集
iris = datasets.load_iris()

X = iris.data
y = iris.target
# 得到训练集合和验证集合, 8: 2
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)

(120, 4)
(30, 4)
(120,)
(30,)

#Step3: 训练模型
clf = KNeighborsClassifier(n_neighbors=5, p=2, metric="minkowski")
clf.fit(X_train, y_train)

KNeighborsClassifier(algorithm=‘auto’, leaf_size=30, metric=‘minkowski’,
metric_params=None, n_jobs=1, n_neighbors=5, p=2,
weights=‘uniform’)

# #Step4:模型预测&可视化
X_pred = clf.predict(X_test)
acc = sum(X_pred == y_test) / X_pred.shape[0]
print("预测的准确率ACC: %.3f" % acc)

预测的准确率ACC: 0.933

5、模拟数据集——KNN回归

#Step1: 库函数导入  #Step2: 数据导入&分析 #Step3: 模型训练&预测可视化 #Step4:模型分析
#Step1: 库函数导入 
#Demo来自sklearn官网
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
#Step2: 数据导入&分析
np.random.seed(0)
# 随机生成40(0, 1)之前的数,乘以5,再进行升序
X = np.sort(5 * np.random.rand(40, 1), axis=0)
print("X的维度:{}".format(X.shape))
# 创建[0, 5]之间的500个数的等差数列, 作为测试数据
T = np.linspace(0, 5, 500)[:, np.newaxis]
print("T的维度:{}".format(T.shape))
# 使用sin函数得到y值,并拉伸到一维
y = np.sin(X).ravel()
print("y的维度:{}".format(y.shape))
# Add noise to targets[y值增加噪声]
y[::5] += 1 * (0.5 - np.random.rand(8))
print("y的维度:{}".format(y.shape))

X的维度:(40, 1)
T的维度:(500, 1)
y的维度:(40,)
y的维度:(40,)

#Step3: 模型训练&预测可视化
# Fit regression model
# 设置多个k近邻进行比较
n_neighbors = [1, 3, 5, 8, 10, 40]
# 设置图片大小
plt.figure(figsize=(10,20))
for i, k in enumerate(n_neighbors):
    # 默认使用加权平均进行计算predictor
    clf = KNeighborsRegressor(n_neighbors=k, p=2, metric="minkowski")
    # 训练
    clf.fit(X, y)
    # 预测
    y_ = clf.predict(T)
    plt.subplot(6, 1, i + 1)
    plt.scatter(X, y, color='red', label='data')
    plt.plot(T, y_, color='navy', label='prediction')
    plt.axis('tight')
    plt.legend()
    plt.title("KNeighborsRegressor (k = %i)" % (k))

plt.tight_layout()
plt.show()

在这里插入图片描述
Step4:模型分析
当k=1时,预测的结果只和最近的一个训练样本相关,从预测曲线中可以看出当k很小时候很容易发生过拟合。
当k=40时,预测的结果和最近的40个样本相关,因为我们只有40个样本,此时是所有样本的平均值,此时所有预测值都是均值,很容易发生欠拟合。
一般情况下,使用knn的时候,根据数据规模我们会从[3, 20]之间进行尝试,选择最好的k,例如上图中的[3, 10]相对1和40都是还不错的选择。

6、马绞痛数据–kNN数据预处理+kNN分类pipeline

6.1 Step1: 库函数导入

# Step1: 库函数导入 #Step2: 数据导入&分析  #Step3: KNNImputer空值填充--使用和原理介绍  #Step4: KNNImputer空值填充--欧式距离的计算  
#Step5: 基于pipeline模型训练&可视化 #Step 6: 结果分析

# 下载需要用到的数据集
!wget https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/3K/horse-colic.csv
# 下载数据集介绍
!wget https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/3K/horse-colic.names
#下面3句代码是升级了一下sklern版本 运行完之后重启一下cell
# import sklearn
# print(sklearn.__version__)
# !pip install --ignore-installed scikit-learn

在这里插入图片描述

# Step1: 库函数导入
import numpy as np
import pandas as pd
# kNN分类器
from sklearn.neighbors import KNeighborsClassifier
# kNN数据空值填充
from sklearn.impute import KNNImputer
# 计算带有空值的欧式距离
from sklearn.metrics.pairwise import nan_euclidean_distances
# 交叉验证
from sklearn.model_selection import cross_val_score
# KFlod的函数
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.pipeline import Pipeline
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

6.2 Step2: 数据导入&分析

在这里插入图片描述

6.3 Step3: KNNImputer空值填充–使用和原理介绍

#我们先来看下KNNImputer的运行原理:
# Step3: KNNImputer空值填充--使用和原理介绍
X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]]
imputer = KNNImputer(n_neighbors=2, metric='nan_euclidean')
imputer.fit_transform(X)

array([[1. , 2. , 4. ],
[3. , 4. , 3. ],
[5.5, 6. , 5. ],
[8. , 8. , 7. ]])

#带有空值的欧式距离计算公式
nan_euclidean_distances([[np.nan, 6, 5], [3, 4, 3]], [[3, 4, 3], [1, 2, np.nan], [8, 8, 7]])

array([[3.46410162, 6.92820323, 3.46410162],
[0. , 3.46410162, 7.54983444]])

6.4 Step4: KNNImputer空值填充–欧式距离的计算

在这里插入图片描述

# load dataset, 将?变成空值
input_file = './horse-colic.csv'
df_data = pd.read_csv(input_file, header=None, na_values='?')

# 得到训练数据和label,23列表示是否发生病变, 1: 表示Yes; 2: 表示No. 
data = df_data.values
ix = [i for i in range(data.shape[1]) if i != 23]
X, y = data[:, ix], data[:, 23]

# 查看所有特征的缺失值个数和缺失率
for i in range(df_data.shape[1]):
    n_miss = df_data[[i]].isnull().sum()
    perc = n_miss / df_data.shape[0] * 100
    if n_miss.values[0] > 0:
        print('>Feat: %d, Missing: %d, Missing ratio: (%.2f%%)' % (i, n_miss, perc))

# 查看总的空值个数
print('KNNImputer before Missing: %d' % sum(np.isnan(X).flatten()))
# 定义 knnimputer
imputer = KNNImputer()
# 填充数据集中的空值
imputer.fit(X)
# 转换数据集
Xtrans = imputer.transform(X)
# 打印转化后的数据集的空值
print('KNNImputer after Missing: %d' % sum(np.isnan(Xtrans).flatten()))

Feat: 0, Missing: 1, Missing ratio: (0.33%)
Feat: 3, Missing: 60, Missing ratio: (20.00%)
Feat: 4, Missing: 24, Missing ratio: (8.00%)
Feat: 5, Missing: 58, Missing ratio: (19.33%)
Feat: 6, Missing: 56, Missing ratio: (18.67%)
Feat: 7, Missing: 69, Missing ratio: (23.00%)
Feat: 8, Missing: 47, Missing ratio: (15.67%)
Feat: 9, Missing: 32, Missing ratio: (10.67%)
Feat: 10, Missing: 55, Missing ratio: (18.33%)
Feat: 11, Missing: 44, Missing ratio: (14.67%)
Feat: 12, Missing: 56, Missing ratio: (18.67%)
Feat: 13, Missing: 104, Missing ratio: (34.67%)
Feat: 14, Missing: 106, Missing ratio: (35.33%)
Feat: 15, Missing: 247, Missing ratio: (82.33%)
Feat: 16, Missing: 102, Missing ratio: (34.00%)
Feat: 17, Missing: 118, Missing ratio: (39.33%)
Feat: 18, Missing: 29, Missing ratio: (9.67%)
Feat: 19, Missing: 33, Missing ratio: (11.00%)
Feat: 20, Missing: 165, Missing ratio: (55.00%)
Feat: 21, Missing: 198, Missing ratio: (66.00%)
Feat: 22, Missing: 1, Missing ratio: (0.33%)
KNNImputer before Missing: 1605
KNNImputer after Missing:

6.5 Step5: 基于pipeline模型训练&可视化

在这里插入图片描述

results = list()
strategies = [str(i) for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 18, 20, 21]]
for s in strategies:
    # create the modeling pipeline
    pipe = Pipeline(steps=[('imputer', KNNImputer(n_neighbors=int(s))), ('model', KNeighborsClassifier())])
    # 数据多次随机划分取平均得分
    scores = []
    for k in range(20):
        # 得到训练集合和验证集合, 8: 2
        X_train, X_test, y_train, y_test = train_test_split(Xtrans, y, test_size=0.2)
        pipe.fit(X_train, y_train)
        # 验证model
        score = pipe.score(X_test, y_test)
        scores.append(score)
    # 保存results
    results.append(np.array(scores))
    print('>k: %s, Acc Mean: %.3f, Std: %.3f' % (s, np.mean(scores), np.std(scores)))
# print(results)
# plot model performance for comparison
plt.boxplot(results, labels=strategies, showmeans=True)
plt.show()

在这里插入图片描述

6.6 Step 6: 结果分析

#Step 6: 结果分析
我们的实验是每个k值下,随机切分20次数据, 从上述的图片中, 根据k值的增加,我们的测试准确率会有先上升再下降再上升的过程。 
[3, 5]之间是一个很好的取值,上文我们提到,k很小的时候会发生过拟合,k很大时候会发生欠拟合,当遇到第一下降节点,
此时我们可以简单认为不在发生过拟合,取当前的k值即可。

猜你喜欢

转载自blog.csdn.net/qq_27328197/article/details/111480033
今日推荐