CS231n:作业1——softmax

前言
详细代码见github


问答总结

  • 在创建softmax标签是,我门需要构造one-hot向量,如何使用切片索引快速构造?

文章目录

  • 一、实验目标
  • 二、数据集
  • 三、实验方法
    • 1、损失函数
    • 2、梯度更新
    • 3、加入正则项
  • 三、代码:
  • 四、实验
  • 五、参考资料

一、实验目标

  • 使用cifar-10数据集实现softmax损失分类器,推导梯度更新公式,使用随机梯度下算法更新梯度。

二、数据集

数据集依然使用cifar-10, 加载方法见此

三、实验方法

1、损失函数

softmax损失函数定义如下:
l = − y T l o g ( e x p ( W x ) 1 T e x p ( W x ) ) l = -\mathbf{y}^Tlog(\frac{exp(W\mathbf{x})}{\mathbf{1}^Texp(W\mathbf{x})}) l=yTlog(1Texp(Wx)exp(Wx))
其中 y ∈ R C \mathbf{y} \in R^C yRC, 且只有样本 x \mathbf{x} x对应类别处值为1,其余值为0. W ∈ R C × K W \in R^{C \times K} WRC×K, x ∈ R K \mathbf{x} \in R^K xRK表示样本.

因此可以进一步化简得到:
l = − y T W x + l o g ( 1 T e x p ( W x ) ) l= -\mathbf{y^T}W\mathbf{x}+log(\mathbf{1}^Texp(Wx)) l=yTWx+log(1Texp(Wx))

2、梯度更新

我们的目标是求:
∂ l ∂ W \frac{\partial{l}}{\partial{W}} Wl
这是标量关于矩阵的求导. 使用矩阵求导术:

d l = − y T d W x + 1 T ( e x p ( W x ) ⊙ ( d W x ) ) 1 T e x p ( W x ) = − y T d W x + e x p T ( W x ) d W x 1 T e x p ( W x ) = ( − y T + s o f t m a x T ( W x ) ) d W x = t r ( ( − y T + s o f t m a x T ( W x ) ) d W x ) = t r ( x ( − y T + s o f t m a x T ( W x ) ) d W ) \begin{aligned} dl &= -\mathbf{y}^TdW\mathbf{x}+\frac{\mathbf{1}^T(exp(W\mathbf{x}) \odot (dW\mathbf{x}))}{\mathbf{1}^Texp(W\mathbf{x})} \\ &= -\mathbf{y}^TdW\mathbf{x}+\frac{exp^T(W\mathbf{x}) dW\mathbf{x}}{\mathbf{1}^Texp(W\mathbf{x})} \\ &=(-\mathbf{y}^T+softmax^T(W\mathbf{x}))dW\mathbf{x} \\ &=tr((-\mathbf{y}^T+softmax^T(W\mathbf{x}))dW\mathbf{x}) \\ &= tr(\mathbf{x}(-\mathbf{y}^T+softmax^T(W\mathbf{x}))dW) \end{aligned} dl=yTdWx+1Texp(Wx)1T(exp(Wx)(dWx))=yTdWx+1Texp(Wx)expT(Wx)dWx=(yT+softmaxT(Wx))dWx=tr((yT+softmaxT(Wx))dWx)=tr(x(yT+softmaxT(Wx))dW)


∂ l ∂ W = ( − y + s o f t m a x ( W x ) ) x T \frac{\partial{l}}{\partial{W}} =(-\mathbf{y}+softmax(W\mathbf{x})) \mathbf{x}^T Wl=(y+softmax(Wx))xT

3、加入正则项

加入正则项后,以二范数举例,一个batch的损失计算如下:

L = 1 N ∑ i = 1 N l i + 1 2 λ ∣ ∣ W ∣ ∣ 2 L = \frac{1}{N}\sum_{i=1}^Nl_i + \frac{1}{2}\lambda||W||^2 L=N1i=1Nli+21λW2

