K近邻算法(KNN)

KNN算法

KNN算法原理

  • K近邻(K-nearst neighbors, KNN)是一种基本的机器学习算法,所谓k近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表
  • 比如:判断一个人的人品,只需要观察与他来往最密切的几个人的人品好坏就可以得出,即“近朱者赤,近墨者黑”;
  • KNN算法既可以应用于 分类应用中,也可以应用在回归应用中。
  • KNN在做回归和分类的主要区别在于最后做预测的时候的决策方式不同。
  • KNN在分类预测时,一般采用多数表决法;而在做回归预测时,一般采用平均值法。
  1. 训练集合中获取K个离待预测样本距离最近的样本数据。【欧氏距离】
  2. 根据获取得到的K个样本数据来预测当前待预测样本的目标属性值。

KNN三要素

在KNN算法中,非常重要的主要是三个因素:

  • K值的选择:对于K值的选择,一般根据样本分布选择一个较小的值,然后通过交叉验证来选择一个比较合适的最终值;当选择比较小的K值的时候,表示使用较小领域中的样本进行预测,训练误差会减小,但是会导致模型变得复杂,容易过拟合;当选择较大的K值的时候,表示使用较大领域中的样本进行预测,训练误差会增大,同时会使模型变得简单,容易导致欠拟合
  • 距离的度量:一般使用欧氏距离(欧几里得距离);
  • 决策规则:在分类模型中,主要使用多数表决法或者加权多数表决法;在回归模型中,主要使用平均值法或者加权平均值法。

KD-Tree

用于查找最近的邻居
KDTree(K-Dimensional Tree)是一种用于高效处理多维数据的数据结构,常用于 K 最近邻(K-Nearest Neighbors,KNN)算法中。

KDTree构建过程

KDTree 将数据点存储在一个二叉树结构中,以便于快速的最近邻搜索。它的构建过程如下:

  1. 选择一个维度作为划分的依据。一般选择方差最大的维度,以便更好地划分数据空间。
  2. 在选择的维度上找到数据中值的中位数,将其作为分割点
  3. 将数据分割成两个子集,左子集包含小于等于分割点的数据,右子集包含大于分割点的数据。
  4. 递归地构建左子树和右子树,重复上述步骤,直到每个子树中只有一个数据点或达到停止条件(例如,子树中的数据点数小于某个阈值)。

构建完成后,KDTree 数据结构将数据点按照树状结构进行组织。这样,在进行最近邻搜索时,可以通过比较数据点在当前维度上的值和分割点的值,选择适当的子树进行搜索,从而减少搜索的范围和复杂度。

KDTree最近邻搜索--具体步骤
在 KNN 算法中,使用 KDTree 可以提高最近邻搜索的效率。具体步骤如下:

  1. 构建 KDTree,将训练集中的数据点存储在树结构中。
  2. 对于每个测试样本,通过 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的训练是一个伪训练过程,仅仅是一个保存数据的过程

你可能感兴趣的:(K近邻算法(KNN))