ML算法——KNN随笔【机器学习】【全国科技工作者日创作】

9、K-Nearest Neighbors (KNN)

9.1、理论部分

K最邻近算法

未知点的判断基于已知点的距离,选出最近的K个点,投票选出未知点的最大可能。

计算两个物体之间的距离/相似度?

欧氏距离

∑ i = 1 n ( x i − x i ) 2 \sqrt{\sum_{i=1}^{n}(x_i^{} - x_i^{})^2} i=1n(xixi)2

其中,xi和xj是空间中的两个点,i和j表示维度。

点数K选取奇数的目的?

偶数更容易出现“平票”,奇数也不可避免地会出现平票(1:1:1)

使用 sklearn 实现,详见9.3。

选择合适的 k 对决策边界的影响?

决策边界:决定线性分类器、非线性分类器。

在这里插入图片描述

图中:

  • 线性
  • 非线性
  • 非线性【最陡峭】,过拟合。

KNN的决策边界举例:

在这里插入图片描述

边界越陡峭,越不稳定,希望得到平滑的边界,理论上,K↑,边界越平滑。

如何选择 K 值?

交叉验证:训练集进一步划分为训练集【train】+验证集【validation】

以常用的五折交叉验证为例,

在这里插入图片描述

  • 对 K= 1执行五次循环,取平均,作为 k= 1的成绩。

  • 换 K ,重复。

  • 千万不能用测试数据【X_test、y_test】来调参

在数据少时,可适当增加折数的合理性?

交叉验证可以通过增加折数来减少主观因素的影响,使得结果更加准确。

主观因素:主要指在进行数据分割的时候,因为某些人为因素导致分割不准确,进而对结果产生影响。比如,如果使用随机拆分数据的方式进行验证,因为随机拆分数据的时候存在不随机的情况,所以就会对验证效果产生影响。

KNN的优化方向

  • 时间复杂度 O(N),适合低维空间内使用,优化方向:KD-tree 在二维空间的优化、LSH(Locality Sensitivity Hashing)不再寻求完全准确的解,加快搜索。
  • 特征间的相关性处理,Mahalanobis Distance

KD-Tree

使用KD树来提高k近邻搜索的效率

平均计算复杂度O(logN)

构造

下面举例子,二维空间数据集如下:

T = ( 2 , 3 ) T , ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 9 ) T , ( 2 , 7 ) T , ( 3 , 6 ) T T={(2,3)^T,(5,4)^T,(9,6)^T,(4,9)^T,(2,7)^T,(3,6)^T} T=(2,3)T,(5,4)T,(9,6)T,(4,9)T,(2,7)T,(3,6)T

过程如下:

先看第一维 T = ( 2 , ) T , ( 5 , ) T , ( 9 , ) T , ( 4 , ) T , ( 2 , ) T , ( 3 , ) T T={(2,)^T,(5,)^T,(9,)^T,(4,)^T,(2,)^T,(3,)^T} T=(2,)T,(5,)T,(9,)T,(4,)T,(2,)T,(3,)T,找出中位数 3 【我这里做的是向下取整,同理也可选4】

由此,(3,) 作为根,两边为左右子树:

左: ( 2 , 3 ) T , ( 2 , 7 ) T {(2,3)^T,(2,7)^T} (2,3)T,(2,7)T

右: ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 9 ) T {(5,4)^T,(9,6)^T,(4,9)^T} (5,4)T,(9,6)T,(4,9)T

同理,在子树上找中位数,这次是第二维度,再分出子树呢?第一维度。。。对的,交替的。

最终结果如下:

在这里插入图片描述

比较

当前点与树中的点比对,一样的,比对一维,选个子树,再下一次比对二维。。。直到到达叶节点。

回溯

在叶结点开始计算 距离 D 叶 D_叶 D,同时,计算父节点与当前节点的距离 D 父 D_父 D,如果 D 叶 < D 父 D_叶 < D_父 D<D,结束【这里讨论的是K=1】

往往是如果 D 叶 > D 父 D_叶 > D_父 D>D,继续计算 D 父 D_父 D另一子树节点以及祖父节点,直到出现 D 当 前节点 < D 父 D_当前节点 < D_父 D前节点<D或者全部计算完为止。

【如果K>1 ,那么 这个过程不变,只是 一直在维护K个最小距离的集合而已。】

9.2、实现

9.2.1、KNN 手写实现

from sklearn import datasets
from collections import Counter # 投票
from sklearn.model_selection import train_test_split
import numpy as np

def euc_dis(instance1, instance2):
    """
    计算欧氏距离
    :param instance1:样本1,array数组
    :param instance2: 样本2,array数组
    :return: 欧氏距离
    """
    d = np.sqrt(sum(instance1-instance2)**2)
    return d

def knn_classify(X , y, testInstance, k):
    """
    给未知点归类
    :param X: train——feature
    :param y: train——label
    :param testInstance:测试数据,array数组
    :param k:选择多少个 neighbors
    :return:最多元素
    """
    distances = [euc_dis(x, testInstance)for x in X]
    kneighbors = np.argsort(distances)[:k] #快排后,取前 k 个  || 优化?改用优先队列,维护前 k 个, O(NlogN)
    count = Counter(y[kneighbors]) # Counter([0,0,0,1,1,1,1,1,1])  -》 Counter({1: 6, 0: 3})
    return count.most_common()[0][0]   # [(1, 6), (0, 3)]  -》 1

