svm训练完保存权重_assignment1-SVM

理论知识

这次的分类器要比之前的KNN更加得灵活,效果也会更好。这种方法主要有两部分组成:一个是评分函数(score function),它是原始图像数据到类别分值的映射。另一个是损失函数(loss function),它是用来量化预测分类标签的得分与真实标签之间一致性的。该方法可转化为一个最优化问题,在最优化过程中,将通过更新评分函数的参数来最小化损失函数值。

评分函数可以表示为:

对于

,可以看下面的图片:

svm训练完保存权重_assignment1-SVM_第1张图片

当然更好的方法是把W和b合并:

svm训练完保存权重_assignment1-SVM_第2张图片

的选取也有很多,这里看做对
不做处理。

评分函数在正确的分类的位置应当得到最高的评分(score)。使用损失函数(Loss Function)(有时也叫代价函数Cost Function目标函数Objective)来衡量我们对结果的不满意程度。直观地讲,当评分函数输出结果与真实结果之间差异越大,损失函数输出越大,反之越小。

损失函数的形式很多,这里使用的是SVM损失函数。

对于第j个类别的得分为

,针对第i个数据的多分类SVM的损失函数定义如下:

SVM的损失函数想要正确分类类别

的分数比不正确类别分数高,而且至少要高
。如果不满足这点,就开始计算损失值。

那么根据之前的评分函数,最终的损失函数为:

被称为
折叶损失(hinge loss)

正则化(Regularization):课程中是这么解释的,由于使所有样本都能正确分类的

并不唯一,可能有很多相似的W都能够正确地分类所有数据,比如对于每个数据,损失值都为0,那么对于
时,任何
都可以使损失值为0。那么哪个
最好呢?

正则化的想法就是向某些特定的权重W添加一些偏好,对其他权重则不添加,以此来消除这种模糊性。常用的正则化惩罚是L2范数:

可以看出,对于比较大的权重,它可以使其损失函数更大,从而抑制大数值的权重。

我的理解是:在训练完后发现了过拟合,那么此时虽然训练集的损失很小了,但是还不是最好的W,所以要调整W,W还有偏好的含义,越大表示对某个特征反应越激烈,而较大的W在预测中往往有着决定性的作用,所以要降低这些权重,对大数值进行惩罚,来让分类器把所有维度上的特征都利用起来,从而提升其泛化能力。

最后的loss如下:

a38e287594e49ade458a0c90debabbaf.png
不想再输公式了。。。

算法实现

整个线性分类器的设计过程总结如下:

  1. 对数据集进行预处理。包括划分训练集,验证集和测试集。零均值化和归一化。
  2. 设计损失函数
  3. 计算梯度和loss
  4. 设计优化算法进行权重更新
  5. 训练
  6. 调优

算法框架如下:

class LinearClassifier(object):
    def __init__(self):
        self.W = None
    def train(self, X, y, learning_rate=1e-3, reg=1e-5, num_iters=100,
              batch_size=200, verbose=False):
        pass
        return loss_history
   def predict(self, X):
        pass
        return y_pred
   def loss(self, X_batch, y_batch, reg):
        pass  

class LinearSVM(LinearClassifier):
    """ A subclass that uses the Multiclass SVM loss function """
    def loss(self, X_batch, y_batch, reg):
        return svm_loss_vectorized(self.W, X_batch, y_batch, reg)

因为不同损失函数的train和predict的代码是相同的,所以作业中使用了继承来复用这些代码,以后只需要写loss的代码即可。

train代码实现:

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 # 这里用 0...K-1 来表示不同的标签
        #权重初始化,使用小随机数
        if self.W is None:
            # lazily initialize W
            self.W = 0.001 * np.random.randn(dim, num_classes)

        #使用GD进行W的优化
        loss_history = []
        for it in range(num_iters):
            X_batch = None
            y_batch = None
            num_train = X.shape[0]
            batch_indices = np.random.choice(num_train, batch_size)
            X_batch = X[batch_indices]
            y_batch = y[batch_indices]
            loss, grad = self.loss(X_batch, y_batch, reg)
            loss_history.append(loss)
            self.W -=learning_rate*grad
            if verbose and it % 100 == 0:#用于观察loss的情况
                print('iteration %d / %d: loss %f' % (it, num_iters, loss))
        return loss_history

这里说明下batch_size,epoch的区别。

