初识图像分类——K近邻法(cs231n assignment)

作者:非妃是公主
专栏:《计算机视觉》
个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩
在这里插入图片描述

专栏系列文章

Cannot find reference ‘imread‘ in ‘init.py‘

error: (-209:Sizes of input arguments do not match) The operation is neither ‘array op array‘ (where

cs231n-2022-01 Assignments1-numpy的使用

ModuleNotFoundError: No module named ‘cs231n‘

图像分类任务

图像可以理解为一个矩阵,如果是彩色图像,就可以理解为一个3层的矩阵,比如3×64×64的RGB图像。每个像素点的值为0~255,图像分类的任务就是通过这个图像矩阵作为输入,输出它所属不同类别的标签。

图像分类所面临的挑战

因此,根据以上图像分类任务的要求不难发现,图像分类具有很多挑战:
① 从不同的角度来看,一个物体是不同的。
② 不同的缩放比例,一个物体的大小也是不同的。
③ 不同的形状,一个物体可能有不同的形状,比如躺着的小猫和站着的小猫。
④ 分类目标可能是被遮挡的。
⑤ 光照对物体像素的影响是十分严重的。同一个物体不同光照下,像素差异很大。
⑥ 背景掩护,待分类的对象可能和其背景非常相似,使得他们很难被识别出来。
⑦ 许多物体存在较大的类内差异,比如都是椅子,但不同的椅子外形看起来差别很大。

在这里插入图片描述

应对策略

对于以上挑战,采用传统意义上的算法是很难进行准确的分类的,我们无法将这个物体到底属于哪一类用代码描绘出来,所以需要采用数据驱动的方式(Data-driven approach)——它就像我们教小孩子一样,不是直接告诉它,什么是小猫、小狗、杯子……而是给它展示,这就是小猫、小狗、杯子,相同的道理,我们通过某种学习算法来学习这些样例,学习到每种类别的图像到底是什么样子的,这就是数据驱动的方法。请添加图片描述
四个视觉类别的训练集例子。实际情况中,可能会有数千的类别和数以千万计的图像样本。

图像分类步骤

学习算法主要分为以下几个步骤:
① 输入(Input):学习算法的输入是一个N张图片的集合,其中,每张图片都标有K个不同类别中的一个,称为标签。我们把这N张图片的集合和标签的整体称为训练集。
② 学习(Learning):通过训练集让学习算法学到每一个类别到底是什么样子的。这也被叫做训练(training)或者学习(learning)
③ 评估(Evalution):最后,通过一个在训练过程中没有用到的图像数据集(测试集),来预测(predict)它们的标签,将预测的结果与测试集的真实标签(ground truth)比对评估模型的学习效果。

最近邻分类

数据集

我们使用CIFAR-10 dataset,这个数据集包含了60000张3×32×32的图像,每个图像被分成了10类(飞机、汽车、鸟等)。这60000张图像被分为50000张的训练集和10000张测试集。

左图:CIFAR-10数据集中的一些样例. 右图:第一列展示了测试集中10个类别的图像,后面的展示了训练集中与这10个图像最相近的图像。

L1距离

从图中可以看出,10张测试图像中只有3张图片是分类正确的,即:最相近的图片和测试图像相同,其它7张图片是分类错误的(随机分类的正确率在10%)。例如第8个样例,训练集中最相近的图像的标签是车,但实际上测试图像是一个马的头部。因为两张图片都具有相同的黑色背景,这使得两张图片的相似度较大。
两张图片的差异就是通过对应像素点之间的差值来衡量的——L1距离(L1 distance):
d 1 ( I 1 , I 2 ) = ∑ p ∣ I 1 p − I 2 p ∣ \boldsymbol{d_1(I_1,I_2)=\sum\limits_p|I_1^p-I_2^p|} d1(I1,I2)=pI1pI2p
其中 I 1 , I 2 \boldsymbol{I_1,I_2} I1,I2表示两张图像对应像素点展开的向量,p表示像素点的数量。计算过程如下图:
请添加图片描述
展开向量:

Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/') # a magic function we provide
# flatten out all images to be one-dimensional
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) # Xtr_rows becomes 50000 x 3072
Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows becomes 10000 x 3072

