KNN是一种可以用于分类和回归任务的算法。

分类

首先我们先看一下knn用在分类任务上。
我们的题目是:用一个的身高个体重去预测性别,一个二元分类问题。

数据可视化

首先让我们看一下训练样本数据。

身高(单位:cm) 体重(单位:kg) 标签
158 64 男性
170 86 男性
183 84 男性
191 80 男性
155 49 女性
163 59 女性
180 67 女性
158 54 女性
170 67 女性

同样,我们还是先对数据进行可视化
Figure_1.png
代码如下

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 中文显示
  4. def datas_plt():
  5. X_train = np.array([[158, 64],
  6. [170, 86],
  7. [183, 84],
  8. [191, 80],
  9. [155, 49],
  10. [163, 59],
  11. [180, 67],
  12. [158, 54],
  13. [170, 67]])
  14. y_train = np.array(["male", "male", "male", "male", "female",
  15. "female", "female", "female", "female"])
  16. plt.figure()
  17. # 使用'x'来标记训练数据中的男性,使用菱形来标记训练数据中的女性
  18. for i, v in enumerate(X_train):
  19. plt.scatter(v[0], v[1], c='k',
  20. marker='x' if y_train[i] == "male" else 'D')
  21. plt.title("分性别人类身高体重图")
  22. plt.xlabel("身高(单位:cm)")
  23. plt.ylabel("体重(单位:kg)")
  24. plt.grid(True)
  25. plt.show()
  26. if __name__ == '__main__':
  27. datas_plt()

举例

下面,假设我们有一个身高是155cm,体重是70kg的网友,我们想知道是小哥哥还是小姐姐?根据KNN算法的原理,我们首先确定一个距离衡量方案,这里我们选用欧几里得距离(欧几里得空间中两点之间的直线距离)。
在二维空间中欧几里得距离计算公式如下所示:
第三章 KNN模型的分类和回归 - 图2
知道公式后让我们来计算一下网友和我们的训练数据之前的距离😜

身高 体重 标签 和网友的距离
158 64 男性 6.71
170 86 男性 21.93
183 84 男性 31.30
191 80 男性 37.36
155 49 女性 21.00
163 59 女性 13.60
180 67 女性 25.18
158 54 女性 16.28
170 67 女性 24.04

假设我们设置KNN算法的参数k为3,下面我们从上面选取3个最近的训练数据距离,并查看他们的性别。他们依次是6.71(男性)、13.60(女性)、16.28(女性)。可以看出两个邻居是女性,一个是邻居是男性,因此我们推测我们的网友是小姐姐😍。
上述操作,代码实现方式如下:

  1. import numpy as np
  2. from collections import Counter
  3. def main():
  4. X_train = np.array([[158, 64],
  5. [170, 86],
  6. [183, 84],
  7. [191, 80],
  8. [155, 49],
  9. [163, 59],
  10. [180, 67],
  11. [158, 54],
  12. [170, 67]])
  13. y_train = np.array(["male", "male", "male", "male", "female",
  14. "female", "female", "female", "female"])
  15. X_test = np.array([[155, 70]])
  16. distances = np.sqrt(np.sum(np.square(X_train-X_test), axis=1))
  17. nearest_neighbor_indices = distances.argsort() # [0 5 8 7 4 1 6 2 3]
  18. nearest_neighbor_indices = nearest_neighbor_indices[:3] # k=3,这里我们只取前三个索引
  19. nearest_neighbor_genders = np.take(y_train, nearest_neighbor_indices) # ['male' 'female' 'female']
  20. nearest_neighbor_sex = Counter(nearest_neighbor_genders)
  21. nearest_neighbor_sex = nearest_neighbor_sex.most_common() # [('female', 2), ('male', 1)]
  22. nearest_neighbor_sex = nearest_neighbor_sex[0][0] # 'female
  23. return nearest_neighbor_sex
  24. if __name__ == '__main__':
  25. pre_sex = main()

