KNN 是一种可以运用于分类和回归任务的算法。
K-近邻模型
KNN算法中的“邻居”代表的是度量空间中的训练实例,度量空间是定义了集合中所有成员之间距离的特征空间。
邻居是用于估计一个测试实例对应的响应变量值,超参k用来指定估计过程应该包含多少个邻居。超参是用来控制算法如何学习的参数,它不通过训练数据估计,一般需要认为指定。最后,算法通过某种距离函数,从度量空间中选出k个距离测试实例最近的邻居。
惰性学习和非参数模型
KNN是一种惰性学习模型,也被称为基于实例的学习模型,会对训练数据集进行少量的处理或者完全不处理。和勤奋学习模型不同,KNN在训练阶段不会估计由模型生成的参数。惰性学习模型几乎可以进行即刻预测,但是需要付出高昂的代价。
KNN是一种非参数模型,参数模型使用固定数量的参数或者系数去定义能够对数据进行总结的模型,参数的数量独立于训练实例的数量。非参数模型意味着模型的参数个数并不固定,它可能随着训练实例数量的增加而增加。
KNN模型分类
下面看一个使用人的身高和体重去预测性别的例子,由于响应变量只能从2个标签(男或者女)中二选一,因此这种问题被称为二元分类。
身高(cm) | 体重(kg) | 标签 |
---|---|---|
158 | 64 | 男 |
170 | 66 | 男 |
183 | 84 | 男 |
191 | 80 | 男 |
155 | 49 | 女 |
163 | 59 | 女 |
180 | 67 | 女 |
158 | 54 | 女 |
178 | 77 | 女 |
这里使用了2个特征,实际上KNN并不仅限于2个特征,但特征值太多不利于可视化。
首先使用matplotlib类库绘制散点图进行可视化。
import numpy as np
import matplotlib.pyplot as 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 = ['male', 'male', 'male', 'male', 'female', 'female', 'female', 'female', 'female']
plt.figure()
plt.title('Human Heights and Weights by Sex')
plt.xlabel('Height in cm')
plt.ylabel('Weight in kg')
for i, x in enumerate(X_train):
plt.scatter(x[0], x[1], c='k', marker='x' if y_train[i] == 'male' else 'D')
plt.grid(True)
plt.show()