#导入 iris 数据
iris = datasets.load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=50)

#预测结果
predictions = [knn_classify(X_train, y_train, data, 3) for data in X_test]
correct = np.count_nonzero((predictions == y_test) == True)
# acc
print("Accuaracy is :%.3f" %(correct/len(X_test)))

9.2.2、KNN sklearn 实现

鸢尾花【三分类问题

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

读取数据 X,y

data = datasets.load_iris()
X = data.data   #N*D:sample * Dimension
y = data.target #label:(0,1,2)

分为训练数据 & 测试数据 (默认比例: 3/4 : 1/4)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=500)

构建KNN模型,K=3 & 训练

clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)

计算准确率(二选一)

方法一

from sklearn.metrics import accuracy_score

acc  = accuracy_score(y_test,clf.predict(X_test))
print(acc)

方法二

import  numpy as np

correct = np.count_nonzero((clf.predict(X_test) == y_test) == True)
print(f'acc is {correct/len(X_test)}')

完整代码

#
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
#
data = datasets.load_iris()
X = data.data   #N*D:sample * Dimension
y = data.target #label:(0,1,2)
#
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=500)
#
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)
#
from sklearn.metrics import accuracy_score

acc  = accuracy_score(y_test,clf.predict(X_test))
print(acc)

KNN没有训练痕迹,那么 clf.fit() 在做什么?

它是机器学习中唯一一个不需要训练过程的算法,它在训练阶段只是把数据保存下来,训练时间开销为 0,等收到测试样本后进行处理。

knn 算法手写实现的意义?较 sklearn 实现的 knn 优势在哪里?

  1. 简单易理解:KNN算法非常简单,易于理解。通过自己实现该算法,可以更深入地了解KNN算法的工作原理。
  2. 可扩展性:自己实现KNN算法可以让你更好地了解如何扩展算法以适应不同的数据集和场景。例如,你可以尝试使用不同的距离度量(如曼哈顿距离或切比雪夫距离),或者调整K值以获得更好的性能。
  3. 性能优化:在大数据集上,KNN算法的计算复杂度较高。通过自己实现该算法,你可以对算法进行优化,例如使用KD树来加速搜索邻居。
  4. 无依赖:自己实现KNN算法可以让你更好地了解算法的内部工作原理,无需依赖外部库。

想更深入地了解KNN算法的工作原理,或者需要对算法进行定制和优化,自己实现KNN算法是有意义的。只是想快速应用到项目上,那么使用sklearn是更好的选择。

9.3、案例

二手车预测案例【回归问题】

import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
#读取数据
df = pd.read_csv('data.csv')

#清洗数据
# 把颜色独热编码
df_colors = df['Color'].str.get_dummies().add_prefix('Color: ')
# 把类型独热编码
df_type = df['Type'].apply(str).str.get_dummies().add_prefix('Type: ')
# 添加独热编码数据列
df = pd.concat([df, df_colors, df_type], axis=1)
# 去除独热编码对应的原始列
df = df.drop(['Brand', 'Type', 'Color'], axis=1)
# 数据转换
matrix = df.corr()
f, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(matrix, square=True)
plt.title('Car Price Variables')

from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.preprocessing import StandardScaler
import numpy as np

X = df[['Construction Year', 'Days Until MOT', 'Odometer']]
y = df['Ask Price'].values.reshape(-1, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=41)

X_normalizer = StandardScaler() # N(0,1) 
X_train = X_normalizer.fit_transform(X_train)
X_test = X_normalizer.transform(X_test)

y_normalizer = StandardScaler()
y_train = y_normalizer.fit_transform(y_train)
y_test = y_normalizer.transform(y_test)

knn = KNeighborsRegressor(n_neighbors=2)
knn.fit(X_train, y_train.ravel())

#Now we can predict prices:
y_pred = knn.predict(X_test)
y_pred_inv = y_normalizer.inverse_transform(y_pred)
y_test_inv = y_normalizer.inverse_transform(y_test)

# Build a plot
plt.scatter(y_pred_inv, y_test_inv)
plt.xlabel('Prediction')
plt.ylabel('Real value')

# Now add the perfect prediction line
diagonal = np.linspace(500, 1500, 100)
plt.plot(diagonal, diagonal, '-r')
plt.xlabel('Predicted ask price')
plt.ylabel('Ask price')
plt.show()

print(y_pred_inv)
  • StandardScaler(),特征缩放函数
  • fit_transform(),根据给定数据集的特点来调整模型的参数,同时可以对数据进行转换
  • inverse_transform(),在scikit-learn中,转换回原始数据并不是通过计算数据中的协方差矩阵和特征向量来实现的

KNN如何解决回归问题的?

KNN用于回归问题时,模型从训练数据集中选择离该数据点最近的k个数据点,并且把这些数据的y值取均值,把求出的这个均值作为新数据点的预测值。【对应:分类中投票高者做结果】

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来杯Sherry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值