KNN算法
KNN算法原理
- K近邻(K-nearst neighbors, KNN)是一种基本的机器学习算法,所谓k近邻,就是k个最近的邻居的意思,说的是
每个样本都可以用它最接近的k个邻居来代表
。 - 比如:判断一个人的人品,只需要观察与他来往最密切的几个人的人品好坏就可以得出,即“近朱者赤,近墨者黑”;
- KNN算法既可以应用于
分类
应用中,也可以应用在回归
应用中。 - KNN在做回归和分类的主要区别在于最后做预测的时候的决策方式不同。
- KNN在分类预测时,一般采用多数表决法;而在做回归预测时,一般采用平均值法。
- 从
训练集合
中获取K个离待预测样本
距离最近的样本数据。【欧氏距离】 - 根据获取得到的K个样本数据来预测当前待预测样本的目标属性值。
KNN三要素
在KNN算法中,非常重要的主要是三个因素:
- K值的选择:对于K值的选择,一般根据样本分布选择一个较小的值,然后通过交叉验证来选择一个比较合适的最终值;当选择
比较小的K值
的时候,表示使用较小领域
中的样本进行预测,训练误差会减小
,但是会导致模型变得复杂
,容易过拟合
;当选择较大的K值
的时候,表示使用较大领域
中的样本进行预测,训练误差会增大
,同时会使模型变得简单
,容易导致欠拟合
; - 距离的度量:一般使用欧氏距离(欧几里得距离);
- 决策规则:在分类模型中,主要使用多数表决法或者加权多数表决法;在回归模型中,主要使用平均值法或者加权平均值法。
KD-Tree
用于查找最近的邻居
KDTree(K-Dimensional Tree)是一种用于高效处理多维数据的数据结构,常用于 K 最近邻(K-Nearest Neighbors,KNN)算法中。
KDTree构建过程
KDTree 将数据点存储在一个二叉树结构中,以便于快速的最近邻搜索。它的构建过程如下:
- 选择一个维度作为划分的依据。一般选择
方差最大的维度
,以便更好地划分数据空间。 - 在选择的维度上找到数据中值的
中位数
,将其作为分割点
。 - 将数据分割成
两个子集
,左子集包含小于等于分割点的数据,右子集包含大于分割点的数据。 -
递归
地构建左子树和右子树,重复上述步骤,直到每个子树中只有一个数据点或达到停止条件(例如,子树中的数据点数小于某个阈值)。
构建完成后,KDTree 数据结构将数据点按照树状结构进行组织。这样,在进行最近邻搜索
时,可以通过比较数据点在当前维度上的值和分割点的值,选择适当的子树进行搜索,从而减少搜索的范围和复杂度。
KDTree最近邻搜索--具体步骤
在 KNN 算法中,使用 KDTree 可以提高最近邻搜索的效率。具体步骤如下:
- 构建 KDTree,将训练集中的数据点存储在树结构中。
- 对于每个测试样本,通过 KDTree 进行最近邻搜索:
- 从根节点开始,根据测试样本在当前维度上的值与分割点的关系,选择左子树或右子树进行搜索。
- 递归地向下搜索,直到叶节点。
- 在搜索过程中,
维护一个优先级队列
,保存距离最近的 K 个邻居
。 - 如果当前节点的距离比队列中的最远距离更近,更新队列。
- 在叶节点中,返回队列中的 K 个最近邻居。
代码示例
import numpy as np
# ## KNN加权投票--分类
# #初始化数据
T = [
[3, 104, -1],
[2, 100, -1],
[1, 81, -1],
[101, 10, 1],
[99, 5, 1],
[98, 2, 1]]
# #初始化待测样本
x = [18, 90]
# x = [3,104]
# x = [50, 50]
# #初始化邻居数
K = 3
# #初始化存储距离列表[[距离1,标签1],[距离2,标签2]....]
listDistance = []
# #循环每一个数据点,把计算结果放入dis
for i in T:
dis = np.sum((np.array(i[:-1]) - np.array(x)) ** 2) ** 0.5 ##欧氏距离
listDistance.append([dis, i[-1]])
# #对dis按照距离排序
print("距离列表 : {}".format(listDistance))
listDistance.sort()
print("排序后距离列表 : {}".format(listDistance))
# 权重 & 归一化
weight = [1/i[0] for i in listDistance[:K]]
print("权重:{}".format(weight))
# weight /= sum(weight)
# print("权重归一化:{}".format(weight))
# 分类
# sum(权重 * 标签)
# 这里的权重没有归一化
pre = -1 if sum([1 / i[0] * i[1] for i in listDistance[:K]]) < 0 else 1
print("预测结果:{}".format(pre))
# 将权重归一化
pre = -1 if sum([(1 / i[0])/sum(weight) * i[1] for i in listDistance[:K]]) < 0 else 1
print("预测结果:{}".format(pre))
# 回归
pre = sum([1 / i[0] * i[1] for i in listDistance[:K]])
print(pre)
鸢尾花KNN分类
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier,KNeighborsRegressor
from sklearn.metrics import accuracy_score, recall_score, precision_score
# 1. 加载数据(数据一般存在于磁盘或者数据库)
path = "./iris.data"
names = ['x1', 'x2', 'x3', 'x4', 'y']
df = pd.read_csv(path, header=None, names=names, sep=",")
# 数据文件中第一行不是表头,是数据 --> header=None
# 给数据文件的各列取得名字 names
print(df.head())
# x1 x2 x3 x4 y
# 0 5.1 3.5 1.4 0.2 Iris-setosa
# 1 4.9 3.0 1.4 0.2 Iris-setosa
print(df.shape)
# (150, 5)
print(df['y'].value_counts())
# Iris-setosa 50
# Iris-versicolor 50
# Iris-virginica 50
# Name: y, dtype: int64
# 说明是一个三分类问题
# 2. 数据清洗
# NOTE: 不需要做数据处理
def parse_record(row):
result = []
r = zip(names, row)
for name, value in r:
if name == 'y':
if value == 'Iris-setosa':
result.append(1)
elif value == 'Iris-versicolor':
result.append(2)
elif value == 'Iris-virginica':
result.append(3)
else:
result.append(0)
else:
result.append(value)
return result
df = df.apply(lambda row: pd.Series(parse_record(row), index=names), axis=1)
df['y'] = df['y'].astype(np.int32)
print(df.head())
df.info()
#
# RangeIndex: 150 entries, 0 to 149
# Data columns (total 5 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 x1 150 non-null float64
# 1 x2 150 non-null float64
# 2 x3 150 non-null float64
# 3 x4 150 non-null float64
# 4 y 150 non-null int32
# dtypes: float64(4), int32(1)
# memory usage: 5.4 KB
# 3. 根据需求获取最原始的特征属性矩阵X和目标属性Y
# X = df[names[0:-1]]
X = df.iloc[:,:-1]
Y = df[names[-1]]
# 4. 数据分割
# train_size: 给定划分之后的训练数据的占比是多少,默认0.75
# random_state:给定在数据划分过程中,使用到的随机数种子,默认为None,使用当前的时间戳;给定非None的值,可以保证多次运行的结果是一致的。
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.6, random_state=28)
print("训练数据X的格式:{}, 以及类型:{}".format(x_train.shape, type(x_train)))
print("测试数据X的格式:{}".format(x_test.shape))
print("训练数据Y的类型:{}".format(type(y_train)))
# 5. 特征工程的操作
# NOTE: 不做特征工程
# 6. 模型对象的构建
"""
KNN:
n_neighbors=5,
weights='uniform',
algorithm='auto',
leaf_size=30,
p=2,
metric='minkowski',
metric_params=None,
n_jobs=1
"""
KNN = KNeighborsClassifier(n_neighbors=1, weights='uniform', algorithm='kd_tree')
# 7. 模型的训练
KNN.fit(x_train, y_train)
# 8. 模型效果评估
train_predict = KNN.predict(x_train)
test_predict = KNN.predict(x_test)
print("KNN算法:训练集上的效果(准确率):{}".format(KNN.score(x_train, y_train)))
print("KNN算法:测试集上的效果(准确率):{}".format(KNN.score(x_test, y_test)))
print("accuracy_score(准确率):{}".format(accuracy_score(y_true=y_train, y_pred=train_predict)))
print("accuracy_score(准确率):{}".format(accuracy_score(y_true=y_test, y_pred=test_predict)))
# 模型的保存与加载
import joblib
joblib.dump(KNN, "./knn.m") # 保存模型
# joblib.load() # 加载模型
KNN在训练的时候训练了什么?学习到了什么?
KNN 在训练的时候只是进行了一个存储的操作
如果不使用KDTree,KNN的训练是一个伪训练过程,仅仅是一个保存数据的过程