前言
使用numpy复现KNN算法。
(1)更加了解算法以及熟练复现能力。
(2)为后续优化改进KNN算法做铺垫。
(3)涉及到的运算使用numpy,加快运算。
一、KNN是什么?
1.1 KNN伪代码
以下是 K 最近邻算法(K-Nearest Neighbors,KNN)的伪代码:
函数 KNN:
输入:
- 训练数据集 X_train
- 训练数据集的标签 y_train
- 待预测样本 x_test
- 参数 K
输出:
- 预测样本的标签 y_pred
# 计算距离
distances = 计算 x_test 与 X_train 中每个样本的距离
# 根据距离排序样本
sorted_indices = 对 distances 进行排序,返回索引
# 取前 K 个样本的标签
k_nearest_labels = 取出 sorted_indices 的前 K 个样本的标签 y_train
# 根据投票选择获胜的标签
y_pred = 统计 k_nearest_labels 中出现最频繁的标签
返回 y_pred
KNN 算法的主要步骤包括计算待预测样本与训练样本之间的距离、排序样本、选择最近的 K 个样本,并通过投票方式决定预测样本的标签。
需要注意的是,在实际应用中,通常需要根据具体情况选择合适的距离度量方法(例如欧氏距离、曼哈顿距离等),以及相应的 K 值。此外,还需要进行数据预处理、特征选择和模型评估等步骤,以获得更好的预测性能。
1.2 范数的计算
np.linalg.norm
函数用于计算向量或矩阵的范数(norm)。它可以计算各种范数,包括 L1 范数、L2 范数、无穷范数等。
具体而言,np.linalg.norm(x, ord=None, axis=None, keepdims=False)
函数接受以下参数:
x
:要计算范数的向量或矩阵。ord
:可选参数,用于指定要计算的范数类型。默认为None,表示计算矩阵或向量的 Frobenius 范数。常用的范数类型有:- ord=None:计算 Frobenius 范数。对于向量,等同于 L2 范数。
- ord=1:计算 L1 范数(向量元素绝对值之和)。
- ord=2:计算 L2 范数(向量元素的平方和的平方根)。默认情况下使用的范数。
- ord=np.inf:计算无穷范数(向量中绝对值最大的元素)。
- ord=-np.inf:计算负无穷范数(向量中绝对值最小的元素)。
axis
:可选参数,用于指定计算的轴方向。默认是 None,表示计算整个矩阵或向量的范数。当 axis=0 时,计算列范数;当 axis=1 时,计算行范数。keepdims
:可选参数,指定是否保持计算后的维度。如果设置为 True,则输出结果会保持输入的维度;如果设置为 False(默认),则输出结果是一个标量或一维数组。
故有参数ord是进行设置的,通常默认为ord=2,表示取得是欧式距离。
二、复现KNN
1.引入库
import numpy as np
from scipy.stats import mode
2.封装成类
并给出详细的注释
class KNN():
def __init__(self,k,ord=2):
self.k = k
self.ord = ord
def fit(self,x_train,y_train):
self.x_train = x_train
self.y_train = y_train
# 涉及到的操作都是numpy类型
if type(self.x_train) != np.ndarray:
self.x_train = np.array(self.x_train)
if type(self.y_train) != np.ndarray:
self.y_train = np.array(self.y_train)
if len(self.y_train.shape)==1: # 维度进行延伸
self.y_train = np.expand_dims(self.y_train, axis=1)
# 计算的是data2中每一行数据与data1所有数据之间的各种距离,且用distances保存
# data2:k行m列
# data1:n行m列
# distances:k行n列
def calculate_distances(self,dataset1,dataset2,ord): # 默认使用欧式距离
distances = np.zeros((len(dataset2), len(dataset1))) # 初始化距离矩阵
for i in range(len(dataset2)):
distances[i] = np.linalg.norm(dataset1 - dataset2[i], axis=1,ord=ord)
return distances
def predict(self,x_test):
if type(x_test) != np.ndarray:
x_test = np.array(x_test)
distances = self.calculate_distances(self.x_train,x_test,self.ord) # 获取距离矩阵
partitioned_indexes = np.argpartition(distances, self.k, axis=1)[:, :self.k] # 找到每行最小的前k个值的索引位置
# 即获取距离最近得前k个样本
sub_labels = self.y_train[partitioned_indexes, -1] # 获取选定得最近前k个样本对应的标签
modes, counts = mode(sub_labels, axis=1,keepdims=False) # 获取每行的众数以及出现次数
return modes # 即为预测数
三、与skleanr中knn进行对比
3.1 数据预处理
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
data = pd.read_csv(r"heart.csv")
for column in data.columns:
data[column].replace("N",np.nan,inplace=True) # 将错误的值换成缺失值格式np.Nan
print(data.shape)
data = data.dropna() # 删除nan值
print(data.shape)
data["sex"].replace("male",0,inplace=True)
data["sex"].replace("female",1,inplace=True)
data["thal"].replace("fixed defect",0,inplace=True)
data["thal"].replace("reversable defect",1,inplace=True)
data["thal"].replace("normal",2,inplace=True)
Y = data['target']
X = data.drop("target",axis=1)
# 切分数据集,设置测试集占比为30%
x_train, x_test, y_train, y_test = train_test_split(X,Y,test_size=0.2, random_state=3220822)
3.2 使用sklearn中的KNeighborsClassifier类
# 创建 KNN 模型对象
knn = KNeighborsClassifier(n_neighbors=3,)
# 训练模型
knn.fit(x_train, y_train)
# 进行预测
y_pred = knn.predict(x_test)
# 评估模型
accuracy = accuracy_score(y_test, y_pred)
print("准确率:", accuracy)
3.3 使用复现的KNN算法
MYknn = KNN(k=3)# 默认范数ord=2,即默认为欧式距离
# 训练模型
MYknn.fit(x_train, y_train)
# 进行预测
y_pred = MYknn.predict(x_test)
# 评估模型
accuracy = accuracy_score(y_test, y_pred)
print("准确率:", accuracy)
3.4 范数ord设定的影响
我们可以通过设定范数ord分别为1,2,3的情况,看其效果如何 。
可以看到范数ord取值不同对结果的影响是很大的,但并不是越大越好,或越小越好。
而是不同的数据集有不同的最佳选择,因此需要我们多加测试,选出数据集对应的最优参数值。
总结
后续,将会如kmeans算法一样,优化算法。使得knn算法的效果提升的更好。