CS231N assignment1——KNN

k-Nearest Neighbor (kNN) exercise

题目提示:

The kNN classifier consists of two stages:

  • During training, the classifier takes the training data and simply remembers it
  • During testing, kNN classifies every test image by comparing to all training images and transfering the labels of the k most similar training examples
    The value of k is cross-validated

关键点:

K近邻是一种很直观的想法:从训练数据中找出K个最相近的点,然后投票计算结果。
将上述步骤进行拆分,那便是1)定义相似函数的计算;2)分数的计算;
我认为这个任务里面有两个很值得学习和思考的地方,一个是它对整个机器学习任务的流程定义,另外一个便是它启示我们应该如何使用矩阵快速运算。下面的总结就是从这两个角度出发。

矩阵运算:

  • 两层循环运算
  • 一层循环运算
  • 无循环运算

距离公式: d2(I1,I2)=di(Idi1Idi2)2 d 2 ( I 1 , I 2 ) = ∑ d i ( I 1 d i − I 2 d i ) 2 ,I1,I2指的是两张需要比较相似度的图片,使用的是L2距离,考虑一张图片的向量表示方式有D个维度,最后的计算便是可以视为逐个维度的分量差的平方的求和项最后开平方。

计算目的:这个矩阵运算目的是计算出当前需预测的样本集和当前所有样本的距离或者说相似度,最后会根据这个相似度高的取它的标签作为参考标签集,最后的标签是由这个参考标签集里得出。

运算矩阵形式:矩阵的每一行是一个样本,行中的每一列是一个维度的数据。
这里写图片描述
(1)两层循环运算
如果是两层的话就是比较比较直观的想法,先以预测样本集为外层遍历,然后到训练样本集为第二层中计算每一个训练样本和预测样本的距离或者说相似度,保存计算结果。若定义一共有N个预测目标,即需要预测的样本数,M为训练集的样本数,最后的运算结果应该是N*M的矩阵,矩阵的一行表示每个预测样本分别和训练样本集的相似度。

    num_test = X.shape[0]
    num_train = self.X_train.shape[0]  #得到训练样本的数目
    dists = np.zeros((num_test, num_train))  #定义结果矩阵
    for i in xrange(num_test):  #遍历预测样本集
      for j in xrange(num_train):  #遍历训练样本集
        #####################################################################
        # TODO:                                                             #
        # Compute the l2 distance between the ith test point and the jth    #
        # training point, and store the result in dists[i, j]. You should   #
        # not use a loop over dimension.                                    #
        #####################################################################
        train_vec = self.X_train[j]  #得到遍历到的训练样本
        test_vec = X[i]  #得到遍历到的测试样本
        dists[i,j] = np.sqrt(np.sum(np.square(train_vec-test_vec))) #保存结果
        #####################################################################
        #                       END OF YOUR CODE                            #
        #####################################################################
    return dists

(2)一层循环:
其实一层循环和二层循环的想法是一致的,区别的是它利用了广播机制,让一个待预测样本一次性就和一整个训练样本矩阵进行了运算,节省了这里的一层循环。
这里先简单介绍一下广播机制:1)广播机制是numpy中提供的对两个形状不同的阵列进行数学计算的处理机制。较小的阵列“广播”到较大阵列相同的形状尺度上,使它们对等以可以进行数学计算。广播提供了一种向量化阵列的操作方式,因此Python不需要像C一样循环。广播操作不需要数据复制,通常执行效率非常高。2)广播机制需要满足列相等或者行相等(另一维对应为1)或者其中一个为标量
列相等:

import numpy as np
a = np.array([[1,2,3],[4,5,6]])
b = np.array([1,2,3])
result = a-b

行相等:

import numpy as np
a = np.array([[1,2,3],[4,5,6]])
b = np.array([[1],[2]])
result = a-b

标量:

import numpy as np
a = np.array([[1,2,3],[4,5,6]])
b = np.array([1])
#b=1 the same result
result = a-b

(3)无循环:
对于无循环的求距离的方式就和上面的不一样了,想法还是还是差不多,但是如果是不需要循环的就需要把其中的循环去掉,为什么会有循环,根本原因就在于你需要去遍历样本,参考减少内层循环的方式,它是因为让一个样本通过广播机制运算替代循环,那有没有类似的方法能够不遍历样本,或者说也让它们每一个预测样本和训练样本分别比较计算相似度的操作也缩减至一次,或者说一次性就能完成,下面就是整个简化的想法:

d2(I1,I2)=di(Idi1Idi2)2=di((Idi1)2+(Idi2)22Idi1Idi2)=di((Idi1)2+di(Idi2)2di2Idi1Idi2) d 2 ( I 1 , I 2 ) = ∑ d i ( I 1 d i − I 2 d i ) 2 = ∑ d i ( ( I 1 d i ) 2 + ( I 2 d i ) 2 − 2 ∗ I 1 d i ∗ I 2 d i ) = ∑ d i ( ( I 1 d i ) 2 + ∑ d i ( I 2 d i ) 2 − ∑ d i 2 ∗ I 1 d i ∗ I 2 d i )

首先要明确 I1 I 1 , I2 I 2 都是相同维度的向量,然后记有M个预测样本,N个训练样本的话,结果应该是一个M*N的矩阵,对应的是每个预测样本和训练样本的相似度。
上面的 2Idi1Idi2 2 ∗ I 1 d i ∗ I 2 d i 最后体现出来的应该是样本间的点乘,考虑到我们是两个样本集间的运算,单考虑这个结果集的话就应该是两个矩阵的相乘,也就是预测样本和训练样本矩阵的相乘,这时候的训练样本矩阵应该要进行转置才能够让它们的运算维度合法即第一个矩阵的列等于第二个矩阵的行,其实也就是两个样本对应维度的相乘。
对于 di((Idi1)2 ∑ d i ( ( I 1 d i ) 2 di(Idi2)2 ∑ d i ( I 2 d i ) 2 ,对应到整个样本集里的话在求和后的后续操作上是有所不同的,从一个预测样本来说,它提供的 di((Idi1)2 ∑ d i ( ( I 1 d i ) 2 在列的维度上应该都是一样的,如果记 l1 l 1 是预测样本的,那最后从运算上来说就相当于一个列向量,向量元素为原本预测样本集内的每个元素自己平方求和,然后通过广播机制加到最后的结果集上。如果从一个训练样本的角度上来说的话,就是每个训练样本都会在每一行即每一个样本里做一遍叠加,这不也是广播机制么。所以它俩是分别从列和行两个角度做了一遍广播机制传递结果。
最后便是矩阵相乘的结果乘上-2系数和使用了广播机制的预测样本集向量和训练样本集向量进行运算,最后整体求根号开方。

总的来说,不管是两层一层还是无循环的计算方式,本质上都是会对每一个样本都进行了操作,只不过因为定义了较为高效的矩阵运算,所以希望通过使用这些矩阵运算来代替较为原始的运算,感觉这对于大型矩阵运算还是很有必要的。

代码学习总结:

1、切分好数据集;
2、通过交叉验证法找到较好的超参;
3、通过可视化方式直观上进行判断超参结果

k_choices = [1,2,3]
k_to_accuracies = {1:[1,2,3],2:[4,8,9],3:[10,8,7]}
# plot the raw observations
for k in k_choices:  #k_choices是一个列表[1,2,3], k_to_accuracies 是一个字典,键是k_choices,值是一个数字列表
    accuracies = k_to_accuracies[k]
    plt.scatter([k] * len(accuracies), accuracies)  #scatter画的是散点图,第一个参数是横坐标列表,第二个是纵坐标列表,一一对应,

# plot the trend line with error bars that correspond to standard deviation
accuracies_mean = np.array([np.mean(v) for k,v in sorted(k_to_accuracies.items())])
accuracies_std = np.array([np.std(v) for k,v in sorted(k_to_accuracies.items())])
plt.errorbar(k_choices, accuracies_mean, yerr=accuracies_std) #第一个参数是横坐标,第二个是平均值,最后一个是标准平方差
plt.title('Cross-validation on k')
plt.xlabel('k')
plt.ylabel('Cross-validation accuracy')
plt.show()  #让上面画的图显示出来

这里写图片描述

>>accuracies_mean
>>array([ 2. , 7. , 8.33333333])
>>accuracies_std
>>array([ 0.81649658, 2.1602469 , 1.24721913])

这个是平方差和均值的结果,从上图可以看出errorbar是把均值上上下偏一个平方差然后画一条误差线。然后散点图是标了彩色的点,连起来的是平均值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值