通过以上代码,我们将图像矩阵转化为了3072维度的向量。
通过下面代码可以训练并评估一个分类器

nn = NearestNeighbor() # create a Nearest Neighbor classifier class
nn.train(Xtr_rows, Ytr) # train the classifier on the training images and labels
Yte_predict = nn.predict(Xte_rows) # predict labels on the test images
# and now print the classification accuracy, which is the average number
# of examples that are correctly predicted (i.e. label matches)
print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )

下面为分类器内部代码的实现,其中

import numpy as np

class NearestNeighbor(object):
  def __init__(self):
    pass

  def train(self, X, y):
    """ X is N x D where each row is an example. Y is 1-dimension of size N """
    # the nearest neighbor classifier simply remembers all the training data
    self.Xtr = X
    self.ytr = y

  def predict(self, X):
    """ X is N x D where each row is an example we wish to predict label for """
    num_test = X.shape[0]
    # lets make sure that the output type matches the input type
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # loop over all test rows
    for i in range(num_test):
      # find the nearest training image to the i'th test image
      # using the L1 distance (sum of absolute value differences)
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
      min_index = np.argmin(distances) # get the index with smallest distance
      Ypred[i] = self.ytr[min_index] # predict the label of the nearest example

    return Ypred

通过下面代码计算L1距离

distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)

其中对,得到的距离进行可视化如下灰度图,越亮的位置代表L1值越大,距离越大:
请添加图片描述
其中每一行代表一个测试样例(0~500)到它的训练样本的距离(0~5000)。
实现NearestNeighbor类后,在colab上运行上面代码,可以看到分类器的分类正确率在27%左右。
在这里插入图片描述

L2距离

有不同的距离来计算两个向量直接按的距离,另一个比较常用的选择就是L2距离(L2 distance)
d 2 ( I 1 , I 2 ) = ∑ p ( I 1 p − I 2 p ) 2 \boldsymbol{d_2(I_1,I_2)=\sqrt{\sum\limits_p(I_1^p-I_2^p)^2}} d2(I1,I2)=p(I1pI2p)2
这次,对两个向量的差先平方,再求和,最后开放,不再同L1距离一样直接将两个向量的差取绝对值然后求和。

distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))

L1 vs. L2

由于L2距离存在一个平方的过程,这会使得许多中等大小的数变成一个非常大的数,因此,两个向量之间的差距被放大。

K-近邻分类

根据K-近邻算法,以及上述定义的损失函数,可以得到如下结果,关于K-近邻算法的具体说明可以参考机器学习08:最近邻学习,最终得到的分类结果,如下:
在这里插入图片描述
从图中不难发现,最近邻(1-近邻)算法的边角十分分明,也就是说,会存在过拟合的情况,而5-近邻过拟合的情况得到了一定的缓解!其中空白区域部分的面积为2:2:1的情况,这种情况下,由于存在两个最大可能的类别,因此分类器无法判定到底属于哪一类,所以不去进行分类。

矩阵运算与循环运算效率比较

值得注意的是,在进行矩阵运算的时候,由于可以利用GPU的并行运算的特性,采用矩阵运算可以大大加快模型的求运行速度,对于KNN算法,采用矩阵运算、一层循环、两层循环对算法分别进行实现后,如下:

# Let's compare how fast the implementations are
def time_function(f, *args):
    """
    Call a function f with args and return the time (in seconds) that it took to execute.
    """
    import time
    tic = time.time()
    f(*args)
    toc = time.time()
    return toc - tic

two_loop_time = time_function(classifier.compute_distances_two_loops, X_test)
print('Two loop version took %f seconds' % two_loop_time)

one_loop_time = time_function(classifier.compute_distances_one_loop, X_test)
print('One loop version took %f seconds' % one_loop_time)

no_loop_time = time_function(classifier.compute_distances_no_loops, X_test)
print('No loop version took %f seconds' % no_loop_time)

# You should see significantly faster performance with the fully vectorized implementation!

# NOTE: depending on what machine you're using, 
# you might not see a speedup when you go from two loops to one loop, 
# and might even see a slow-down.

效果如下:

Two loop version took 38.739486 seconds
One loop version took 23.500014 seconds
No loop version took 0.336616 seconds

