Numpy from cs231n assignment1【我的笔记本】

一、k_nearest_neighbor.py

1)compute_distances_two_loops(self, X):

该函数要求用两层循环实现计算测试集和训练集的l2距离计算,核心代码如下:

  def compute_distances_two_loops(self, X):
    """
    Compute the distance between each test point in X and each training point
    in self.X_train using a nested loop over both the training data and the
    test data.

    Inputs:
    - X: A numpy array of shape (num_test, D) containing test data.

    Returns:
    - dists: A numpy array of shape (num_test, num_train) where dists[i, j]
      is the Euclidean distance between the ith test point and the jth training
      point.
    """
    num_test = X.shape[0]
    num_train = self.X_train.shape[0]
    dists = np.zeros((num_test, num_train))
    for i in range(num_test):
      for j in range(num_train):
        dists[i][j]=np.linalg.norm(X[i]-self.X_train[j])
    return dists

需要我填写的代码只有黑体一行:dist[i][j]=np.linalg.norm(X[i]-self.X_train[j]),这个函数几乎不考察任何知识,只考察numpy里的linalg模块里的norm函数。linalg是线性代数(linear algebra)的简称,norm是范数的意思,以上代码计算l2距离,其他距离的计算方式,help(np.linalg.norm)。

 

2)compute_distances_one_loop(self, X):

该函数要求使用一层循环计算训练集与测试集的l2距离,是两层计算的一次优化,两层循环体现了numpy的基本用法,那么一层循环则体现了numpy的进阶用法:

代码如下:

  def compute_distances_one_loop(self, X):
    num_test = X.shape[0]
    num_train = self.X_train.shape[0]
    dists = np.zeros((num_test, num_train))
    for i in range(num_test):
      dists[i,:]=np.linalg.norm(X[i]-self.X_train,axis=1)
    return dists

需要我写的也只是一行黑体代码,和上面的区别在哪里呢?如果把dist[i][j]当成一个数,那么dist[i,:]就是一个行向量,也就是说减少循环的一个方式是向量化,Numpy数值计算的秘籍就在于向量运算和矩阵运算的速度快,那么axis=1这个参数怎么理解呢?

首先来看dist[i,:]的维数是(1,num_train),X[i]的维数是(1,3072),self.X_train的维数是(num_train,3072),那么这里又用到Numpy数值计算的另一个神奇之处了:broadcast,那么X[i]-self.X_train的结果的维数呢?答案是(num_train,3072),在axis=1的配合下就能得到(1,num_train)了,也就是结果的维数,那么norm是怎么计算l2距离的呢?很暴力所有元素平方和然后开根号仅此而已(验证过)

总结:何为向量化,dist[i,:]即为向量化,那么向量化怎么运算?axis=xxx,向量化能干啥?体现numpy的优点,速度快

3)compute_distances_no_loops(self, X):

该函数是上一个函数的优化,要求不使用循环实现计算,也就是说这个的向量化更加彻底或者使用了数学优化技巧:

代码:

def compute_distances_no_loops(self, X):
    num_test = X.shape[0]
    num_train = self.X_train.shape[0]
    dists = np.zeros((num_test, num_train))
    X_mat=(np.ones((num_train,1))*np.sum(np.square(X),axis=1)).T
    X_train_mat=np.ones((num_test,1))*np.sum(np.square(self.X_train),axis=1)
    dists=np.sqrt(X_mat+X_train_mat-2*np.dot(X,self.X_train.T))
    return dists

这个就有点骚气了,不是利用的numpy的技巧而是数学技巧,利用数学推导从而实现优化:参考博客:https://blog.csdn.net/zhyh1435589631/article/details/54236643

 

所以说数学功底还是真正需要的,毕竟numpy之所以能实现数值计算的加速靠的也一定是数学

想对自己说:虽然k最近邻算法和后续学习关系不大,但是我还是认真实现了并且也有过认真思考,也初步理解了向量化的优化理念和axis参数的奥秘。最想对自己说的是,百度参考别人的代码完全可以,因为初使用numpy很多好的特性不会用是正常的,借鉴别人的代码可以让我进步,但是一定要深刻理解才行。以上内容是写完代码后一个星期写的,发现很多东西忘记了,而且没能形成深刻理解,还好此时此刻挽救了即将忘掉的知识。再接再厉!

 

