一.作业要求

原版:http://cs231n.github.io/assignments2017/assignment1/

翻译:http://www.mooc.ai/course/268/learn?lessonid=1962#lesson/1962

二.作业收获及代码

完整代码地址:https://github.com/coldyan123/Assignment1

1 KNN

(1)有用的numpy API:

np.flatnonzero:返回展平数组的非零元素索引(结合布尔数组访问可筛选特定条件元素索引)

np.random.choice:随机采样常用(第一个参数可以是一维数组或整数)

np.argsort:返回排序后的索引值

np.argmax: 返回最大元素的索引值

np.array_split: 划分k折交叉验证集常用

np.vstack:纵向把列表中的数组拼起来(要求每个数组列数相同)

np.hstack:横向把列表中的数组拼起来(要求每个数组行数相同)

np.random.randn: 常用来初始化权重矩阵

(2)三种计算训练集与测试集L2距离矩阵的方式(two loop,one loop,no loop):

two loop(很暴力的方法):

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 xrange(num_test):
for j in xrange(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. #
#####################################################################
dists[i, j] = np.sqrt(np.sum((X[i, :] - self.X_train[j, :]) ** 2))
#####################################################################
# END OF YOUR CODE #
#####################################################################
return dists

one loop(用到了numpy数组的广播):

def compute_distances_one_loop(self, X):
"""
Compute the distance between each test point in X and each training point
in self.X_train using a single loop over the test data. Input / Output: Same as compute_distances_two_loops
"""
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):
#######################################################################
# TODO: #
# Compute the l2 distance between the ith test point and all training #
# points, and store the result in dists[i, :]. #
#######################################################################
dists[i] += np.sqrt(np.sum((X[i, :] - self.X_train) ** 2, axis=1))
#######################################################################
# END OF YOUR CODE #
#######################################################################
return dists

no loop (将L2距离表达式展开,然后使用向量化方式巧妙实现):

def compute_distances_no_loops(self, X):
"""
Compute the distance between each test point in X and each training point
in self.X_train using no explicit loops. Input / Output: Same as compute_distances_two_loops
"""
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
#########################################################################
# TODO: #
# Compute the l2 distance between all test points and all training #
# points without using any explicit loops, and store the result in #
# dists. #
# #
# You should implement this function using only basic array operations; #
# in particular you should not use functions from scipy. #
# #
# HINT: Try to formulate the l2 distance using matrix multiplication #
# and two broadcast sums. #
#########################################################################
dists += np.sum(X ** 2, axis=1).reshape((num_test, 1))
dists += np.sum(self.X_train ** 2, axis=1)
dists += X.dot(self.X_train.T) * (-2)
dists = np.sqrt(dists)
#########################################################################
# END OF YOUR CODE #
#########################################################################
return dists

(3) 完整代码

在这个练习中,我编写了knn的训练和测试步骤并理解基本的图像分类pipeline,交叉验证以及熟练编写高效的向量化代码。

https://nbviewer.jupyter.org/github/coldyan123/Assignments1/blob/master/knn.ipynb

2 多分类SVM

(1)多分类SVM损失函数及梯度:

样本i的损失函数:

(其中yi是样本i的真实标签,sj是该样本在类别j上的线性得分值,三角形是一个常数,表示保护值)

梯度(学会推导):

(2)两种实现SVM损失和解析梯度的方式

朴素法(两重循环):

def svm_loss_naive(W, X, y, reg):
"""
Structured SVM loss function, naive implementation (with loops). Inputs have dimension D, there are C classes, and we operate on minibatches
of N examples. 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
"""
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 xrange(num_train):
scores = X[i].dot(W)
correct_class_score = scores[y[i]]
for j in xrange(num_classes):
if j == y[i]:
continue
margin = scores[j] - correct_class_score + 1 # note delta = 1
if margin > 0:
dW[:, j] += X[i, :]
dW[:, y[i]] += -X[i, :]
loss += margin # Right now the loss is a sum over all training examples, but we want it
# to be an average instead so we divide by num_train.
loss /= num_train
dW /= num_train # Add regularization to the loss.
loss += reg * np.sum(W * W)
dW += 2 * reg * W
#############################################################################
# TODO: #
# Compute the gradient of the loss function and store it dW. #
# Rather that first computing the loss and then computing the derivative, #
# it may be simpler to compute the derivative at the same time that the #
# loss is being computed. As a result you may need to modify some of the #
# code above to compute the gradient. #
############################################################################# return loss, dW

