使用KNN和增广数据提升分类器的准确率

1,033 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

1. 使用KNN制作一个超过97%准确率的分类器

我们已经通过SGDClassifier实现了对mnist数据集的分类,但是我们的准确率即使在优化后也不够好, 只有90%左右。

现在我有了新的需求,需要制作一个能够超过97%的准确率的分类器,我应该怎么做?

这时候我们就需要使用kk-近邻(kk-Nearest Neighbor,简称KNN)学习,它是一种常用的监督学习方法,也是最常用的一种分类算法。

KNN的工作机制很简单:给定测试样本,基于某种距离度量找出训练集中于最靠近的kk个训练样本,然后基于这kk个“邻居”的信息来进行预测。

我们可以通过sklearn.neighbors模块导入KNeighborsClassifier,读者也可以在sklearn.neighbors.KNeighborsClassifier这里查看完整的介绍,这边我主要讲几个等会需要用到的参数:

KNeighborsClassifier(n_neighbors=5, *, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None)

  • n_neighbors:邻居的数量,也就是knn在训练时需要找几个邻居样本
  • weights: 设置权重,可以设置如下:{‘uniform’, ‘distance’} or callable
    • uniform 所有点都一样的权重
    • distance 根据邻居之间的距离设置权重,距离越近权重越大
    • callable 用户自定义函数,可以接受一个数组,但需要返回同形且含有权重的数组

了解了KNN后,我们就需要开始训练分类器了,但是我们应该怎么设置上面两个参数呢?还记得网格搜索(GridSearch)吧,我们现在可以用它来寻找上面两个超参数比较适合的值。

from sklearn.datasets import fetch_openml
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV

# 首先获取数据并分割训练集和测试集
mnist = fetch_openml("mnist_784",version=1,as_frame=False)
X, y =mnist['data'],mnist['target']
X_train,X_test,y_train,y_test = X[:60000],X[60000:],y[:60000],y[60000:]

# 引入KNN
knn_clf = KNeighborsClassifier()
# 配置网格搜索需要搜索的范围
grid = [
    {"n_neighbors":[3, 4,5] ,"weights":['uniform','distance'] }
]

grid_cv = GridSearchCV(knn_clf, grid, cv=5, verbose=4,n_jobs=3)
grid_cv.fit(X_train, y_train)

grid_cv.best_score_ 
最好的分数:0.9716166666666666
grid_cv.best_params_ 
最优的超参数:{'n_neighbors': 4, 'weights': 'distance'}
grid_cv.best_estimator_ 
最优的估算器:KNeighborsClassifier(n_neighbors=4, weights='distance')

网格搜索真的帮了我大忙,自动化的处理省了不少我需要做的步骤。最后的效果还是不错的,满足了我的需要。

但是我还能不能继续提升这个准确率?

2. 数据增广

不知道读者是否还记得我上一篇的误差分析中,看了第八列的数据存在问题-对于数字8的预测存在很多错误。我们当时提出的解决办法是:继续增加相关数据。而这正是数据增广或训练集拓展

3. 移动图片位置提升准确度

我们知道,图片的移动可能会增加我们预测的误差,我们希望数字最好能够处于中心位置。那我们就试着通过上下左右移动数字位置一像素移动,扩充图片数据集,看看是否能够提升准确度。

移动图像的位置,我们需要使用一个函数scipy.ndimage.shift(还是一样的配方,附上官网文档地址^^),shift的实现的就是数组的移动,我选择比较常用的属性简单讲一下:

scipy.ndimage.shift(input, shift, output=None, order=3, mode='constant', cval=0.0, prefilter=True)

  • input 需要移动的数组
  • mode 选择当移动数组后,用什么模式去填充空出的位置,这里选择默认值“constant”,他会更讲cval的值填入移动后空缺的位置
  • cval 填充的值,只有mode='constant'时生效
import matplotlib.pyplot as plt
import matplotlib as mpl
from scipy.ndimage import shift

# 根据(dx,dy)移动照片位置
def shift_image(digit, dx, dy):
    # 注意这边我们需要先讲传入的digit重塑成图片大小(28,28),否则就没有移动数字的作用
    image = digit.reshape(28,28)
    # 将原本图片的数字位置 向x轴移动dx,向y轴移动dy
    image = shift(image, [dx, dy], mode="constant")
    # 重新变形成[1,784]
    return image.reshape([-1])

# 增广数据,首先填入原本的测试集
X_train_augmented = [image for image in X_train]
y_train_augmented = [label for label in y_train]

# 实现训练集的每个点的上下左右移动一像素
# 一重循环x轴和y轴位置
# 二重循环训练集的每个元素,并且移动位置后加入原本的数据集
for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
    for image, label in zip(X_train, y_train):
        X_train_augmented.append(shift_image(image, dx, dy))
        y_train_augmented.append(label)

# 将list编程np数组
X_train_augmented = np.array(X_train_augmented)
y_train_augmented = np.array(y_train_augmented)

# 别忘了混淆数组哦
shuffled_indices = np.random.permutation(len(X_train_augmented))
X_train_augmented = X_train_augmented[shuffled_indices]
y_train_augmented = y_train_augmented[shuffled_indices]

# 添加找到的最好的KNN的超参数
# 也可以使用 grid_cv.best_estimator_
knn_clf_new = KNeighborsClassifier(**grid_cv.best_params_)
knn_clf_new.fit(X_train_augmented,y_train_augmented)

X_train_augmented_pred = knn_clf_new.predict(X_test)
accuracy_score(y_test,X_train_augmented_pred) # 输出分数 0.973

嗯哼,效果还是不错的,看来又成功完成了今日需求,终于实现了超过97%准确率的分类器。