这里先给出阿里云机器学习训练营地址:阿里云机器学习训练营,可以将其代码下载进行学习或者参加最后一个任务的比赛。
1 KNN的介绍和应用
1.1 KNN的介绍
kNN(k-nearest neighbors),中文翻译K近邻。我们常常听到一个故事:如果要了解一个人的经济水平,只需要知道他最好的5个朋友的经济能力, 对他的这五个人的经济水平求平均就是这个人的经济水平。这句话里面就包含着kNN的算法思想。
示例 :如上图,绿色圆要被决定赋予哪个类,是红色三角形还是蓝色四方形?如果K=3,由于红色三角形所占比例为2/3,绿色圆将被赋予红色三角形那个类,如果K=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。
1) KNN建立过程
1 给定测试样本,计算它与训练集中的每一个样本的距离。
2 找出距离近期的K个训练样本。作为测试样本的近邻。
3 依据这K个近邻归属的类别来确定样本的类别。
2) 类别的判定
①投票决定,少数服从多数。取类别最多的为测试样本类别。
②加权投票法,依据计算得出距离的远近,对近邻的投票进行加权,距离越近则权重越大,设定权重为距离平方的倒数。
1.2 KNN的应用
KNN虽然很简单,但是人们常说"大道至简",一句"物以类聚,人以群分"就能揭开其面纱,看似简单的KNN即能做分类又能做回归, 还能用来做数据预处理的缺失值填充。由于KNN模型具有很好的解释性,一般情况下对于简单的机器学习问题,我们可以使用KNN作为 Baseline,对于每一个预测结果,我们可以很好的进行解释。推荐系统的中,也有着KNN的影子。例如文章推荐系统中, 对于一个用户A,我们可以把和A最相近的k个用户,浏览过的文章推送给A。
机器学习领域中,数据往往很重要,有句话叫做:"数据决定任务的上限, 模型的目标是无限接近这个上限"。 可以看到好的数据非常重要,但是由于各种原因,我们得到的数据是有缺失的,如果我们能够很好的填充这些缺失值, 就能够得到更好的数据,以至于训练出来更鲁棒的模型。接下来我们就来看看KNN如果做分类,怎么做回归以及怎么填充空值。
补充:
KNN可以说是最简单的分类算法之一,同时,它也是最常用的分类算法之一,注意KNN算法是有监督学习中的分类算法,它看起来和另一个机器学习算法Kmeans有点像(Kmeans是无监督学习算法),但却是有本质区别的。KNN的全称是K Nearest Neighbors,意思是K个最近的邻居,从这个名字我们就能看出一些KNN算法的蛛丝马迹了。K个最近邻居,毫无疑问,K的取值肯定是至关重要的。KNN的原理就是当预测一个新的值x的时候,根据它距离最近的K个点是什么类别来判断x属于哪个类别。
KNN算法优点
- 简单易用,相比其他算法,KNN算是比较简洁明了的算法。即使没有很高的数学基础也能搞清楚它的原理。
- 模型训练时间快,上面说到KNN算法是惰性的,这里也就不再过多讲述。
- 预测效果好。
- 对异常值不敏感
KNN算法缺点
- 对内存要求较高,因为该算法存储了所有训练数据
- 预测阶段可能很慢
- 对不相关的功能和数据规模敏感
2 实验室手册
2.1 实验环境
1. python3.7
2. numpy >= '1.16.4'
3. sklearn >= '0.23.1'
2.2 学习目标
- 了解KNN怎么做分类问题
- 了解KNN如何做回归
- 了解KNN怎么做空值填充, 如何使用knn构建带有空值的pipeline
2.3 代码流程
- 二维数据集--knn分类
- Step1: 库函数导入
- Step2: 数据导入
- Step3: 模型训练&可视化
- Step4: 原理简析
- 莺尾花数据集--kNN分类
- Step1: 库函数导入
- Step2: 数据导入&分析
- Step3: 模型训练
- Step4: 模型预测&可视化
- 模拟数据集--kNN回归
- Step1: 库函数导入
- Step2: 数据导入&分析
- Step3: 模型训练&可视化
- 马绞痛数据--kNN数据预处理+kNN分类pipeline
- Step1: 库函数导入
- Step2: 数据导入&分析
- Step3: KNNImputer空值填充--使用和原理介绍
- Step4: KNNImputer空值填充--欧式距离的计算
- Step5: 基于pipeline模型预测&可视化
2.4 算法实战
2.4.1 Demo数据集--kNN分类
Step1: 库函数导入
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
#这里导入了ListedColormap,用来选择颜色的。
from sklearn.neighbors import KNeighborsClassifier
#导入KNN分类器函数
from sklearn import datasets
Step2: 数据导入
# 使用莺尾花数据集的前两维数据,便于数据可视化
iris = datasets.load_iris()
#下载数据集
X = iris.data[:, :2]
#iris.data里面只有4个特征
#取所有数据前两列数据
y = iris.target
#取数据的标签值
Step3: 模型训练&可视化
k_list = [1, 3, 5, 8, 10, 15]
#k值列表
h = .02
# 创建不同颜色的画布
cmap_light = ListedColormap(['orange', 'cyan', 'cornflowerblue'])
cmap_bold = ListedColormap(['darkorange', 'c', 'darkblue'])
#cmap需要使用ListedColormap来先进行一个处理
plt.figure(figsize=(15,14))
# 根据不同的k值进行可视化
for ind,k in enumerate(k_list):
#ind是索引值
clf = KNeighborsClassifier(k)
#调用KNN分类器
#参数是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
#找出x轴和y轴的范围
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
#这里的xx和yy是二维坐标矩阵
#h=0.02
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
#展平成一个一维数组。对很多点进行一个预测。
#这里的Z是一个类别数值
# 根据边界填充颜色
Z = Z.reshape(xx.shape)
#xx的形状是一个二维矩阵
#再次将Z的形状转换成一个二维矩阵
plt.subplot(321+ind)
#ind表示子图的顺序
plt.pcolormesh(xx, yy, Z, cmap=cmap_light)
#利用plt.pcolormesh来绘制分类图
#plt.pcolormesh()会根据y_predict的结果自动在cmap里选择颜色
#Z是预测值,二维矩阵
# 数据点可视化到画布
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())
#设定x轴和y轴的取值范围
plt.title("3-Class classification (k = %i)"% k)
#命名标题
plt.show()
Step4: 原理简析
如果选择较小的K值,就相当于用较小的领域中的训练实例进行预测,例如当k=1的时候,在分界点位置的数据很容易受到局部的影响,图中蓝色的部分中还有部分绿色块,主要是数据太局部敏感。当k=15的时候,不同的数据基本根据颜色分开,当时进行预测的时候,会直接落到对应的区域,模型相对更加鲁棒。
2.4.2 莺尾花数据集--kNN分类
Step1: 库函数导入
import numpy as np
# 加载莺尾花数据集
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
# 导入KNN分类器 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)
#划分其中20%为测试数据集
Step3: 模型训练
这里我们设置参数k(n_neighbors)=5, 使用欧式距离(metric=minkowski & p=2)
# 训练模型
clf = KNeighborsClassifier(n_neighbors=5, p=2, metric="minkowski")
#当p=1时,这等价于使用曼哈顿距离(L1),欧几里得距离(L2)等价于p=2时,对于任意的p,则使用Minkowski_距离(L_P)。
#metric:用于树的距离度量。默认度量是Minkowski,p=2等价于标准的欧几里德度量。有关可用度量的列表,
#可以查阅距离度量类的文档。如果度量是“预先计算的”,则假定X是距离矩阵,在拟合期间必须是平方。
clf.fit(X_train, y_train)
Step4:模型预测&可视化
# 预测
X_pred = clf.predict(X_test)
#X_test的形状是(30, 4),数据类型是numpy.ndarray
#X_pred的形状是(30,),数据类型是numpy.ndarray
acc = sum(X_pred == y_test) / X_pred.shape[0]
print("预测的准确率ACC: %.3f" % acc)
2.4.3 模拟数据集--kNN回归
Step1: 库函数导入
#Demo来自sklearn官网
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
#k近邻有分类也有回归,其实两者原理一样:KNeighborsClassifier和KNeighborsRegressor
#1.定量输出是回归,进行预测比如明天的降水概率
#2.定性输出是分类,需要定性的描述
#kNN回归的原理:
#通过找出一个样本的k个最近邻居,将这些邻居的某个(些)属性的平均值赋给该样本,就可以得到该样本对应属性的值。
Step2: 数据导入&分析
np.random.seed(0)
#设置随机种子
# 随机生成40个(0, 1)之前的数,乘以5,再进行升序
X = np.sort(5 * np.random.rand(40, 1), axis=0)
#通过本函数可以返回一个或一组服从“0~1”均匀分布的随机样本值。随机样本取值范围是[0,1),不包括1。
#np.random.rand(40, 1),40行1列的二维矩阵。
#np.sort()函数的作用是对给定的数组的元素进行排序,axis=0表示在列里面进行升序排列。
#所以X的形状是(40, 1),数据类型是numpy.ndarray
# 创建[0, 5]之间的500个数的等差数列, 作为测试数据
T = np.linspace(0, 5, 500)[:, np.newaxis]
#T的形状是(500,1),数据类型是numpy.ndarray
# 使用sin函数得到y值,并拉伸到一维
y = np.sin(X).ravel()
#y的形状是(40,),数据类型是numpy.ndarray
# Add noise to targets[y值增加噪声]
y[::5] += 1 * (0.5 - np.random.rand(8))
#其中的5表示间隔,也就是每隔5个就加一个噪音。
#其中8表示形状
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)
#T的形状是(500,1),数据类型是numpy.ndarray
#y_的形状是(500,),数据类型是numpy.ndarray
plt.subplot(6, 1, i + 1)
#子图
plt.scatter(X, y, color='red', label='data')
#X的形状是(40, 1),数据类型是numpy.ndarray
##y的形状是(40,),数据类型是numpy.ndarray
#画散点图
plt.plot(T, y_, color='navy', label='prediction')
#T的形状是(500,1),数据类型是numpy.ndarray
#y_的形状是(500,),数据类型是numpy.ndarray
#y_值是进行回归得到的点的值。
plt.axis('tight')
##按照图形的内容自动收紧坐标轴,不留空白区域
plt.legend()
#加上图例
plt.title("KNeighborsRegressor (k = %i)" % (k))
plt.tight_layout()
#tight_layout会自动调整子图参数,使之填充整个图像区域。
plt.show()
Step4:模型分析
当k=1时,预测的结果只和最近的一个训练样本相关,从预测曲线中可以看出当k很小时候很容易发生过拟合。
当k=40时,预测的结果和最近的40个样本相关,因为我们只有40个样本,此时是所有样本的平均值,此时所有预测值都是均值,很容易发生欠拟合。
一般情况下,使用knn的时候,根据数据规模我们会从[3, 20]之间进行尝试,选择最好的k,例如上图中的[3, 10]相对1和40都是还不错的选择。
2.4.4 马绞痛数据--kNN数据预处理+kNN分类pipeline
# 下载需要用到的数据集
!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
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
Step2: 数据导入&分析
数据中的'?'表示空值,如果我们使用KNN分类器,'?'不能数值,不能进行计算,因此我们需要进行数据预处理对空值进行填充。
这里我们使用KNNImputer进行空值填充,KNNImputer填充的原来很简单,计算每个样本最近的k个样本,进行空值填充。
我们先来看下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)
带有空值的欧式距离计算公式
nan_euclidean_distances([[np.nan, 6, 5], [3, 4, 3]], [[3, 4, 3], [1, 2, np.nan], [8, 8, 7]])
# 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()
#df_data[[i]]的形状是(300,1),数据类型是DataFrame
#df_data[[i]].isnull()的形状是(300,1),数据类型是DataFrame
#n_miss的形状是(1,),数据类型是Series
perc = n_miss / df_data.shape[0] * 100
if n_miss.values[0] > 0:
#因为n_miss的数据类型是Series
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()))
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())])
#Pipeline可以将许多算法模型串联起来,比如将特征提取、归一化、分类组织在一起形成一个典型的机器学习问题工作流。
#好处:
#1.直接调用fit和predict方法来对pipeline中的所有算法模型进行训练和预测。
#2.可以结合grid search对参数进行选择。
# 数据多次随机划分取平均得分
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()
Step 6: 结果分析
我们的实验是每个k值下,随机切分20次数据, 从上述的图片中, 根据k值的增加,我们的测试准确率会有先上升再下降再上升的过程。 [3, 5]之间是一个很好的取值,上文我们提到,k很小的时候会发生过拟合,k很大时候会发生欠拟合,当遇到第一下降节点,此时我们可以 简单认为不在发生过拟合,取当前的k值即可。
2.5 KNN原理介绍
k近邻方法是一种惰性学习算法,可以用于回归和分类,它的主要思想是投票机制,对于一个测试实例x, 我们在有标签的训练数据集上找到和最相近的k个数据,用他们的label进行投票,分类问题则进行表决投票,回归问题使用加权平均或者直接平均的方法。knn算法中我们最需要关注两个问题:k值的选择和距离的计算。 kNN中的k是一个超参数,需要我们进行指定,一般情况下这个k和数据有很大关系,都是交叉验证进行选择,但是建议使用交叉验证的时候,k∈[2,20],使用交叉验证得到一个很好的k值。
k值还可以表示我们的模型复杂度,当k值越小意味着模型复杂度变大,更容易过拟合,(用极少数的样例来绝对这个预测的结果,很容易产生偏见,这就是过拟合)。我们有这样一句话,k值越多学习的估计误差越小,但是学习的近似误差就会增大。