1、概述
俗话说得好:“物以类聚,人以群分”。在工作生活中,人们通常和身边的人具有一些相同的特质,KNN就是利用了这一原理实现数据的归类。KNN是一种监督学习算法,常用于处理分类和回归任务,适用于数值型和标称型数据。
2、基本原理
KNN是一种惰性学习算法,因为它不需要从训练集中学习任何的特征,只需要计算两个点之间的距离。将一组带标签的数据作为样本集,在输入一组没有标签的数据后,统计这些数据与样本集中数据的相似度,当距离越大时说明相似度更小,反之则越大,按照距离进行排序,选出前K个数据点的标签汇总成待分类数据点的标签。 如下图所示:
圆形和正方形代表两个不同的类别,现在我们需要辨别出处于中心的❌属于哪一类,按照KNN的思想:
- 当K = 3时,绿色样本的3个近邻中有两个橙色正方形的样本,一个蓝色圆形的样本,此时应当把绿色样本点归类为正方形那一类。
- 当K=5时,绿色样本的5个近邻中有两个橙色正方形的样本,三个蓝色圆形的样本,此时应当把绿色样本点归类为圆形那一类。 从上例可以看出,KNN在确定了K值后,按照一定的度量(这里是两个点的距离)选了离自己近的K个点,然后根据一定的规则(这里是少数服从多数)来决定最终的结果。其基本流程如下:
3、K值的选取
3.1K值选取的影响
当K值过小时,说明参考的邻居样本个数过少,存在一定的偶然性,从而导致算法的预测变得很不稳定。此外,若单个样本的影响越大,需要模型花更多的节点去拟合,最终导致模型的复杂度变高,并且模型与样本数据会十分契合,从而导致过拟合。 K值过大时,说明参考的邻居样本个数太多,可以减少噪声数据的影响,但模型会变得简单,不能很好地覆盖训练数据,导致出现欠拟合。
3.2K值选取的方法
那么应该如何选取恰当的K值才能获得比较好的效果呢?通常用交叉验证方法进行求解。: 将训练集分成K个子集,将其中一个子集作为测试集,其余作为训练集;然后换另一个子集作为测试集,其余作为训练集,如此重复K次,将表现最好的k作为最终的k值。
KNN的K值和K折交叉验证的K值不一样
- KNN的K值是算法需要参考的邻居个数;
- K折交叉验证的K值是数据要分割的份数以及需要验证的轮数。
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
# 定义k值的范围,选取1-30中的奇数
param_grid = {'n_neighbors': list(range(1, 31,2))}
# 5折交叉验证
grid = GridSearchCV(
KNeighborsClassifier(),
param_grid,
cv=5,
scoring='accuracy')
grid.fit(X_train, y_train)
print("最佳K值:", grid.best_params_['n_neighbors'])
print("验证集准确率:", grid.best_score_)'
4、距离度量
距离度量是用来衡量两个数据点之间的相似度的,通常有如下三种距离计算公式:欧式距离、闵可夫斯基距离、曼哈顿距离,三者表达式如下:
| 类别 | 公式 |
|---|---|
| 欧式距离 | |
| 曼哈顿距离 | |
| 闵可夫斯基距离 |
设训练样本,其中每个具有个特征,且,则闵可夫斯基距离的定义为:
- 当p = 1时,为曼哈顿距离,即
- 当p = 2时,为欧式距离,也是最常用的距离,即
5、决策规则
相较于回归算法,KNN更常用与分类算法。在分类算法中,在决定最后属于哪种类别时有如下几种策略:
- 多数表决法:分类问题中最常用的分类决策方法,将待分类样本归类为邻居中出现次数最多的类别
- 加权表决法:在多数表决法的基础上,对特定的邻居样本进行加权,从而提高对距离较近的样本的分类准确性。
- 贝叶斯决策法:利用贝叶斯公式,根据邻居样本的类别和先验概率,计算后验概率。
6、示例
下面以Mnist数据集为例使用KNN分类算法,MNIST 是手写数字数据集,其中包含了很多手写数字0~9 的黑白图像,每张图像都由 个像素点组成。数据读入后,每个像素点用 1 或 0 表示,1 代表黑色像素,属于图像背景;0 代表白色像素,属于手写数字.
6.1 手动实现的KNN
import matplotlib.pyplot as plt
import numpy as np
import os
m_x = np.loadtxt('mnist_x.txt', delimiter = ' ')
m_y = np.loadtxt('mnist_y.txt')
data = np.reshape(np.array(m_x[0], dtype=int), [28, 28])
plt.figure()
plt.imshow(data, cmap='gray')
# 训练集和测试集 八二分
ratio = 0.8
split = int(len(m_x) * 0.8)
np.random.seed(0)
idx = np.random.permutation(np.arange(len(m_x))) # 下标随机打乱成一个新的顺序,存到 idx 里
m_x = m_x[idx]
m_y = m_y[idx]
x_train, x_test = m_x[:split], m_x[split:]
y_train, y_test = m_y[:split], m_y[split:]
# 距离度量选择欧式距离
def distance(a, b):
return np.sqrt(np.sum(np.square(a - b)))
class KNN:
def __init__(self, k, label_num):
self.k = k
self.label_num = label_num
def fit(self, x_train, y_train):
self.x_train = x_train
self.y_train = y_train
def get_knn_indics(self, x):
dis = list(map(lambda a: distance(a, x), self.x_train)) #把训练集里每个样本与当前样本 x 的距离算出来
knn_indics = np.argsort(dis)
knn_indics = knn_indics[:self.k]
return knn_indics
def get_label(self, x):
knn_indices = self.get_knn_indics(x)
label_statistic = np.zeros(shape=[self.label_num])
for index in knn_indices:
label = int(self.y_train[index])
label_statistic[label] += 1
return np.argmax(label_statistic)
def predict(self, x_test):
predicted_test_labels = np.zeros(shape=[len(x_test)], dtype=int)
for i, x in enumerate(x_test):
predicted_test_labels[i] = self.get_label(x)
return predicted_test_labels
for k in range(1, 10):
knn = KNN(k, label_num=10)
knn.fit(x_train, y_train)
predicted_labels = knn.predict(x_test)
accuracy = np.mean(predicted_labels == y_test)
print(f'k的取值为{k}, 预测值为{accuracy * 100:.1f}%')
6.2 sklearn实现KNN分类算法
from sklearn.neighbors import KNeighborsClassifier
from matplotlib.colors import ListedColormap
data = np.loadtxt('gauss.csv', delimiter=',')
x_train = data[:, :2]
y_train = data[:, 2]
print(f'数据集大小为:{len(x_train)}')
plt.figure()
plt.scatter(x_train[y_train==0, 0], x_train[y_train==0, 1], c='blue', marker='o')
plt.scatter(x_train[y_train==1, 0], x_train[y_train==1, 1], c='red', marker='x')
plt.xlabel('x-axis')
plt.ylabel('y-axis')
plt.show()
step = 0.02
x_min, x_max = np.min(x_train[:, 0]) - 1, np.max(x_train[:, 0]) + 1
y_min, y_max = np.min(x_train[:, 1]) - 1, np.max(x_train[:, 1]) + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, step), np.arange(y_min, y_max, step))
grid_data = np.concatenate([xx.reshape(-1, 1), yy.reshape(-1, 1)], axis = 1)
fig = plt.figure(figsize=(16, 4.5))
ks = [1, 3, 10]
cmap_light = ListedColormap(['royalblue', 'lightcoral'])
for i, k in enumerate(ks):
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(x_train, y_train)
z = knn.predict(grid_data)
ax = fig.add_subplot(1, 3, i+1)
ax.pcolormesh(xx, yy, z.reshape(xx.shape), cmap=cmap_light, alpha=0.7)
ax.scatter(x_train[y_train==0, 0], x_train[y_train == 0, 1], c='blue', marker='o')
ax.scatter(x_train[y_train==1, 0], x_train[y_train == 1, 1], c='red', marker='x')
ax.set_xlabel('x-axis')
ax.set_ylabel('y-axis')
ax.set_title(f'k = {k}')
plt.show()
6.3 sklearn实现KNN回归算法
现在需要把一张黑白RGB图片颜色空间的值映射到LAB区间,其基本原理如下:
```python
from skimage import io
from skimage.color import rgb2lab, lab2rgb
from sklearn.neighbors import KNeighborsRegressor
import os
path = 'style_transfer'
data_dir = os.path.join(path, 'vangogh')
fig = plt.figure(figsize=(16,5))
for i, file in enumerate(np.sort(os.listdir(data_dir))[:3]):
img = io.imread(os.path.join(data_dir, file))
ax = fig.add_subplot(1, 3, i+1)
ax.imshow(img)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_title(file)
plt.show()
block_size = 1
def read_style_image(file_name, size=block_size):
img = io.imread(file_name)
fig = plt.figure()
plt.imshow(img)
plt.xlabel('X axis')
plt.ylabel('Y axis')
plt.show()
img = rgb2lab(img)
w, h = img.shape[:2]
X = []
Y = []
for x in range(size, w - size):
for y in range(size, h - size):
X.append(img[x - size: x+size+1, y-size:y+size+1, 0].flatten())
Y.append(img[x, y, 1:])
return X, Y
X, Y = read_style_image(os.path.join(path, 'style.jpg'))
knn = KNeighborsRegressor(n_neighbors=4, weights='distance')
knn.fit(X, Y)
def rebuild(img, size=block_size):
# 打印内容图像
fig = plt.figure()
plt.imshow(img)
plt.xlabel('X axis')
plt.ylabel('Y axis')
plt.show()
# 将内容图像转为LAB表示
img = rgb2lab(img)
w, h = img.shape[:2]
# 初始化输出图像对应的矩阵
photo = np.zeros([w, h, 3])
# 枚举内容图像的中心点,保存所有窗口
print('Constructing window...')
X = []
for x in range(size, w - size):
for y in range(size, h - size):
# 得到中心点对应的窗口
window = img[x - size: x + size + 1, \
y - size: y + size + 1, 0].flatten()
X.append(window)
X = np.array(X)
# 用KNN回归器预测颜色
print('Predicting...')
pred_ab = knn.predict(X).reshape(w - 2 * size, h - 2 * size, -1)
# 设置输出图像
photo[:, :, 0] = img[:, :, 0]
photo[size: w - size, size: h - size, 1:] = pred_ab
# 由于最外面size层无法构造窗口,简单起见,我们直接把这些像素裁剪掉
photo = photo[size: w - size, size: h - size, :]
return photo
content = io.imread(os.path.join(path, 'input.jpg'))
new_photo = rebuild(content)
# 为了展示图像,我们将其再转换为RGB表示
new_photo = lab2rgb(new_photo)
fig = plt.figure()
plt.imshow(new_photo)
plt.xlabel('X axis')
plt.ylabel('Y axis')
plt.show()