完全向量法:
很有技巧性,使用数组的广播来计算loss。由于观察到每次的梯度是训练集向量的线性叠加,使用计算loss中产生的中间矩阵lossMat来构造该线性权重矩阵H,该权重矩阵H大小为n*10,对于Hij,当样本i的正确分类不为j,如果max(si-syi+1)>0,则Hij为1,否则为0;当样本i的正确分类为j,则Hij为10个分类中max(si-syi+1)大于0的个数的倒数。其中max(si-syi+1)就是lossMat矩阵中的值。

def svm_loss_vectorized(W, X, y, reg):
"""
Structured SVM loss function, vectorized implementation. Inputs and outputs are the same as svm_loss_naive.
"""
loss = 0.0
dW = np.zeros(W.shape) # initialize the gradient as zero
num_classes = W.shape[1]
num_train = X.shape[0]
#############################################################################
# TODO: #
# Implement a vectorized version of the structured SVM loss, storing the #
# result in loss. #
#############################################################################
scores = X.dot(W)
rightClassScores = scores[range(0, num_train), list(y)].reshape(num_train, 1)
lossMat = scores - rightClassScores + 1 lossMat[lossMat < 0] = 0.0
loss = (np.sum(lossMat) - num_train) / num_train #############################################################################
# END OF YOUR CODE #
############################################################################# #############################################################################
# TODO: #
# Implement a vectorized version of the gradient for the structured SVM #
# loss, storing the result in dW. #
# #
# Hint: Instead of computing the gradient from scratch, it may be easier #
# to reuse some of the intermediate values that you used to compute the #
# loss. #
#############################################################################
lossMat[lossMat > 0] = 1.0
lossMat[range(0, num_train), list(y)] = -np.sum(lossMat, axis=1) + 1
dW = X.T.dot(lossMat) / num_train + 2 * reg * W
#############################################################################
# END OF YOUR CODE #
############################################################################# return loss, dW

实验表明完全向量化的代码比朴素法快十几倍。

(3)完整代码

- 实现了SVM完全向量化的损失函数
- 实现了解析梯度完全向量化的表达式
- 使用数值梯度检查了解析梯度的正确性
- 使用验证集调参:学习速率和正则化强度
- 实现了优化损失函数的SGD算法
- 可视化最终学习权重,可以看出线性分类器相当于为每个类学出一个模版(对应权重矩阵的一行),进行模版匹配(可视化的方法是将权重进行归一化,然后乘以255)。
 https://nbviewer.jupyter.org/github/coldyan123/Assignments1/blob/master/svm.ipynb
 

3 softmax

(1)损失函数及梯度
样本i的损失函数:
梯度:
 
(2) 两种实现softmax损失及其梯度的方式
朴素法:
def softmax_loss_naive(W, X, y, reg):
"""
Softmax loss function, naive implementation (with loops) Inputs have dimension D, there are C classes, and we operate on minibatches
of N examples. 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
"""
# Initialize the loss and gradient to zero.
loss = 0.0
dW = np.zeros_like(W) #############################################################################
# TODO: Compute the softmax loss and its gradient using explicit loops. #
# Store the loss in loss and the gradient in dW. If you are not careful #
# here, it is easy to run into numeric instability. Don't forget the #
# regularization! #
#############################################################################
train_num = X.shape[0]
dim = X.shape[1]
class_num = W.shape[1]
for i in range(0, train_num):
scores = X[i].dot(W)
Sum = 0
for j in range(0, class_num):
Sum += math.exp(scores[j])
for j in range(0, class_num):
if j == y[i]:
dW[:, j] += (math.exp(scores[y[i]]) / Sum - 1) * X[i]
else:
dW[:, j] += math.exp(scores[j]) / Sum * X[i]
loss += -math.log(math.exp(scores[y[i]]) / Sum)
loss /= train_num
dW /= train_num
dW += 2 * reg * W #############################################################################
# END OF YOUR CODE #
############################################################################# return loss, dW

