Files
2026-07-13 13:39:55 +08:00

252 lines
8.6 KiB
Python

import logging
import time
from collections import defaultdict
import numpy as np
from tqdm import tqdm
from mla.utils import batch_iterator
"""
References:
Gradient descent optimization algorithms https://ruder.io/optimizing-gradient-descent/
"""
class Optimizer(object):
def optimize(self, network):
loss_history = []
for i in range(network.max_epochs):
if network.shuffle:
network.shuffle_dataset()
start_time = time.time()
loss = self.train_epoch(network)
loss_history.append(loss)
if network.verbose:
msg = "Epoch:%s, train loss: %s" % (i, loss)
if network.log_metric:
msg += ", train %s: %s" % (network.metric_name, network.error())
msg += ", elapsed: %s sec." % (time.time() - start_time)
logging.info(msg)
return loss_history
def update(self, network):
"""Performs an update of parameters."""
raise NotImplementedError
def train_epoch(self, network):
losses = []
# Create batch iterator
X_batch = batch_iterator(network.X, network.batch_size)
y_batch = batch_iterator(network.y, network.batch_size)
batch = zip(X_batch, y_batch)
if network.verbose:
batch = tqdm(
batch, total=int(np.ceil(network.n_samples / network.batch_size))
)
for X, y in batch:
loss = np.mean(network.update(X, y))
self.update(network)
losses.append(loss)
epoch_loss = np.mean(losses)
return epoch_loss
def train_batch(self, network, X, y):
loss = np.mean(network.update(X, y))
self.update(network)
return loss
def setup(self, network):
"""Creates additional variables.
Note: Must be called before optimization process."""
raise NotImplementedError
class SGD(Optimizer):
def __init__(self, learning_rate=0.01, momentum=0.9, decay=0.0, nesterov=False):
self.nesterov = nesterov
self.decay = decay
self.momentum = momentum
self.lr = learning_rate
self.iteration = 0
self.velocity = None
def update(self, network):
lr = self.lr * (1.0 / (1.0 + self.decay * self.iteration))
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
# Get gradient values
grad = layer.parameters.grad[n]
update = self.momentum * self.velocity[i][n] - lr * grad
self.velocity[i][n] = update
if self.nesterov:
# Adjust using updated velocity
update = self.momentum * self.velocity[i][n] - lr * grad
layer.parameters.step(n, update)
self.iteration += 1
def setup(self, network):
self.velocity = defaultdict(dict)
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
self.velocity[i][n] = np.zeros_like(layer.parameters[n])
class Adagrad(Optimizer):
def __init__(self, learning_rate=0.01, epsilon=1e-8):
self.eps = epsilon
self.lr = learning_rate
def update(self, network):
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
grad = layer.parameters.grad[n]
self.accu[i][n] += grad**2
step = self.lr * grad / (np.sqrt(self.accu[i][n]) + self.eps)
layer.parameters.step(n, -step)
def setup(self, network):
# Accumulators
self.accu = defaultdict(dict)
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
self.accu[i][n] = np.zeros_like(layer.parameters[n])
class Adadelta(Optimizer):
def __init__(self, learning_rate=1.0, rho=0.95, epsilon=1e-8):
self.rho = rho
self.eps = epsilon
self.lr = learning_rate
def update(self, network):
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
grad = layer.parameters.grad[n]
self.accu[i][n] = (
self.rho * self.accu[i][n] + (1.0 - self.rho) * grad**2
)
step = (
grad
* np.sqrt(self.d_accu[i][n] + self.eps)
/ np.sqrt(self.accu[i][n] + self.eps)
)
layer.parameters.step(n, -step * self.lr)
# Update delta accumulator
self.d_accu[i][n] = (
self.rho * self.d_accu[i][n] + (1.0 - self.rho) * step**2
)
def setup(self, network):
# Accumulators
self.accu = defaultdict(dict)
self.d_accu = defaultdict(dict)
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
self.accu[i][n] = np.zeros_like(layer.parameters[n])
self.d_accu[i][n] = np.zeros_like(layer.parameters[n])
class RMSprop(Optimizer):
def __init__(self, learning_rate=0.001, rho=0.9, epsilon=1e-8):
self.eps = epsilon
self.rho = rho
self.lr = learning_rate
def update(self, network):
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
grad = layer.parameters.grad[n]
self.accu[i][n] = (self.rho * self.accu[i][n]) + (1.0 - self.rho) * (
grad**2
)
step = self.lr * grad / (np.sqrt(self.accu[i][n]) + self.eps)
layer.parameters.step(n, -step)
def setup(self, network):
# Accumulators
self.accu = defaultdict(dict)
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
self.accu[i][n] = np.zeros_like(layer.parameters[n])
class Adam(Optimizer):
def __init__(self, learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-8):
self.epsilon = epsilon
self.beta_2 = beta_2
self.beta_1 = beta_1
self.lr = learning_rate
self.iterations = 0
self.t = 1
def update(self, network):
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
grad = layer.parameters.grad[n]
self.ms[i][n] = (self.beta_1 * self.ms[i][n]) + (
1.0 - self.beta_1
) * grad
self.vs[i][n] = (self.beta_2 * self.vs[i][n]) + (
1.0 - self.beta_2
) * grad**2
lr = (
self.lr
* np.sqrt(1.0 - self.beta_2**self.t)
/ (1.0 - self.beta_1**self.t)
)
step = lr * self.ms[i][n] / (np.sqrt(self.vs[i][n]) + self.epsilon)
layer.parameters.step(n, -step)
self.t += 1
def setup(self, network):
# Accumulators
self.ms = defaultdict(dict)
self.vs = defaultdict(dict)
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
self.ms[i][n] = np.zeros_like(layer.parameters[n])
self.vs[i][n] = np.zeros_like(layer.parameters[n])
class Adamax(Optimizer):
def __init__(self, learning_rate=0.002, beta_1=0.9, beta_2=0.999, epsilon=1e-8):
self.epsilon = epsilon
self.beta_2 = beta_2
self.beta_1 = beta_1
self.lr = learning_rate
self.t = 1
def update(self, network):
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
grad = layer.parameters.grad[n]
self.ms[i][n] = self.beta_1 * self.ms[i][n] + (1.0 - self.beta_1) * grad
self.us[i][n] = np.maximum(self.beta_2 * self.us[i][n], np.abs(grad))
step = (
self.lr
/ (1 - self.beta_1**self.t)
* self.ms[i][n]
/ (self.us[i][n] + self.epsilon)
)
layer.parameters.step(n, -step)
self.t += 1
def setup(self, network):
self.ms = defaultdict(dict)
self.us = defaultdict(dict)
for i, layer in enumerate(network.parametric_layers):
for n in layer.parameters.keys():
self.ms[i][n] = np.zeros_like(layer.parameters[n])
self.us[i][n] = np.zeros_like(layer.parameters[n])