前面介绍了两部分:
1. 评分函数:将原始图像像素映射为分类评分值
2. 损失函数:根据评分函数和训练集图像数据实际分类的一致性,衡量某个具体参数集的质量好坏。
那么寻找到能使损失函数值最小化的参数的过程就是最优化 Optimization。
损失函数L可以看作是权重W的函数,在CIFAR-10中一个分类器的权重矩阵大小是[10,3073],即 L(W1,W2,....,W10) L ( W 1 , W 2 , . . . . , W 10 ) ,对其中某一个分类器 Wi W i 有3073个参数,想要得到 L L 关于 Wi W i 的可视化很难。
但是方法还是有的,随机生成一个权重矩阵W,并沿着此方向计算损失值, L(W+αW1) L ( W + α W 1 ) 。
具体参考这里!
还有这样一篇paper!
总而言之,就是将高维空间压缩到二维, Wi[1,3073] W i [ 1 , 3073 ] 转换到 [1,1] [ 1 , 1 ] 然后在此基础上,画出loss关于它的值。
如果是压缩到三维,就是[1,3073]->[1,2],那么完整的loss就是这个形状的3073/2*10维的版本。
在数学上我们已经知道loss下降最快的方向就是梯度方向(gradient)。
公式: df(x)dx=limh→0f(x+h)−f(x)h d f ( x ) d x = lim h → 0 f ( x + h ) − f ( x ) h
下面代码是一个输入为函数f和向量x,计算f的梯度的通用函数,它返回函数f在点x处的梯度:
def eval_numerical_gradient(f, x):
"""
一个f在x处的数值梯度法的简单实现
- f是只有一个参数的函数
- x是计算梯度的点
"""
fx = f(x) # 在原点计算函数值
grad = np.zeros(x.shape) ##
h = 0.00001
# 对x中所有的索引进行迭代
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
# 计算x+h处的函数值
ix = it.multi_index
old_value = x[ix]
x[ix] = old_value + h # 增加h
fxh = f(x) # 计算f(x + h)
x[ix] = old_value # 存到前一个值中 (非常重要)
# 计算偏导数
grad[ix] = (fxh - fx) / h # 坡度
it.iternext() # 到下个维度
return grad
可以使用上面这个公式来计算任意函数在任意点上的梯度。下面计算权重空间中的某些随机点上,CIFAR-10损失函数的梯度:
import random
import numpy as np
from cs231n.data_utils import load_CIFAR_batch
from cs231n.classifiers.softmax import softmax_loss_vectorized
##原始数据
cifar10_dir = 'cs231n/datasets/cifar-10-batches-py/data_batch_1' #只取一组数据
X_train, y_train = load_CIFAR_batch(cifar10_dir) #(1000,32,32,3) (1000,1)
X_train = np.reshape(X_train, (X_train.shape[0], -1)) ##(1000,3072)
#归一化
mean_image = np.mean(X_train, axis = 0)
X_train -= mean_image
# add bias dimension and transform into columns
X_train = np.hstack([X_train, np.ones((X_train.shape[0], 1))])
##根据softmax损失函数据算loss,这个函数里面有用微分法计算梯度,但我们只取loss
def CIFAR_loss_fun(W):
loss,dw = softmax_loss_vectorized(W, X_train, y_train, 0.000005)
return loss
W = np.random.randn(3073, 10) * 0.0001 ##随机权重向量
df = eval_numerical_gradient(CIFAR_loss_fun, W) ##计算权重空间下任意点关于loss的梯度
loss_original = CIFAR_loss_fun(W) ##初始损失值
print("original loss: %f"%(loss_original,))
#查看不同步长的效果
for step_size_log in [-10,-9,-8,-7,-6,-5,-4,-3,-2,-1]:
step_size = 10**step_size_log
W_new = W-step_size*df
loss_new = CIFAR_loss_fun(W_new)
print("for step size %f new loss: %f" % (step_size, loss_new))
original loss: 2.379955
for step size 0.000000 new loss: 2.379945
for step size 0.000000 new loss: 2.379855
for step size 0.000000 new loss: 2.378953
for step size 0.000000 new loss: 2.370042
for step size 0.000001 new loss: 2.291368
for step size 0.000010 new loss: 2.294673
for step size 0.000100 new loss: 12.200434
/home/panxie/文档/cs231n/assignment1/cs231n/classifiers/softmax.py:85: RuntimeWarning: divide by zero encountered in log
loss = np.sum(-np.log(coef[range(num_train), y]))
for step size 0.001000 new loss: inf
for step size 0.010000 new loss: inf
for step size 0.100000 new loss: inf
有限差值法太太太太慢了,而且终究只是近似。第二个梯度计算方法是利用微分来分析,能得到计算梯度的公式(不是近似),用公式计算梯度速度很快,唯一不好的就是实现的时候容易出错。为了解决这个问题,在实际操作时常常将分析梯度法的结果和数值梯度法的结果作比较,以此来检查其实现的正确性,这个步骤叫做梯度检查。
以sofemax为例进行推导:
对单个样本其损失值:
Li=−log(efyi∑jefj) L i = − log ( e f y i ∑ j e f j )
整个数据集的损失值:
L=1N∑iLi+λ∑k∑lW2k,l L = 1 N ∑ i L i + λ ∑ k ∑ l W k , l 2
L=1N∑i−log(efyi∑jefj)+λ∑k∑lW2k,l L = 1 N ∑ i − log ( e f y i ∑ j e f j ) + λ ∑ k ∑ l W k , l 2
这里 fyi f y i 是一个样本对应的真实标签的值,shape=(1,)
L=1N∑i−log(efyi∑jefj)+λ∑k∑lW2k,l=1N∑i(log(∑jefj)−fyi)+λ∑k∑lW2k,l L = 1 N ∑ i − log ( e f y i ∑ j e f j ) + λ ∑ k ∑ l W k , l 2 = 1 N ∑ i ( l o g ( ∑ j e f j ) − f y i ) + λ ∑ k ∑ l W k , l 2
这里 fyi f y i 是所有样本对应的真实标签的值,shape=(N,1) 代码中: score[range(N),y]
其中 fyi=Wyix f y i = W y i x , fi=Wj≠yix f i = W j ≠ y i x
对W求导, Wyi W y i 和 Wj≠yi W j ≠ y i 的求导是不一样的
∂L∂Wyi=1N(∑ieWyix∑jeWjxx−xI(j==yi))+2λW ∂ L ∂ W y i = 1 N ( ∑ i e W y i x ∑ j e W j x x − x I ( j == y i ) ) + 2 λ W
代码实现:
score = X.dot(W) ##(N,3073)*(3073,10) = (N,10)
score -= np.max(score, axis=1, keepdims=True) # [N,10]
exp_score = np.exp(score) # [N,10]
sum_score = np.sum(exp_score, axis=1, keepdims=True) ##(N,1)以行为单位求和
coef = exp_score / sum_score #(N,10)
loss = np.sum(-np.log(coef[range(num_train), y])) ##取出coef中每个样本真实标签对应的那列,然后以行为单位求和,即总loss
loss /= num_train
loss += reg * np.sum(W * W)
coef_yi = np.zeros_like(coef)
coef_yi[range(num_train), y] = 1 ##W矩阵中{j==y_i}每一行真实标签对应的位置参数要-1
dW = X.T.dot(coef - coef_yi)
dW /= num_train
dW += reg * 2 * W