完全向量法:

这里与SVM的完全向量实现思路基本相同,都是构造出样本对于梯度的权重贡献矩阵H。搞懂了SVM的再来写这个简直易如反掌。

def softmax_loss_vectorized(W, X, y, reg):
"""
Softmax loss function, vectorized version. Inputs and outputs are the same as softmax_loss_naive.
"""
# Initialize the loss and gradient to zero.
loss = 0.0
dW = np.zeros_like(W) #############################################################################
# TODO: Compute the softmax loss and its gradient using no explicit loops. #
# Store the loss in loss and the gradient in dW. If you are not careful #
# here, it is easy to run into numeric instability. Don't forget the #
# regularization! #
#############################################################################
train_num = X.shape[0]
dim = X.shape[1]
class_num = W.shape[1] scores = X.dot(W)
exp_scores = np.exp(scores)
tmp = exp_scores[range(0, train_num), y] / np.sum(exp_scores, axis=1)
loss = np.sum(-np.log(tmp)) / train_num H = exp_scores / np.sum(exp_scores, axis=1).reshape((train_num, 1))
H[range(0, train_num), y] -= 1
dW = X.T.dot(H) / train_num + 2 * reg * W
#############################################################################
# END OF YOUR CODE #
############################################################################# return loss, dW

(3)完整代码

- 实现了Softmax分类器完全向量化的损失函数

- 实现了解析梯度完全向量化的代码
- 用数值梯度检查了实现
- 使用验证集调整学习速度和正则化强度
- 使用SGD优化损失函数
- 可视化最终学习权重

https://nbviewer.jupyter.org/github/coldyan123/Assignments1/blob/master/softmax.ipynb

4 两层神经网络

(1)softMax loss和梯度的计算(完全向量法)

loss的计算非常简单:

# Compute the loss
loss = None
#############################################################################
# TODO: Finish the forward pass, and compute the loss. This should include #
# both the data loss and L2 regularization for W1 and W2. Store the result #
# in the variable loss, which should be a scalar. Use the Softmax #
# classifier loss. #
#############################################################################
exp_scores = np.exp(scores)
loss = np.sum(-np.log(exp_scores[range(0, N), y] / np.sum(exp_scores, axis=1)))
loss /= N
loss += reg * (np.sum(W1 * W1) + np.sum(W2 * W2))
#############################################################################
# END OF YOUR CODE #
############################################################################

梯度的计算比较复杂,主要难在涉及到了矩阵对矩阵的导数(WX+B对W或X的导数),以及Relu层的导数。

a 矩阵线性变换的导数是一个常用的结论,需要记住(使用平铺矩阵jocabian法可以推出这个结论):

(等式右边是左乘右乘还是转置不用记忆,根据维度相容的方法可以现推出来)

b ReLu层的导数

Relu层表示为:

其中是对矩阵A进行逐元素地max(0,Aij)操作的,所以很容易得出:

其中,运算符表示逐元素想乘,函数表示将矩阵H中的元素大于0的置为1,其余置为0。

因此梯度的计算代码如下:

# Backward pass: compute gradients
grads = {}
#############################################################################
# TODO: Compute the backward pass, computing the derivatives of the weights #
# and biases. Store the results in the grads dictionary. For example, #
# grads['W1'] should store the gradient on W1, and be a matrix of same size #
#############################################################################
#cal gradsOfLossByScore
gradsOfLossByScore = exp_scores / np.sum(exp_scores, axis=1).reshape((N,1))
gradsOfLossByScore[range(0, N), y] -= 1
#cal grads['b2']
gradsOfLossByb2 = gradsOfLossByScore
grads['b2'] = np.sum(gradsOfLossByb2, axis=0) / N
#
grads['W2'] = h.T.dot(gradsOfLossByScore) / N + 2 * reg * W2
#
gradsOfLossByh = gradsOfLossByScore.dot(W2.T)
gradsOfLossBya1 = gradsOfLossByh * (h > 0)
gradsOfLossByb1 = gradsOfLossBya1
grads['b1'] = np.sum(gradsOfLossByb1, axis=0) / N
grads['W1'] = X.T.dot(gradsOfLossBya1) / N + 2 * reg * W1
#############################################################################
# END OF YOUR CODE #
#############################################################################