现在让我们的使用KNN来预测其性别,首先我们需要定义距离衡量的方法,这里使用的是欧几里得距离,即在一个欧几里得空间中两点之间的直线距离。
公式如下:
下面我们计算出测试实例和所有训练实例的距离,并设置参数k为3,选出3个距离最近的训练实例,并根据者3个邻居预测测试实例的性别。
x = np.array([155, 70]) # 测试数据
distances = np.sqrt(np.sum((X_train - x)**2, axis=1))
distances
array([ 6.70820393, 21.9317122 , 31.30495168, 37.36308338, 21. ,
13.60147051, 25.17935662, 16.2788206 , 15.29705854])
nearest_neighbor_indices = distances.argsort()[:3]
nearest_neighbor_genders = np.take(y_train, nearest_neighbor_indices)
nearest_neighbor_genders
array(['male', 'female', 'female'], dtype='<U6')
from collections import Counter
b = Counter(np.take(y_train, distances.argsort()[:3]))
b.most_common(1)[0][0]
'female'
最后的预测结果为女性,下面我们使用scikit-learn类库的KNN分类器。
from sklearn.preprocessing import LabelBinarizer
from sklearn.neighbors import KNeighborsClassifier
# 将标签转换为整数
lb = LabelBinarizer()
y_train_binarized = lb.fit_transform(y_train)
y_train_binarized
array([[1],
[1],
[1],
[1],
[0],
[0],
[0],
[0],
[0]])
K = 3
clf = KNeighborsClassifier(n_neighbors=K)
clf.fit(X_train, y_train_binarized.reshape(-1))
prediction_binarized = clf.predict(np.array([155, 70]).reshape(1, -1))[0]
predicted_label = lb.inverse_transform(prediction_binarized)
predicted_label
array(['female'], dtype='<U6')
结果和前面预测相同。
下面我们使用分类器对一个测试数据集进行预测,同时对其效果进行评估。
身高(cm) | 体重(kg) | 标签 |
---|---|---|
168 | 65 | 男 |
180 | 96 | 男 |
160 | 52 | 女 |
169 | 67 | 女 |
X_test = np.array([
[168, 65],
[180, 96],
[160, 52],
[169, 67]
])
y_test = ['male', 'male', 'female', 'female']
y_test_binarized = lb.transform(y_test)
print('Binarized labels: %s' % y_test_binarized.T[0])
predictions_binarized = clf.predict(X_test)
print('Binarized predictions: %s' % predictions_binarized)
print('predicted labels: %s' % lb.inverse_transform(predictions_binarized))
Binarized labels: [1 1 0 0]
Binarized predictions: [0 1 0 0]
predicted labels: ['female' 'male' 'female' 'female']
可以看到其中的一个男性测试实例被错误的划分为女性,二元分类任务中有2种错误类型:误报和漏报,根据错误类型有几种常见的衡量方法:包括准确率、精准率和召回率。
准确率是测试实例中正确分类比例。
# 计算准确率
from sklearn.metrics import accuracy_score
print('Accuracy: %s' % accuracy_score(y_test_binarized, predictions_binarized))
Accuracy: 0.75
精准率是为正向类测试实例被预测为正向类的比率。
# 计算精准率,只有一个结果预测为男性,应该是100%
from sklearn.metrics import precision_score
print('Precision: %s' % precision_score(y_test_binarized, predictions_binarized))
Precision: 1.0
召回率是真实为正向类的测试实例被预测为正向类的比率。
# 计算召回率
from sklearn.metrics import recall_score
print('Recall: %s' % recall_score(y_test_binarized, predictions_binarized))
Recall: 0.5
精准率和召回率的平均值被称为F1得分或者F1度量。
from sklearn.metrics import f1_score
print('F1 score: %s' % f1_score(y_test_binarized, predictions_binarized))
F1 score: 0.6666666666666666
scikit-learn类还提供了一个非常有用的函数 classification_report 用于生成精准率、召回率和F1得分。
from sklearn.metrics import classification_report
print(classification_report(y_test_binarized, predictions_binarized, target_names=['male'], labels=[1]))
precision recall f1-score support
male 1.00 0.50 0.67 2
micro avg 1.00 0.50 0.67 2
macro avg 1.00 0.50 0.67 2
weighted avg 1.00 0.50 0.67 2
KNN模型回归
现在使用KNN模型进行一个回归任务,根据一个人的身高和性别预测其体重。
训练数据:
身高(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 |
对于回归问题,除了前面的R2,首先引入两个新的衡量指标:
平均绝对误差MAE,是预测结果误差绝对值的均值。公式如下:
均方误差MSE是预测结果误差的平方的均值,比起MAE更加常用,公式如下:
特征缩放
当特征有相同的取值范围时,许多学习算法将会运行的更好。scikit-learn类库中的StandardScaler类是一个用于特征缩放的转换器,他能确保所有的特征都有单位方差。
它首先将所有的实例特征值减去均值来将其据中,其次将每个实例特征值除以特征的标准差对其进行缩放。均值为0,方差为1的数据被称为标准化数据。
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler
X_train = np.array([
[158, 1],
[170, 1],
[183, 1],
[191, 1],
[155, 0],
[163, 0],
[180, 0],
[158, 0],
[170, 0]
])
y_train = [64, 86, 84, 80, 49, 59, 67, 54, 67]
X_test = np.array([
[168, 1],
[180, 1],
[160, 0],
[169, 0]
])
y_test = [65, 96, 52, 67]
# 标准化数据
ss = StandardScaler()
X_train_scaled = ss.fit_transform(X_train)
print(X_train)
print(X_train_scaled)
[[158 1]
[170 1]
[183 1]
[191 1]
[155 0]
[163 0]
[180 0]
[158 0]
[170 0]]
[[-0.9908706 1.11803399]
[ 0.01869567 1.11803399]
[ 1.11239246 1.11803399]
[ 1.78543664 1.11803399]
[-1.24326216 -0.89442719]
[-0.57021798 -0.89442719]
[ 0.86000089 -0.89442719]
[-0.9908706 -0.89442719]
[ 0.01869567 -0.89442719]]
from sklearn.neighbors import KNeighborsRegressor
X_test_scaled = ss.transform(X_test)
K = 3
clf = KNeighborsRegressor(n_neighbors=K)
clf.fit(X_train_scaled, y_train)
predictions = clf.predict(X_test_scaled)
print('Predicted weights: %s' % predictions)
print('Coefficient of determination: %s' % r2_score(y_test, predictions))
print('Mean absolute error: %s' % mean_absolute_error(y_test, predictions))
print('Mean squared error: %s' % mean_squared_error(y_test, predictions))
Predicted weights: [78. 83.33333333 54. 64.33333333]
Coefficient of determination: 0.6706425961745109
Mean absolute error: 7.583333333333336
Mean squared error: 85.13888888888893
网友评论