可以很明显的看出,矩阵运算效率远高于循环运算!因此,后续在处理图像的时候我们应该多使用矩阵运算以利用GPU的并行能力。

超参数的选择

由于K近邻算法中的K值属于一个超参数,因此需要进行选择。而选择合适的超参数是采用实验的方法,在测试集上运行模型,观测哪一个超参数下模型的准确率最高,代码如下:

num_folds = 5
k_choices = [1, 3, 5, 8, 10, 12, 15, 20, 50, 100]

X_train_folds = []
y_train_folds = []
################################################################################
# TODO:                                                                        #
# Split up the training data into folds. After splitting, X_train_folds and    #
# y_train_folds should each be lists of length num_folds, where                #
# y_train_folds[i] is the label vector for the points in X_train_folds[i].     #
# Hint: Look up the numpy array_split function.                                #
################################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****

# split self.X_train to 5 folds
avg_size = int(X_train.shape[0] / num_folds) # will abandon the rest if not divided evenly.
for i in range(num_folds):
    X_train_folds.append(X_train[i * avg_size : (i+1) * avg_size])
    y_train_folds.append(y_train[i * avg_size : (i+1) * avg_size])

# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****

# A dictionary holding the accuracies for different values of k that we find
# when running cross-validation. After running cross-validation,
# k_to_accuracies[k] should be a list of length num_folds giving the different
# accuracy values that we found when using that value of k.
k_to_accuracies = {}


################################################################################
# TODO:                                                                        #
# Perform k-fold cross validation to find the best value of k. For each        #
# possible value of k, run the k-nearest-neighbor algorithm num_folds times,   #
# where in each case you use all but one of the folds as training data and the #
# last fold as a validation set. Store the accuracies for all fold and all     #
# values of k in the k_to_accuracies dictionary.                               #
################################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****

for k in k_choices:
    accuracies = []
    print(k)
    for i in range(num_folds):
        X_train_cv = np.vstack(X_train_folds[0:i] + X_train_folds[i+1:])
        y_train_cv = np.hstack(y_train_folds[0:i] + y_train_folds[i+1:])
        X_valid_cv = X_train_folds[i]
        y_valid_cv = y_train_folds[i]
        
        classifier.train(X_train_cv, y_train_cv)
        dists = classifier.compute_distances_no_loops(X_valid_cv)
        accuracy = float(np.sum(classifier.predict_labels(dists, k) == y_valid_cv)) / y_valid_cv.shape[0]
        accuracies.append(accuracy)
    k_to_accuracies[k] = accuracies

# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****

# Print out the computed accuracies
for k in sorted(k_to_accuracies):
    for accuracy in k_to_accuracies[k]:
        print('k = %d, accuracy = %f' % (k, accuracy))

输出结果如下:

1
3
5
8
10
12
15
20
50
100
k = 1, accuracy = 0.263000
k = 1, accuracy = 0.257000
k = 1, accuracy = 0.264000
k = 1, accuracy = 0.278000
k = 1, accuracy = 0.266000
k = 3, accuracy = 0.239000
k = 3, accuracy = 0.249000
k = 3, accuracy = 0.240000
k = 3, accuracy = 0.266000
k = 3, accuracy = 0.254000
k = 5, accuracy = 0.248000
k = 5, accuracy = 0.266000
k = 5, accuracy = 0.280000
k = 5, accuracy = 0.292000
k = 5, accuracy = 0.280000
k = 8, accuracy = 0.262000
k = 8, accuracy = 0.282000
k = 8, accuracy = 0.273000
k = 8, accuracy = 0.290000
k = 8, accuracy = 0.273000
k = 10, accuracy = 0.265000
k = 10, accuracy = 0.296000
k = 10, accuracy = 0.276000
k = 10, accuracy = 0.284000
k = 10, accuracy = 0.280000
k = 12, accuracy = 0.260000
k = 12, accuracy = 0.295000
k = 12, accuracy = 0.279000
k = 12, accuracy = 0.283000
k = 12, accuracy = 0.280000
k = 15, accuracy = 0.252000
k = 15, accuracy = 0.289000
k = 15, accuracy = 0.278000
k = 15, accuracy = 0.282000
k = 15, accuracy = 0.274000
k = 20, accuracy = 0.270000
k = 20, accuracy = 0.279000
k = 20, accuracy = 0.279000
k = 20, accuracy = 0.282000
k = 20, accuracy = 0.285000
k = 50, accuracy = 0.271000
k = 50, accuracy = 0.288000
k = 50, accuracy = 0.278000
k = 50, accuracy = 0.269000
k = 50, accuracy = 0.266000
k = 100, accuracy = 0.256000
k = 100, accuracy = 0.270000
k = 100, accuracy = 0.263000
k = 100, accuracy = 0.256000
k = 100, accuracy = 0.263000