(2)完整代码

在这项练习中,我使用了高效的向量化代码实现了两层全连接神经网络的前向传播,反向传播,训练以及预测。并通过调节超参数,在验证集上达到了0.525的准确率,测试集上达到了0.519的准确率。

https://nbviewer.jupyter.org/github/coldyan123/Assignments1/blob/master/two_layer_net.ipynb

(3)关于反向传播的心得体会

  在反向传播中,我们使用上游传过来的梯度乘以jocabian矩阵,得到特定参数的梯度,或者是使梯度往下传。但是注意,jocobian矩阵的定义是向量对向量的导数结果,如果遇到向量对矩阵,或者矩阵对矩阵的时候,我们应该用什么来乘以上游梯度呢?考察下面一个例子:

问题:已知上游传过来的梯度(或称为G),并且有,要求梯度。其中

  解法:在这个例子中,我们仍然使用雅可比矩阵进行传播,但是首先需要将S矩阵平展成一个(1,mn)的向量,将W矩阵平展成一个(1,pq)的向量,得到的雅可比矩阵是(mn,pq)大小的。然后我们将上游梯度平展成(1,mn)的向量,使用这个向量乘以雅可比矩阵,得到(1,pq)的向量,将这个向量恢复成(p,q)的形状,就是我们要求的梯度矩阵

证明:现在证明上面解法的正确性。

  记分别为三个矩阵平展开之后的第i个元素,那么,得到的(mn,pq)的雅可比矩阵如下:

而G展开后为。将其乘以雅可比矩阵,得到向量:

这个向量中的第k个元素为:

  

很明显,将其恢复成(p,q)的形状就是要求的梯度矩阵

5 图像特征实验

  这一部分验证了提取一些图像特征能够达到更高的分类准确率。提取的特征有HOG和color histogram,用到已经实现的SVM和两层神经网络上。经过调参,神经网络在验证集上准确率超过了60%,测试集上达到了58.3%。

完整代码:

https://nbviewer.jupyter.org/github/coldyan123/Assignments1/blob/master/features.ipynb

 

Spring 2017 Assignments1的更多相关文章

  1. cs231n spring 2017 lecture13 Generative Models 听课笔记

    1. 非监督学习 监督学习有数据有标签,目的是学习数据和标签之间的映射关系.而无监督学习只有数据,没有标签,目的是学习数据额隐藏结构. 2. 生成模型(Generative Models) 已知训练数 ...

  2. cs231n spring 2017 lecture11 Detection and Segmentation 听课笔记

    1. Semantic Segmentation 把每个像素分类到某个语义. 为了减少运算量,会先降采样再升采样.降采样一般用池化层,升采样有各种"Unpooling"." ...

  3. cs231n spring 2017 lecture9 CNN Architectures 听课笔记

    参考<deeplearning.ai 卷积神经网络 Week 2 听课笔记>. 1. AlexNet(Krizhevsky et al. 2012),8层网络. 学会计算每一层的输出的sh ...

  4. cs231n spring 2017 lecture7 Training Neural Networks II 听课笔记

    1. 优化: 1.1 随机梯度下降法(Stochasitc Gradient Decent, SGD)的问题: 1)对于condition number(Hessian矩阵最大和最小的奇异值的比值)很 ...

  5. cs231n spring 2017 lecture13 Generative Models

    1. 非监督学习 监督学习有数据有标签,目的是学习数据和标签之间的映射关系.而无监督学习只有数据,没有标签,目的是学习数据额隐藏结构. 2. 生成模型(Generative Models) 已知训练数 ...

  6. cs231n spring 2017 lecture11 Detection and Segmentation

    1. Semantic Segmentation 把每个像素分类到某个语义. 为了减少运算量,会先降采样再升采样.降采样一般用池化层,升采样有各种“Unpooling”.“Transpose Conv ...

  7. cs231n spring 2017 lecture9 CNN Architectures

    参考<deeplearning.ai 卷积神经网络 Week 2 听课笔记>. 1. AlexNet(Krizhevsky et al. 2012),8层网络. 学会计算每一层的输出的sh ...

  8. cs231n spring 2017 lecture7 Training Neural Networks II

    1. 优化: 1.1 随机梯度下降法(Stochasitc Gradient Decent, SGD)的问题: 1)对于condition number(Hessian矩阵最大和最小的奇异值的比值)很 ...

  9. cs231n spring 2017 lecture16 Adversarial Examples and Adversarial Training 听课笔记

    (没太听明白,以后再听) 1. 如何欺骗神经网络? 这部分研究最开始是想探究神经网络到底是如何工作的.结果人们意外的发现,可以只改变原图一点点,人眼根本看不出变化,但是神经网络会给出完全不同的答案.比 ...

