@[TOC](CS231n Assignment 1(一))
这篇文章就用来说说CS231n A1 Q1-Q3主要教的东西,还有计算SVM、Softmax损失函数和梯度的全矢量化(fully-vectorized)代码吧。
CS231n是讲卷积神经网络(Convolutional Neural Network,CNN)图像识别的,当然,不可能一开始就来讲CNN(可能也可以吧),所以前面的几个lecture从简单的图像识别器开始讲起。
A1前三问主要讲了两大类图像识别分别器:KNN (nonlinear classifier中的一种) 和 linear classifier,linear classifier 根据Loss的不同又可以再分成svm loss linear classifer 和 softmax loss linear classfier。
KNN分类器比较简单。它的基本逻辑是,在训练的时候记住所有的训练样本,包括样本的特征和标签。在预测的时候,它计算测试样本到各个训练样本的“距离”,然后根据前 K K K 个最近的训练样本的标签来决定预测的标签是什么。
好比说,我们取 K = 3 K=3 K=3,然后计算出来离测试样本最近的3个样本的标签分别为 1 , 1 , 2 1,1,2 1,1,2,由于这里面 1 1 1 最多,所以测试样本的预测标签是1。
这个算法最关键的地方是计算距离矩阵,也就是各个测试样本到训练样本的距离。如果测试样本的数目是 N t e N_{te} Nte,训练样本的数目是 N t r N_{tr} Ntr,那这个距离矩阵的维度是 N t e × N t r N_{te}×N_{tr} Nte×Ntr,其中 ( i , j ) (i,j) (i,j)位置的元素代表测试样本 i i i与训练样本 j j j的距离。
比如说我们要用 L 2 L^2 L2 范数来表征距离。最简单的计算方法是写两个loop,一个循环test set,一个循环training set,逐一计算距离。当然,这种办法的计算速度是贼慢。
我们可以使用fully-vectorized的代码来加快速度,因为numpy的库已经为我们优化过矩阵运算的速度了。Python代码如下:
X_te_square=np.sum(X**2,axis=1).reshape(num_test,1)
X_tr_square=np.sum(self.X_train**2,axis=1).reshape(1,num_train)
X_te_tr_cross=np.dot(X,self.X_train.T)
dists+=X_te_square
dists+=X_tr_square
dists-=2*X_te_tr_cross
dists=np.sqrt(dists)
原理就是将平方项展开为二次项、交叉项,然后用pyhton numpy自带的broadcast功能将二次项和交叉项拼接回去。
我们可以把每张图片拉长成一个长向量 x ∈ R D x\in R^D x∈RD,这里 D D D是图片的像素点数乘上RGB通道数。一般来说,我们希望训练一个分类器,把图片分为 { 0 , 1 , . . . , C − 1 } \{0,1,...,C-1\} { 0,1,...,C−1}中的一类,这里 C C C 是图片的总类别数。我们的想法是通过一个线性变换 W ∈ R C × D W\in R^{C×D} W∈RC×D,使得
f ( W , x ) = W T x ∈ R C f(W,x)=W^Tx \in R^C f(W,x)=WTx∈RC 也就是说,我们通过参数矩阵 W W W 将样本映射成了对应到 C C C 类的分数。如果我们能找到一个很好的 W W W,使得映射的分数 f ( W , x ) f(W,x) f(W,x) 刚好在每个样本label对应的得分很高,那我们就可以通过这个参数矩阵 W W W 进行图像识别了。
我们怎么评判参数矩阵的好坏呢?直观来说,如果参数矩阵能让 W T x W^Tx WTx 在样本真实标签维度的得分特别高,在其他维度的得分比较低,那这个矩阵 W W W 是不错的。为了定量衡量 W W W 的好坏,下面介绍两种损失函数。
对于某个样本 ( x i , y i ) (x_i,y_i) (xi,yi),它对应的SVM损失是
L i ( W , x i ) = ∑ j ≠ y i m a x ( 0 , W j T x − W y i T x + 1 ) L_i(W,x_i)=\sum \limits_{j\neq y_i} max(0,W_j^Tx-W_{y_i}^Tx+1) Li(W,xi)=j=yi∑max(0,WjTx−WyiTx+1)
导数是:
对应的求loss和grad的代码是:
Inputs:
- W: A numpy array of shape (D, C) containing weights.
- X: A numpy array of shape (N, D) containing a minibatch of data.
- y: A numpy array of shape (N,) containing training labels; y[i] = c means
that X[i] has label c, where 0 <= c < C.
- reg: (float) regularization strength
Returns a tuple of:
- loss as single float
- gradient with respect to weights W; an array of same shape as W
'''
N=X.shape[0]
score=X.dot(W)# N*D D*C = N*C
listr=range(0,N)
margin=score-score[listr,y].reshape(N,1)+1
margin[listr,y]=0
cnt=(margin>0).astype(int)
sum_cnt=np.sum(cnt,axis=1)
margin=margin[margin>0] #N*C
cnt[listr,y]-=sum_cnt
dW=(X.T).dot(cnt)#D*N N*C = D*N
dW/=N
dW+=reg*2*W
loss=np.sum(margin)
loss/=N
loss+=reg * np.sum(W * W)
Return loss,dW
有空再来补解释。
对于某个样本 ( x i , y i ) (x_i,y_i) (xi,yi),它对应的Softmax损失是
L i = − log ( e f y i ∑ j e f j ) L_i = - \log \left( \frac{e^{f_{y_i}}}{\sum_j e^{f_j}} \right) Li=−log(∑jefjefyi)。
导数是:
{ ∇ w y i L i = − x i + e f y i ∑ j e f j x i j = y i ∇ w j L i = e f j ∑ j e f j x i j ≠ y i \left\{\begin{aligned} \nabla_{w_{y_i}} L_i = & -x_i + \frac{e^{f_{y_i}}}{\sum_j e^{f_j}} x_i & j = y_i \\ \nabla_{w_j} L_i = & \frac{e^{f_j}}{\sum_j e^{f_j}} x_i & j \ne y_i \end{aligned}\right. ⎩⎪⎪⎪⎨⎪⎪⎪⎧∇wyiLi=∇wjLi=−xi+∑jefjefyixi∑jefjefjxij=yij=yi
对应的求loss和grad的代码是:
Inputs:
- W: A numpy array of shape (D, C) containing weights.
- X: A numpy array of shape (N, D) containing a minibatch of data.
- y: A numpy array of shape (N,) containing training labels; y[i] = c means
that X[i] has label c, where 0 <= c < C.
- reg: (float) regularization strength
Returns a tuple of:
- loss as single float
- gradient with respect to weights W; an array of same shape as W
'''
N=X.shape[0]
listr=range(0,N)
probs=X.dot(W)
probs-=np.max(probs,axis=1).reshape(N,1)
probs=np.exp(probs)/np.sum(np.exp(probs),axis=1).reshape(N,1)
log_probs_on_label=np.log(probs[listr,y])
loss+=-np.sum(log_probs_on_label)
loss/=N
loss+=reg * np.sum(W * W)
probs[listr,y]-=1
dW+=(X.T).dot(probs)
dW/=N
dW+=reg*2*W
Return loss,dW
有空再来补+1。