前言
详细代码见github
问答总结
softmax
标签是,我门需要构造one-hot
向量,如何使用切片索引
快速构造?cifar-10
数据集实现softmax
损失分类器,推导梯度更新公式,使用随机梯度下算法更新梯度。数据集依然使用cifar-10
, 加载方法见此
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 y∈RC, 且只有样本 x \mathbf{x} x对应类别处值为1,其余值为0. W ∈ R C × K W \in R^{C \times K} W∈RC×K, x ∈ R K \mathbf{x} \in R^K x∈RK表示样本.
因此可以进一步化简得到:
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))
我们的目标是求:
∂ l ∂ W \frac{\partial{l}}{\partial{W}} ∂W∂l
这是标量
关于矩阵
的求导. 使用矩阵求导术:
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 ∂W∂l=(−y+softmax(Wx))xT
加入正则项后,以二范数举例,一个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=1∑Nli+21λ∣∣W∣∣2
很容易求得, 加入正则项后,梯度会增加 λ 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 ∂W∂L=N−YXT+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} Y∈RC×N,X∈RK×N,W∈RC×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))