在这里插入图片描述

代码

为了逻辑的连续性,本文省略了一些细节,用到的详细代码已放置到代码仓库,github仓库地址:https://github.com/ManYufei888/cs231n

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 36
    评论
### 回答1: CS231n 第三次作业的内容包括使用深度学习来完成图像分类任务。具体来说,包括使用卷积神经网络 (CNN) 来训练图像分类器,并使用预训练网络 (pre-trained network) 来进行微调 (fine-tuning)。还可能包括使用数据增强 (data augmentation) 来提高模型的泛化能力,以及使用可视化工具来理解 CNN 的内部工作原理。 ### 回答2: CS231n作业3是斯坦福计算机视觉课程的第三个作业,该作业涵盖深度学习模型的生成和推理,以及如何创建生成性对抗网络(GAN)。 此次作业主要涉及三个任务: 1. Image Captioning 图片说明任务,也是本次作业的第一个任务。仔细研读与Image Captioning任务相关的代码,并以此为基础,使用RNN中的LSTM层来生成图像的描述。这个一项技术非常实用,可以让图片在搜索引擎中体现出来,提高用户使用体验。学生需要研究encoder和decoder的实现,了解他们生成文本的方。最终,利用逆向传播算法(反向传播算法)训练神经网络,学习生成图像标题。 2. Generative Adversarial Networks 生成对抗网络是GAN。G和D两个模型构成了GAN模型,是一种强大的生成模型。在这个任务中,学生需要学习如何训练GAN模型,以生成看起来像真实图像的图像样本。这是一个非常复杂的问题,需要合理运用损失函数,较好的优化GAN的训练中表现良好。 3. Neural Style Transfer 神经风格迁移属于图像处理范畴,学生需要实现单张图像的神经风格迁移。方是,利用一些随机初始化参数,以迭代方式计算输入图像的内容特征和样式特征。最终,需要使用反向传播算法来搜索图像处理的最佳策略。 总之,本次作业难度系数较大,但同时学生在操作过程中也能够学到很多使用深度学习技术解决实际问题的方,提高对于深度学习的理解、掌握和技能。同时,希望学生能够在本次作业中体验到收获成功带来的成就感。 ### 回答3: CS231n Assignment 3是斯坦福大学计算机视觉课程中的一项作业,主要涉及深度强化学习。它由三个部分组成:Q-learning,Policy Gradients和Actor-Critic。 在Q-learning部分,学生需编写代码来实现Q-learning算法,在智能体与环境之间折衷时间、奖励和学习。Q-learning是一种基于回合的控制算法,它基于时间步长内的奖励和马尔科夫决策过程。在此过程中,学生需要选择一个平衡折衷,以便在训练智能体时最大限度地提高其性能。 在Policy Gradients部分,学生需实现策略梯度算法,该算法通过学习如何最大化预期回报来优化策略。在此步骤中,学生还将学习如何使用梯度上升确定策略参数。策略梯度算法基于沿向目标策略方向更新参数的概念。 在Actor-Critic部分,学生需实现Actor-Critic算法,这是一种Q-learning和策略梯度算法的组合。该算法包括两个部分:演员即策略,用于决定智能体应采取的行动,评论家即Q值估算器,根据当前状态值和行动值返回平均价值。这两个部分相互作用,以帮助智能体学习最佳策略。 总的来说,CS231n Assignment 3是一项具有挑战性的作业,涉及深度强化学习的各个方面,需要学生掌握许多概念和算法,并将它们应用于代码实现中。完成此项作业需要时间和耐心,但完成后,学生将获得对深度强化学习的深刻理解,这对于今后从事计算机视觉工作将大有裨益。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherries Man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值