SGD在ravines的情况下容易被困住,ravines就是曲面的一个方向比另一个方向更陡,这时SGD会发生震荡而迟迟不能接近极小值:
如下图所示
• 函数主体缓缓向右方下降
• 在主体方向两侧各有一面高墙,导致垂直于主体方向有更大的梯
度
• 梯度下降法会在隧道两侧频繁震荡
因此我们引入动量法:
动量这个词语来自物理,表示力对时间的累积效应
SGD+动量公式:
v = mu * v - learning_rate * dw
w = w + v
在普通的梯度下降法中,每次的更新量为,其中为目标函数对w的一阶导数。
当使用动量时,则把每次的更新量考虑为本次的梯度下降量与上次w的更新量乘上一个介于的因子的和,即。
其中,v初始化为0,mu是设定的一个超变量,最常见的设定值是0.9
从公式上可看出:
由于动量积攒了历史的梯度,如点P前一刻的梯度与当前的梯度方向几乎相反。因此原本在P点原本要大幅徘徊的梯度,主要受到前一时刻的影响,而导致在当前时刻的梯度幅度减小。
直观上讲就是,要是当前时刻的梯度与历史时刻梯度方向相似,这种趋势在当前时刻则会加强;要是不同,则当前时刻的梯度方向减弱。
python实现:
def backward(model, xs, hs, errs):
dW2 = hs.T @ errs
dh = errs @ model['W2'].T
dh[hs
dW1 = xs.T @ dh
return dict(W1=dW1, W2=dW2)
def forward(x, model):
h = x @ model['W1']
h[h
prob = softmax(h @ model['W2'])
return h, prob
def get_minibatch_grad(model, X_train, y_train):
xs, hs, errs = [], [], []
for x, cls_idx in zip(X_train, y_train):
h, y_pred = forward(x, model)
y_true = np.zeros(n_class)
y_true[int(cls_idx)] = 1.
err = y_true - y_pred
xs.append(x)
hs.append(h)
errs.append(err)
return backward(model, np.array(xs), np.array(hs), np.array(errs))
def get_minibatch(X, y, minibatch_size):
minibatches = []
X, y = shuffle(X, y)
for i in range(0, X.shape[0], minibatch_size):
X_mini = X[i:i + minibatch_size]
y_mini = y[i:i + minibatch_size]
minibatches.append((X_mini, y_mini))
return minibatches
def sgd(model, X_train, y_train, minibatch_size):
minibatches = get_minibatch(X_train, y_train, minibatch_size)
for iter in range(1, n_iter + 1):
#送入多少批
X_mini, y_mini = minibatches[idx]
grad = get_minibatch_grad(model, X_mini, y_mini)
for layer in grad:
model[layer] += alpha * grad[layer]
return model
def momentum(model, X_train, y_train, minibatch_size):
velocity =
gamma = .9
minibatches = get_minibatch(X_train, y_train, minibatch_size)
for iter in range(1, n_iter + 1):
X_mini, y_mini = minibatches[idx]
grad = get_minibatch_grad(model, X_mini, y_mini)
for layer in grad:
velocity[layer] = gamma * velocity[layer] + alpha * grad[layer]
model[layer] += velocity[layer]
return model
领取专属 10元无门槛券
私享最新 技术干货