本文介绍: 深入探索K-最近邻居(KNN)算法的全面指南。从基本原理、优点到Python中的实际实现,本指南涵盖一切。无论您是渴望掌握这一概念的初学者还是寻求复习的经验丰富的专家,这篇文章都非常适合您。

(x2x1)2+(y2y1)2+(z2z1)2+(a2a1)2)

例子:

from sklearn.neighbors import KNeighborsClassifier


# 使用欧几里得距离
knn_euclidean = KNeighborsClassifier(n_neighbors=5, metric='euclidean')
knn_euclidean.fit(X, y)

曼哈顿距离

曼哈顿距离 (Manhattan Distance) 是通过计算在一个格子形状路径上从第一个点到另一个点的总距离, 及沿轴绝对差值的总和.

在这里插入图片描述
计算公式:

m

a

n

h

a

t

t

a

n
  

d

i

s

t

a

n

c

e

=

x

1

x

2

+

y

1

y

2

manhattan ; distance = |x_1 – x_2| + |y_1 – y_2|

manhattandistance=x1x2+y1y2

同理, 如果我们有多个特征, 只需扩展上述公式:

  • 2 个特征:

    m

    a

    n

    h

    a

    t

    t

    a

    n
      

    d

    i

    s

    t

    a

    n

    c

    e

    =

    x

    1

    x

    2

    +

    y

    1

    y

    2

    manhattan ; distance = |x_1 – x_2| + |y_1 – y_2|

    manhattandistance=x1x2+y1y2$

  • 3 个特征:

    m

    a

    n

    h

    a

    t

    t

    a

    n
      

    d

    i

    s

    t

    a

    n

    c

    e

    =

    x

    1

    x

    2

    +

    y

    1

    y

    2

    +

    z

    1

    z

    2

    manhattan ; distance = |x_1 – x_2| + |y_1 – y_2| + |z_1 – z_2|

    manhattandistance=x1x2+y1y2+z1z2

  • 4 个特征:

    m

    a

    n

    h

    a

    t

    t

    a

    n
      

    d

    i

    s

    t

    a

    n

    c

    e

    =

    x

    1

    x

    2

    +

    y

    1

    y

    2

    +

    z

    1

    z

    2

    +

    a

    1

    a

    2

    manhattan ; distance = |x_1 – x_2| + |y_1 – y_2| + |z_1 – z_2| + |a_1 – a_2|

    manhattandistance=x1x2+y1y2+z1z2+a1a2

例子:

from sklearn.neighbors import KNeighborsClassifier

# 使用曼哈顿距离
knn_manhattan = KNeighborsClassifier(n_neighbors=5, metric='manhattan')
knn_manhattan.fit(X, y)

余弦相似度

余弦相似度 (Cosine Similarity) 是通过计算两个向量的夹角来判断相似度. 当我们遇到需要数据之间的方向而不是绝对距离的时候会用到余弦相似度. 举个例子, 在文本分析中判断文本的相似度 (距离).

选择合适的 K 值

K 值的选择对 K 近邻算法来说非常重要. K 值决定了算法需要考虑的 “邻居” 的数量. 如果 k=1, 意味着只用一个 “邻居” 对结果进行预测, 容易造成过度拟合问题; 如果 k 的取值过大, 意味着不那么相似的 “邻居” 也会影响模型的判断.

举个栗子, 当我们到了一个陌生的城市, 想找一个地方吃饭:

  • k=1 (考虑一个最近的邻居): 我们只问了一个路过的人他最喜欢的餐馆. 有概率这个人可能喜欢偏僻, “味道独特” 的小店, 然而大概率我们并不喜欢. 由于我们参考了一个人的意见 (k=1), 所以我们可能错过了该城市真正受欢迎的餐馆. 同理, 当我们选择的 k 值过小的时候, 模型就会收到各别异常意见的影响, 过度拟合.
  • k=∞ (考虑所有邻居): 我们问变了每个路过的人, 得到了上百条建议, 然后选择了大多数人都提到的餐馆 (沙县小吃, 兰州牛肉面). 这可能是一个非常普通和大众的连锁店, 但不会是一个你想要的地方特色餐馆. 同理, 当我们选择的 k 值过大的时候, 和你不那么相近的 “邻居” 也会影响模型, 导致模型过度简化 (欠拟合).

