Skip to content

Commit 8b479a0

Browse files
committed
feature: visualization
1 parent fb72e10 commit 8b479a0

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

examples/__init__.py

Whitespace-only changes.

examples/visualize_optimizers.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import math
2+
from pathlib import Path
3+
4+
import hyperopt.exceptions
5+
import numpy as np
6+
import torch
7+
from hyperopt import fmin, hp, tpe
8+
from matplotlib import pyplot as plt
9+
10+
from pytorch_optimizer import OPTIMIZERS
11+
12+
13+
def rosenbrock(tensors) -> torch.Tensor:
14+
"""https://en.wikipedia.org/wiki/Test_functions_for_optimization"""
15+
x, y = tensors
16+
return (1 - x) ** 2 + 100 * (y - x ** 2) ** 2 # fmt: skip
17+
18+
19+
def rastrigin(tensors, a: float = 10) -> torch.tensor:
20+
"""https://en.wikipedia.org/wiki/Test_functions_for_optimization"""
21+
x, y = tensors
22+
return (
23+
a * 2
24+
+ (x ** 2 - a * torch.cos(x * math.pi * 2))
25+
+ (y ** 2 - a * torch.cos(y * math.pi * 2))
26+
) # fmt: skip
27+
28+
29+
def execute_steps(func, initial_state, optimizer_class, optimizer_config, num_iters: int = 500):
30+
x = torch.Tensor(initial_state).requires_grad_(True)
31+
32+
if optimizer_class.__name__ == 'Ranger21':
33+
optimizer_config.update({'num_iterations': num_iters})
34+
35+
optimizer = optimizer_class([x], **optimizer_config)
36+
37+
steps = np.zeros((2, num_iters + 1), dtype=np.float32)
38+
steps[:, 0] = np.array(initial_state)
39+
40+
for i in range(1, num_iters + 1):
41+
optimizer.zero_grad()
42+
43+
output = func(x)
44+
output.backward(create_graph=True, retain_graph=True)
45+
46+
torch.nn.utils.clip_grad_norm_(x, 1.0)
47+
optimizer.step()
48+
49+
steps[:, i] = x.detach().numpy()
50+
51+
return steps
52+
53+
54+
def objective_rastrigin(params, minimum=(0, 0)):
55+
steps = execute_steps(rastrigin, (-2.0, 3.5), params['optimizer_class'], {'lr': params['lr']}, 100)
56+
57+
return (steps[0][-1] - minimum[0]) ** 2 + (steps[1][-1] - minimum[1]) ** 2
58+
59+
60+
def objective_rosenbrok(params, minimum=(1.0, 1.0)):
61+
steps = execute_steps(rastrigin, (-2.0, 2.0), params['optimizer_class'], {'lr': params['lr']}, 100)
62+
63+
return (steps[0][-1] - minimum[0]) ** 2 + (steps[1][-1] - minimum[1]) ** 2
64+
65+
66+
def plot_rastrigin(grad_iter, optimizer_name, lr) -> None:
67+
x = torch.linspace(-4.5, 4.5, 250)
68+
y = torch.linspace(-4.5, 4.5, 250)
69+
70+
x, y = torch.meshgrid(x, y)
71+
z = rastrigin([x, y])
72+
73+
iter_x, iter_y = grad_iter[0, :], grad_iter[1, :]
74+
75+
fig = plt.figure(figsize=(8, 8))
76+
77+
ax = fig.add_subplot(1, 1, 1)
78+
ax.contour(x.numpy(), y.numpy(), z.numpy(), 20, cmap='jet')
79+
ax.plot(iter_x, iter_y, color='r', marker='x')
80+
ax.set_title(f'Rastrigin func: {optimizer_name} with {len(iter_x)} iterations, lr={lr:.6f}')
81+
82+
plt.plot(0, 0, 'gD')
83+
plt.plot(iter_x[-1], iter_y[-1], 'rD')
84+
plt.savefig(f'../docs/visualizations/rastrigin_{optimizer_name}.png')
85+
plt.close()
86+
87+
88+
def plot_rosenbrok(grad_iter, optimizer_name, lr):
89+
x = torch.linspace(-2, 2, 250)
90+
y = torch.linspace(-1, 3, 250)
91+
92+
x, y = torch.meshgrid(x, y)
93+
z = rosenbrock([x, y])
94+
95+
iter_x, iter_y = grad_iter[0, :], grad_iter[1, :]
96+
97+
fig = plt.figure(figsize=(8, 8))
98+
99+
ax = fig.add_subplot(1, 1, 1)
100+
ax.contour(x.numpy(), y.numpy(), z.numpy(), 90, cmap='jet')
101+
ax.plot(iter_x, iter_y, color='r', marker='x')
102+
103+
ax.set_title(f'Rosenbrock func: {optimizer_name} with {len(iter_x)} iterations, lr={lr:.6f}')
104+
plt.plot(1.0, 1.0, 'gD')
105+
plt.plot(iter_x[-1], iter_y[-1], 'rD')
106+
plt.savefig(f'../docs/visualizations/rosenbrock_{optimizer_name}.png')
107+
plt.close()
108+
109+
110+
def execute_experiments(
111+
optimizers, objective, func, plot_func, initial_state, root_path: Path, exp_name: str, seed: int = 42
112+
):
113+
for item in optimizers:
114+
optimizer_class, lr_low, lr_hi = item
115+
116+
if (root_path / f'{exp_name}_{optimizer_class.__name__}.png').exists():
117+
continue
118+
119+
space = {
120+
'optimizer_class': hp.choice('optimizer_class', [optimizer_class]),
121+
'lr': hp.loguniform('lr', lr_low, lr_hi),
122+
}
123+
124+
try:
125+
best = fmin(
126+
fn=objective,
127+
space=space,
128+
algo=tpe.suggest,
129+
max_evals=200,
130+
rstate=np.random.default_rng(seed),
131+
)
132+
except hyperopt.exceptions.AllTrialsFailed:
133+
continue
134+
135+
steps = execute_steps(
136+
func,
137+
initial_state,
138+
optimizer_class,
139+
{'lr': best['lr']},
140+
500,
141+
)
142+
143+
plot_func(steps, optimizer_class.__name__, best['lr'])
144+
145+
146+
def main():
147+
np.random.seed(42)
148+
torch.manual_seed(42)
149+
150+
root_path = Path('..') / 'docs' / 'visualizations'
151+
152+
optimizers = [
153+
(torch.optim.AdamW, -6, 0.5),
154+
(torch.optim.Adam, -6, 0.5),
155+
(torch.optim.SGD, -6, -1.0),
156+
]
157+
158+
for optimizer_name, optimizer in OPTIMIZERS.items():
159+
if optimizer_name.lower() in {'alig', 'lomo', 'bsam', 'adammini'}:
160+
continue
161+
162+
optimizers.append((optimizer, -6, 0.2))
163+
164+
execute_experiments(
165+
optimizers,
166+
objective_rastrigin,
167+
rastrigin,
168+
plot_rastrigin,
169+
(-2.0, 3.5),
170+
root_path,
171+
'rastrigin',
172+
)
173+
174+
execute_experiments(
175+
optimizers,
176+
objective_rosenbrok,
177+
rosenbrock,
178+
plot_rosenbrok,
179+
(-2.0, 2.0),
180+
root_path,
181+
'rosenbrok',
182+
)

0 commit comments

Comments
 (0)