介绍
KNN算法,全称为K-Nearest Neighbors Algorithm,中文名K近邻算法,是一种监督学习算法,既可以用于分类问题也可以用于回归问题。
步骤
1.选择K个数和一个距离度量方法。
2.找到距离最近的K个样本(k-近邻)。
3.以服从多数原则确定预测结果。
常用的距离度量方法
1.曼哈顿距离
∑
∣
x
i
−
x
i
′
∣
\sum |x_{i} - x^{'}_{i}|
∑∣xi−xi′∣
2.欧几里得距离
∑
(
x
i
−
x
i
′
)
2
\sqrt{\sum (x_{i} - x^{'}_{i})^{2}}
∑(xi−xi′)2
以下为使用欧几里得距离的原理图:
优缺点
KNN算法的优点包括:
算法简单,易于理解和实现。
不需要训练模型,即不需要显式的训练过程,模型维护成本低。
对于多分类问题有较好的效果。
对数据分布没有假设,适用于各种类型的数据。
然而,KNN算法也有一些缺点:
计算量大,尤其是当数据集非常大时,计算所有点之间的距离会非常耗时。
对于高维数据,KNN算法的表现可能不佳,因为高维空间中“近邻”的概念可能变得模糊。
对于不平衡的数据集,KNN算法容易偏向于数量较多的类别。
K值的选择对结果影响较大,需要通过交叉验证等方法来确定最优的K值。
KNN算法实现识别手写数字
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
from sklearn.neighbors import KNeighborsClassifier
import joblib
# 加载MNIST数据集
mnist = fetch_openml('mnist_784')
X, y = mnist['data'], mnist['target']
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(float))
X_test_scaled = scaler.transform(X_test.astype(float))
# 创建KNN分类器
knn = KNeighborsClassifier(n_neighbors=3)
# 训练模型
knn.fit(X_train_scaled, y_train)
joblib.dump(knn, 'knn_model.pkl') # 模型将被保存为knn_model.pkl文件
# 加载模型
with open('static/knn_model.pkl', 'rb') as file:
model = joblib.load(file)
# 使用模型
# 注意data的格式为[[1 * 784]]的二维数组
prediction = model.predict(data)
print('Prediction:', prediction[0])
手写KNN算法
我们采用最普通的写法(别管效率 ,看懂就行),优化可以采用numpy的各项操作
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
class KNN:
def __init__(self, c=100, n=28 * 28, k=30):
self.c = c # 系数
self.n = n # 数组大小
self.k = k # 选取前几
def fit(self, X, y):
# 处理数据
self.X_train = X
self.y_train = y
def d(self, x1, x2):
# 求距离
k = 0
for i in range(self.n):
k += abs(x1[i] - x2[i])
k /= self.c
return k
def find(self, X):
# 判断属于哪个类别
d = []
for i in range(len(self.X_train)):
d.append(self.d(X, self.X_train.iloc[i]))
# 初始化数组计数,注意y中的数据为字符
m = {}
for i in range(10):
m[str(i)] = 0
for i in range(self.k):
mn = 1e9
id = -1
for j in range(len(d)):
if d[j] < mn:
mn = d[j]
id = j
m[self.y_train.iloc[id]] += 1
d[id] = 1e9
ans = -1
cnt = 0
for u, v in m.items():
if v > cnt:
ans = u
cnt = v
return ans
# 加载MNIST数据集
mnist = fetch_openml('mnist_784')
# 注意,X,y为dataframe类型
X, y = mnist['data'], mnist['target']
knn = KNN()
knn.fit(X, y)
# 随便测试一下
print(knn.find(X.iloc[0]))
print(y.iloc[0])
补充
问:上文提到,KNN算法不适合用在高维数据上,那为什么识别手写数字高达28*28维却可以用呢?
答:尽管每个图像有784(28x28)个像素,但手写数字图像通常在信息量上并不是完全独立的。像素之间存在相关性,这意味着数据的实际信息维度可能远低于784。例如,一个数字的某些部分(如“1”的直线或“0”的环形)在多个像素上表现了一致的特征。这种内在的结构可以被KNN利用来识别模式。
此外,MNIST数据集足够大。在高维空间中,数据点分散,但如果数据集足够大,仍然可以找到足够多的“近邻”点来做出准确的分类。