-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanswer.py
More file actions
261 lines (231 loc) · 10.6 KB
/
Copy pathanswer.py
File metadata and controls
261 lines (231 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# Author: Suvansh Sanjeev (suvansh@berkeley.edu)
# Course: EECS 127 (UC Berkeley)
# Notes: Parts adapted from http://louistiao.me/notes/visualizing-and-animating-optimization-algorithms-with-matplotlib/
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.colors import LogNorm
from matplotlib import animation
from scipy.optimize import minimize, OptimizeResult
class Answer:
def __init__(self, methods, func, grad):
self.methods = methods
self.func = func
self.grad = grad
self.has_set = self.has_set_fn = False
""" For your use """
def set_fn_settings(self, fn_name):
self.fn_name = fn_name
self.xmin, self.xmax, self.xstep = self.get_coord_bounds(fn_name)
self.ymin, self.ymax, self.ystep = self.get_coord_bounds(fn_name)
self.x, self.y = np.meshgrid(np.arange(self.xmin, self.xmax + self.xstep, self.xstep),
np.arange(self.ymin, self.ymax + self.ystep, self.ystep))
self.f = self.get_fg(fn_name)
self.z = self.f((self.x, self.y))[0]
self.minima_ = self.get_minimum(fn_name)
self.elev, self.azim = self.get_elev_azim(fn_name)
self.has_set_fn = True
def set_settings(self, fn_name, method, x0, **kwargs):
if method not in self.methods:
raise ValueError('Invalid method %s' % method)
self.set_fn_settings(fn_name)
self.method = self.methods[method]
self.x0 = x0
self.options = kwargs
path_ = [x0]
result = minimize(self.f, x0=x0, method=self.method,
jac=True, tol=1e-20, callback=self.make_minimize_cb(path_),
options=kwargs)
assert len(result) == 2 and isinstance(result[0], OptimizeResult) and isinstance(result[1], np.ndarray)
self.res, self.losses = result
self.path = np.array(path_).T
self.has_set = True
def get_settings(self):
return self.fn_name, self.method.__name__, self.x0, self.options
def compare(self, method, start_iter=0, **kwargs):
res1, losses1 = self.res, self.losses
curr_settings = self.get_settings()
self.set_settings(self.fn_name, method, self.x0, **kwargs)
res2, losses2 = self.res, self.losses
# plot training curves
method1 = curr_settings[1]
method2 = self.method.__name__
plt.plot(np.arange(len(losses1)-start_iter), losses1[start_iter:], label=method1)
plt.plot(np.arange(len(losses2)-start_iter), losses2[start_iter:], label=method2)
plt.title('Training Curve')
plt.legend()
plt.show()
print('[Method {:>10}] Final loss: {:.4f}, Final x: [{:.4f}, {:.4f}]'.format(method1, losses1[-1], res1.x[0], res1.x[1]))
print('[Method {:>10}] Final loss: {:.4f}, Final x: [{:.4f}, {:.4f}]'.format(method2, losses2[-1], res2.x[0], res2.x[1]))
self.set_settings(*curr_settings[:-1], **curr_settings[-1])
def plot2d(self):
self.check_set_fn()
fig, ax = plt.subplots(figsize=(10, 6))
ax.contour(self.x, self.y, self.z, levels=np.logspace(0, 5, 35), norm=LogNorm(), cmap=plt.cm.jet)
ax.plot(*self.minima_, 'r*', markersize=18)
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
plt.show()
def plot3d(self):
self.check_set_fn()
fig = plt.figure(figsize=(8, 5))
ax = plt.axes(projection='3d', elev=self.elev, azim=self.azim)
ax.plot_surface(self.x, self.y, self.z, norm=LogNorm(), rstride=1, cstride=1,
edgecolor='none', alpha=.8, cmap=plt.cm.jet)
ax.plot(*self.minima_, self.f(self.minima_)[0], 'r*', markersize=10)
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel(self.method.__name__)
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
plt.show()
def path2d(self):
self.check_set()
fig, ax = plt.subplots(figsize=(10, 6))
ax.contour(self.x, self.y, self.z, levels=np.logspace(0, 5, 35), norm=LogNorm(), cmap=plt.cm.jet)
ax.quiver(self.path[0,:-1], self.path[1,:-1], self.path[0,1:]-self.path[0,:-1], self.path[1,1:]-self.path[1,:-1], scale_units='xy', angles='xy', scale=1, color='k')
ax.plot(*self.minima_, 'r*', markersize=18)
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
plt.show()
def path3d(self):
self.check_set()
fig = plt.figure(figsize=(8, 5))
ax = plt.axes(projection='3d', elev=self.elev, azim=self.azim)
ax.plot_surface(self.x, self.y, self.z, norm=LogNorm(), rstride=1, cstride=1, edgecolor='none', alpha=.8, cmap=plt.cm.jet)
ax.quiver(self.path[0,:-1], self.path[1,:-1], self.f(self.path[::,:-1])[0],
self.path[0,1:]-self.path[0,:-1], self.path[1,1:]-self.path[1,:-1],
self.f(self.path[::,1:])[0]-self.f(self.path[::,:-1])[0],
normalize=True, color='k')
ax.plot(*self.minima_, self.f(self.minima_)[0], 'r*', markersize=10)
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel(self.method.__name__)
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
plt.show()
def video2d(self):
self.check_set()
fig, ax = plt.subplots(figsize=(10, 6))
ax.contour(self.x, self.y, self.z, levels=np.logspace(0, 5, 35), norm=LogNorm(), cmap=plt.cm.jet)
ax.plot(*self.minima_, 'r*', markersize=18)
line, = ax.plot([], [], 'b', label=self.method.__name__, lw=2)
point, = ax.plot([], [], 'bo')
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
ax.legend(loc='upper left')
anim = animation.FuncAnimation(fig, self.get_animate2d(line, point), init_func=self.get_init2d(line, point),
frames=self.path.shape[1], interval=60,
repeat_delay=5, blit=True)
filename = filename or '%s_%s_2d.mp4' % (self.fn_name, self.method.__name__)
if not filename.endswith('.mp4'):
filename += '.mp4'
anim.save(filename, writer=animation.writers['imagemagick'](fps=15))
def video3d(self, filename=None):
self.check_set()
fig = plt.figure(figsize=(8, 5))
ax = plt.axes(projection='3d', elev=self.elev, azim=self.azim)
ax.plot_surface(self.x, self.y, self.z, norm=LogNorm(), rstride=1, cstride=1, edgecolor='none', alpha=.8, cmap=plt.cm.jet)
ax.plot(*self.minima_, self.f(self.minima_)[0], 'r*', markersize=10)
line, = ax.plot([], [], [], 'b', label=self.method.__name__, lw=2)
point, = ax.plot([], [], [], 'bo')
ax.set_xlabel('$x_1$')
ax.set_ylabel('$x_2$')
ax.set_zlabel(self.method.__name__)
ax.set_xlim((self.xmin, self.xmax))
ax.set_ylim((self.ymin, self.ymax))
anim = animation.FuncAnimation(fig, self.get_animate3d(line, point), init_func=self.get_init3d(line, point),
frames=self.path.shape[1], interval=60,
repeat_delay=5, blit=True)
filename = filename or '%s_%s_3d.mp4' % (self.fn_name, self.method.__name__)
if not filename.endswith('.mp4'):
filename += '.mp4'
anim.save(filename, writer=animation.FFMpegFileWriter(fps=15))
def get_xs_losses(self):
self.check_set()
return self.path.T, self.losses
def get_min_errs(self):
""" Returns the best x differences and function differences over the run. """
x_err = np.linalg.norm(self.path - self.minima_, axis=0).min()
loss_err = (self.losses - self.f(self.minima_)[0]).min()
return x_err, loss_err
def func_val(self, x):
return self.f(x)[0]
def grad_val(self, x):
return self.f(x)[1]
""" Under the hood """
def check_set_fn(self):
assert self.has_set_fn, "Need to call `set_fn_settings` first."
def check_set(self):
assert self.has_set, "Need to call `set_settings` first."
def get_fg(self, fn_name):
return lambda x: (self.func(fn_name, x[0], x[1]), self.grad(fn_name, x[0], x[1]))
def get_coord_bounds(self, fn):
if fn == 'booth':
return -10, 10, 0.4
elif fn == 'beale':
return -4.5, 4.5, 0.2
elif fn == 'rosen2d':
return -5, 10, 0.3
elif fn == 'ackley2d':
return -32.768, 32.768, 0.8192
else:
raise ValueError('Invalid function %s' % fn)
def get_minimum(self, fn):
if fn == 'booth':
return np.array([1., 3.]).reshape(-1, 1)
elif fn == 'beale':
return np.array([3., .5]).reshape(-1, 1)
elif fn == 'rosen2d':
return np.array([1., 1.]).reshape(-1, 1)
elif fn == 'ackley2d':
return np.array([0., 0.]).reshape(-1, 1)
else:
raise ValueError('Invalid function %s' % fn)
def get_elev_azim(self, fn):
if fn == 'booth':
return 30, -50
elif fn == 'beale':
return 50, -140
elif fn == 'rosen2d':
return 40, 140
elif fn == 'ackley2d':
return 30, 40
else:
raise ValueError('Invalid function %s' % fn)
def make_minimize_cb(self, path=[]):
return lambda xk: path.append(np.copy(xk))
def get_init2d(self, line, point):
def init2d():
line.set_data([], [])
point.set_data([], [])
return line, point
return init2d
def get_animate2d(self, line, point):
def animate2d(i):
line.set_data(*self.path[::,:i])
point.set_data(*self.path[::,i-1:i])
return line, point
return animate2d
def get_init3d(self, line, point):
def init3d():
line.set_data([], [])
line.set_3d_properties([])
point.set_data([], [])
point.set_3d_properties([])
return line, point
return init3d
def get_animate3d(self, line, point):
def animate3d(i):
line.set_data(self.path[0,:i], self.path[1,:i])
line.set_3d_properties(self.f(self.path[::,:i])[0])
point.set_data(self.path[0,i-1:i], self.path[1,i-1:i])
point.set_3d_properties(self.f(self.path[::,i-1:i])[0])
return line, point
return animate3d