K近邻算法是一种基于距离度量的数据分类模型,其基本做法是首先确定输入实例的[插图]个最近邻实例,然后利用这[插图]个训练实例的多数所属的类别来预测新的输入实例所属类别。
k最近邻(k-nearest neighbors,KNN)算法是一种基本的分类和回归算法。其基本原理如下:
1. 训练阶段:将训练样本集中的数据和对应的标签存储起来,构建一个训练模型。
2. 预测阶段:对于一个新的测试样本,计算它与训练样本集中所有样本的距离(通常使用欧氏距离或其他距离度量方法)。
3. 选择k个最近邻:根据距离计算结果,选择与测试样本距离最近的k个训练样本。
4. 进行投票或计算平均值:对于分类任务,根据k个最近邻的标签进行投票,将得票最多的标签作为测试样本的预测标签。对于回归任务,根据k个最近邻的标签或值进行平均计算,将计算结果作为测试样本的预测值。
KNN算法的关键是选择合适的k值和距离度量方法。k值的选择会影响算法的性能,较小的k值可能会导致过拟合,较大的k值可能会导致欠拟合。距离度量方法的选择应根据具体问题和数据特点进行合理选择。
KNN算法的优点包括简单易懂、易于实现、适用于多分类和回归问题。然而,KNN算法的缺点是计算复杂度高,对于大规模数据集的预测速度较慢。此外,KNN算法对于特征空间的维度敏感,需要进行特征选择或降维等预处理操作。
# 导入相关模块
import numpy as np
from collections import Counter
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.utils import shuffle
### 定义欧氏距离
def compute_distances(X, X_train):
'''
输入:
X:测试样本实例矩阵
X_train:训练样本实例矩阵
输出:
dists:欧式距离
'''
# 测试实例样本量
num_test = X.shape[0]
# 训练实例样本量
num_train = X_train.shape[0]
# 基于训练和测试维度的欧氏距离初始化
dists = np.zeros((num_test, num_train))
# 测试样本与训练样本的矩阵点乘
M = np.dot(X, X_train.T)
# 测试样本矩阵平方
te = np.square(X).sum(axis=1)
"""
>>> import numpy as np
>>> a = np.array([[1,2,3],[4,5,6]])
>>> np.sum(a)
21
>>> np.sum(a, axis=0)
array([5, 7, 9])
>>> np.sum(a, axis=1)
array([ 6, 15])
X = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
==>np.square(X).sum(axis=1) 结果为 [14 77 194]
"""
# 训练样本矩阵平方
tr = np.square(X_train).sum(axis=1)
# 计算欧式距离
dists = np.sqrt(-2 * M + tr + np.matrix(te).T) # 自己可以简单推导下 的确如此
return dists
### 定义预测函数
def predict_labels(y_train, dists, k=1):
'''
输入:
y_train:训练集标签
dists:测试集与训练集之间的欧氏距离矩阵
k:k值
输出:
y_pred:测试集预测结果
'''
# 测试样本量
num_test = dists.shape[0]
# 初始化测试集预测结果
y_pred = np.zeros(num_test)
# 遍历
for i in range(num_test):
# 初始化最近邻列表
closest_y = []
# 按欧氏距离矩阵排序后取索引,并用训练集标签按排序后的索引取值
# 最后展平列表
# 注意np.argsort函数的用法
labels = y_train[np.argsort(dists[i, :])].flatten()
# 取最近的k个值
closest_y = labels[0:k]
# 对最近的k个值进行计数统计
# 这里注意collections模块中的计数器Counter的用法
c = Counter(closest_y)
# 取计数最多的那个类别
y_pred[i] = c.most_common(1)[0][0]
return y_pred
"""
>>> x = np.array([3, 1, 2])
>>> np.argsort(x)
array([1, 2, 0])
# 创建计数对象
>>> c = Counter('abcdeabcdabcaba')
# 取计数前三的元素
>>> c.most_common(3)
[('a', 5), ('b', 4), ('c', 3)]
"""
# 导入sklearn iris数据集
iris = datasets.load_iris()
# 打乱数据后的数据与标签
X, y = shuffle(iris.data, iris.target, random_state=13)
# 数据转换为float32格式
X = X.astype(np.float32)
# 简单划分训练集与测试集,训练样本-测试样本比例为7:3
offset = int(X.shape[0] * 0.7)
X_train, y_train = X[:offset], y[:offset]
X_test, y_test = X[offset:], y[offset:]
# 将标签转换为竖向量
y_train = y_train.reshape((-1,1))
y_test = y_test.reshape((-1,1))
# 打印训练集和测试集大小
print('X_train=', X_train.shape)
print('X_test=', X_test.shape)
print('y_train=', y_train.shape)
print('y_test=', y_test.shape)
dists = compute_distances(X_test, X_train)
# 测试集预测结果
y_test_pred = predict_labels(y_train, dists, k=10)
y_test_pred = y_test_pred.reshape((-1, 1))
# 找出预测正确的实例
num_correct = np.sum(y_test_pred == y_test)
# 计算分类准确率
accuracy = float(num_correct) / X_test.shape[0]
print('KNN Accuracy based on NumPy: ', accuracy)
# 导入KNeighborsClassifier模块
from sklearn.neighbors import KNeighborsClassifier
# 创建k近邻实例
neigh = KNeighborsClassifier(n_neighbors=10)
# k近邻模型拟合
neigh.fit(X_train, y_train)
# k近邻模型预测
y_pred = neigh.predict(X_test)
# 预测结果数组重塑
y_pred = y_pred.reshape((-1, 1))
# 统计预测正确的个数
num_correct = np.sum(y_pred == y_test)
# 计算分类准确率
accuracy = float(num_correct) / X_test.shape[0]
print('KNN Accuracy based on sklearn: ', accuracy)
运行结果:
X_train= (105, 4)
X_test= (45, 4)
y_train= (105, 1)
y_test= (45, 1)
KNN Accuracy based on NumPy: 0.9777777777777777
KNN Accuracy based on sklearn: 0.9777777777777777
其中,计算举例的可以使用下面更加简洁的函数:
def compute_distances(X, X_train):
num_test = X.shape[0]
num_train = X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
for j in range(num_train):
dists[i, j] = np.sqrt(np.sum(np.square(X[i] - X_train[j])))
return dists
predict我自己写的话,是下面这样:
def predict_labels(y_train, dists, k=1):
num_test = dists.shape[0]
y_pred = np.zeros(num_test)
for i in range(num_test):
sorted_indices = np.argsort(dists[i])
closest_y = y_train[sorted_indices[:k]].flatten()
y_pred[i] = Counter(closest_y).most_common()[0][0]
return y_pred