Task09 基于模拟数据集的KNN回归、基于马绞痛数据集的KNN数据预处理+KNN分类pipeline
一、学习内容概括
学习资料:
1.阿里云天池:https://tianchi.aliyun.com/specials/promotion/aicampml?invite_channel=1
2.sklearnAPI:https://scikit-learn.org/stable/modules/classes.html
3.matplotlib.pyplot:https://matplotlib.org/api/pyplot_summary.html
4.pandasAPI:https://pandas.pydata.org/pandas-docs/version/0.21/index.html
二、具体学习内容
-
模拟数据集--kNN回归
- Step1: 库函数导入
- Step2: 数据导入&分析
- Step3: 模型训练&可视化
-
马绞痛数据--kNN数据预处理+kNN分类pipeline
- Step1: 库函数导入
- Step2: 数据导入&分析
- Step3: KNNImputer空值填充--使用和原理介绍
- Step4: KNNImputer空值填充--欧式距离的计算
- Step5: 基于pipeline模型预测&可视化
1 模拟数据集---KNN回归
step1:库函数导入
# step1:库函数导入
#Demo来自sklearn官网
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
step2:数据导入&分析
# Step2: 数据导入&分析
np.random.seed(0)
# 随机生成40个(0, 1)之前的数,乘以5,再进行升序
## numpy.random.rand(d0, d1, …, dn)的随机样本位于[0, 1)中。
X = np.sort(5 * np.random.rand(40, 1), axis=0)
# 创建[0, 5]之间的500个数的等差数列, 作为测试数据
T = np.linspace(0, 5, 500)[:, np.newaxis]
# 使用sin函数得到y值,并拉伸到一维
y = np.sin(X).ravel()
# Add noise to targets[y值增加噪声]
y[::5] += 1 * (0.5 - np.random.rand(8))
step3:模型训练&预测可视化
# Step3: 模型训练&预测可视化
# 设置多个k近邻进行比较
n_neighbors = [1, 3, 5, 8, 10, 40]
# 创建一个新图形,宽度为10英寸,高度为20英寸
plt.figure(figsize=(10,20))
for i, k in enumerate(n_neighbors):
# 默认使用加权平均进行计算predictor
# 默认度量标准为minkowski,p = 2等效于标准欧几里德度量标准。
clf = KNeighborsRegressor(n_neighbors=k, p=2, metric="minkowski")
# 训练
clf.fit(X, y)
# 预测
y_ = clf.predict(T)
# subplot:在当前图形上添加一个子图。三个整数值6,1,i+1描述子图位置。
plt.subplot(6, 1, i + 1)
# 绘制y与X的散点图
plt.scatter(X, y, color='red', label='data')
# 绘制y_对T的线条
plt.plot(T, y_, color='navy', label='prediction')
# 设置轴属性:将限制设置得足够大以显示所有数据,然后禁用进一步的自动缩放。
plt.axis('tight')
# 在轴上放置图例。不设置参数则自动检测要在图例中显示的元素
plt.legend()
plt.title("KNeighborsRegressor (k = %i)" % (k))
# 调整子图之间及其周围的填充
plt.tight_layout()
plt.show()
参考资料:
matplotlib.pyplot.figure:创建一个新图形,或激活一个现有图形。https://matplotlib.org/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure
sklearn.neighbors.KNeighborsRegressor:基于k近邻的回归。https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsRegressor.html#sklearn.neighbors.KNeighborsRegressor
matplotlib.pyplot.subplot:在当前图形上添加一个子图。https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplot.html#matplotlib.pyplot.subplot
matplotlib.pyplot.scatter:y与x的散点图,其中标记大小和/或颜色不同。https://matplotlib.org/api/_as_gen/matplotlib.pyplot.scatter.html#matplotlib.pyplot.scatter
matplotlib.pyplot.plot:将y对x绘制为线条和/或标记。https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot
matplotlib.pyplot.axis:获取或设置一些轴属性的便捷方法。https://matplotlib.org/api/_as_gen/matplotlib.pyplot.axis.html#matplotlib.pyplot.axis
matplotlib.pyplot.legend:在轴上放置图例。https://matplotlib.org/api/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.legend
模型分析:
当k=1时,预测的结果只和最近的一个训练样本相关,从预测曲线中可以看出当k很小的时候很容易发生过拟合。
当k=40时,预测的结果和最近的40个样本相关,因为我们只有40个样本,此时是所有样本的平均值,此时所有预测值都是均值,很容易发生欠拟合。
一般情况下,使用knn的时候,根据数据规模我们会从[3, 20]之间进行尝试,选择最好的k,例如上图中的[3, 10]相对1和40都是还不错的选择。
2 马绞痛数据--kNN数据预处理+kNN分类pipeline
下载数据集:
# 下载需要用到的数据集
!wget https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/3K/horse-colic.csv
--2020-12-23 20:09:28-- https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/3K/horse-colic.csv
Resolving tianchi-media.oss-cn-beijing.aliyuncs.com (tianchi-media.oss-cn-beijing.aliyuncs.com)... 47.95.85.21
Connecting to tianchi-media.oss-cn-beijing.aliyuncs.com (tianchi-media.oss-cn-beijing.aliyuncs.com)|47.95.85.21|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 25151 (25K) [text/csv]
Saving to: ‘horse-colic.csv’
100%[======================================>] 25,151 --.-K/s in 0.03s
2020-12-23 20:09:28 (732 KB/s) - ‘horse-colic.csv’ saved [25151/25151]
得到一个文件:horse-colic.csv
下载数据集介绍:
# 下载数据集介绍
!wget https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/3K/horse-colic.names
--2020-12-23 20:09:49-- https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/3K/horse-colic.names
Resolving tianchi-media.oss-cn-beijing.aliyuncs.com (tianchi-media.oss-cn-beijing.aliyuncs.com)... 47.95.85.21
Connecting to tianchi-media.oss-cn-beijing.aliyuncs.com (tianchi-media.oss-cn-beijing.aliyuncs.com)|47.95.85.21|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 9011 (8.8K) [application/octet-stream]
Saving to: ‘horse-colic.names’
100%[======================================>] 9,011 --.-K/s in 0s
2020-12-23 20:09:49 (93.3 MB/s) - ‘horse-colic.names’ saved [9011/9011]
得到一个文件:horse-colic.names
step1:库函数导入
# 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:数据导入&分析
数据集horse-colic.csv:
2,1,530101,38.50,66,28,3,3,?,2,5,4,4,?,?,?,3,5,45.00,8.40,?,?,2,2,11300,00000,00000,2
1,1,534817,39.2,88,20,?,?,4,1,3,4,2,?,?,?,4,2,50,85,2,2,3,2,02208,00000,00000,2
2,1,530334,38.30,40,24,1,1,3,1,3,3,1,?,?,?,1,1,33.00,6.70,?,?,1,2,00000,00000,00000,1
1,9,5290409,39.10,164,84,4,1,6,2,2,4,4,1,2,5.00,3,?,48.00,7.20,3,5.30,2,1,02208,00000,00000,1
2,1,530255,37.30,104,35,?,?,6,2,?,?,?,?,?,?,?,?,74.00,7.40,?,?,2,2,04300,00000,00000,2
......
数据集介绍horse-colic.names:
数据中的'?'表示空值,如果我们使用KNN分类器,'?'没有数值,不能进行计算,因此我们需要进行数据预处理---对空值进行填充。
这里我们使用KNNImputer进行空值填充,KNNImputer填充的原理很简单,计算每个样本最近的k个样本,进行空值填充。
我们先来看下KNNImputer的运行原理:
Step3: KNNImputer空值填充---使用和原理介绍
# Step3: KNNImputer空值填充---使用和原理介绍
# nan=非数字。np.nan是非数字(NaN)的IEEE 754浮点表示。
X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]]
# n_neighbors:用于插补的相邻样本数。metric:用于搜索邻居的距离度量。
imputer = KNNImputer(n_neighbors=2, metric='nan_euclidean')
# fit_transform:适合数据,然后对其进行转换。
imputer.fit_transform(X)
运行结果:
array([[1. , 2. , 4. ],
[3. , 4. , 3. ],
[5.5, 6. , 5. ],
[8. , 8. , 7. ]])
参考资料:
numpy.nan:非数字(NaN)的IEEE 754浮点表示。https://numpy.org/doc/stable/reference/constants.html?highlight=nan#numpy.nan
sklearn.impute.KNNImputer:使用k近邻来完成缺失值的估算。每个样本的缺失值都是使用n_neighbors
训练集中找到的最近邻的平均值估算的 。如果两个都不缺失的特征都接近,则两个样本接近。https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html#sklearn.impute.KNNImputer
# 带有空值的欧式距离计算公式
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, 3.46410162]])
结果分析:
3.46410162是[np.nan, 6, 5]与[3, 4, 3]之间的欧式距离;6.92820323是[np.nan, 6, 5]与[1, 2, np.nan]之间的欧式距离;3.46410162是[np.nan, 6, 5]与[8, 8, 7]之间的欧式距离。
0. 是[3, 4, 3]与[3, 4, 3]之间的欧式距离;3.46410162是[3, 4, 3]与[1, 2, np.nan]之间的欧式距离;3.46410162是[3, 4, 3]与[8, 8, 7]之间的欧式距离。
参考资料:
sklearn.metrics.pairwise.nan_euclidean_distances(X,Y = None,*,squared = False,missing_values = nan,copy = True):在缺少值的情况下计算欧几里得距离。https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.nan_euclidean_distances.html#sklearn.metrics.pairwise.nan_euclidean_distances
计算X和Y中每对样本之间的欧式距离,如果Y = None,则假定Y = X。当计算一对样本之间的距离时,此公式将忽略两个样本中均缺少值的要素坐标,并按比例增加其余坐标的权重:dict(x,y)=sqrt(权重*离当前坐标的平方距离),其中,权重=总坐标数/当前坐标数。
例子:[3, na, na, 6][1, na, 4, 5]之间的距离和是:。
如果缺少所有坐标或没有共同的当前坐标,则为该对返回NaN。
Step4:KNNImputer空值填充---欧式距离的计算
正常的欧式距离举例:
带有空值的欧式距离举例:
在计算带有空值的欧式距离时,忽略x和y中缺值的维度,只计算有值的共同维度。如x忽略第三维、y忽略第一维,只计算第二维,x拿出一个2,y拿出一个6。计算有值的共同维度时,要乘以一个权重,这个权重的分子代表y的总维度数3个,权重的分母代表当前用到的维度数1个(第二维)。再看前面的例子,[3, na, na, 6][1, na, 4, 5]都是4维,x忽略第二维、第三维,y忽略第二维,只计算有值的第一维和第四维。x拿出第一维的3,y拿出第一维的1,二者求差的平方;x拿出第四维的6,y拿出第四维的5,二者求差的平方。然后这两个平方求和,再乘以一个权重,这个权重的分子是y的总维度数4个,权重的分母是当前用到的维度数2个。
# Step4:KNNImputer空值填充
# load dataset, 将?变成空值
input_file = './horse-colic.csv'
# read_csv将CSV文件读取到DataFrame中。df_data.shape(300,28)
df_data = pd.read_csv(input_file, header=None, na_values='?')
# 得到训练数据和label, 第23列表示是否发生病变, 1: 表示Yes; 2: 表示No.
data = df_data.values #data.shape(300,28)
ix = [i for i in range(data.shape[1]) if i != 23] #x是从0到27(除去23)的一维数组,共27个数
X, y = data[:, ix], data[:, 23] #X.shape(300, 27);y.shape(300,)
# 查看所有特征的缺失值个数和缺失率
for i in range(df_data.shape[1]):
#当前行的所有特质的缺失值个数n_miss
n_miss = df_data[[i]].isnull().sum()
#当前行的所有特质的缺失率perc
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)
# 转换数据集
Xtrains = imputer.transform(X)
# 打印转化后的数据集的空值
print('KNNImputer after Missing: %d' % sum(np.isnan(Xtrains).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: 0
参考资料:
pandas.read_csv:将CSV(逗号分隔)文件读取到DataFrame中。https://pandas.pydata.org/pandas-docs/version/0.21/generated/pandas.read_csv.html?highlight=read_csv#pandas.read_csv
Step5:基于pipeline模型训练&可视化
Pipeline,可以直接翻译成数据管道。任何有序的操作有可以看做pipeline,例如工厂流水线,对于机器学习模型来说,就是数据流水线---数据通过管道中的每一个节点,出了结果之后,继续流向下游。对于我们这个例子,数据是有空值,我们会有一个KNNImputer节点用来填充空值, 之后继续流向下一个kNN分类节点,最后输出模型。step4已经完成了空值填充,下面step5进行训练和可视化。
# 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
# steps是元祖列表,实现拟合变换
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(Xtrains, 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()
运行结果:
>k: 1, Acc Mean: 0.800, Std: 0.031
>k: 2, Acc Mean: 0.821, Std: 0.041
>k: 3, Acc Mean: 0.833, Std: 0.053
>k: 4, Acc Mean: 0.824, Std: 0.037
>k: 5, Acc Mean: 0.802, Std: 0.038
>k: 6, Acc Mean: 0.811, Std: 0.030
>k: 7, Acc Mean: 0.797, Std: 0.056
>k: 8, Acc Mean: 0.819, Std: 0.044
>k: 9, Acc Mean: 0.820, Std: 0.032
>k: 10, Acc Mean: 0.815, Std: 0.046
>k: 15, Acc Mean: 0.818, Std: 0.037
>k: 16, Acc Mean: 0.811, Std: 0.048
>k: 18, Acc Mean: 0.809, Std: 0.043
>k: 20, Acc Mean: 0.810, Std: 0.038
>k: 21, Acc Mean: 0.828, Std: 0.038
sklearn.pipeline.Pipeline:带有最终估算器的变换数据管道。https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline
结果分析:
我们的实验是每个k值下,随机切分20次数据,从上述的图片中,根据k值的增加,我们的测试准确率会有先上升再下降再上升的过程。 [3, 5]之间是一个很好的取值,上文我们提到,k很小的时候会发生过拟合,k很大时候会发生欠拟合,当遇到第一下降节点,此时我们可以 简单认为不在发生过拟合,取当前的k值即可。