参考文章:①机器学习:工作台 - Heywhale.com
②KNN算法介绍:【机器学习】KNN 算法介绍_knn回归-CSDN博客
本文仅作学习总结。
一、KNN算法介绍
一、KNN简介:
KNN 算法,或者称 k-最近邻算法,是 有监督学习 中的 分类算法 。它可以用于分类或回归问题,但它通常用作分类算法。简单的来说就是近朱者赤近墨者黑。
举个简单例子:如图,绿色圆要被决定赋予哪个类,是红色三角形还是蓝色四方形?如果K=3,由于红色三角形所占比例为2/3,绿色圆将被赋予红色三角形那个类,如果K=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。
其中K为圆的半径,当K在不断变化过程中,圆内分布的种类占比决定了预测点的种类。
二、KNN的关键
一、点之间的距离计算
样本空间内的两个点之间的距离量度表示两个样本点之间的相似程度:距离越短,表示相似程度越高;反之,相似程度越低。
闵可夫斯基距离
给定两个点 𝑝=(𝑝1,𝑝2,…,𝑝𝑛) 和 𝑞=(𝑞1,𝑞2,…,𝑞𝑛) 在 𝑛维空间中,它们之间的闵可夫斯基距离 𝐷(𝑝,𝑞) 可以用以下公式表示:
其中,𝑟 是一个实数,称为闵可夫斯基距离的参数。当 𝑟=1 时,这就是曼哈顿距离(Manhattan distance);当 𝑟=2时,这就是欧氏距离(Euclidean distance)。当 𝑟→∞时,闵可夫斯基距离趋向于切比雪夫距离(Chebyshev distance)。
曼哈顿距离
当 𝑟=1 时,闵可夫斯基距离变为曼哈顿距离(Manhattan distance):
欧氏距离
当 𝑟=2 时,闵可夫斯基距离变为欧氏距离(Euclidean distance):
切比雪夫距离
当 𝑟=∞ 时,闵可夫斯基距离变为切比雪夫距离(Chebyshev distance):
余弦距离
余弦相似度,就是计算两个向量间的夹角的余弦值。
二、K的取值
通过上面简单介绍,发现K值的大小也决定了对预测值的影响。
如果k值比较小,相当于我们用较小的领域内的训练样本对实例进行预测。这时,算法的近似误差(Approximate Error)会比较小,因为只有与输入实例相近的训练样本才会对预测结果起作用。但是,它也有明显的缺点:算法的估计误差比较大,预测结果会对近邻点十分敏感,也就是说,如果近邻点是噪声点的话,预测就会出错。因此,k值过小容易导致KNN算法的过拟合。
同理,如果k值选择较大的话,距离较远的训练样本也能够对实例预测结果产生影响。这时候,模型相对比较鲁棒,不会因为个别噪声点对最终预测结果产生影响。但是缺点也十分明显:算法的近邻误差会偏大,距离较远的点(与预测实例不相似)也会同样对预测结果产生影响,使得预测结果产生较大偏差,此时模型容易发生 欠拟合。
在取值过程中可以先取较大一点的K值,当它继续增大或减小的时候,错误率都会上升。
二、KNN分类与回归简单示例
一、分类
from sklearn.neighbors import KNeighborsClassifier
def dm01_knnapi():
#初始化模型
estimator = KNeighborsClassifier(n_neighbors=2)
#x,y数据
X = [[0], [1], [2], [3]]
y = [1, 1, 1, 0]
#模型训练
estimator.fit(X, y)
#模型预测
myret = estimator.predict([[4]])
print('myret-->', myret)
dm01_knnapi()
# myret--> [0]
二、回归
from sklearn.neighbors import KNeighborsRegressor
def dm02_knnapi_():
estimator =KNeighborsRegressor(n_neighbors=2)
X = [[0, 0, 1],
[1, 1, 0],
[3, 10, 10],
[4, 11, 12]]
y = [0.1, 0.2, 0.3, 0.4]
estimator.fit(X, y)
myret= estimator.predict([[3, 11, 10]])
print('myret-->', myret)
dm02_knnapi_()
# myret--> [0.35]
三、小结
以上通过调用 KNeighborsClassifier 和 KNeighborsResressor 两个模型,进行简单的模型训练,模型预测。测试了一下使用sklear进行KNN分类和回归的实现。
三、数据处理
首先,为什么要进行数据处理?
- 去除噪音和异常值:原始数据可能包含错误、缺失或异常的值,需要进行清洗和处理,以提高数据质量和可靠性。
- 填补缺失值:处理缺失数据,选择合适的方法填充缺失值,使得数据集完整。
- 数据去重:识别和删除重复的数据条目,确保数据集中每条数据是唯一的。
- 数据格式转换:将数据转换为适合分析和建模的格式,如将日期时间转换为标准格式,将文本数据进行分词和编码等。
在这里也简单用数据归一化和数据标准化进行数据预处理。
一、数据归一化
数据归一化:通过对原始数据进行变换把数据映射到【mi,mx】(默认为[0,1])之间
from sklearn.preprocessing import MinMaxScaler
def gy():
data = [[90, 2, 10, 40],
[60, 4, 15, 45],
[75, 3, 13, 46]]
# 对数据进行归一化并进行转化
data = MinMaxScaler().fit_transform(data)
print(data)
gy()
# data:
# [[1. 0. 0. 0. ]
# [0. 1. 1. 0.83333333]
# [0.5 0.5 0.6 1. ]]
二、数据标准化
数据标准化:通过对原始数据进行标准化,转换为均值为0标准差为1的标准正态分布的数据
from sklearn.preprocessing import StandardScaler
def bz():
data = [[90, 2, 10, 40],
[60, 4, 15, 45],
[75, 3, 13, 46]]
#对数据进行标准化并进行转化
data = StandardScaler().fit_transform(data)
print(data)
bz()
# data:
# [[ 1.22474487 -1.22474487 -1.29777137 -1.3970014 ]
# [-1.22474487 1.22474487 1.13554995 0.50800051]
# [ 0. 0. 0.16222142 0.88900089]]
三、小结
归一化:
- 如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
- 应用场景:最大值与最小值非常容易受异常点影响,鲁棒性较差,只适合传统精确小数据场景
标准化:
- 如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大
- 应用场景:适合现代嘈杂大数据场景。
四、以鸢尾花为数据集进行分类预测
一、导入数据集
import numpy as np
# 加载莺尾花数据集
from sklearn import datasets
# 导入KNN分类器
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
# 导入莺尾花数据集
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)
导入数据集并设置训练集和验证集。
二、模型训练
其中,本次训练使用了参数K为5,距离公式Minkowski
# 训练模型
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=None, n_neighbors=5, p=2, weights='uniform')
这个是指使用了 scikit-learn 库中的 KNeighborsClassifier 类来创建一个 k-最近邻分类器。
其中KNeighborsClassifier的参数为:
algorithm='auto'
: 指定用于计算最近邻的算法。这里设置为 'auto' 表示根据数据自动选择适合的算法。
leaf_size=30
: 在 BallTree 或 KDTree 中构造过程中停止建子树的叶子节点数量的阈值。默认为 30。
metric='minkowski'
: 用于计算距离的度量方式。这里使用的是 Minkowski 距离,也就是通用的闵可夫斯基距离。
p=2
: Minkowski 距离的参数。当 p=2 时,计算的是欧几里德距离;当 p=1 时,计算的是曼哈顿距离。
n_neighbors=5
: 指定用于预测的最近邻居的数量 k。
metric_params=None
: 度量函数的其他关键字参数。
n_jobs=None
: 并行运行的作业数量。None 表示不使用并行化,-1 表示使用所有可用的 CPU 核心。
weights='uniform'
: 用于预测的权重函数。'uniform' 表示所有邻居的权重相等,即简单的投票方式。
三、预测
# 预测
X_pred = clf.predict(X_test)
acc = sum(X_pred == y_test) / X_pred.shape[0]
print("预测的准确率ACC: %.3f" % acc)
预测的准确率ACC: 0.967
四、可视化
接下来通过表格的形式进行一个可视化操作。
训练的数据: clf.fit(X_train, y_train)的过程可看作数据的存储
其中预测值 x = [5 , 3.6 , 1.4 , 0.2] y = 0
setp1:计算出所有点与预测值的距离 setp2:标注出前五个最接近的值
step3: k近邻的label进行投票:
nn_labels = [0, 0, 0, 0, 1] --> 得到最后的结果0。
五、KNN回归预测
一、库导入
#Demo来自sklearn官网
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
二、数据导入&分析
np.random.seed(0)
# 随机生成40个(0, 1)之前的数,乘以5,再进行升序
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))
- 使用
np.random.seed(0)
设置随机数种子,以确保每次运行代码时都得到相同的随机结果。- 使用
np.random.rand(40, 1)
生成一个形状为 (40, 1) 的随机数组,表示在区间 [0, 1) 内生成 40 个随机数。然后乘以 5,将数据范围扩展到 [0, 5)。np.sort(..., axis=0)
对这些随机数按列进行升序排序,得到特征矩阵 X。- 使用
np.linspace(0, 5, 500)
在 [0, 5] 区间生成 500 个等间隔的数值,并通过[:, np.newaxis]
将其转换为列向量,形状为 (500, 1)。这将作为测试数据集 T。- 使用正弦函数
np.sin(X)
计算特征数据 X 对应的目标值 y。然后通过.ravel()
将 y 摊平为一维数组,形状变为 (40,),以符合后续的操作要求。- y[::5] += 1 * (0.5 - np.random.rand(8))
- 在目标值 y 中每隔 5 个元素(即每隔5个样本),将对应位置的值加上一定范围的随机噪声。对y的值增加一些噪声,噪声的大小是从-0.5到0.5的随机数。
np.random.rand(8)
生成一个长度为 8 的随机数组,值在 [0, 1) 区间。(0.5 - np.random.rand(8))
生成一个均匀分布在 [-0.5, 0.5) 区间的随机数组。1 * (0.5 - np.random.rand(8))
将上述随机数组乘以 1,得到的随机数范围为 [-0.5, 0.5)。y[::5]
选择 y 数组中每隔 5 个元素的位置。y[::5] += ...
将随机噪声添加到这些位置的目标值 y 中。
这部分代码主要是为了对数据进行加工,其目的主要是为以下四点:
模拟真实数据: 在实际的数据应用中,很少有完全干净且没有噪声的数据。数据通常会受到各种因素的影响,例如测量误差、环境变化、不完整性等,这些因素会导致数据中的一些随机变化或错误。通过在生成的数据上添加噪声,可以更真实地模拟这种数据不确定性。
评估模型鲁棒性: 在机器学习中,模型的性能不仅取决于其在干净数据上的表现,还取决于其对噪声和不确定性的处理能力。通过向目标值 y 添加噪声,可以测试模型在真实世界中具有噪声数据时的表现。一个对噪声数据具有良好鲁棒性的模型,通常能够更好地泛化到未见过的数据。
增加数据多样性: 添加噪声可以增加数据的多样性,使模型在训练时能够学习到更广泛的情况和变化。这有助于防止模型过度拟合训练数据,并提高模型对于不同情况的适应能力。
模拟数据缺失或错误: 在某些情况下,数据集可能存在缺失值或错误的标记,这也可以被视为一种数据的噪声。通过向数据添加随机噪声,可以模拟这种数据缺失或错误的情况,从而帮助模型更好地应对实际应用中可能遇到的问题。
三、 模型训练&可视化
# 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) #其中 X 是特征数据,y 是目标值。
# 预测
y_ = clf.predict(T)
#总共 6 行 1 列的子图,i + 1 表示当前绘制的子图位置(从 1 开始计数)。
plt.subplot(6, 1, i + 1)
#绘制散点图,其中 X 是特征数据,y 是目标值。散点的颜色设置为红色 (color='red'),图例标签为 'data'。
plt.scatter(X, y, color='red', label='data')
#绘制折线图,其中 T 是用于预测的测试数据,y_ 是模型预测的结果。折线的颜色设置为深蓝色 (color='navy'),图例标签为 'prediction'。
plt.plot(T, y_, color='navy', label='prediction')
#根据数据范围自动调整坐标轴范围,使得所有数据点都能显示在图中。
plt.axis('tight')
#添加图例,显示散点图和折线图的标签
plt.legend()
#设置子图的标题,显示当前子图对应的 k 值。
plt.title("KNeighborsRegressor (k = %i)" % (k))
plt.tight_layout()
plt.show()
四、模型分析
- 通过上面的六个不同的K值,可以看出在K小于3时,预测曲线容易过拟合。
- 而在K大于40,超过样本数量时,预测曲线容易发生欠拟合。
- 所以,一般情况下,使用KNN的时候,根据数据规模我们会从[3, 20]之间进行尝试,选择最好的k,例如上图中的[3, 10]相对1和40都是还不错的选择。
六、KNN原理介绍
k近邻方法是一种惰性学习算法,可以用于回归和分类,它的主要思想是投票机制,对于一个测试实例x, 我们在有标签的训练数据集上找到和最相近的k个数据,用他们的label进行投票,分类问题则进行表决投票,回归问题使用加权平均或者直接平均的方法。knn算法中我们最需要关注两个问题:k值的选择和距离的计算。
kNN中的k是一个超参数,需要我们进行指定,一般情况下这个k和数据有很大关系,都是交叉验证进行选择,但是建议使用交叉验证的时候,k∈[2,20],使用交叉验证得到一个很好的k值。
k值还可以表示我们的模型复杂度,当k值越小意味着模型复杂度表达,更容易过拟合,(用极少树的样例来绝对这个预测的结果,很容易产生偏见,这就是过拟合)。我们有这样一句话,k值越多学习的估计误差越小,但是学习的近似误差就会增大。
七、KNN学习总结
K-NN(k最近邻)算法是一种基于实例的学习方法,它依赖于训练数据集来对新的测试实例进行分类或回归。
- k值的选择:在K-NN算法中,k值代表我们选择的邻居数量。在分类问题中,k个邻居中最多的标签将作为测试实例的预测标签;在回归问题中,k个邻居的标签值将用于计算加权平均或直接平均的预测值。
- 距离的计算:k-NN算法中,距离计算是确定邻居的关键步骤。常见的距离度量包括欧几里得距离(Euclidean distance)、曼哈顿距离(Manhattan distance)和余弦相似度(Cosine similarity)等。选择哪种距离度量取决于数据的特征和问题的性质。
- 交叉验证:为了选择最佳的k值,通常会使用交叉验证(Cross-validation)方法。交叉验证通过将数据集分为训练集和验证集,多次训练模型并验证其性能,以评估模型在不同配置下的泛化能力。建议的k值范围(如[2, 20])是基于经验,但实际上k值的选择应该根据具体的数据集和问题来确定。
- 模型复杂度:k值的大小直接影响到模型的复杂度。当k值较小时,模型可能过于复杂,容易过拟合,即在训练数据上表现很好,但在未知数据上表现不佳。当k值较大时,模型可能过于简单,无法捕捉数据中的复杂关系,导致欠拟合。因此,找到一个合适的k值对于获得良好的模型性能至关重要。
- 学习的估计误差和近似误差:学习的估计误差是指模型预测值与真实值之间的差异,而学习的近似误差是指模型对数据分布的拟合程度。通常,我们希望估计误差较小,而近似误差适中,这样模型才能在未知数据上具有良好的泛化能力。
- 预测值的性质:在回归中,预测值是连续的;在分类中,预测值是离散的。
总结来说,k-NN算法的性能很大程度上取决于k值的选择和距离的计算。通过交叉验证和其他模型选择技术,可以找到最佳的k值,从而提高模型的泛化能力和预测准确性。
机器学习知识拓展
机器学习数据处理:噪声
在数据分析和机器学习中,添加噪声到数据是一种常见的技术,用于模拟真实世界中的数据的不确定性和随机性。这样做有几个原因:
- 增加数据的多样性:现实世界中的数据很少是完美干净的,它们通常包含噪声和随机误差。通过添加噪声,可以使训练数据更加多样化,更接近真实世界的情况。
- 提高模型的泛化能力:在训练模型时,如果数据太干净,模型可能会过于拟合到这些数据上,而无法很好地泛化到新的、带有噪声的数据上。添加噪声可以帮助模型学习数据的基本模式,而不是仅仅拟合到噪声上。
- 模拟输入数据的变动:在许多应用中,输入数据可能会因为各种原因而发生微小的变化。通过添加噪声,可以模拟这些变化,从而使得模型更加健壮,能够处理实际应用中的数据变动。
- 提高鲁棒性:在某些情况下,例如在信号处理或语音识别中,噪声是不可避免的。通过在训练过程中添加噪声,可以提高模型对噪声的鲁棒性,使其在噪声环境下表现得更好。
- 增加挑战性:在某些机器学习的竞赛或测试中,添加噪声可以增加问题的难度,考验模型的性能是否能够在噪声干扰下仍然准确。
在你的例子中,添加噪声可能是为了使数据更加有趣,或者是为了测试模型在处理带有噪声的目标变量时的性能。噪声的添加通常是基于一定的概率分布,例如正态分布(高斯噪声),这样可以确保噪声的添加是可控和可重复的。
机器学习 - 鲁棒性
鲁棒性(Robustness)是指机器学习模型在面对噪声、异常值、数据丢失或其他不理想的情况时,仍然能够保持性能的能力。一个鲁棒性强的模型能够在这些不利条件下给出准确或接近准确的预测,而一个鲁棒性弱的模型则可能会对这些干扰非常敏感,导致性能大幅下降。
在现实世界的应用中,数据往往是不完美的,可能会包含各种各样的干扰。例如,传感器数据可能会受到环境噪声的影响,用户输入可能会包含错误或者不完整的信息,网络数据可能会遭受攻击或篡改。一个鲁棒性强的模型能够更好地应对这些情况,从而在实际应用中表现出更好的性能。
提高模型的鲁棒性通常涉及到以下几个方面:
- 异常值检测和处理:在训练过程中检测和处理异常值,或者在预测时对异常值给予特殊的处理。
- 数据清洗:在训练之前对数据进行清洗,去除噪声和不相关信息。
- 正则化:在模型训练中使用正则化技术,如L1或L2正则化,可以减少模型的复杂度,提高其对噪声的鲁棒性。
- 模型结构选择:选择适当的模型结构,例如使用集成学习方法或者决策树,这些模型往往具有更好的鲁棒性。
- 对抗训练:这是一种在训练过程中包括故意设计的对抗样本(adversarial examples)的方法,从而使模型能够学习到这些干扰,并提高其在面对这类攻击时的鲁棒性。
- 稳健性度量:在模型评估时使用鲁棒性度量,例如最小化最大误差(Minimum Maximum Error, MME)或者均方误差(Mean Squared Error, MSE)等。
鲁棒性的提高通常需要在模型的设计、训练和评估等多个阶段进行综合考虑和优化。