EUAdvancer

CS231n-Softmax模型分类Cifar10

bg
同样是线性分类器,整体结构的实现和SVM基本是一致的,所以主要的区别还是在代价函数上,而在实际应用上,Softmax模型广泛用于多分类器的输出层,比如CNN,它其实就是拓展版的逻辑回归

代价函数

不同于SVM的折叶损失,softmax的代价函数是交叉熵代价函数,这个我已经在以前的 机器学习-交叉熵与均方误差代价函数 中介绍过

该总损失值其实就是每个样本计算在其正确分类上的概率并将所有的样本的损失相加,下面这个图虽然是用来比较SVM和Softmax的,但是同样可以帮助我们更清晰的看出计算过程

其中的normalize过程就是对分数进行压缩(除以总分数值),输出一个向量,其中每个元素值在0到1之间,最后对正确分类的分数取对数函数log。

梯度计算

以上公式是对于每个分类的参数的梯度,每个样本都会对每个分类产生分数,通过这些分数每个样本都会对偏导数进行一次改变,将所有样本对偏导值的改变相加就得到了每次迭代的偏导值。

非向量化实现代价函数和偏导值

以下是非向量化的实现方式,虽然它并不适用于实际应用,但是对于我们理解算法有着很多的好处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def softmax_loss_naive(W, X, y, reg):
"""
Softmax 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
"""

# Initialize the loss and gradient to zero.
loss = 0.0
dW = np.zeros_like(W)

for i in range(X.shape[0]):
score = X[i, :].dot(W) # C * 1
score -= np.max(score) # avoid numeric instability
exp_score = np.exp(score)

# cal loss
loss += np.log(exp_score[y[i]] / np.sum(exp_score))

# cal gred
dW[:, y[i]] += X[i, :]
for j in range(W.shape[1]):
dW[:, j] -= exp_score[j] / np.sum(exp_score) * X[i, :]

loss = -1 * loss / X.shape[0] + reg / 2 * np.sum(W * W)
dW = -1 * dW / X.shape[0] + reg * W

return loss, dW

向量化实现代价函数和偏导值

向量化的实现其实没什么特别的,首先就是计算所有样本对每个样本的分数(利用numpy库),并转换成概率,然后将所有正确分类的分数加起来就是损失值了(matlab可以用sparse和full),偏导值也就不难计算了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def softmax_cost_function(self, X, y, reg):
# X: N * D, y: N
num_train = X.shape[0]
scores = X.dot(self.W.T)
scores = (scores - np.matrix(np.max(scores, axis=1)).T).getA()
exp_scores = np.exp(scores) # N * C
pro_scores = (exp_scores / np.matrix(np.sum(exp_scores, axis=1)).T).getA()

ground_true = np.zeros(scores.shape)
ground_true[range(num_train), y] = 1

loss = -1 * np.sum(ground_true * np.log(pro_scores)) / num_train + 0.5 * reg * np.sum(self.W * self.W)
gred = -1 * (ground_true - pro_scores).T.dot(X) / num_train + reg * self.W

return loss, gred

完整代码

由于很大一部分代码和SVM中的重复,所以我就不重复说了,详细的流程介绍可以看 CS231n-线性SVM分类Cifar10, 结果大概有32%左右的识别率,代码见 github/cs231n

对最后训练好的参数进行可视化效果如下

结语

因为刚实现完SVM,所以实现softmax感觉还是很轻松的,softmax模型在以后的dl模型中也应该会经常遇到的