(ROW(行)与COLUMN(列))
这里涉及到了图像处理中常用的归一化问题。数据预处理中,标准的第一步是数据归一化。虽然这里有一系列可行的方法,但是这一步通常是根据数据的具体情况而明确选择的。特征归一化常用的方法包含如下几种:
https://blog.csdn.net/dcxhun3/article/details/47999281 萌面女xia的文章有详细的讲解
我们通过权重矩阵 W 计算得到了预测分类的分数(predicted class scores),例如在课程中举例的cat,dog,ship
在我们得到预测分数后,需要一种方法衡量预测值和真实值之间的误差,这就需要用到了损失函数(loss function)
常用的损失函数:
svm loss希望正确的分类的得分高于不正确项的得分,如果正确项≥错误项得分+边界值,我们认为没有误差,如果正确项<错误项+边界值(这里先不考虑边界值的选取),我们认为存在误差(loss)。svm loss计算公式
由于W是矩阵,所以sj = wj×xi ,这里的×是点乘也就是矩阵乘法,xi为一列,Wi为W矩阵的第i行,所以公式可以写为:(公式中的转置符号让我有些困惑,大佬可以帮忙解答一下)
regularization penalty
应用于解决过拟合问题。在我们上文提到的Li公式中,可能我们会有一组W,完美的将数据集分类全部正确,这时会造成过拟合的问题(下图)。因为我们通过训练数据集需要找到的并不是完美分类训练集的模型参数,而是要找到一组模型参数具有很好的泛化能力,可以在训练集之外的数据上正确预测分类。所以我们为了防止出现过拟合的情况,加入了惩罚项(regularization penalty)R(W)。
模型选择的典型方法是正则化。正则化是结构风险最小化策略的实现,是在经验风险上加一个正则化项(regularizer)或罚项(penalty term)。正则化项一般是模型复杂度的单调递增函数,模型越复杂,正则化值就越大。比如,正则化项可以是模型参数向量的范数。
则化符合奥卡姆剃刀(Occam’s razor)原理。奥卡姆剃刀原理应用于模型选择时变为以下想法:在所有可能选择的模型中,能够很好地解释已知数据并且十分简单才是最好的模型,也就是应该选择的模型。从贝叶斯估计的角度来看,正则化项对应于模型的先验概率。可以假设复杂的模型有较大的先验概率,简单的模型有较小的先验概率。-----李航博士《统计学习方法》
最常见的正则化惩罚是L2范数,其通过对所有参数的元素二次惩罚来阻止大权重:
在上面的公式中我们可以看到惩罚项和训练数据无关,之和权重矩阵W相关。加入惩罚项后我们实现了完整的SVM loss,它由两部分组成:data loss + regularization loss, 完整公式如下:
N是训练数据的数量。我们用超参数λ来作为惩罚项的权重,没有简单有效的方法去设置超参数λ的值,它通常和交叉验证有关。
总结关于引入惩罚项(regularization penalty)的优点:惩罚大的权重项,提高模型的泛化能力。因为我们不希望W矩阵中的某一个很大的权重值对我们的预测产生很大的影响。(The most appealing property is that penalizing large weights tends to improve generalization, because it means that no input dimension can have a very large influence on the scores all by itself.)分类器更希望通过衡量所有输入维度去综合得出分类结果,而不是仅仅考虑某一两个有很高的权重的维度。我们做的就是找到权重矩阵得到尽可能小的损失值。
现在回到我们的第一张图,我们会发现影响我们最终分类结果的不仅仅有权重矩阵W,还有误差b,但是由于b并不影响我们的输入数据,所以我们并不对b进行正则化。
a. Setting Delta(Δ)边界值
可以安全的设置成 Δ = 1.0 下面解释的很清楚,Δ的设置影响的是预测得分差异(score differences)的大小,并不影响相对结果。因此真正有用的是如何设置超参数λ来控制权重矩阵。
在这一节的作业中,主要包含两个难点:一是损失函数的求值,二是对得损失函数求梯度。这里涉及到了一个很蠢但又很关键的问题:为什么要求梯度?
这里我们要再次回顾一下我们到底在干什么:我们现在研究的是图像的分类问题,将数据集分成了训练集(training),验证集(validation),测试集(test)。
注:区分参数(parameters)和超参数(hyperparameters),参数是模型可以自己学习出的变量,超参数是人为根据经验调整的变量。
并且我们规定了一个简单的得分函数(score function) f(xi,W)=Wxi ,其中W为我们的权重矩阵,我们的目的是通过训练集和验证集不断的去优化我们的权重矩阵,最后在我们的测试集上得到很好的分类结果。如何判断权重矩阵的优劣,这时我们引入了损失函数(loss function) 来衡量预测结果和真实结果的偏差,并且我们通过优化器(optimization) 来找到最合适的权重矩阵W使得我们的损失函数尽可能的小。回想我们高中学过的知识,求一个函数的最小值往往是求导找零点,这里也是用到的这个原理。
梯度可以分为数值梯度(numerical gradient)和分析梯度(analytic gradient),简单理解是数值梯度根据梯度的定义计算的,分析梯度根据函数求导得到。数值梯度计算效率低,所以我们使用效率高但是容易出错的分析梯度。
这里介绍了一个梯度检验(gradient check)的概念
下面我们来详细介绍分析梯度(analytic gradient)的计算:
计算分析梯度需要微积分知识(函数求导、链式法则等)。
当我们计算出梯度后在应用梯度下降法就可以不断的更新参数了。
下面讲解代码中损失值和梯度的计算:
在应用循环求损失值的过程很简单,就是应用上面两个公式。
在使用向量运算求损失值和梯度时,我们必须首先明确不同矩阵行和列代表的含义。
看下面的 svm_loss_vectorized()
函数,scores = X.dot(W)
首先找到我们的得分矩阵(N by C),N代表输入的N个用例,C代表最后分类结果总共有C类。 scores_correct = scores[np.arange(num_train), y]
这一行代码的用法很巧妙,用到的原理还不是很清楚,但实现的操作是找到scores中每一行y位置的元素,最后返回scores_correct(1 by N),下图有这种用法的举例。所以scores_correct中记录的是每一行(每一个样本)正确的分类得分结果。为了方便计算我们把它reshape成(N by 1)的矩阵,margins = scores - scores_correct + 1.0
就是我们上面求Li的公式,scores(N by C) - scores_correct(N by 1)
这里用到的是广播(broadcasting)原理可自行百度,得到结果为margins(N by C)
。margins[np.arange(num_train), y] = 0.0
现在这种用法应该不陌生了吧(看看下面的图片),实现的是margins矩阵上正确分类位置的数值置0,为什么置零,因为我们是对j≠yi位置的元素求和看上面Li公式。 margins[margins<=0] = 0.0 #这种用法很秀
这种用法很秀,值得我们好好学习,完成的是公式中max的任务。
(代码中关于矩阵的行列大小一定要搞清楚)
下面求梯度的方法和上面公式实现方法一致。详细介绍可以参考这篇博客讲的很详细,重点还是在于对上面两个求梯度公式的理解。
import numpy as np
from random import shuffle
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 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
dW[:,y[i]] += -X[i,:]
dW[:,j] += X[i,:]
# 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 += reg* W
return loss, dW
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_train= X.shape[0] # N
num_classes = W.shape[1] # C
scores = X.dot(W) # N by C
scores_correct = scores[np.arange(num_train), y] # 1 by N broadcasting
scores_correct = np.reshape(scores_correct, (num_train, 1)) # N by 1
margins = scores - scores_correct + 1.0
margins[np.arange(num_train), y] = 0.0
margins[margins<=0] = 0.0 #
loss += np.sum(margins) / num_train
loss += 0.5 * reg *np.sum(W * W)
#compute the gradient
margins[margins>0] = 1.0
row_sum = np.sum(margins, axis=1)
margins[np.arange(num_train), y] = -row_sum
dW += np.dot(X.T, margins)/num_train + reg* W
return loss, dW