当一个完整的数据集通过了分类器一次并且完成了一次梯度更新,这个过程称为一个epoch。然而,当一个 epoch 对于计算机而言太庞大的时候,就需要把它分成多个小块。分成的块数就是batch的数量,每一个batch的大小就为batchsize。现在变成了每一个batch进行一次梯度更新。batch_size和梯度下降也有一定关系:

批量梯度下降(BGD)。batch_size=训练集的大小

随机梯度下降(SGB)。batch_size= 1

小批量梯度下降(MBGD)。1

predict的代码实现:

def predict(self, X):
    y_pred = np.zeros(X.shape[0])
    y_pred = np.argmax(X.dot(self.W),axis=1)
    return y_pred

svm_loss_vectorized的实现:

def svm_loss_vectorized(W, X, y, reg):
    loss = 0.0
    dW = np.zeros(W.shape) # initialize the gradient as zero
    scores = X.dot(W)  # N by C
    num_train = X.shape[0]
    num_classes = W.shape[1]
    #计算loss
    #numpy的这种技巧要记牢啊
    scores_correct = scores[np.arange(num_train), y] #1 by N
    scores_correct = np.reshape(scores_correct, (num_train, 1)) # N by 1
    margins = scores - scores_correct + 1.0 # N by C
    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)
    #计算梯度
    margins[margins > 0] = 1.0                         # 示性函数的意义
    row_sum = np.sum(margins, axis=1)                  # 1 by N
    margins[np.arange(num_train), y] = -row_sum        
    dW += np.dot(X.T, margins)/num_train + reg * W     # D by C
    return loss, dW

(关于loss和梯度计算的实现想专门总结下)

训练集和验证集的正确率为:

02b4bb9606cdd15caf6e9da5fd891d1f.png

调优

这次直接使用验证集来进行调整学习率和正则化系数,具体代码如下:

learning_rates = [1e-7,5e-5]
regularization_strengths = [2.5e4,5e4]
#对于每一个超参数集合,用训练集训练一个SVM,计算训练集和验证集的准确率,并存放在
#result字典中,存放最好的验证集准确率在best_val中,最好的模型放在best_svm中
#result中是(learning_rate, regularization_strength):(training_accuracy, validation_accuracy)
results = {}
best_val = -1   
best_svm = None 
for lr in learning_rates:
    for rs in regularization_strengths:
        svm = LinearSVM()
        loss_hist = svm.train(X_train, y_train, learning_rate=lr, reg=rs,
                      num_iters=500, verbose=False)#num_iters设为500,调高查找速度
        y_train_pred = svm.predict(X_train)
        train_accuracy = np.mean(y_train == y_train_pred)
        y_val_pred = svm.predict(X_val)
        validation_accuracy = np.mean(y_val == y_val_pred)
                               
        results[(lr,rs)] = (train_accuracy,validation_accuracy)
        if best_val < validation_accuracy:
                               best_svm = svm
                               best_val = validation_accuracy
# Print out results.
for lr, reg in sorted(results):
    train_accuracy, val_accuracy = results[(lr, reg)]
    print('lr %e reg %e train accuracy: %f val accuracy: %f' % (
                lr, reg, train_accuracy, val_accuracy))
print('best validation accuracy achieved during cross-validation: %f' % best_val)

正确率与超参数的关系如下:

svm训练完保存权重_assignment1-SVM_第3张图片
在原有的数量上又增加了一些取值

左上方的颜色为深红色,表明准确率最高。学习率为1e-7 ,正则化系数为 5e4,验证集上的准确率为38%左右。

之后要注意的是在对测试集进行预测之前,要把num_iters调到较大的值,这里是1500,来更好地优化W。最后测试集的准确率为37%左右。

作业中还把权重矩阵中各个类别的权重绘图,结果如下:

svm训练完保存权重_assignment1-SVM_第4张图片

可以发现,SVM分类器是在进行着模板匹配的工作。

一些问题:

1.多类SVM损失函数的最大/最小值是多少?

最小值:0 最大值:无穷大

2.如果初始化时w和b很小,损失L会是多少?

设标签数为n,单个样本的L为n-1。这可以验证编码是否正确

3.考虑所有类别(包括j=yi),损失Li会有什么变化?

会比原来多1

4.在求总损失L计算时,如果用求和代替平均?

没有什么影响,可以调学习率

5.如果使用

caa0e40d333cf82aedd479daf57b4a2d.png

会使损失值变很大,对最终的效果有影响。

你可能感兴趣的:(svm训练完保存权重)