CS231N的第一次作业,因为是第一次用PY来写作业,对NP的很多函数还不是很熟悉,所以比较吃力。
第一部分是写knn classifier的四个函数。第一个是二重循环,第二个是预测函数,第三个是一重循环,第四个是零循环。KNN基本原理本来比较简单,就是算出测试点和样本点的距离,然后选出距离最近的k个点,然后这k个点里面哪个点出现次数最多。但是实际上用PY的时候,会用到np的函数,包括向量化的一些技巧,所以对我这种初学者还是比较吃力的。
1.二重循环
distaces=np.sqrt(np.sum(np.square(self.X_train[j]-X[i])))
dists[i][j]=distaces
比较好解释,就是先让相减,平方,求和,开方
2.预测函数
for i in range(num_test):
closest_y = []
distances=dists[i,:]
indexes = np.argsort(distances)
closest_y=self.y_train[indexes[:k]]
count = np.bincount(closest_y)
y_pred[i] = np.argmax(count)
这里用到了argsort和argmax函数
np.argsort(a,axis,kind=“quicksort”,order=“None”) 返回与a同样大小的一个array,其中返回array的值表示原数组的下标,array的下标表示其对应值在原数组的从小到大第几个。
np.argmax(a,axis,out) 返回这个数组里面最大值的下标
3.一重循环
distances = np.sqrt(np.sum(np.square(self.X_train - X[i]),axis = 1))
dists[i, :] = distances
这个和二重循环差不多一个思想,只不过这里用到了python的广播(broadcast)原则。大概就是,python在按位运算中,会自动复制其中一些矩阵长度为1的维度,使等式能够正常进行运算
4.无循环
M=np.dot(X,self.X_train.T)
xi=np.sum(np.square(X),axis=1).reshape([500,1])
# print(xi.shape)
yj=np.sum(np.square(self.X_train),axis=1).reshape([5000,1])
dists=np.sqrt(xi+np.array(yj).T-2*M)
这个无循环的分类器和之前的都不一样。这里用了这样一个式子
所以,可以直接用上述代码,两次broadcast和一次矩阵乘法,计算出dists矩阵。
第二部分是SVM
1.首先是svm_loss_naive函数,定义如下
def svm_loss_naive(W, X, y, reg):
dW = np.zeros(W.shape) # initialize the gradient as zero
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
#矩阵乘法的求导还是要加强
#margin>0,说明有误差,而从L的定义式可以看出,在margin>0的情况下,L就是一个简单和式,dL/dWj=X[0:49000]
#j系数为正
dW[:,j]+=X[i]
#yi的系数为负
dW[:,y[i]]+=(-1*X[i])
loss /= num_train
dW /= num_train
loss += reg * np.sum(W * W)
dW += 2*reg * W
return loss, dW
返回一个这个W对应的总loss值,以及dW(L对W求导结果,W下降方向)。值得注意的是,一开始我在dW处写的是dW+=2regnp.sum(W),明显错误,这也反映了对矩阵乘法求导的不敏感,还需要多思考与实践。
2.编写svm_loss_vectorized函数
这个函数就是将loss矩阵直接与构造的“正确矩阵”相减,并进行相关操作。最后可以看到,事件缩短了成原来的近百分之一,这也反映了python对向量运算速度很快(也算是python这个语言的特性叭)
但是编写这个程序的时候出现了困难。。。我实在不知道这个方法怎么更新值,所以说看了一下网上的source code。
def svm_loss_vectorized(W, X, y, reg):
loss = 0.0
dW = np.zeros(W.shape) # initialize the gradient as zero
num_train=X.shape[0]
num_classes = W.shape[1]
scores=X.dot(W)
correct_scores=scores[np.arange(num_train),y]
correct_scores=np.reshape(np.repeat(correct_scores,num_classes),(num_train,num_classes))
#correct_scores的每一行都是一样的,是正确的分数
margin=scores+1.0-correct_scores
#对应相减的地方会是1,则置零
margin[np.arange(num_train),y]=0
#margin现在是loss的初级,要得到loss只需要将正的项加起来
loss=(np.sum(margin[margin>0]))/num_train
loss+=reg*np.sum(W*W)
#下面求导实在看不懂了。。
margin[margin>0]=1
margin[margin<=0]=0
row_sum = np.sum(margin, axis=1) # 1 by N
margin[np.arange(num_train), y] = -1*row_sum
dW += np.dot(X.T, margin) # D by C#
#这里我看不懂。。。。应该是矩阵乘法的求导
#除以N再正则化
dW/=num_train
dW+=2*reg*W
#一开始这里写的np.sum(W)报错了,不应该这么写
return loss, dW
3.训练函数 svm.train()
这里是用的SGD,就是将随机抽取原训练集的一部分做梯度下降。
def train(self, X, y, learning_rate=1e-3, reg=1e-5, num_iters=100,
batch_size=200, verbose=False):
num_train, dim = X.shape
num_classes = np.max(y) + 1 # assume y takes values 0...K-1 where K is number of classes
if self.W is None:
self.W = 0.001 * np.random.randn(dim, num_classes)
# Run stochastic gradient descent to optimize W
loss_history = []
for it in range(num_iters):
X_batch = None
y_batch = None
randi = np.random.choice(len(X),batch_size,replace=True)
X_batch = X[randi]
y_batch = y[randi]
# evaluate loss and gradient
loss, grad = self.loss(X_batch, y_batch, reg)
loss_history.append(loss)
# perform parameter update
self.W += (-grad * learning_rate)
if verbose and it % 100 == 0:
print('iteration %d / %d: loss %f' % (it, num_iters, loss))
return loss_history