最近开始学习CS231N课程,还是通过自己的理解,将编程中问题或者重点进行记录。
准备阶段
对比了历年CS231N开课情况,发现从2017春季课程开始后,每一年的assignment都没有发生变化,故以下记录都是适用所有作业的。
首先,根据2019年cs231n课程的准备指导,将代码和数据先下载下来。随后,使用jupyter notebook
进行编辑。
所谓磨刀不误砍柴工,在进行作业前,建议花一点时间将numpy库、matplotlib库的使用方法进行阅读,链接:Python Numpy Tutorial
本次作业分为五个部分:knn、SVM、Softmax classifier、2层神经网络、Higher Level Representations: Image Features。
k-Nearest Neighbor classifier
通过阅读knn文件,可以知道在knn中调用了k_nearest_neighbor.py文件,第一部分首先完成两重循环计算测试集数据到训练集数据的欧式距离。
k_nearest_neighbor.py中的compute_distances_two_loops(self, X)函数
for i in range(num_test):
for j in range(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, nor use np.linalg.norm(). #
#####################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
# 使用numpy库对两个向量进行欧几里得距离计算
dists[i,j] = np.sqrt(np.sum(np.square(X[i] - self.X_train[j])))
pass
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
通过之前的学习,可以使用numpy进行代码完成,在这里我是偷了懒,将代码写成了一行,但是我相信这段代码还是很好理解的。首先在两重循环中将测试集和训练集各一行向量通过减法运算后,平方,然后求和,最后开根号,这样就完成了欧式距离计算,运行结果如图:
嵌入在knn文件中的思考问题见最后。
接下来,继续阅读knn代码,可以看到第二部分需要完成的是预测函数predict_labels()
首先,在for循环中,选择与第i个点最近的k个元素的下标,并找到其对应训练数据集的分类值。然后选择出现类别最高的一个元素作为预测分类。
使用到了几个新的函数,具体使用方法见注释。
k_nearest_neighbor.py中的predict_labels()函数 [只展示for循环]
for i in range(num_test):
# A list of length k storing the labels of the k nearest neighbors to
# the ith test point.
closest_y = []
#########################################################################
# TODO: #
# Use the distance matrix to find the k nearest neighbors of the ith #
# testing point, and use self.y_train to find the labels of these #
# neighbors. Store these labels in closest_y. #
# Hint: Look up the function numpy.argsort. #
#########################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
# 找到最小的k个值的下标
min_idxs = np.argsort(dists[i])[0:k]
# 选择训练数据标签的值
closest_y = self.y_train[min_idxs]
pass
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
#########################################################################
# TODO: #
# Now that you have found the labels of the k nearest neighbors, you #
# need to find the most common label in the list closest_y of labels. #
# Store this label in y_pred[i]. Break ties by choosing the smaller #
# label. #
#########################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
# 使用np.bincount()和np.argmax()函数来实现
# np.bincount()返回一个列表,每一位值为Yi,意思是i这个数在列表中出现的次数为Yi次。
# np.argmax()返回列表中最大值的下标
y_pred[i] = np.argmax(np.bincount(closest_y))
pass
接下来实现一重循环计算距离。需要注意的是,内层循环去掉后,只能直接对x_train操作,需要注意求和时,注明求和的纬度。
例如np.sum(X,axis= 1)是在行维度上进行求和,即把每一行加起来;np.sum(X , axis = 0)是在列维度上进行求和,即把每一列加起来。
k_nearest_neighbor.py中的compute_distances_one_loops(self, X)函数 [只展示for循环]
for i in range(num_test):
#######################################################################
# TODO: #
# Compute the l2 distance between the ith test point and all training #
# points, and store the result in dists[i, :]. #
# Do not use np.linalg.norm(). #
#######################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
# 求和是需注意,这次是对列纬度上的求和,即把每一列加起来后再开根号
dists[i,:] = np.sqrt(np.sum(((X[i,:] - self.X_train) ** 2 ) , axis=1))
pass
接下来实现完全向量化代码,不得不说这部分实现起来和matlab不一样,原因在于numpy引入了broadcast机制,简单说就是当操作的元素维度不匹配时,广播机制开始将一个矩阵向着维度更多的矩阵填充。具体可参考这篇文章:https://www.runoob.com/numpy/numpy-broadcast.html
k_nearest_neighbor.py中的compute_distances_no_loops(self, X)函数
# 要计算L2距离,使用向量化代码,则只能将完全平方项展开,然后加起来
# 其中乘法部分注意维度
# 使用reshape触发broadcast
dists += np.sum(X ** 2, axis=1).reshape(num_test,1)
dists += np.sum(self.X_train ** 2, axis=1).reshape(1,num_train)
dists