好了,回归正题,在上面我们用数学公式的方式预测出了我们的网友是位小姐姐, 下面我们用sklearn的API来预测一下。

  1. import numpy as np
  2. from sklearn.preprocessing import LabelBinarizer
  3. from sklearn.neighbors import KNeighborsClassifier
  4. def main():
  5. X_train = np.array([[158, 64],
  6. [170, 86],
  7. [183, 84],
  8. [191, 80],
  9. [155, 49],
  10. [163, 59],
  11. [180, 67],
  12. [158, 54],
  13. [170, 67]])
  14. y_train = np.array(["male", "male", "male", "male", "female",
  15. "female", "female", "female", "female"])
  16. X_test = np.array([[155, 70]])
  17. lb = LabelBinarizer()
  18. # 将label从字符串映射成整型
  19. y_train = lb.fit_transform(y_train) # [[1], [1], [1], [1], [0], [0], [0], [0], [0]]
  20. knn_model = KNeighborsClassifier(n_neighbors=3)
  21. knn_model.fit(X_train, y_train.reshape(-1))
  22. pred_y = knn_model.predict(X_test) # [0]
  23. pred_sex = lb.inverse_transform(pred_y)[0] # 'female'
  24. return pred_sex
  25. if __name__ == '__main__':
  26. pre_sex = main()

模型预测和指标统计

现在我们有一个测试数据,就让我们来预测一下他们的结果吧,并测试一下模型的好坏。
在开始之前,我们先来介绍一下分类模型的评估指标

  • 准确率:测试数据中被正确分类的比率
  • 精准率:正向测试数据被预测为正向的比率
  • 召回率:真实为正向的测试数据被预测为正向的比率
  • F1得分:精准率和召回率的算术平均值
  • MCC(马修斯相关系数),一个完美的分类模型的MCC为1,随机预测的模型MCC为0,完全预测错误的模型MCC为-1,即使测试数据的类别比例不平衡,MCC得分也非常有用。

测试数据:

身高(单位:cm) 体重(单位:kg) 标签
168 65 男性
180 96 男性
160 52 女性
169 67 女性

测试结果

  1. import numpy as np
  2. from sklearn.preprocessing import LabelBinarizer
  3. from sklearn.neighbors import KNeighborsClassifier
  4. from sklearn.metrics import accuracy_score, precision_score, \
  5. recall_score, f1_score, matthews_corrcoef, classification_report
  6. def main():
  7. X_train = np.array([[158, 64],
  8. [170, 86],
  9. [183, 84],
  10. [191, 80],
  11. [155, 49],
  12. [163, 59],
  13. [180, 67],
  14. [158, 54],
  15. [170, 67]])
  16. y_train = np.array(["male", "male", "male", "male", "female",
  17. "female", "female", "female", "female"])
  18. X_test = np.array([[168, 65],
  19. [180, 96],
  20. [160, 52],
  21. [169, 67]])
  22. y_test = np.array(["male", "male", "female", "female"])
  23. lb = LabelBinarizer()
  24. # 将label从字符串映射成整型
  25. y_train = lb.fit_transform(y_train) # [[1], [1], [1], [1], [0], [0], [0], [0], [0]]
  26. knn_model = KNeighborsClassifier(n_neighbors=3)
  27. knn_model.fit(X_train, y_train.reshape(-1))
  28. pred_y = knn_model.predict(X_test)
  29. # 预测结果
  30. pred_sex = lb.inverse_transform(pred_y) # ['female' 'male' 'female' 'female']
  31. y_test = lb.transform(y_test).reshape(-1)
  32. ################################模型评估########################################
  33. # acc score
  34. acc_score = accuracy_score(y_test, pred_y)
  35. # precison score
  36. prec_score = precision_score(y_test, pred_y)
  37. # recall score
  38. rec_score = recall_score(y_test, pred_y)
  39. # f1 score
  40. f1score = f1_score(y_test, pred_y)
  41. # mcc score
  42. mcc_score = matthews_corrcoef(y_test, pred_y)
  43. print(f"accuracy_score:{acc_score}\n"
  44. f"precision_score:{prec_score}\n"
  45. f"recall_score:{rec_score}\n"
  46. f"f1_score:{f1score}\n"
  47. f"matthews_corrcoef:{mcc_score}")
  48. """
  49. accuracy_score:0.75
  50. precision_score:1.0
  51. recall_score:0.5
  52. f1_score:0.6666666666666666
  53. matthews_corrcoef:0.5773502691896258
  54. """
  55. print(classification_report(y_test, pred_y, labels=[0, 1], target_names=['female', 'male']))
  56. return pred_sex
  57. if __name__ == '__main__':
  58. pre_sex = main()