奇数 vs 偶数

选择奇数作为 k 值可以避在二分类中平局的情况. 例如一个二分类问题, k=2 时, 俩邻居意见不同.

通过交叉验证选择 k 值

from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier


# 假设我们有数据 X, y
best_score = 0
best_k = 1
for k in range(1, 31):
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X, y, cv=10)
    mean_score = scores.mean()
    if mean_score > best_score:
        best_score = mean_score
        best_k = k

print(f"Best k value: {best_k}")

实战

分类问题

我们拿鸢尾花数据集来带大家实践一下 KNN 算法. 鸢尾花数据集包三种鸢尾花, 各50组数据构成的数据集. 每个样本包含 4 个特征, 分别为萼片 (sepals) 的长和宽, 花瓣 (petals) 的长和宽. 通过 K 近邻算法, 我们可以来预测花的种类.

例子:

"""
@Module Name: knn分类.py
@Author: CSDN@我是小白呀
@Date: October 16, 2023

Description:
使用 K近邻算法对鸢尾花进行分类
"""
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, recall_score, f1_score

# 加载数据集
iris = load_iris()
X = iris.data
y = iris.target

# 调试输出数据基本信息
print("输出特征:", X[:5])
print("输出标签:", y[:5])

# 分割数据集
X_train, X_vaild, y_train, y_vaild = train_test_split(X, y, test_size=0.2)

# 实例化模型
knn = KNeighborsClassifier(n_neighbors=3)

# 训练模型
knn.fit(X_train, y_train)

# 预测
y_pred = knn.predict(X_vaild)

# 评估指标
print("精度:", accuracy_score(y_vaild, y_pred))
print("召回率:", recall_score(y_vaild, y_pred, average='macro'))  # 多分类问题使用宏平均
print("F1分数:", f1_score(y_vaild, y_pred, average='macro'))  # 多分类问题使用宏平均

输出结果:

输出特征: [[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]
输出标签: [0 0 0 0 0]
精度: 0.9
召回率: 0.8777777777777779
F1分数: 0.8656126482213438

我们进一步优化一下以上代码:

"""
@Module Name: knn分类.py
@Author: CSDN@我是小白呀
@Date: October 16, 2023

Description:
使用 K近邻算法对鸢尾花进行分类
"""
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report
from matplotlib import pyplot as plt
plt.style.use("fivethirtyeight")

# 加载数据集
iris = load_iris()
X = iris.data
y = iris.target

# 调试输出数据基本信息
print("输出特征:", X[:5])
print("输出标签:", y[:5])

# 分割数据集
X_train, X_vaild, y_train, y_vaild = train_test_split(X, y, test_size=0.2)

# 通过交叉验证选择k值
k_value_score = []

for k in range(1, 31):
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X, y, cv=10)
    mean_score = scores.mean()
    k_value_score.append(mean_score)

# 绘图
plt.figure(figsize=(12, 8))
plt.plot([i for i in range(1, 31)], k_value_score)
plt.xlabel('Value of K for KNN')
plt.ylabel('Score')
plt.show()

# 实例化模型
knn = KNeighborsClassifier(n_neighbors=12)

# 训练模型
knn.fit(X_train, y_train)

# 预测
y_pred = knn.predict(X_vaild)

# 评估指标
print(classification_report(y_vaild, y_pred))

K 取值
在 k=12 的时候, 模型预测结果最优.

输出结果:

输出特征: [[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]
输出标签: [0 0 0 0 0]
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        10
           1       0.90      1.00      0.95         9
           2       1.00      0.91      0.95        11

    accuracy                           0.97        30
   macro avg       0.97      0.97      0.97        30
weighted avg       0.97      0.97      0.97        30

回归问题

波士顿房价数据集是一个经典的回归数据集, 其中包含了波士顿各个城区的房屋中位数价格与其他相关特征, 如犯罪率, 教育水平等. 使用K近邻回归, 我们可以预测新的城区的房价.

代码:

"""
@Module Name: knn回归.py
@Author: CSDN@我是小白呀
@Date: October 16, 2023

Description:
使用 K近邻算法预测波士顿房价
"""
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error

# 加载数据集
boston = load_boston()
X = boston.data
y = boston.target

# 调试输出数据基本信息
print("输出特征:", X[:5])
print("输出标签:", y[:5])

# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# K 近邻回归
knn_reg = KNeighborsRegressor(n_neighbors=5)
knn_reg.fit(X_train, y_train)

# 预测
y_pred = knn_reg.predict(X_test)
print("平方差:", mean_squared_error(y_test, y_pred))

输出结果:

输出特征: [[6.3200e-03 1.8000e+01 2.3100e+00 0.0000e+00 5.3800e-01 6.5750e+00
  6.5200e+01 4.0900e+00 1.0000e+00 2.9600e+02 1.5300e+01 3.9690e+02
  4.9800e+00]
 [2.7310e-02 0.0000e+00 7.0700e+00 0.0000e+00 4.6900e-01 6.4210e+00
  7.8900e+01 4.9671e+00 2.0000e+00 2.4200e+02 1.7800e+01 3.9690e+02
  9.1400e+00]
 [2.7290e-02 0.0000e+00 7.0700e+00 0.0000e+00 4.6900e-01 7.1850e+00
  6.1100e+01 4.9671e+00 2.0000e+00 2.4200e+02 1.7800e+01 3.9283e+02
  4.0300e+00]
 [3.2370e-02 0.0000e+00 2.1800e+00 0.0000e+00 4.5800e-01 6.9980e+00
  4.5800e+01 6.0622e+00 3.0000e+00 2.2200e+02 1.8700e+01 3.9463e+02
  2.9400e+00]
 [6.9050e-02 0.0000e+00 2.1800e+00 0.0000e+00 4.5800e-01 7.1470e+00
  5.4200e+01 6.0622e+00 3.0000e+00 2.2200e+02 1.8700e+01 3.9690e+02
  5.3300e+00]]
输出标签: [24.  21.6 34.7 33.4 36.2]
平方差: 47.335607843137254

K 近邻算法的优缺点

优点

缺点

  • 计算密集型: 由于算法需要每个新的数据搜索 k 个最近值, 因此在大数据计算成本直线上
  • 对不平衡数据敏感: 在数据集中, 如果某个类的数据量大于另一个类, 那么数据很可能会分类为该大类

手把手实现 k 近邻

为了帮助大家更好的理解, 小白我带领大家手把手实现一下 knn 算法.

手搓算法

手搓 KNN:

"""
@Module Name: 手把手实现knn.py
@Author: CSDN@我是小白呀
@Date: October 16, 2023

Description:
手把手实现knn算法
"""
class KNN:
    def __init__(self, k=3):
        """
        初始化参数
        :param k: k值, 默认为 3
        """
        self.k = k
        self.X_train = None
        self.y_train = None

    def fit(self, X_train, y_train):
        """
        为训练集 / 测试集成赋值
        :param X_train: 训练集
        :param y_train: 测试集
        :return:
        """
        self.X_train = X_train
        self.y_train = y_train

    def predict(self, X_test):
        """
        预测
        :param X_test: 训练特征集
        :return: 预测值
        """
        y_pred = [self._predict(x) for x in X_test]
        return y_pred

    def _predict(self, x):
        """
        预测
        :param x: 需要预测的数据
        :return: 预测标签
        """
        # 计算距离
        distances = [self._euclidean_distance(x, x_train) for x_train in self.X_train]

        # 得到 k 个最近邻的索引
        k_indices = sorted(range(len(distances)), key=lambda i: distances[i])[:self.k]

        # k个最近邻的标签
        k_nearest_labels = [self.y_train[i] for i in k_indices]

        # 投票
        most_common = self._vote(k_nearest_labels)

        # 返回标签
        return most_common

    def _euclidean_distance(self, x1, x2):
        """
        计算欧式距离
        :param x1: 数据1
        :param x2: 数据2
        :return: 距离
        """
        return sum((xi - xj) ** 2 for xi, xj in zip(x1, x2)) ** 0.5

    def _vote(self, labels):
        # 使用字典统计每个类别的票数
        votes = {}
        for label in labels:
            if label in votes:
                votes[label] += 1
            else:
                votes[label] = 1

        # 根据票数排序返回得票数最多的类别
        return sorted(votes.items(), key=lambda x: x[1], reverse=True)[0][0]

实战分类

用手搓 KNN 进行鸢尾花分类:

"""
@Module Name: 手把手实现knn.py
@Author: CSDN@我是小白呀
@Date: October 16, 2023

Description:
手把手实现knn算法
"""
class KNN:
    def __init__(self, k=3):
        """
        初始化参数
        :param k: k值, 默认为 3
        """
        self.k = k
        self.X_train = None
        self.y_train = None

    def fit(self, X_train, y_train):
        """
        为训练集 / 测试集成赋值
        :param X_train: 训练集
        :param y_train: 测试集
        :return:
        """
        self.X_train = X_train
        self.y_train = y_train

    def predict(self, X_test):
        """
        预测
        :param X_test: 训练特征集
        :return: 预测值
        """
        y_pred = [self._predict(x) for x in X_test]
        return y_pred

    def _predict(self, x):
        """
        预测
        :param x: 需要预测的数据
        :return: 预测标签
        """
        # 计算距离
        distances = [self._euclidean_distance(x, x_train) for x_train in self.X_train]

        # 得到 k 个最近邻的索引
        k_indices = sorted(range(len(distances)), key=lambda i: distances[i])[:self.k]

        # k个最近邻的标签
        k_nearest_labels = [self.y_train[i] for i in k_indices]

        # 投票
        most_common = self._vote(k_nearest_labels)

        # 返回标签
        return most_common

    def _euclidean_distance(self, x1, x2):
        """
        计算欧式距离
        :param x1: 数据1
        :param x2: 数据2
        :return: 距离
        """
        return sum((xi - xj) ** 2 for xi, xj in zip(x1, x2)) ** 0.5

    def _vote(self, labels):
        # 使用字典统计每个类别的票数
        votes = {}
        for label in labels:
            if label in votes:
                votes[label] += 1
            else:
                votes[label] = 1

        # 根据票数排序返回得票数最多的类别
        return sorted(votes.items(), key=lambda x: x[1], reverse=True)[0][0]

if __name__ == '__main__':

    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import classification_report

    # 加载数据集
    iris = load_iris()
    X = iris.data
    y = iris.target

    # 调试输出数据基本信息
    print("输出特征:", X[:5])
    print("输出标签:", y[:5])

    # 分割数据集
    X_train, X_vaild, y_train, y_vaild = train_test_split(X, y, test_size=0.2)

    # 实例化模型
    knn = KNN(12)

    # 训练模型
    knn.fit(X_train, y_train)

    # 预测
    y_pred = knn.predict(X_vaild)

    # 评估指标
    print(classification_report(y_vaild, y_pred))

输出结果:

输出特征: [[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]]
输出标签: [0 0 0 0 0]
              precision    recall  f1-score   support

           0       1.00      1.00      1.00         8
           1       0.83      1.00      0.91        10
           2       1.00      0.83      0.91        12

    accuracy                           0.93        30
   macro avg       0.94      0.94      0.94        30
weighted avg       0.94      0.93      0.93        30

原文地址:https://blog.csdn.net/weixin_46274168/article/details/133870683

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_5313.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注