CS231n-KNN实现(附坑)

从效果看来,KNN并不适合图像识别,它的识别更多基于背景,而不是图片的语义主体。所以在实际应用中我们一般不适用KNN识别图像,但是在学习过程中,通过KNN算法我们可以学习到图像识别的整个流程,还是有些许帮助的

  • 图像识别流程

无论是哪种分类算法,图像识别的流程主要为以下流程

输入图像:一般来说,输入的是图像的像素值
训练模型:通过输入的图像来训练模型
评价模型:用测试数据来测试模型的分类能力从而评估模型的泛化能力
在训练模型阶段,由于很多模型都会有或多或少的参数需要设定(这些参数我们称为“超参”),所以我们需要增加一些步骤来确定这些参数,比如验证集或者交叉验证等方法,这些在接下来都会提到

  • 可视化Cifar10数据集

在下载完 cifar10数据集 后,我们首先需要读取cifar10数据集中的数据,这个我就不多介绍了,网上应该也有很多,注意Python版本就可以了,这里使用Python3.7。然后我们随机抽取一部分图像进行可视化

def VisualizeImage(X_train, y_train):
    """可视化数据集

    :param X_train: 训练集
    :param y_train: 训练标签
    :return:
    """
    plt.rcParams['figure.figsize'] = (10.0, 8.0)  # set default size of plots
    plt.rcParams['image.interpolation'] = 'nearest'
    plt.rcParams['image.cmap'] = 'gray'
    classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
    num_classes = len(classes)
    samples_per_class = 8
    for y, cls in enumerate(classes):
        # 得到该标签训练样本下标索引
        idxs = np.flatnonzero(y_train == y)
        # 从某一分类的下标中随机选择8个图像(replace设为False确保不会选择到同一个图像)
        idxs = np.random.choice(idxs, samples_per_class, replace=False)
        # 将每个分类的8个图像显示出来
        for i, idx in enumerate(idxs):
            plt_idx = i * num_classes + y + 1
            # 创建子图像
            plt.subplot(samples_per_class, num_classes, plt_idx)
            plt.imshow(X_train[idx].astype('uint8'))
            plt.axis('off')
            # 增加标题
            if i == 0:
                plt.title(cls)
    plt.show()

可视化的效果如下:

  • 构建KNN模型

一般构造机器学习模型,我们需要定义训练以及测试函数,而KNN模型是无需训练的,所以我们主要步骤在于预测功能,它的思路很简单:

如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。
那么什么叫做最相邻呢?即两个像素点越接近越相邻,这里我们用L2-范式(欧氏距离)来比较像素的接近程度,然后逐个像素比较,最后将差异值全部加起来得到两幅图像接近程度。

  • 计算欧式距离

它的公式如下:

公式

有很多方式可以实现这个公式,比如我们可以采取每一个测试集和每一个训练集分别计算欧氏距离的方式(即嵌套for循环),也可以用向量化的方式实现(numpy科学计算库,涉及到矩阵计算)。而事实上逐一计算方式所需的时间大概师向量化的实现的几百倍,所以我们要尽量使用向量化实现。向量化实现的思路是:

因为一张图片我们一般会将它reshape到一个一维向量,所以对于两张图片的距离就是计算两个向量之间的欧氏距离。我们将欧氏距离公式展开,我们可以得到两个向量之间的距离为x^2 - 2xy + y^2 (X, Y分别代表一个向量,也就是一张图片), 这样我们就可以用矩阵的方式求解了。
可查看KNN中的距离矩阵vectorize的实现方法

def compute_distances(self, X_test):
        """计算测试集和每个训练集的欧氏距离

        向量化实现需转化公式后实现(单个循环不需要)
        :param X_test: 测试集 numpy.ndarray
        :return: 测试集与训练集的欧氏距离数组 numpy.ndarray
        """
        dists = np.zeros((X_test.shape[0], self.X_train.shape[0]))

        value_2xy = np.multiply(X_test.dot(self.X_train.T), -2)
        value_x2 = np.sum(np.square(X_test), axis=1, keepdims=True)
        value_y2 = np.sum(np.square(self.X_train), axis=1)
        dists = value_2xy + value_x2 + value_y2
        return dists