很容易求得, 加入正则项后,梯度会增加 λ W \lambda W λW:

三、代码:

(1) 纯循环代码:
根据推导公式,我们很容易使用循环计算梯度和损失。

def cal_dw_with_loop(self, X, Y, reg):
     """
     功能: 计算损失和梯度
     输入:
         X(Tensor):(K:3*32*32+1, N)
         Y(Tensor):(C, N)
         reg(float):                    # 正则化系数
     输出:
         L(int): 1                      # 损失               
         dW(Tensor):(C,K)             # 参数梯度       
     """
     L = 0.0
     N = X.size(1)
     K, C = self.W.size()
     dW = torch.zeros(K, C)
     
     # (1) 求解损失
     for i in range(N):
         x = X[:,i].unsqueeze(1)            # (K,1)
         y = Y[:,i].unsqueeze(1)            # (C,1)
         L += -y.t().matmul(self.W).matmul(x).item() + torch.log(torch.sum(torch.exp(self.W.matmul(x)))).item()
         dW = dW + (-y + torch.softmax(self.W.matmul(x), 0)) * x.t()
     
     # (2) 正则化
     L = L / N +  0.5*reg*torch.sum(torch.pow(self.W, 2)).item()
     dW = dW / N +  reg*self.W
     
     return L, dW
 

(2) 向量化代码
对于每批 N N N个数据,也可以很容易得到向量化的梯度更新公式:
∂ L ∂ W = − Y X T + s o f t m a x ( W X ) X T N + λ W \frac{\partial L}{\partial W} = \frac{-YX^T+softmax(WX)X^T}{N} + \lambda W WL=NYXT+softmax(WX)XT+λW
其中 Y ∈ R C × N , X ∈ R K × N , W ∈ R C × K Y \in R^{C \times N}, X \in R^{K \times N}, W\in R^{C \times K} YRC×N,XRK×N,WRC×K

def cal_dw_with_vec(self, X, Y, reg):
     """
     功能: 计算损失和梯度
     输入:
         X(Tensor):(K:3*32*32+1, N)
         Y(Tensor):(C, N)
         reg(float):                    # 正则化系数
     输出:
         L(int): 1                      # 损失               
         dW(Tensor):(K,C)             # 参数梯度      
     """
     
     N = X.size(1)
     K, C = self.W.size()
     
     L1 = -Y.t().matmul(self.W).matmul(X)  # (N, N) 
     L2 = torch.sum(torch.exp(self.W.matmul(X)), 0)     # (C, N)
     L = torch.sum(L1[range(N), range(N)]).item() + torch.sum(torch.log(L2)).item()
     dW = -Y.matmul(X.t()) + torch.softmax(self.W.matmul(X), 0).matmul(X.t())
     
     L = L / N +  0.5*reg*torch.sum(torch.pow(self.W, 2)).item()
     dW = dW / N + reg*self.W
     return L, dW

四、实验

由于pytorch直接加载的数据集不便划分为训练集、验证集、测试集,我们仅仅使用训练集和测试集。在训练集上训练,在测试集上测试。过程如下:

lrs = [1e-2, 1e-3, 1e-4, 1e-5]
reg_strs = [0, 1, 10, 100, 1000]

result = {}

best_lr = None
best_reg = None
best_model = None
best_acc = -1

for lr in lrs:
    for reg in reg_strs:
        model = train(lr, reg, 100)
        acc = evaluate(model)
        print("lr:{}; reg:{}; acc:{}".format(lr, reg, acc))
        if acc > best_acc:
            best_lr = lr
            best_reg = reg
            best_model = model
        result[(lr, reg)] = acc
print("the best: lr:{}; reg:{}; acc:{}".format(best_lr, best_reg, best_acc))

五、参考资料

  • [1] 矩阵求导术

你可能感兴趣的:(信息科学,cs231n)