Files
2026-07-13 12:09:03 +08:00

288 lines
8.7 KiB
Python

import math
def rosenbrock(params):
x, y = params
return (1 - x) ** 2 + 100 * (y - x ** 2) ** 2
def rosenbrock_gradient(params):
x, y = params
df_dx = -2 * (1 - x) + 200 * (y - x ** 2) * (-2 * x)
df_dy = 200 * (y - x ** 2)
return [df_dx, df_dy]
class GradientDescent:
def __init__(self, lr=0.001):
self.lr = lr
def step(self, params, grads):
return [p - self.lr * g for p, g in zip(params, grads)]
class SGDMomentum:
def __init__(self, lr=0.001, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.velocity = None
def step(self, params, grads):
if self.velocity is None:
self.velocity = [0.0] * len(params)
self.velocity = [
self.momentum * v + g
for v, g in zip(self.velocity, grads)
]
return [p - self.lr * v for p, v in zip(params, self.velocity)]
class Adam:
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.epsilon = epsilon
self.m = None
self.v = None
self.t = 0
def step(self, params, grads):
if self.m is None:
self.m = [0.0] * len(params)
self.v = [0.0] * len(params)
self.t += 1
self.m = [
self.beta1 * m + (1 - self.beta1) * g
for m, g in zip(self.m, grads)
]
self.v = [
self.beta2 * v + (1 - self.beta2) * g ** 2
for v, g in zip(self.v, grads)
]
m_hat = [m / (1 - self.beta1 ** self.t) for m in self.m]
v_hat = [v / (1 - self.beta2 ** self.t) for v in self.v]
return [
p - self.lr * mh / (vh ** 0.5 + self.epsilon)
for p, mh, vh in zip(params, m_hat, v_hat)
]
def optimize(optimizer, func, grad_func, start, steps=5000):
params = list(start)
history = [params[:]]
for _ in range(steps):
try:
grads = grad_func(params)
if any(math.isnan(g) or math.isinf(g) or abs(g) > 1e15 for g in grads):
break
params = optimizer.step(params, grads)
if any(math.isnan(p) or math.isinf(p) or abs(p) > 1e15 for p in params):
break
history.append(params[:])
except (OverflowError, ValueError):
break
return history
def distance_to_minimum(params, target=(1.0, 1.0)):
return math.sqrt(sum((p - t) ** 2 for p, t in zip(params, target)))
def find_convergence_step(history, func, threshold=1e-4):
for i, params in enumerate(history):
if func(params) < threshold:
return i
return len(history)
def print_trajectory(name, history, func, steps_to_show=10):
total = len(history) - 1
interval = max(1, total // steps_to_show)
print(f"\n{'=' * 60}")
print(f" {name}")
print(f"{'=' * 60}")
print(f" {'Step':>6s} {'x':>10s} {'y':>10s} {'Loss':>14s} {'Dist':>8s}")
print(f" {'-' * 52}")
for i in range(0, total + 1, interval):
p = history[i]
loss = func(p)
dist = distance_to_minimum(p)
print(f" {i:6d} {p[0]:10.6f} {p[1]:10.6f} {loss:14.8f} {dist:8.4f}")
final = history[-1]
if total % interval != 0:
loss = func(final)
dist = distance_to_minimum(final)
print(f" {total:6d} {final[0]:10.6f} {final[1]:10.6f} {loss:14.8f} {dist:8.4f}")
def print_ascii_convergence(results, func, steps=5000):
print(f"\n{'=' * 60}")
print(" CONVERGENCE COMPARISON (log10 loss over steps)")
print(f"{'=' * 60}")
width = 50
sample_points = 40
interval = max(1, steps // sample_points)
for name, history in results:
losses = []
for i in range(0, min(len(history), steps + 1), interval):
loss = func(history[i])
losses.append(loss)
if not losses:
continue
max_log = 5.0
min_log = -8.0
log_range = max_log - min_log
bar = []
for loss in losses:
log_loss = math.log10(loss + 1e-15)
log_loss = max(min_log, min(max_log, log_loss))
normalized = (log_loss - min_log) / log_range
pos = int(normalized * (width - 1))
bar.append(pos)
print(f"\n {name}:")
print(f" loss 1e-8 {'.' * width} 1e+5")
for i, pos in enumerate(bar):
step_num = i * interval
line = [' '] * width
line[pos] = '*'
print(f" {step_num:5d} |{''.join(line)}|")
final_loss = func(history[-1])
conv_step = find_convergence_step(history, func)
conv_msg = f"step {conv_step}" if conv_step < len(history) else "did not converge"
print(f" final loss: {final_loss:.2e}, converged (< 1e-4): {conv_msg}")
def demo_comparison():
print("OPTIMIZATION METHODS COMPARISON")
print("Minimizing the Rosenbrock function: f(x,y) = (1-x)^2 + 100(y-x^2)^2")
print("Global minimum at (1, 1) where f = 0")
print(f"Starting point: (-1.0, 1.0), f = {rosenbrock([-1.0, 1.0]):.1f}")
start = [-1.0, 1.0]
steps = 5000
configs = [
("Gradient Descent", GradientDescent(lr=0.0005)),
("SGD + Momentum", SGDMomentum(lr=0.0001, momentum=0.9)),
("Adam", Adam(lr=0.01)),
]
results = []
for name, optimizer in configs:
history = optimize(optimizer, rosenbrock, rosenbrock_gradient, start, steps)
results.append((name, history))
print_trajectory(name, history, rosenbrock)
print_ascii_convergence(results, rosenbrock, steps)
print(f"\n{'=' * 60}")
print(" FINAL RESULTS")
print(f"{'=' * 60}")
print(f" {'Method':<22s} {'x':>10s} {'y':>10s} {'Loss':>14s}")
print(f" {'-' * 58}")
for name, history in results:
final = history[-1]
loss = rosenbrock(final)
print(f" {name:<22s} {final[0]:10.6f} {final[1]:10.6f} {loss:14.8f}")
print(f"\n Target: x=1.000000, y=1.000000, loss=0.00000000")
def demo_learning_rate_effect():
print(f"\n\n{'=' * 60}")
print(" LEARNING RATE EFFECT ON GRADIENT DESCENT")
print(f"{'=' * 60}")
start = [-1.0, 1.0]
rates = [0.0001, 0.0005, 0.001, 0.005]
print(f"\n {'LR':>8s} {'Final x':>10s} {'Final y':>10s} {'Loss':>14s} {'Status'}")
print(f" {'-' * 60}")
for lr in rates:
gd = GradientDescent(lr=lr)
history = optimize(gd, rosenbrock, rosenbrock_gradient, start, 5000)
final = history[-1]
loss = rosenbrock(final)
diverged = loss > 1e10 or math.isnan(loss) or math.isinf(loss)
status = "DIVERGED" if diverged else ("converged" if loss < 0.01 else "slow")
if diverged:
print(f" {lr:8.4f} {'nan':>10s} {'nan':>10s} {'inf':>14s} {status}")
else:
print(f" {lr:8.4f} {final[0]:10.6f} {final[1]:10.6f} {loss:14.8f} {status}")
def demo_momentum_effect():
print(f"\n\n{'=' * 60}")
print(" MOMENTUM EFFECT ON SGD")
print(f"{'=' * 60}")
start = [-1.0, 1.0]
betas = [0.0, 0.5, 0.9, 0.99]
print(f"\n {'Beta':>6s} {'Final x':>10s} {'Final y':>10s} {'Loss':>14s}")
print(f" {'-' * 46}")
for beta in betas:
sgd = SGDMomentum(lr=0.0001, momentum=beta)
history = optimize(sgd, rosenbrock, rosenbrock_gradient, start, 5000)
final = history[-1]
loss = rosenbrock(final)
if math.isnan(loss) or math.isinf(loss):
print(f" {beta:6.2f} {'nan':>10s} {'nan':>10s} {'inf':>14s}")
else:
print(f" {beta:6.2f} {final[0]:10.6f} {final[1]:10.6f} {loss:14.8f}")
def demo_saddle_point():
print(f"\n\n{'=' * 60}")
print(" SADDLE POINT ESCAPE: f(x,y) = x^2 - y^2")
print(f"{'=' * 60}")
def saddle(params):
x, y = params
return x ** 2 - y ** 2
def saddle_gradient(params):
x, y = params
return [2 * x, -2 * y]
start = [0.01, 0.01]
steps = 200
configs = [
("Gradient Descent", GradientDescent(lr=0.01)),
("SGD + Momentum", SGDMomentum(lr=0.01, momentum=0.9)),
("Adam", Adam(lr=0.01)),
]
print(f"\n Start: x=0.01, y=0.01 (near saddle at origin)")
print(f"\n {'Method':<22s} {'x':>10s} {'y':>10s} {'f(x,y)':>12s} {'Escaped?'}")
print(f" {'-' * 62}")
for name, optimizer in configs:
history = optimize(optimizer, saddle, saddle_gradient, start, steps)
final = history[-1]
val = saddle(final)
escaped = abs(final[1]) > 1.0
print(f" {name:<22s} {final[0]:10.6f} {final[1]:10.6f} {val:12.6f} {'yes' if escaped else 'no'}")
if __name__ == "__main__":
demo_comparison()
demo_learning_rate_effect()
demo_momentum_effect()
demo_saddle_point()