前言
完整代码见github
问答总结
mask[score>0] = 1
)的含义是什么?x[[0,1],[1,2]]
)的含义是什么?main
,train
,evaluate
三个部分。说明他们各自的作用。多类别SVM
损失函数进行图片分类,推导梯度更新公式,使用随机梯度下降算法
更新梯度。no-reg
,L1-reg
, L2-reg
结果差异。数据集依然使用cifar-10
数据,其使用方式见此。
对样本 i i i, 多类别SVM
损失函数定义如下:
L i = ∑ j ≠ y i { 0 s y i − s j − 1 ≥ 0 s j − s y i + 1 e l s e L_i=\sum_{j \ne y_i}\begin{cases} 0 & s_{y_i}-s_j-1 \ge 0 \\ s_j-s_{y_i}+1 & else \end{cases} Li=j̸=yi∑{0sj−syi+1syi−sj−1≥0else
其中 y i y_i yi是样本 i i i的真实类别, s s s表示其得分,其意义在于:希望使得正确类别的分数比其他类别至少高1,其中由于超平面的特性,阈值1
可以为任意正数。
首先明确模型为 f ( W , b ) f(W,b) f(W,b), 其中 W ∈ R K × C , b ∈ R C W \in R^{K \times C}, b\in R^{C} W∈RK×C,b∈RC, 其中 C C C为类别数。对于样本 X i ∈ R 1 × K X_i \in R^{1 \times K} Xi∈R1×K, 我们计算样本第 c c c类得分: s c = X i W c + b c s_c=X_iW_c+b_c sc=XiWc+bc
我们最终目标是更新参数 W , b W, b W,b。令每一个batch的样本数量为N, 令每个batch的总损失为 L L L, 则有
L = 1 N ∑ i = 1 N L i L = \frac{1}{N}\sum_{i=1}^NL_i L=N1i=1∑NLi
我们目标是求 ∂ L ∂ W , ∂ L ∂ b \frac{\partial L}{\partial W}, \frac{\partial L}{\partial b} ∂W∂L,∂b∂L, 稍作变换:
∂ L ∂ W = 1 N ∑ i = 1 N ∂ L i ∂ W \frac{\partial L}{\partial W}= \frac{1}{N}\sum_{i=1}^N\frac{\partial L_i}{\partial W} ∂W∂L=N1i=1∑N∂W∂Li
∂ L ∂ b = 1 N ∑ i = 1 N ∂ L i ∂ b \frac{\partial L}{\partial b}= \frac{1}{N}\sum_{i=1}^N\frac{\partial L_i}{\partial b} ∂b∂L=N1i=1∑N∂b∂Li
由于:
L i = ∑ j ≠ y i a n d s y i − s j − 1 < 0 s j − s y i + 1 = ∑ j ≠ y i a n d s y i − s j − 1 < 0 X i W j + b j − X i W y i − b y i + 1 L_i = \sum_{j \ne y_i \ and \ s_{y_i}-s_j-1 < 0} s_j-s_{y_i}+1=\sum_{j \ne y_i \ and \ s_{y_i}-s_j-1 < 0} X_iW_{j}+b_j-X_iW_{y_i}-b_{y_i}+1 Li=j̸=yi and syi−sj−1<0∑sj−syi+1=j̸=yi and syi−sj−1<0∑XiWj+bj−XiWyi−byi+1
故:
∂ L i ∂ W j = ∑ j ≠ y i a n d s y i − s j − 1 < 0 X i T \frac{\partial L_i}{\partial W_j}=\sum_{j \ne y_i \ and \ s_{y_i}-s_j-1 < 0}X_i^T ∂Wj∂Li=j̸=yi and syi−sj−1<0∑XiT
∂ L i ∂ W y i = ∑ j ≠ y i a n d s y i − s j − 1 < 0 − X i T \frac{\partial L_i}{\partial W_{y_i}}=\sum_{j \ne y_i \ and \ s_{y_i}-s_j-1 < 0}-X_i^T ∂Wyi∂Li=j̸=yi and syi−sj−1<0∑−XiT
∂ L i ∂ b j = ∑ j ≠ y i a n d s y i − s j − 1 < 0 1 \frac{\partial L_i}{\partial b_j}=\sum_{j \ne y_i \ and \ s_{y_i}-s_j-1 < 0}1 ∂bj∂Li=j̸=yi and syi−sj−1<0∑1
∂ L i ∂ b y i = ∑ j ≠ y i a n d s y i − s j − 1 < 0 − 1 \frac{\partial L_i}{\partial b_{y_i}}=\sum_{j \ne y_i \ and \ s_{y_i}-s_j-1 < 0}-1 ∂byi∂Li=j̸=yi and syi−sj−1<0∑−1
这样,我们就能够进行更新了。
加入正则项后,以二范数举例,一个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):(N, K:3*32*32+1)
Y(Tensor):(N)
reg(float): # 正则化系数
输出:
L(int): 1 # 损失
dW(Tensor):(K+1,C) # 参数梯度
"""
L = 0.0
N = X.size(0)
F, C = self.W.size()
dW = torch.zeros(F, C)
# (1) 求解损失
for idx, Xi in enumerate(X):
yi = Y[idx]
scores = Xi.matmul(self.W)
syi = scores[yi]
for j in range(self.N):
if j == yi:
continue
sj = scores[j]
if syi - sj - 1 < 0:
L += (sj - syi + 1).item()
dW[:,j] += Xi.t()
dW[:,yi] -= Xi.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) 向量化
基于此,写出向量化代码:
def cal_dw_with_vec(self, X, Y, reg):
"""
功能: 计算损失和梯度
输入:
X(Tensor):(N, K:3*32*32+1)
Y(Tensor):(N)
reg(float): # 正则化系数
输出:
L(int): 1 # 损失
dW(Tensor):(K+1,C) # 参数梯度
"""
N = X.size(0)
F, C = self.W.size()
score = X.matmul(self.W) # (N, C)
correct = score[range(N), Y.tolist()].unsqueeze(1) # (N, 1)
score = torch.relu(score-correct+1) # (N, C)
score[range(N), Y.tolist()] = 0
L = torch.sum(score).item()
L = L / N + 0.5*reg*torch.sum(torch.pow(self.W, 2)).item()
dW = torch.zeros(F, C)
mask = torch.zeros(N, C)
mask[score>0] = 1 # (N,C)
mask[range(N), Y.tolist()] = -torch.sum(mask, 1) # (N,C)
dW = X.t().matmul(mask) # (F,C)
dW = dW / N + reg*self.W
return L, dW
这个代码中有很多需要注意的小细节。比如mask[[1,2],[2,1]] = 1
的含义。比如mask[score>0] = 1
的含义。
由于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_svm = None
best_acc = -1
for lr in lrs:
for reg in reg_strs:
svmEr = train(lr, reg, 25)
acc = evaluate(svmEr)
print("lr:{}; reg:{}; acc:{}".format(lr, reg, acc))
if acc > best_acc:
best_lr = lr
best_reg = reg
best_svm = svmEr
result[(lr, reg)] = acc
print("the best: lr:{}; reg:{}; acc:{}".format(best_lr, best_reg, best_acc))