这里注意一下,因为python的广播无法将(5000 x 500)的数组和(500,)的向量相加,所以我们有两种选择,一种是将向量先转成矩阵转置后相加,最后再转回数组,另一种就是设置关键字参数keepdims=True,它可以保证python的广播机制正确执行

  • 预测分类

原理也很简单:

根据上述计算的欧氏距离,对于每一个需要预测的样本,我们取前K个最接近的训练数据的分类,然后将这些分类中个数最多的分类作为预测结果.
python实现如下:
 

def predict_label(self, dists, k):
    """选择前K个距离最近的标签,从这些标签中选择个数最多的作为预测分类

    :param dists: 欧氏距离
    :param k: 前K个分类
    :return: 预测分类(向量)
    """
    y_pred = np.zeros(dists.shape[0])

    for i in range(dists.shape[0]):
        # 取前K个标签
        closest_y = self.y_train[np.argsort(dists[i, :])[:k]]
        # 取K个标签中个数最多的标签
        y_pred[i] = np.argmax(np.bincount(closest_y))
    return y_pred

  • 交叉验证

在我们训练完整数据集之前,我们必须先确定超参的值,这里我是用交叉验证方法,该方法主要思路为将训练集分为n份,然后每份分别作为测试集,剩下的作为训练集来检验不同的超参的效果,它适用于数据集较少的情况,因为交叉验证训练次数比较多,所以n的值不宜取过大,我们一般取5-10就可以了;如果数据集够大,我们可以使用验证集的方式,即从训练集取一小部分作为验证集来检验不同超参的效果,这样我们所需的训练次数就会少很多了。代码如下
 

def Cross_validation(X_train, y_train):
    """交叉验证,确定超参K,同时可视化K值

    :param X_train: 训练集
    :param y_train: 训练标签
    """
    num_folds = 5
    k_choices = [1, 3, 5, 8, 10, 12, 15, 20, 50, 100]
    k_accuracy = {}
    # 将数据集分为5份
    X_train_folds = np.array_split(X_train, num_folds)
    y_train_folds = np.array_split(y_train, num_folds)
    # 计算每种K值
    for k in k_choices:
        k_accuracy[k] = []
        # 每个K值分别计算每份数据集作为测试集时的正确率
        for index in range(num_folds):
            # 构建数据集
            X_te = X_train_folds[index]
            y_te = y_train_folds[index]
            X_tr = np.reshape(X_train_folds[:index] + X_train_folds[index + 1:], (X_train.shape[0] * (num_folds - 1) / num_folds, -1))
            y_tr = np.reshape(y_train_folds[:index] + y_train_folds[index + 1:], (X_train.shape[0] * (num_folds - 1) / num_folds))
            # 预测结果
            classify = KNearestNeighbor()
            classify.train(X_tr, y_tr)
            y_te_pred = classify.predict(X_te, k=k)
            accuracy = np.sum(y_te_pred == y_te) / float(X_te.shape[0])
            k_accuracy[k].append(accuracy)

    for k, accuracylist in k_accuracy.items():
        for accuracy in accuracylist:
            print("k = %d, accuracy = %.3f" % (k, accuracy))
  • 训练评估完整数据模型

当我们确定了超参以后,我们就可以将完整的数据进行训练了,由于KNN在数据集比较大的时候(图片的数据维度很大)计算量特别大,所以我将5000个数据作为完整训练集,500个数据作为测试集(50000个训练集和10000个训练集在我的电脑不能正常运行,内存崩溃)。在上述数据中我得到的结果是正确率为28.2%,虽然识别率比较低,但这应该是个比较正常的结果,我们在使用KNN识别图片之前就应该明白它的识别率不会很高

 

  • 附代码用到的numpy函数介绍

numpy.bincount

numpy.sum

numpy.flatnonzero

matplotlib.pyplot.subplot(XXX):

numpy.argsort

numpy.min/max

  • 遇到的坑

首先是load数据集的时候 注意python3是没有pickle模块的 要import _pickle

然后load后有人会报数据集字节的问题,这个要加入

最坑的是转换数据的时候一定要看好np的shape到底是多少

我今天down别人的代码 没有将图片压成一维向量结果在knn代码中老是报index溢出的问题,一定要注意呀。

  • 图像KNN分类核心笔记知乎

图像分类器​​​​​​​

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值