可以看出最后预测结果为

身高(单位:cm) 体重(单位:kg) 标签 预测结果
168 65 男性 女性
180 96 男性 男性
160 52 女性 女性
169 67 女性 女性

回归

上面讲完分类,现在唠一唠回归任务。
任务是用一个人的身高和性别来预测他的体重
下面让我们来看一下训练和测试数据。
训练数据

身高(单位:cm) 性别 体重(单位:kg)
158 男性 64
170 男性 66
183 男性 84
191 男性 80
155 女性 49
163 女性 59
180 女性 67
158 女性 54
178 女性 77

测试数据

身高(单位:cm) 性别 体重(单位:kg)
168 男性 65
170 男性 61
160 女性 52
169 女性 67

任务指标

回归任务模型评估指标-MAE(平均绝对误差)、MSE(均方误差)
MAE是预测结果误差绝对值的均值。计算方法如下:
第三章 KNN模型的分类和回归 - 图3
MSE是预测结果误差平方的均值,MSE又被称为均方偏差,比起MAE来说是一种更常见的指标。
第三章 KNN模型的分类和回归 - 图4

MAE和MSE分别通过对误差求平方和求绝对值来衡量模型。对一个较大的误差值求平方会加大它对整体误差的贡献比例,因此MSE比MAE对于异常值的惩罚程度要高。一般来说,MSE是线性回归任务的首选。

模型训练

我们讲完了模型的评估指标,现在我们准备训练模型。不过在训练之前,我们要先处理一下数据,主要包括两个方面:离散特征处理数据标准化
sklearn 库里的StandardScaler类常用来用作特征缩放。它能确保所有的特征都有单位方差。它首先将所有实例特征减去均值将其居中,其次将每个实例特征值除以特征的标准差来对其进行缩放。均值为0,方差为1的数据称为标准化数据

  1. import numpy as np
  2. from sklearn.preprocessing import LabelBinarizer, StandardScaler
  3. from sklearn.neighbors import KNeighborsRegressor
  4. from sklearn.metrics import mean_squared_error, mean_absolute_error
  5. def main():
  6. X_train = np.array([[158, "male"],
  7. [170, "male"],
  8. [183, "male"],
  9. [191, "male"],
  10. [155, "female"],
  11. [163, "female"],
  12. [180, "female"],
  13. [158, "female"],
  14. [170, "female"]])
  15. y_train = np.array([64, 86, 84, 80, 49,
  16. 59, 67, 54, 67])
  17. X_test = np.array([[168, "male"],
  18. [180, "male"],
  19. [160, "female"],
  20. [169, "female"]])
  21. y_test = np.array([65, 96, 52, 67])
  22. lb = LabelBinarizer() # 离散数据处理实例化类
  23. ss = StandardScaler() # 标准化
  24. X_train[:, 1] = lb.fit_transform(X_train[:, 1]).reshape(-1)
  25. X_train[:, 0] = ss.fit_transform(X_train[:, 0].reshape(-1, 1)).reshape(-1) # 只处理身高特征
  26. # X_train = ss.fit_transform(X_train) # 全部特征处理
  27. model = KNeighborsRegressor(n_neighbors=3)
  28. model.fit(X_train, y_train)
  29. X_test[:, 1] = lb.transform(X_test[:, 1]).reshape(-1)
  30. X_test[:, 0] = ss.transform(X_test[:, 0].reshape(-1,1)).reshape(-1) # 只处理身高特征
  31. # X_test = ss.transform(X_test) # 全部特征处理
  32. pred_y = model.predict(X_test) # [72.33333333 83.33333333 54. 64.33333333]
  33. mae = mean_absolute_error(y_test, pred_y) # 6.166666666666668
  34. mse = mean_squared_error(y_test, pred_y) # 56.33333333333336
  35. return pred_y
  36. if __name__ == '__main__':
  37. pre_weight = main()