作者:欧新宇(Xinyu OU)
本文档所展示的测试结果,均运行于:Intel Core i7-7700K CPU 4.2GHz
KNN算法是一个典型的监督学习
算法,它的核心思想是:未标记样本的类别由距离其最近的K个邻居通过投票来决定。
具体而言,假设存在一个已经标记好的数据集。给定一个未标记的数据样本,我们的任务是:预测出该数据样本所属的类别。
下图中所显示的数据集是良好的数据集,即都有对应的标签,一类是蓝色正方形,一类是红色三角形,绿色圆形是待分类数据。
K = 3时,范围内红色三角形较多,待分类样本属于红色三角形类
K = 5时,范围内蓝色正方形较多,待分类样本属于蓝色正方形类
如何选择一个最佳的K值取决于数据。一般情况下,较大的K值能减少噪声的影响,但会使类别之间的界限变得模糊 。因此,K的取值通常较小(通常K<20)。在scikit-learn中,K近邻算法的K值通过参数n_neighbors来调节,默认值为5.
# 导入样数据集生成器
from sklearn.datasets import make_blobs
# 从近邻算法子库中导入K近邻分类器KNeighborsClassifier
from sklearn.neighbors import KNeighborsClassifier
# 导入绘图工具箱 matplotlib
import matplotlib.pyplot as plt
# 从模型选择子库中导入数据集拆分工具
from sklearn.model_selection import train_test_split
#导入计算库
import numpy as np
# 生成数据集,样本数=1000, 类别数=2
X, y = make_blobs(n_samples = 1000, centers = 2, random_state = 8)
# 将生成的数据集进行可视化
plt.scatter(X[:, 0], X[:, 1], c = y, cmap = plt.cm.spring, edgecolor = 'k')
plt.show()
# 使用手动方式指定聚类中心,并生成数据集,样本数=1000,类别数=3
centers = [[2, -2], [2, 2], [0, 4]]
X2, y2 = make_blobs(n_samples = 1000, centers = centers, cluster_std = 0.6, random_state = 8)
c = np.array(centers) # 将聚类中心转换为 numpy矩阵
# 将生成的数据集进行可视化
plt.scatter(X2[:, 0], X2[:, 1], c = y2, cmap = plt.cm.spring, edgecolor = 'k')
plt.scatter(c[:,0], c[:,1], s=100, marker='^',c='blue') # 绘制聚类中心
plt.show()
X_train = X[0:800]
y_train = y[0:800]
X_test = X[800:]
y_test = y[800:]
# 分别可视化训练样本和测试样本
plt.scatter(X_train[:, 0], X_train[:, 1], c = y_train, cmap = plt.cm.spring, edgecolor = 'k', marker='x')
# plt.scatter(X_test[:, 0], X_test[:, 1], c = y_test, cmap = plt.cm.spring, edgecolor = 'k', marker='o')
plt.show()
# 使用生成的训练集数据(X, y)训练KNN分类器
clf = KNeighborsClassifier(n_neighbors = 5) # n_neighbors = 5为默认近邻数量
clf.fit(X_train, y_train)
import time
t_start = time.perf_counter() # 启动计时器
print("正在使用模型,对训练样本的 y 值进行预测...", end="")
# 将分类结果进行可视化
#分别用样本的两个特征值创建图像和横轴和纵轴
x_min, x_max = X_test[:, 0].min() - 1, X_test[:, 1].max() + 1
y_min, y_max = X_test[:, 1].min() - 1, X_test[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
print("执行时间为:{:.2f}".format(time.perf_counter() - t_start)) # 输出执行时间
#给每个分类中的样本分配不同的颜色
Z = Z.reshape(xx.shape)
plt.pcolormesh(xx, yy, Z, cmap = plt.cm.Pastel1)
#用散点把样本表示出来
plt.scatter(X_test[:, 0], X_test[:, 1], c = y_test, cmap = plt.cm.spring, edgecolor = 'k')
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("Classifier: KNN")
plt.show()
# 将训练集进行可视化
plt.pcolormesh(xx, yy, Z, cmap = plt.cm.Pastel1)
plt.scatter(X_train[:, 0], X_train[:, 1], c = y_train, cmap = plt.cm.spring, edgecolor = 'k')
# 指定待分类的新数据点 X_sample, 并使用KNN分类器对其进行分类预测
X_sample = np.array([[6.5, 4.92]])
y_sample = clf.predict(X_sample)
# 将新数据点进行可视化
plt.scatter(X_sample[0][0], X_sample[0][1], marker = "*", c = "red", s = 200)
# 获得样本的近邻点,默认情况下数量(n_neighbors = 5)为默认近邻数量,同时输出这些点于预测点连线
# 此处 kneighbors()获取到的是X中的近邻样本的索引值
neighbors = clf.kneighbors(X_sample, return_distance = False)
for i in neighbors[0]:
plt.plot([X_train[i][0], X_sample[0][0]], [X_train[i][1], X_sample[0][1]], 'b--', linewidth = 0.6)
plt.show()
# 输出分类预测结果
print("新数据点({0})在KNN分类器上的分类结果为:{1}".format(X_sample[0], y_sample))
neighbors = clf.kneighbors(X_sample, return_distance=False)
print("新数据点{0}, 包含{1}个近邻,它们分别是: {2}.".format(X_sample, neighbors.size, neighbors))
使用clf.score()函数可以实现在给定数据集上测试模型的准确率
# 输出模型评分结果
print("模型正确率为:{:.2f}".format(clf.score(X_test, y_test)))
在make_blobs()函数中,参数centers表示聚类中心的数量,参数n_samples表示样本数。
# 生成数据集,样本数=1000, 类别数=2
n_samples = 1500
X2, y2 = make_blobs(n_samples = n_samples, centers = 5, random_state = 8)
# 将生成的数据集进行可视化
plt.scatter(X2[:, 0], X2[:, 1], c = y2, cmap = plt.cm.spring, edgecolor = 'k')
plt.show()
test_rate = 0.7
split_index = int(n_samples * test_rate)
X2_train = X2[0:split_index]
y2_train = y2[0:split_index]
X2_test = X2[split_index:]
y2_test = y2[split_index:]
# 分别可视化训练样本和测试样本, x: 训练集, o: 测试集
plt.scatter(X2_train[:, 0], X2_train[:, 1], c = y2_train, cmap = plt.cm.spring, edgecolor = 'k', marker='x')
plt.scatter(X2_test[:, 0], X2_test[:, 1], c = y2_test, cmap = plt.cm.spring, edgecolor = 'k', marker='o')
plt.show()
下代码将使用K近邻算法创建分类模型。
# 使用生成的数据集(X, y)训练KNN分类器
clf2 = KNeighborsClassifier()
clf2.fit(X2_train, y2_train)
import time
t_start = time.perf_counter() # 启动计时器
print("正在使用模型,对训练样本的 y 值进行预测...", end="")
# 将分类结果进行可视化
#分别用样本的两个特征值创建图像和横轴和纵轴
x2_min, x2_max = X2_test[:, 0].min() - 1, X2_test[:, 1].max() + 1
y2_min, y2_max = X2_test[:, 1].min() - 1, X2_test[:, 1].max() + 1
xx2, yy2 = np.meshgrid(np.arange(x2_min, x2_max, 0.02), np.arange(y2_min, y2_max, 0.02))
Z2 = clf2.predict(np.c_[xx2.ravel(), yy2.ravel()])
print("执行时间为:{:.2f}".format(time.perf_counter() - t_start)) # 输出执行时间
#给每个分类中的样本分配不同的颜色
Z2 = Z2.reshape(xx2.shape)
plt.pcolormesh(xx2, yy2, Z2, cmap = plt.cm.Pastel1)
#用散点把样本表示出来
plt.scatter(X2_test[:, 0], X2_test[:, 1], c = y2_test, cmap = plt.cm.spring, edgecolor = 'k')
plt.xlim(xx2.min(), xx2.max())
plt.ylim(yy2.min(), yy2.max())
plt.title("Classifier: KNN")
plt.show()
# 输出模型评分结果
print("模型正确率为:{:.2f}".format(clf2.score(X2_test,y2_test)))
回归分析数据集生成器:sklearn.datasets.make_regression
# 导入绘图工具箱 matplotlib
import matplotlib.pyplot as plt
# 导入make_regression数据集生成器
from sklearn.datasets import make_regression
# 从模型选择子库中导入数据集拆分工具
from sklearn.model_selection import train_test_split
#生成特征数为 1, 噪声为 50的的数据集
X, y = make_regression(n_samples=1000, n_features = 1, n_informative = 1, noise = 50, random_state = 8)
# 用散点图将数据点纪念性可视化
plt.scatter(X, y, c = 'orange', edgecolor = 'k')
plt.show()
使用train_test_split()函数对数据集进行划分
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 8, test_size = 0.3)
from sklearn.neighbors import KNeighborsRegressor
# 用 KNN模型拟合数据
reg = KNeighborsRegressor(n_neighbors = 5) # n_neighbors=5 是 K近邻算法的默认值
reg.fit(X_train, y_train)
import numpy as np
# 把预测结果用图像进行可视化
z = np.linspace(-3, 3, 200).reshape(-1, 1)
plt.scatter(X, y, c = 'orange', edgecolor = 'k')
plt.plot(z, reg.predict(z), c = 'k', linewidth = 3)
# 向图像添加标题
plt.title("KNN Regressor")
plt.show()
print("模型评分:{:.2f}".format(reg.score(X_test, y_test)))
机器学习算法对于超参数是非常敏感的,对于不同的数据集,需要设置不同的超参数,否则就会容易产生欠拟合问题
或过拟合问题
,因此我们尝试将近邻数量进行修改,其中一个典型值 n_neighbors = 13.
from sklearn.neighbors import KNeighborsRegressor
# 减少模型的近邻数 n_neighbors参数为 2
reg2 = KNeighborsRegressor(n_neighbors = 2)
reg2.fit(X_train, y_train)
# 重新进行可视化
plt.scatter(X, y, c = 'orange', edgecolor = 'k')
plt.plot(z, reg2.predict(z), c='k', linewidth = 3)
plt.title('KNN Regressor: n_neighbors = 2')
plt.show()
输出模型评分:
print("优化后的模型评分: {:.2f}".format(reg2.score(X_test, y_test)))
下面将使用scikit-learning库对内置数据集“酒数据集”进行实验,该数据集包含在scikit-learning的datasets模块中。
from sklearn.datasets import load_wine
# 从sklearn的datasets模块中载入数据集
wine_dataset = load_wine()
print("红酒数据集中的键:{}".format(wine_dataset.keys()))
通过print()函数,可以看到数据集wine中包含的字段, 即数据集的相关数据和内容:
所谓数据集的形状,包括数据的样本数(行数)和数据的特征数(列数)
print("数据形状概况: {}".format(wine_dataset['data'].shape))
print(wine_dataset['target'])
print(wine_dataset['target_names'])
print(wine_dataset['feature_names'])
print(wine_dataset['DESCR'])
在scikit-learn中,使用train_test_split()函数实现数据集的拆分,默认情况下训练集和测试集按照75%:25%
的比例进行拆分。但在实际工作中,习惯使用将数据集拆分成三个部分,
在进行性能评估的时候,只要保证各个算法拥有一致的划分比例,则不管采用什么样的比例进行拆分都是可以接受的。
此处,我们同样可以使用train_test_split()函数实现这种划分。
**【知识点】**:关于训练集、验证集和测试集的知识请参考:train_val_test
# 导入数据集拆分工具
from sklearn.model_selection import train_test_split
# 将数据集进行拆分,按照默认的比例进行拆分,其中训练集占75%,测试集占25%
X_train, X_test, y_train, y_test = train_test_split(wine_dataset['data'],
wine_dataset['target'],
random_state = 0)
函数train_test_split()可以实现将数据集划分为训练集和测试集,该函数包含4个主要的参数。下面给出该函数的基本格式: X_train, X_test, y_train, y_test = train_test_split(X, y, test_size, random_state)
random_state = 0
时,每次生成的随机数都是一样的,该方法用于生成每次都一样的数据集划分。在上述的代码中,我们设置 X = wine_dataset['data'],表示将wine酒数据集中的data字段设置为待划分样本
;设置 y = wine_dataset['target'],表示将wine酒数据集中的target字段设置为待划分样本的标签
;test_size 去默认值,表示以75%:25%的比例进行划分;设置random_state = 0, 表示关闭随机参数,每次都将生成同样的划分。
print("X: {}".format(wine_dataset['data'].shape))
print("y: {}".format(wine_dataset['target'].shape))
print("X_train shape: {}".format(X_train.shape))
print("X_test shape: {}".format(X_test.shape))
print("y_train shape: {}".format(y_train.shape))
print("y_test shape: {}".format(y_test.shape))
# 引入KNN分类模型, 并配置KNN分类器,设置近邻数 = 1
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors = 15, weights='distance')
使用fit()函数对训练集数据进行拟合,即: 使用训练数据来训练KNN分类模型。
knn.fit(X_train, y_train)
score_train = knn.score(X_train, y_train)
score_test = knn.score(X_test, y_test)
print("训练集评分:{0:.2f}; 测试集评分:{1:.2f}".format(score_train, score_test))
print("数据集中的样本总共有{}种特征,分别是:{}".format(
len(wine_dataset['feature_names']), wine_dataset['feature_names']))
此处我们按照样本的特征的数量,利用numpy.array
手动生成一个数据(矩阵形式)
import numpy as np
X_new = np.array([[32, 1, 3, 3, 3, 3, 3, 32, 1, 3, 3, 3, 3]])
prediction = knn.predict(X_new)
print("新酒的分类为{}".format(wine_dataset['target_names'][prediction]))
此处,我们通过将近邻数量设置为 1~20 共20个值,然后分别求出模型在训练集和测试集上的准确率。最后,通过可视化分数曲线图来对模型进行性能分析。
#建立两个空列表,分别用于保存训练集和测试集的模型评分
training_score = []
test_score = []
neighbors_amount = range(1, 30)
for n_neighbors in neighbors_amount:
clf3 = KNeighborsClassifier(n_neighbors = n_neighbors)
clf3.fit(X_train, y_train)
score_train = clf3.score(X_train, y_train)
score_test = clf3.score(X_test, y_test)
print("n_neighbors:{0}; train_score:{1:.2f}; test_score:{2:.2f}.\n".format(n_neighbors, score_train, score_test))
#把不同的n_neighbors数量对应的得分放进列表
training_score.append(score_train)
test_score.append(score_test)
#下面用matplotlib将得分进行绘图
plt.plot(neighbors_amount, training_score, label = "training score")
plt.plot(neighbors_amount, test_score, label = "test score")
plt.ylabel("score")
plt.xlabel("n_neighbors")
plt.title("Learning Curve for the different number of neighbors")
plt.legend()
plt.show()
PS:可以尝试一下其他近邻数的输出结果,例如:n_neighbors = 50, 100, 200,并试着分析一下输出结果.
此处,展示如何使用外置数据集进行预测分析。
# 加载 pandas库,并使用read_csv()函数读取糖尿病预测数据集diabetes
import pandas as pd
data = pd.read_csv('../Datasets/diabetes.csv')
# 输出数据集的形状,即展示数据包含的样本数(行)和特征数(列)
print("数据的形状为{}\n".format(data.shape))
data.info()
从输出可以看出,数据集中共包含768个样本(entries),每个样本8种特征。其中Outcome是样本的标签(即类别),0表示没有糖尿病,1表示患有糖尿病。此处的8种特征分别是:
data.head()
此处基于样本的分类进行分组,可以使用pandas数据类型的groupby()函数实现
data.groupby('Outcome').size()
可以看到,阴性样本共有500例,阳性样本共有268例
# 将数据中的特征和标签进行分离,其中第0位为索引号,第1-8位为特征,第9位为标签
X = data.iloc[:, 0:8]
y = data.iloc[:, 8]
print("X的形状为{0},Y的形状为{1}".format(X.shape, y.shape))
# 以 80%:20%的比例对训练集和测试集进行拆分
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)
# 引入KNN分类模型, 并配置KNN分类器,设置近邻数 = 2
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors = 4)
使用fit()函数对训练集数据进行拟合,即使用训练数据来训练KNN分类模型。
knn.fit(X_train, y_train)
分别对训练集和测试集中的数据进行预测评分
train_score = knn.score(X_train, y_train)
test_score = knn.score(X_test, y_test)
print("训练集评分:{0:.2f};测试集评分:{1:.2f}".format(train_score, test_score))
从结果来看,仅0.73的预测结果表明模型过于简单,无法很好拟合训练数据,因此性能并不理想
from sklearn.model_selection import learning_curve
import numpy as np
# 定义一个函数用于绘制学习曲线
def plot_learning_curve(plt, estimator, title, X, y, ylim=None, cv=None,
n_jobs=1, train_sizes=np.linspace(0.1, 1.0, 5)):
"""
函数功能描述:生成训练集和测试集的简单学习曲线。
参数:
----------
estimator : 分类器或预测器对象。
title: 字符串类型, 图的标题
X : 矩阵类型, shape (n_samples, n_features), 用于保存训练样本,其中行表示样本的数量,列表示特征的数量
y : 矩阵类型, shape (n_samples) 或者 (n_samples, n_features), 该参数用于保存样本的标签值,为空时表示无监督学习。
ylim : 元组类型, shape (ymin, ymax), 可选。定义图的y轴的取值范围。
cv : 整型, cross-validation 交叉验证生成器或可迭代设置,可选。用于确定交叉验证的分割策略。
cv有以下几种输入:
- None, 默认状态下,使用 3-fold cross-validation,
- integer, 指定倍数为整数 n.
- 指定用作交叉验证生成器的对象.
- 一个可迭代的产生序列/测试的分割对象.
当标签"y"是二进制或者多类时,使用参数integer或None,此时会使用StratifsiedKFold用法类
当标签"y"不是二进制值,或estimtor不是分类器时,此时会使用KFold用法类
n_jobs : 整型, 可选。并行运算的数量,通常在使用GPU,或CPU并行的时候使用。
"""
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel("Training examples")
plt.ylabel("score")
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="g")
plt.plot(train_sizes, train_scores_mean, 'o--', color="r",
label="Training score")
plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
label="Cross-validation score")
plt.legend(loc="best")
plt.show()
return plt
from sklearn.model_selection import ShuffleSplit
import matplotlib.pyplot as plt
# 从模型选择子库中导入数据集拆分工具
knn = KNeighborsClassifier(n_neighbors = 2)
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
plt.figure(figsize = (8, 6), dpi = 100)
plot_learning_curve(plt, knn, 'Learning Curve for KNN Diabetes',
X, y, ylim = (0, 1.01), cv = cv)
从上面的可视化结果可以看出:
==> 这是典型的欠拟合现象
然而,KNN算法没有太好的解决办法,我们将在后续的学习中使用更优秀的算法来解决以上的问题。