二、linear_svm.py

1)svm_loss_naive(W, X, y, reg):

该函数直接要求使用向量操作,但是仍然使用了两个循环,不过仍然让我这个新手感到醍醐灌顶
def svm_loss_naive(W, X, y, reg):
  dW = np.zeros(W.shape) # initialize the gradient as zero

  # compute the loss and the gradient
  num_classes = W.shape[1]
  num_train = X.shape[0]
  loss = 0.0
  for i in range(num_train): #对于每一个训练样本
    scores = X[i].dot(W)
    correct_class_score = scores[y[i]]
    for j in range(num_classes):
      if j == y[i]:
        continue
      margin = scores[j] - correct_class_score + 1 # note delta = 1
      if margin > 0:
        loss += margin
        dW[:,y[i]]+=-X[i,:]  #行列向量可以直接这样玩
        dW[:,j]+=X[i,:]

  loss /= num_train
  dW /= num_train #梯度也是求的和,也要算个平均值
  # Add regularization to the loss.
  loss += reg * np.sum(W * W)
  dW += reg * W #梯度也要正则化一次

  return loss, dW

要求我写的代码只有黑体的两行,这是第二次使用向量运算了,可以很好的理解dW[:,y[i]]和dW[:,j]这两个显然是列向量,stop!!!不要肯定!!!test一下!

现在再看是不是列向量(斜眼笑),其实不必如此较真,dW[:,y[i]]我觉得理解成列向量是一点毛病没有的,只不过在输出的时候没有按照列向量的形式输出,dW[:,y[i]]+=-X[i,:]其实就是对dW的一列进行的操作。这里也是运用了broadcast,可以自己写个简单的例子验证下。

以上是语言层面的理解,求导原理层面呢?看这段代码:scores = X[i].dot(W),W是作为两个向量点乘的后面一个,所以可以形象的理解成W的每个列向量是每个物体的评分函数,所以在算梯度时也是按照列来算的

2)svm_loss_vectorized(W, X, y, reg):

这里很秀很秀,给我的感觉就是使用numpy带来效率上的提升不仅仅是单单使用numpy的运算函数,更要对原理有深刻的理解,并且能够手动化简一些公式配合使用numpy从而得到效率上最大的提升

代码如下:

def svm_loss_vectorized(W, X, y, reg):

  loss = 0.0
  dW = np.zeros(W.shape) # initialize the gradient as zero


  num_classes = W.shape[1]
  num_train = X.shape[0]
  scores=X.dot(W) # shape (500,10)
  corrent_class_score=scores[np.arange(num_train),y] #shape(500,)
  corrent_class_score=np.reshape(corrent_class_score,(num_train,1)) #shape(500,1)
  #print('########','corrent_class_score',corrent_class_score.shape)
  margins=scores-corrent_class_score+1.0
  margins[np.arange(num_train),y]=0.0
  margins[margins<0]=0.0
  loss=np.sum(margins)/num_train
  loss += reg * np.sum(W * W)
 
  margins[margins>0]=1.0
  row_sum=np.sum(margins,axis=1)
  margins[np.arange(num_train),y]=-row_sum
  dW+=np.dot(X.T,margins)/num_train+reg*W

 

  return loss, dW

黑体代码全是需要我写的,主要还是参考百度上的东西,不过还算颇有心得。首先看语法秀:margins[margins<0]=0.0,这个操作很关键,margins>0得到的是矩阵中所有大于0元素的下标。同理margins[margins>0]=1.0也是如此。求解loss值还好理解,比较难的是求解梯度,后面的四行代码实在不是很好解释,不过关系最大的一句话是:如果一个变量有多个分支。梯度回传时需要累加(sum出现的意义在于此)。

所以我得出结论:数学功底+numpy的各种加速运算才能带来效率上的最大提高。

 

三、

先对自己批评一下:直接看别人写好的代码倒是省事,不用debug了,不用看手册了,这是舍本逐末,不可取,能力都是在debug和看手册中获得的,请改正这一错误。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值