随机推荐

  1. node调试工具--nodemon使用简介

    这个工具和node-supervisor基本上是一致的,但是其功能比较强大,个人觉得在开发环境还是用 nodemon,因为配置比较方便,文档也很清晰.所以这里先主要讲 nodemon. nodemon ...

  2. js继承的6种方式

    想要继承,就必须要提供个父类(继承谁,提供继承的属性) 一.原型链继承 重点:让新实例的原型等于父类的实例. 特点:1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性.(新 ...

  3. 【webpack系列】webpack4.x入门配置基础(一)

    一.前言 webpack在不断的迭代优化,目前已经到了4.29.6.在webpack4这个版本中,做了很多优化,引入了很多特性,将获得更多模块类型,.mjs支持,更好的默认值,更为简洁的模式设置,更加 ...

  4. 自实现input上传指定文件到服务器

    遇到问题,解决问题,记录问题,成长就是一步一步走出来的. 一.添加 input 标签 我的工作中遇到了,需要上传pdf文件到服务器的需求,而且只能上传pdf文件,accept指定了 pdf 类型. & ...

  5. 个人永久性免费-Excel催化剂功能第54波-批量图片导出,调整大小等

    图片作为一种数据存在,较一般的存放在Excel单元格或其他形式存在的文本数据,对其管理更为不易,特别是仅有Excel原生的简单的插入图片功能时,Excel催化剂已全面覆盖图片数据的使用场景,无论是图片 ...

  6. 2019年7月20日 - LeetCode0003

    https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/submissions/ 我的解法: c ...

  7. 一个简单的JS倒计时

    看到很多商城都是抢购倒计时的功能,今天闲来无事做了个倒计时.全当学习JS. 主要思路:主要用到Date对象,声明一个变量获取当前时间,在声明一个变量获取结束时间,结束时间-当前时间=剩余时间(倒计时) ...

  8. 从0系统学Android-2.4隐式Intent

    本系列文章,参考<第一行代码>,作为个人笔记 更多内容:更多精品文章分类 使用隐式 Intent 相对于显示 Intent ,隐式 Intent 比较含蓄.这种方式不明确指出我们想要启动哪 ...

  9. Jenkins持续部署-创建差量更新包

    目录 Jenkins持续部署-创建差量更新包 目录 前言 目的 详细流程 生成版本号 获取版本号 创建文件更新清单 压缩 获取上个版本的包 创建差量更新包 读取服务器Json配置 远程创建文件夹目录 ...

  10. 异常 Java oop

    1.捕获异常 try——执行可能产生异常的代码 catch——捕获异常 finally——无论是否发生异常,代码总能执行 2.声明异常 throws——声明方法可能要要抛出的各种异常 3.抛出异常—— ...