本篇博客是对斯坦福大学课程cs231n中第一个作业assignment1中对k-Nearest Neighbor部分的解答,解答过程使得对Python库numpy的应用更加熟悉,以及对train/val/test集有了更深的了解,对交叉验证法有了一定的掌握。下面直接进入正题:
KNN算法基本思路:通过测试集与训练集的比较,作业中用的衡量标准是欧氏距离。比如,对于作业中的数据集,其中测试集(test)X是一个[500,3072]的矩阵,其中每一行都是一幅32*32*3大小的图片拉伸成的一个向量,共500张图片,训练集(train)X_train是一个大小为[5000,3072]的矩阵,同理有5000张图片。KNN所做的就是对X中的每一行与X_train中的5000行依次计算欧氏距离,并将距离保存在矩阵dists[500,5000],dists[i,j]即表示X[i,:]与X_train[j,:]的距离。若k=1,对于X中第i幅图,则选取所计算的5000个距离中的最小值,该最小值所对应的X_train中那幅图的类别就作为第i幅图的类别。若k=5,则选取距离最小的5个,其中对应的5张训练集的图片按照其出现次数最多的类别则作为第i幅图的类别。
1、也是在ipython notebook直接调试执行的,很方便,建议大家试一试。关于cifar-10的下载就不说了,打开ipython notebook之后再打开knn.ipynb。下面开始完成作业:首先将所下载的cifar-10数据集所在的路径添加到cifar10_dir中,比如我的:cifar10_dir = '/home/xxx/mydata/CS231n/assignment1/cs231n/datasets/cifar-10-batches-py'。
2、完成距离计算部分。
打开s231n/classifiers/k_nearest_neighbor.py文件
(1)compute_distances_two_loops()函数
def compute_distances_two_loops(self, X):
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):
dists[i,j] = np.sqrt(np.sum(np.square(self.X_train[j,:] - X[i,:]))) #欧式距离的计算,利用两个循环,将距离存入
pass #dists中,该部分比较简单。
return dists
(2)compute_distances_one_loops()函数
直接写距离计算部分,其他都和两个循环函数中相同,在k_nearest_neighbor.py文件中都可以看到。
dists[i,:] = np.sqrt(np.sum(np.square(self.X_train[:,:] - np.tile(X[i,:],(self.X_train.shape[0],1))),1).T)
#主要是理解取出X中第i行X[i,:],将其扩展为[5000,3072],使其可以与X_train直接相减,具体理解可以看np.tile()函数,至于 #转置什么的,希望可以拿两个小一点的矩阵推导一下,这样可以使得对Python向量化计算更加熟练。本部分:np.tile()的理 #解,向量化计算的学习
(3)compute_distances_no_loops()函数
dists=np.sqrt(np.tile(np.sum(self.X_train*self.X_train,1),(X.shape[0],1))+np.tile(np.sum(X*X,1),(self.X_train.shape[0],1)).T-2*np.dot(X,self.X_train.T))
#无循环的欧式距离的计算花费了好长时间,主要是因为对Python的向量化操作不熟悉。主要思路是:(A-B)^2=A^2+B^2-2*AB。里面还需要一些扩展和转置操作,还是希望可以推导一下,这里也简单解释一下每个部分。self.X_train*self.X_train与X*X即是计算出A^2与B^2。Python中的相乘*符号是元素对应相乘的。np.tile(np.sum(self.X_train*self.X_train,1),(X.shape[0],1)以及np.tile(np.sum(X*X,1),(self.X_train.shape[0],1)).T是对A^2与B^2矩阵的求和、扩展及转置,还是需要推导一下,文字描述不清楚。2*np.dot(X,self.X_train.T),Python中矩阵的相乘需要利用np.dot()函数,此部分是对2*AB的计算。无循环的欧式距离计算:np.dot()的了解,向量化计算。确实挺麻烦的,再三强调,自己推导一下或利用两个小的矩阵调试一下,对向量化计算真的很有帮助。
(4)predict_labels()函数
def predict_labels(self, dists, k=1):
num_test = dists.shape[0]
y_pred = np.zeros(num_test)
for i in xrange(num_test):
closest_y = []
closest_y = self.y_train[np.argsort(dists[i,:])[0 : k]] #根据dists,按照大小进行排列,取出最小的5个值,np.argsort() #函数的理解
y_pred[i] = Counter(closest_y).keys()[np.argmax(Counter(closest_y).values()) #选取出最小的5个值中出现次数最多 #的类别作为第i幅图的类别
return y_pred
本部分:np.argsort()、np.argmax().value()、Counter函数的理解
第一部分告一段落,稍后补上交叉验证法的实现。
3、交叉验证法(Cross-validation)的实现
主要思路:本部分不考虑测试集,将训练集分成5等份,循环将其中的一份拿来做验证集(和之前的测试集作用相同,即用来测试准确率),剩下的四份为新的训练集,对于不同的k值都会计算出5个准确率,并将5个准确率求平均值可得准确率最大时候的k值。
(1)将训练集以及训练集的label分为5份
X_train_folds = np.array_split(X_train, num_folds)
y_train_folds = np.array_split(y_train, num_folds) #此部分主要是np.array_split()的使用,详细在此不展开
(2)计算每个k值的5个准确率,并保存在字典中
直接贴代码:
num_val = 100
for k in k_choices:
k_to_accuracies[k] = [] #初始化k的5个准确值列表
for i in range(num_folds):
X_train_folds = np.array_split(X_train, num_folds)
y_train_folds = np.array_split(y_train, num_folds) #由于pop()函数的应用,每次循环都要重新分为5份
m = 0
n = 0
X_val_cross_val = X_train_folds[i] #取出第i份作为验证集
X_train_folds.pop(i) #将第i份从训练集中删去,剩下的作为新的训练集
X_train_cross_val = np.zeros((4000,3072)) #初始化新的训练集
for p in range(4): #以下两个循环都是因为array_split()函数的应用,需要把剩下的四份重新组合成 #一个数组,次部分没找到合适的方法,我所运用的方法很麻烦,新建一个数 #组,一个一个重新复制进来,希望之后可以找到更好的方法
for q in range(1000):
X_train_cross_val[m,:] = X_train_folds[p][q,:] #将剩下的四份组合成一个数组
m = m+1
y_val_cross_val = y_train_folds[i] #label的划分,同上相同
y_train_folds.pop(i)
y_train_cross_val = np.zeros((4000,))
for u in range(4):
for v in range(1000):
y_train_cross_val[n,] = y_train_folds[u][v,]
n = n + 1
classifier.train(X_train_cross_val, y_train_cross_val) #改变新的训练集和验证集
dists = classifier.compute_distances_no_loops(X_val_cross_val) #调用距离计算函数
y_val_pred = classifier.predict_labels(dists, k) #调用预测函数
num_correct = np.sum(y_val_pred == y_val_cross_val) #统计正确分类的数量
accuracy = float(num_correct)/num_val #计算正确率
k_to_accuracies[k].append(accuracy) #保存正确率
结束。语言组织确实不好,希望以后自己还可以看得懂。