分类
首先我们先看一下knn用在分类任务上。
我们的题目是:用一个的身高个体重去预测性别,一个二元分类问题。
数据可视化
首先让我们看一下训练样本数据。
| 身高(单位:cm) | 体重(单位:kg) | 标签 |
|---|---|---|
| 158 | 64 | 男性 |
| 170 | 86 | 男性 |
| 183 | 84 | 男性 |
| 191 | 80 | 男性 |
| 155 | 49 | 女性 |
| 163 | 59 | 女性 |
| 180 | 67 | 女性 |
| 158 | 54 | 女性 |
| 170 | 67 | 女性 |
同样,我们还是先对数据进行可视化
代码如下
import numpy as npimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 中文显示def datas_plt():X_train = np.array([[158, 64],[170, 86],[183, 84],[191, 80],[155, 49],[163, 59],[180, 67],[158, 54],[170, 67]])y_train = np.array(["male", "male", "male", "male", "female","female", "female", "female", "female"])plt.figure()# 使用'x'来标记训练数据中的男性,使用菱形来标记训练数据中的女性for i, v in enumerate(X_train):plt.scatter(v[0], v[1], c='k',marker='x' if y_train[i] == "male" else 'D')plt.title("分性别人类身高体重图")plt.xlabel("身高(单位:cm)")plt.ylabel("体重(单位:kg)")plt.grid(True)plt.show()if __name__ == '__main__':datas_plt()
举例
下面,假设我们有一个身高是155cm,体重是70kg的网友,我们想知道是小哥哥还是小姐姐?根据KNN算法的原理,我们首先确定一个距离衡量方案,这里我们选用欧几里得距离(欧几里得空间中两点之间的直线距离)。
在二维空间中欧几里得距离计算公式如下所示:
知道公式后让我们来计算一下网友和我们的训练数据之前的距离😜
| 身高 | 体重 | 标签 | 和网友的距离 |
|---|---|---|---|
| 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(女性)。可以看出两个邻居是女性,一个是邻居是男性,因此我们推测我们的网友是小姐姐😍。
上述操作,代码实现方式如下:
import numpy as npfrom collections import Counterdef main():X_train = np.array([[158, 64],[170, 86],[183, 84],[191, 80],[155, 49],[163, 59],[180, 67],[158, 54],[170, 67]])y_train = np.array(["male", "male", "male", "male", "female","female", "female", "female", "female"])X_test = np.array([[155, 70]])distances = np.sqrt(np.sum(np.square(X_train-X_test), axis=1))nearest_neighbor_indices = distances.argsort() # [0 5 8 7 4 1 6 2 3]nearest_neighbor_indices = nearest_neighbor_indices[:3] # k=3,这里我们只取前三个索引nearest_neighbor_genders = np.take(y_train, nearest_neighbor_indices) # ['male' 'female' 'female']nearest_neighbor_sex = Counter(nearest_neighbor_genders)nearest_neighbor_sex = nearest_neighbor_sex.most_common() # [('female', 2), ('male', 1)]nearest_neighbor_sex = nearest_neighbor_sex[0][0] # 'femalereturn nearest_neighbor_sexif __name__ == '__main__':pre_sex = main()
好了,回归正题,在上面我们用数学公式的方式预测出了我们的网友是位小姐姐, 下面我们用sklearn的API来预测一下。
import numpy as npfrom sklearn.preprocessing import LabelBinarizerfrom sklearn.neighbors import KNeighborsClassifierdef main():X_train = np.array([[158, 64],[170, 86],[183, 84],[191, 80],[155, 49],[163, 59],[180, 67],[158, 54],[170, 67]])y_train = np.array(["male", "male", "male", "male", "female","female", "female", "female", "female"])X_test = np.array([[155, 70]])lb = LabelBinarizer()# 将label从字符串映射成整型y_train = lb.fit_transform(y_train) # [[1], [1], [1], [1], [0], [0], [0], [0], [0]]knn_model = KNeighborsClassifier(n_neighbors=3)knn_model.fit(X_train, y_train.reshape(-1))pred_y = knn_model.predict(X_test) # [0]pred_sex = lb.inverse_transform(pred_y)[0] # 'female'return pred_sexif __name__ == '__main__':pre_sex = main()
模型预测和指标统计
现在我们有一个测试数据,就让我们来预测一下他们的结果吧,并测试一下模型的好坏。
在开始之前,我们先来介绍一下分类模型的评估指标
- 准确率:测试数据中被正确分类的比率
- 精准率:正向测试数据被预测为正向的比率
- 召回率:真实为正向的测试数据被预测为正向的比率
- F1得分:精准率和召回率的算术平均值
- MCC(马修斯相关系数),一个完美的分类模型的MCC为1,随机预测的模型MCC为0,完全预测错误的模型MCC为-1,即使测试数据的类别比例不平衡,MCC得分也非常有用。
测试数据:
| 身高(单位:cm) | 体重(单位:kg) | 标签 |
|---|---|---|
| 168 | 65 | 男性 |
| 180 | 96 | 男性 |
| 160 | 52 | 女性 |
| 169 | 67 | 女性 |
测试结果
import numpy as npfrom sklearn.preprocessing import LabelBinarizerfrom sklearn.neighbors import KNeighborsClassifierfrom sklearn.metrics import accuracy_score, precision_score, \recall_score, f1_score, matthews_corrcoef, classification_reportdef main():X_train = np.array([[158, 64],[170, 86],[183, 84],[191, 80],[155, 49],[163, 59],[180, 67],[158, 54],[170, 67]])y_train = np.array(["male", "male", "male", "male", "female","female", "female", "female", "female"])X_test = np.array([[168, 65],[180, 96],[160, 52],[169, 67]])y_test = np.array(["male", "male", "female", "female"])lb = LabelBinarizer()# 将label从字符串映射成整型y_train = lb.fit_transform(y_train) # [[1], [1], [1], [1], [0], [0], [0], [0], [0]]knn_model = KNeighborsClassifier(n_neighbors=3)knn_model.fit(X_train, y_train.reshape(-1))pred_y = knn_model.predict(X_test)# 预测结果pred_sex = lb.inverse_transform(pred_y) # ['female' 'male' 'female' 'female']y_test = lb.transform(y_test).reshape(-1)################################模型评估######################################### acc scoreacc_score = accuracy_score(y_test, pred_y)# precison scoreprec_score = precision_score(y_test, pred_y)# recall scorerec_score = recall_score(y_test, pred_y)# f1 scoref1score = f1_score(y_test, pred_y)# mcc scoremcc_score = matthews_corrcoef(y_test, pred_y)print(f"accuracy_score:{acc_score}\n"f"precision_score:{prec_score}\n"f"recall_score:{rec_score}\n"f"f1_score:{f1score}\n"f"matthews_corrcoef:{mcc_score}")"""accuracy_score:0.75precision_score:1.0recall_score:0.5f1_score:0.6666666666666666matthews_corrcoef:0.5773502691896258"""print(classification_report(y_test, pred_y, labels=[0, 1], target_names=['female', 'male']))return pred_sexif __name__ == '__main__':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是预测结果误差绝对值的均值。计算方法如下:
MSE是预测结果误差平方的均值,MSE又被称为均方偏差,比起MAE来说是一种更常见的指标。
MAE和MSE分别通过对误差求平方和求绝对值来衡量模型。对一个较大的误差值求平方会加大它对整体误差的贡献比例,因此MSE比MAE对于异常值的惩罚程度要高。一般来说,MSE是线性回归任务的首选。
模型训练
我们讲完了模型的评估指标,现在我们准备训练模型。不过在训练之前,我们要先处理一下数据,主要包括两个方面:离散特征处理和数据标准化
sklearn 库里的StandardScaler类常用来用作特征缩放。它能确保所有的特征都有单位方差。它首先将所有实例特征减去均值将其居中,其次将每个实例特征值除以特征的标准差来对其进行缩放。均值为0,方差为1的数据称为标准化数据。
import numpy as npfrom sklearn.preprocessing import LabelBinarizer, StandardScalerfrom sklearn.neighbors import KNeighborsRegressorfrom sklearn.metrics import mean_squared_error, mean_absolute_errordef main():X_train = np.array([[158, "male"],[170, "male"],[183, "male"],[191, "male"],[155, "female"],[163, "female"],[180, "female"],[158, "female"],[170, "female"]])y_train = np.array([64, 86, 84, 80, 49,59, 67, 54, 67])X_test = np.array([[168, "male"],[180, "male"],[160, "female"],[169, "female"]])y_test = np.array([65, 96, 52, 67])lb = LabelBinarizer() # 离散数据处理实例化类ss = StandardScaler() # 标准化X_train[:, 1] = lb.fit_transform(X_train[:, 1]).reshape(-1)X_train[:, 0] = ss.fit_transform(X_train[:, 0].reshape(-1, 1)).reshape(-1) # 只处理身高特征# X_train = ss.fit_transform(X_train) # 全部特征处理model = KNeighborsRegressor(n_neighbors=3)model.fit(X_train, y_train)X_test[:, 1] = lb.transform(X_test[:, 1]).reshape(-1)X_test[:, 0] = ss.transform(X_test[:, 0].reshape(-1,1)).reshape(-1) # 只处理身高特征# X_test = ss.transform(X_test) # 全部特征处理pred_y = model.predict(X_test) # [72.33333333 83.33333333 54. 64.33333333]mae = mean_absolute_error(y_test, pred_y) # 6.166666666666668mse = mean_squared_error(y_test, pred_y) # 56.33333333333336return pred_yif __name__ == '__main__':pre_weight = main()
