Skip to content

Instantly share code, notes, and snippets.

@youheiakimoto
Last active January 17, 2023 04:28
Show Gist options
  • Save youheiakimoto/ab51e88c73baf68effd95b750100aad0 to your computer and use it in GitHub Desktop.
Save youheiakimoto/ab51e88c73baf68effd95b750100aad0 to your computer and use it in GitHub Desktop.
Adversarial-CMA-ES: derivative-free min-max optimization solver using (1+1)-CMA-ES.
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
"""Adversarial-CMA-ES: derivative-free min-max optimization solver using (1+1)-CMA-ES [1]
It is an extention of Adversarial Evolution Strategy [2]
(https://gist.github.com/youheiakimoto/15212dbf46dc546af20af38b0b48ff17)
Reference
---------
[1] Youhei Akimoto, Yoshiki Miyauchi, and Atsuo Maki. 2022.
Saddle Point Optimization with Approximate Minimization Oracle and Its Application to Robust Berthing Control.
ACM Trans. Evol. Learn. Optim. 2, 1, Article 2 (March 2022), 32 pages.
DOI:https://doi.org/10.1145/3510425
[2] Youhei Akimoto. 2021.
Saddle point optimization with approximate minimization oracle.
In Proceedings of the Genetic and Evolutionary Computation Conference (GECCO '21).
Association for Computing Machinery, New York, NY, USA, 493–501.
DOI:https://doi.org/10.1145/3449639.3459266
"""
class Cmaes:
"""(1+1)-Active-CMA-ES [Arnold 2010] with Simplified 1/5th Success Rule"""
def __init__(self, dim, tau, tau_offset, tolsigma=0.0, tolh=-np.inf, verbose=False):
self.verbose = verbose
self.tolsigma = tolsigma
self.tolh = tolh
self.dim = dim
self.tau = tau
self.tau_offset = tau_offset
self.rescale_itr = 0
self.aup = np.exp(2.0 / (2.0 + self.dim))
self.adown = self.aup ** (-0.25)
self.pthresh = 0.44
self.cp = 1.0 / 12.0
self.cc = 2.0 / (self.dim + 2)
self.ccovp = 2.0 / (self.dim**2 + 6)
self.ccovm = 0.4 / (self.dim**1.6 + 1)
self.x = np.zeros(self.dim)
self.s = 1.0
self.psucc = 0.5
self.fhist = []
self.p = np.zeros(self.dim)
self.chol = np.eye(self.dim)
self.cholinv = np.eye(self.dim)
def minimize(self, h, hx, x, sigma, chol, maxeval):
"""Locate approximate solutions to min_{x} h(x)
Parameters
----------
h : callable
objective
hx : float
h(x) value
x : ndarray (1d)
initial solution
sigma : float
step-size
chol : ndarray (2d)
cholesky factor
maxeval : int
maximal number of f-calls
Returns
-------
hz (float) : h(zt)
zt (ndarray, 1d) : approximate solution
theta (tuple) : parameters for z
neval (int) : number of h-calls
"""
nsucc = 0
neval = 0
self.x = np.copy(x)
self.s = sigma
self.psucc = 0.5
self.fhist = [hx]
self.p = np.zeros(self.dim)
self.chol = np.copy(chol)
self.cholinv = np.linalg.inv(self.chol)
self.rescale()
self.rescale_itr = 0
while nsucc < self.dim * self.tau + self.tau_offset:
z_, y_, x_ = self.generate()
hx = h(x_)
self.update(z_, y_, x_, hx)
succ = hx <= self.fhist[0]
nsucc += int(succ)
neval += 1
if self.rescale_itr > self.dim:
self.rescale()
self.rescale_itr = 0
else:
self.rescale_itr += 1
if self.verbose:
print(neval, hx, self.s)
if self.s <= self.tolsigma or hx <= self.tolh or neval >= maxeval:
break
return self.fhist[0], self.x, max(self.s, self.tolsigma), self.chol, neval
def generate(self):
z = np.random.randn(self.dim)
y = np.dot(self.chol, z)
x = self.x + self.s * y
return z, y, x
def rescale(self):
frob = np.sum(self.chol ** 2)
scale = np.sqrt(frob / self.dim)
self.s *= scale
self.chol /= scale
self.cholinv *= scale
self.p /= scale
def update(self, z, y, x, fx):
if fx <= self.fhist[0]:
self.fhist = [fx] + self.fhist[:min(4, len(self.fhist))]
self.x = x
self.s *= self.aup
self.psucc = (1.0 - self.cp) * self.psucc + self.cp
if self.psucc > self.pthresh:
self.p = (1.0 - self.cc) * self.p
d = self.ccovp * (1.0 - self.cc * (2.0 - self.cc))
else:
self.p = (1.0 - self.cc) * self.p + np.sqrt(self.cc * (2.0 - self.cc)) * y
d = self.ccovp
w = np.dot(self.cholinv, self.p)
wnorm2 = np.dot(w, w)
if wnorm2 > 1e-6:
a = np.sqrt(1.0 - d)
b = np.sqrt(1.0 - d) * (np.sqrt(1.0 + self.ccovp * wnorm2 / (1.0 - d)) - 1.0) / wnorm2
self.chol = a * self.chol + b * np.outer(self.p, w)
self.cholinv = self.cholinv / a - (b / (a**2 + a * b * wnorm2)) * np.outer(w, np.dot(w, self.cholinv))
else:
self.s *= self.adown
self.psucc = (1.0 - self.cp) * self.psucc
if len(self.fhist) > 4 and fx > self.fhist[4] and self.psucc <= self.pthresh:
znorm2 = np.dot(z, z)
ccov = self.ccovm if 1.0 >= self.ccovm * (2.0 * znorm2 - 1.0) else 1.0 / (2.0 * znorm2 - 1.0)
a = np.sqrt(1.0 + ccov)
b = np.sqrt(1.0 + ccov) * (np.sqrt(1.0 - ccov * znorm2 / (1.0 + ccov)) - 1) / znorm2
self.chol = a * self.chol + b * np.outer(y, z)
self.cholinv = self.cholinv / a - (b / (a**2 + a * b * znorm2)) * np.outer(z, np.dot(z, self.cholinv))
class AdversarialCmaes:
def __init__(self, f, x_dim, y_dim, x_tau=5, y_tau=5, x_tau_offset=5, y_tau_offset=5, tolgap=0, aeta=1, beta=5, ceta=1.1, xsampler=None, ysampler=None, xsigma_min=0, ysigma_min=0, xlbound=None, xubound=None, ylbound=None, yubound=None, logpath=None, logchol=True):
"""Adversarial-CMA-ES
Parameters
----------
f : callable
objective function
x_dim, y_dim : int
dimension of x and y
x_tau, y_tau, x_tau_offset, y_tau_offset : int, optional
number of iterations of the internal (1+1)-CMA-ES is
x_tau * x_dim + x_tau_offset (for x) and
y_tau * y_dim + y_tau_offset (for y).
tolgap : float >= 0, optional
termination condition.
stop if the estimated suboptimality error is smaller than `tolgap`
aeta, beta, ceta : float, optional
hyper-parameters of the learning rate adaptation
aeta, beta : the number of oracle calls to estimate the convergence rate is aeta / eta + beta
ceta : learning rate is increased or decreased by the factor ceta
xsampler, ysampler : callable, optional
x and y samplers
tolxsigma, tolysigma : float >= 0, optional
minimal step-size
xlbound, xubound : numpy.ndarray (1d)
lower and upper bound of the search interval for x
ylbound, yubound : numpy.ndarray (1d)
lower and upper bound of the search interval for y
logpath : str, optional
log file name
logchol : bool, optional
write out the cholesky factor of the CMA-ES into the log file if this is true
"""
self._f = f
self.xsampler = xsampler
self.ysampler = ysampler
self.ysigma_min = ysigma_min
self.x_cma = Cmaes(x_dim, x_tau, x_tau_offset, tolsigma=xsigma_min)
self.y_cma = Cmaes(y_dim, y_tau, y_tau_offset, tolsigma=ysigma_min)
self.tolgap = tolgap
self.aeta = aeta
self.beta = beta
self.ceta = ceta
self.xlbound = xlbound
self.xubound = xubound
self.ylbound = ylbound
self.yubound = yubound
self.logpath = logpath
self.logchol = logchol
self.neval = 0
self.yhist = []
def f(self, x, y):
fxy = self._f(x, y)
self.neval += 1
return fxy
def fyhist(self, x, y, twooutput=False):
fxy = self.f(x, y)
if len(self.yhist) > 0:
fxyhist = np.max([self.f(x, yy) for yy in self.yhist])
if twooutput:
return max(fxyhist, fxy), fxy
else:
return max(fxyhist, fxy)
else:
if twooutput:
return fxy, fxy
else:
return fxy
def optimize_with(self, eta, x0, y0, xsigma0, ysigma0, xchol0, ychol0, nstep, maxeval, minitr):
"""Adversarial-CMA-ES with Fixed Learning Rate
Parameters
----------
eta : float
learning rate
x0, y0 : ndarray (1d)
initial x and x
xsigma0, ysigma0 : float
initial step-size
xchol0, ychol0 : ndarray (2d)
initial cholesky factor
nstep : int
maximum iterations
maxeval : int
maximum number of f-calls
minitr : int
minimum iterations
Returns
-------
x (ndarray, 1D) : solution
y (ndarray, 1D) : solution
gap (ndarray, 1D) : history of gap: f(x', yt) - f(xt, y')
"""
x = np.copy(x0)
y = np.copy(y0)
xp = np.copy(x)
yp = np.copy(y)
x_sigma = xsigma0
y_sigma = ysigma0
x_chol = np.copy(xchol0)
y_chol = np.copy(ychol0)
dim_theta_x = 1 + len(x) ** 2 if self.logchol else 1
dim_theta_y = 1 + len(y) ** 2 if self.logchol else 1
res = np.zeros(1+1+1+1+len(x)+len(y)+dim_theta_x+dim_theta_y)
gap_array = np.zeros(nstep)
for i in range(nstep):
# Determine whether or not to start from x or xp (y or yp)
fxyhist, fxy = self.fyhist(x, y, twooutput=True)
fxpy = self.fyhist(xp, y)
fxyp = self.f(x, yp)
if fxpy > fxyhist:
xp[:] = x[:]
fxpy = fxyhist
x_sigma = xsigma0
x_chol = np.copy(xchol0)
if fxyp < fxy:
yp[:] = y[:]
fxyp = fxy
y_sigma = ysigma0
y_chol = np.copy(ychol0)
# Adversarial steps
fx, xp, x_sigma, x_chol, nx = self.x_cma.minimize(lambda z: self.fyhist(z, y), fxpy, xp, x_sigma, x_chol, maxeval - self.neval)
fy, yp, y_sigma, y_chol, ny = self.y_cma.minimize(lambda z: -self.f(x, z), -fxyp, yp, y_sigma, y_chol, maxeval - self.neval)
fy = -fy
# Mirror solutions if the domain is bounded
if self.xlbound is not None:
xp = mirror(xp, self.xlbound, self.xubound)
if self.ylbound is not None:
yp = mirror(yp, self.ylbound, self.yubound)
# Try random samples
if self.xsampler is not None:
xtrial = self.xsampler()
fxytrial = self.fyhist(xtrial, y)
nx += 1
if fxytrial < fx:
xp = xtrial
fx = fxytrial
if self.ysampler is not None:
ytrial = self.ysampler()
fxytrial = self.f(x, ytrial)
ny += 1
if fxytrial > fy:
if fy >= fxyhist:
self.register(yp)
yp = ytrial
fy = fxytrial
# Estimated Suboptimality Error
gap = max(fxyhist, fy) - fx
# Update
x += eta * (xp - x)
y += eta * (yp - y)
# Output
idx = 0
res[idx] = self.neval; idx += 1
res[idx] = fxyhist; idx += 1
res[idx] = gap; idx += 1
res[idx] = eta; idx += 1
res[idx:idx+len(x)] = x; idx += len(x)
res[idx:idx+len(y)] = y; idx += len(y)
res[idx] = x_sigma; idx += 1
if self.logchol:
res[idx:idx+dim_theta_x-1] = x_chol.flatten(); idx += dim_theta_x - 1
res[idx] = y_sigma; idx += 1
if self.logchol:
res[idx:idx+dim_theta_x-1] = y_chol.flatten(); idx += dim_theta_y - 1
with open(self.logpath, 'ba') as flog:
np.savetxt(flog, res, newline=' ')
flog.write(b"\n")
# Termination condition
gap_array[i] = gap
if self.neval >= maxeval:
break
if gap_array[i] < self.tolgap:
break
if i >= minitr - 1:
gap = gap_array[i+1-minitr:i+1]
if minitr > 1 and np.all(gap[1:minitr] - gap[0:minitr-1] > 0):
break
return x, y, gap_array[:i+1]
def optimize(self, etamin, x0, y0, xsigma0, ysigma0, xchol0, ychol0, maxeval):
"""Adversarial Evolution Strategy with Learning Rate Adaptation
Parameters
----------
etamin : float
minimal learning rate
x0, y0 : ndarray (1d)
initial x and x
xsigma0, ysigma0 : float
initial step-size
xchol0, ychol0 : ndarray (2d)
initial cholesky factor
maxeval : int
maximum number of f-calls
Returns
-------
x (ndarray, 1D) : solution
y (ndarray, 1D) : solution
"""
x = np.copy(x0)
y = np.copy(y0)
x_sigma = xsigma0
y_sigma = ysigma0
x_chol = np.copy(xchol0)
y_chol = np.copy(ychol0)
eta = 1.0
slp = 0.0
while self.neval < maxeval:
u = np.random.rand()
if u < 1/3:
eta1 = eta * self.ceta
elif 1/3 <= u < 2/3:
eta1 = eta
else:
eta1 = eta / self.ceta
eta1 = np.clip(eta1, etamin, 1)
nstep = self.beta + int(self.aeta/eta1)
x1, y1, gap = self.optimize_with(eta1, x, y, x_sigma, y_sigma, x_chol, y_chol, nstep, maxeval, self.beta)
if gap[-1] < self.tolgap or self.neval >= maxeval:
break
xarr = np.arange(len(gap))
slp1, _, _, _, std1 = stats.linregress(xarr, np.log(np.fmax(gap, self.tolgap)))
if slp1 - 2*std1 < 0:
x = x1
y = y1
x_sigma = self.x_cma.s
y_sigma = self.y_cma.s
x_chol = self.x_cma.chol
y_chol = self.y_cma.chol
if slp >= 0 and slp1 >= 0:
eta = np.clip(eta / self.ceta**3, etamin, 1)
slp = 0.0
elif slp1 <= slp:
eta = eta1
slp = slp1
elif eta == eta1:
slp = slp1
return x1, y1
def register(self, yp):
if len(self.yhist) > 0:
dist = np.asarray([np.linalg.norm(yh - yp) for yh in self.yhist])
if np.min(dist) > self.ysigma_min * np.sqrt(len(yp)):
self.yhist += [np.copy(yp)]
else:
self.yhist += [np.copy(yp)]
def mirror(z, lbound, ubound):
width = ubound - lbound
return ubound - np.abs(np.mod(z - lbound, 2 * width) - width)
def generate_cmaes_parameter(lbound, ubound):
dim = len(lbound)
x = lbound + (ubound - lbound) * np.random.rand(dim)
sigma = 1.0
chol = np.diag( (ubound - lbound) / 4. )
return x, sigma, chol
def minmax(f, xlbound, xubound, ylbound, yubound, maxeval, bounded, etamin=1e-4, tolgap=1e-6, tolxsigma=1e-8, tolysigma=1e-8, logpath="advcma.csv", logchol=True, initx=None, initxsigma=None, inity=None, initysigma=None):
"""Optimize min-max problem f with Adversarial-CMA-ES with restart
Parameters
----------
f : callable
min-max objective function f(x, y)
xlbound, xubound : numpy.ndarray (1d)
lower and upper bound of the initialization interval for x
ylbound, yubound : numpy.ndarray (1d)
lower and upper bound of the initialization interval for y
maxeval : int
maximum number of f-calls
bounded : bool
the domain is bounded (== initialization interval) if it is True
etamin : float >= 0, optional
minimal accepted learning rate
tolgap : float >= 0, optional
restart is performed if the suboptimality error reach this tolerance value
tolxsigma, tolysigma : float >= 0, optional
minimal step-size
logpath : str, optional
log file name
logchol : bool, optional
write out the cholesky factor of the CMA-ES into the log file if this is true
initx, inity : numpy.ndarray (1d), optional
initial guess
initxsigma, initysigma : float > 0, optional
initial step size, valid only when initx and inity are given
Returns
-------
float : f(x, y) value
numpy.ndarray (1d) : x best
numpy.ndarray (1d) : y worst
numpy.ndarray (2d) : f(x_i, y_j)
list of numpy.ndarray (1d) : list of candidate x
list of numpy.ndarray (1d) : list of candidate y
"""
def xsampler():
return xlbound + (xubound - xlbound) * np.random.rand(len(xlbound))
def ysampler():
return ylbound + (yubound - ylbound) * np.random.rand(len(ylbound))
xhist = []
if bounded:
xl, xu, yl, yu = xlbound, xubound, ylbound, yubound
def ff(x, y):
xmirror = mirror(x, xlbound, xubound)
ymirror = mirror(y, ylbound, yubound)
return f(xmirror, ymirror)
else:
xl, xu, yl, yu = None, None, None, None
ff = f
advcma = AdversarialCmaes(ff, len(xlbound), len(ylbound), tolgap=tolgap, xsampler=xsampler, ysampler=ysampler, xlbound=xl, xubound=xu, ylbound=yl, yubound=yu, xsigma_min=tolxsigma, ysigma_min=tolysigma, logpath=logpath, logchol=logchol)
while advcma.neval < maxeval:
x0, xsigma0, xchol0 = generate_cmaes_parameter(xlbound, xubound)
if len(xhist) == 0:
if initx is not None:
x0 = np.array(initx, copy=True)
if initxsigma is not None:
xsigma0 = initxsigma
y0, ysigma0, ychol0 = generate_cmaes_parameter(ylbound, yubound)
if len(xhist) == 0:
if inity is not None:
y0 = np.array(inity, copy=True)
if initysigma is not None:
ysigma0 = initysigma
x, y = advcma.optimize(etamin, x0, y0, xsigma0, ysigma0, xchol0, ychol0, maxeval)
xhist += [x]
advcma.register(y)
yhist = advcma.yhist
f_arr = np.zeros((len(xhist), len(yhist)))
for i, x in enumerate(xhist):
for j, y in enumerate(yhist):
f_arr[i, j] = f(x, y)
xidx = np.argmin(np.max(f_arr, axis=1))
yidx = np.argmax(f_arr[xidx])
return f_arr[xidx, yidx], xhist[xidx], yhist[yidx], f_arr, xhist, yhist
def minimize(f, lbound, ubound, bounded, maxeval, totalmaxeval, tolsigma=1e-8, tolf=-np.inf):
"""Locate approximate solutions to min_{x} f(x)
Parameters
----------
f : callable
objective
lbound, ubound : numpy.ndarray (1d)
lower and upper bound of the initialization interval for x
bounded : bool
the domain is bounded (== initialization interval) if it is True
maxeval : int
maximal number of f-calls for each (1+1)-CMA-ES run
totalmaxeval : int
maximal number of f-calls for restart
tolxigma : float >= 0, optional
minimal step-size
tolf : float, optional
minimal objective value
Returns
-------
float : best f(x) value
numpy.ndarray (1d) : best x
numpy.ndarray (1d) : list of f(x)
numpy.ndarray (2d) : list of x
"""
if bounded:
def h(z):
zmirror = mirror(z, lbound, ubound)
return f(zmirror)
else:
def h(z):
return f(z)
f_arr = []
z_arr = []
nfev = 0
while nfev < totalmaxeval:
z, sigma, chol = generate_cmaes_parameter(lbound, ubound)
cma = Cmaes(len(z), tau=0, tau_offset=maxeval, tolsigma=tolsigma, tolh=tolf)
hz, z, _, _, neval = cma.minimize(h, h(z), z, sigma, chol, min(maxeval, totalmaxeval - nfev - 1))
nfev += neval + 1
f_arr += [hz]
z_arr += [mirror(z, lbound, ubound) if bounded else z]
zidx = np.argmin(f_arr)
return f_arr[zidx], z_arr[zidx], f_arr, z_arr
def worstsearch(f, x, ylbound, yubound, bounded, maxeval, tolsigma=1e-8, n_restart=100, inity=None, initysigma=None):
"""Worst case search by the (1+1)-CMA-ES
Parameters
----------
f : callable
min-max objective function
x : numpy.ndarray (1d)
x to be evaluated
ylbound, yubound : numpy.ndarray (1d)
lower and upper bound of the initialization interval for y
maxeval : int
maximum number of f-calls for each restart
tolsigma : float >= 0, optional
minimal step-size
n_restart : int >= 1, optional
number of restart
inity : numpy.ndarray (1d), optional
initial solution
initysigma : float > 0, optional
initial step-size, valid only when inity is given
Returns
-------
float : worst f(x, y) value
numpy.ndarray (1d) : worst y
numpy.ndarray (1d) : f(x, y_j)
numpy.ndarray (2d) : list of candidate y
"""
if bounded:
def h(y):
ymirror = mirror(y, ylbound, yubound)
return -f(x, ymirror)
else:
def h(y):
return -f(x, y)
f_arr = np.zeros(n_restart)
y_arr = np.zeros((n_restart, len(ylbound)))
for i in range(n_restart):
y, sigma, chol = generate_cmaes_parameter(ylbound, yubound)
if i == 0:
if inity is not None:
y = np.array(inity)
if initysigma is not None:
sigma = initysigma
cma = Cmaes(len(y), tau=0, tau_offset=maxeval, tolsigma=tolsigma)
hy, y, _, _, _ = cma.minimize(h, h(y), y, sigma, chol, maxeval)
f_arr[i] = -hy
y_arr[i] = mirror(y, ylbound, yubound) if bounded else y
yidx = np.argmax(f_arr)
return f_arr[yidx], y_arr[yidx], f_arr, y_arr
def ex1(m=10, a=1, b=4, c=1):
"""experiments on f1"""
n = m
def f1(x, y):
fx = (a/2) * np.dot(x, x)
fxy = b * np.dot(x, y)
fy = (c/2) * np.dot(y, y)
return fx + fxy - fy
def f1gap(x, y):
return (1/2 + b**2 / (2 * a * c)) * (np.dot(x, x) + np.dot(y, y))
f = f1
fgap = f1gap
# Initialization Interval
xlbound = -np.ones(m)
xubound = 5 *np.ones(m)
ylbound = -np.ones(n)
yubound = 5 * np.ones(n)
bounded = False
# Optimization
maxeval = 1e6
logpre = 'advcma_f1'
logpath = logpre + '.csv'
logpathx = logpre + '_x.csv'
logpathy = logpre + '_y.csv'
logpathworst = logpre + '_worst_y.csv'
print("Optimization has started.")
fxy, xbest, yworst, f_arr, x_arr, y_arr = minmax(f, xlbound, xubound, ylbound, yubound, maxeval, bounded, etamin=1e-4, tolgap=1e-6, tolxsigma=1e-8, tolysigma=1e-8, logpath=logpath, logchol=False)
print("Optimization has finished.")
print("best x:", xbest)
print("worst y:", yworst)
print("f(x,y):", fxy)
print(f_arr)
np.savetxt(logpathx, x_arr)
np.savetxt(logpathy, y_arr)
# Evaluation
print("Gap evaluation has started.")
dat = np.loadtxt(logpath)
gap_array = np.zeros(len(dat))
for i in range(len(gap_array)):
x = dat[i, 4:4+m]
y = dat[i, 4+m:4+m+n]
gap_array[i] = fgap(x, y)
print("Gap evaluation has finished.")
# Plot
fig = plt.figure()
plt.semilogy(dat[:, 0], gap_array, label="max_y f(x, y) - min_x f(x, y)")
plt.semilogy(dat[:, 0], dat[:, 2], label="max_y f(x, y) - min_x f(x, y) (estimated)")
plt.semilogy(dat[:, 0], dat[:, 3], label="eta")
plt.semilogy(dat[:, 0], dat[:, -2], label="sigma_x")
plt.semilogy(dat[:, 0], dat[:, -1], label="sigma_y")
plt.grid()
plt.xlabel("#f-calls")
plt.legend()
plt.tight_layout()
plt.savefig(logpre + ".pdf")
def ex2(funcid=2, m=50, n=20):
"""experiments on f2--f7"""
# Initialization Interval
xlbound = -np.ones(m)
xubound = 5 *np.ones(m)
ylbound = -np.ones(n)
yubound = 5 * np.ones(n)
bounded = True
def f2(x, y):
xx = np.dot(x, x) / 2.0
xy = np.sum(x) * np.sum(y) / m
yy = np.dot(y, y) / 2.0
return xx + xy - yy
def f2max(x):
y = np.ones(n) * (np.sum(x) / m)
return f2(x, y)
def f3(x, y):
xx = min(np.dot(x, x), np.dot(x-4, x-4)) / 2.0
xy = np.sum(x) * np.sum(y) / m
yy = np.dot(y, y) / 2
return xx + xy - yy
def f3max(x):
y = np.ones(n) * (np.sum(x) / m)
return f3(x, y)
def f4(x, y):
xx = np.dot(x, x) / 2.0
xy = np.sum(x) * np.sum(y) / m
yy = (np.dot(y, y) / 2.0)**2
return xx + xy - yy
def f4max(x):
y = np.ones(n) * (np.abs(np.sum(x)) / (m * n)) ** (1.0/3.0) * np.sign(np.sum(x))
return f4(x, y)
def f5(x, y):
xy = np.linalg.norm(x) * np.sum(y) / np.sqrt(m)
return xy
def f5max(x):
y = yubound
return f5(x, y)
def f6(x, y):
xy = np.sum(x) * np.sum(y) / m
yy = np.dot(y, y) / 2.0
return xy - yy
def f6max(x):
y = np.ones(n) * (np.sum(x) / m)
return f6(x, y)
def f7(x, y):
xx = np.dot(x, x) / 2.0
xy = np.sum(x) * np.sum(y) / m
return xx + xy
def f7max(x):
if np.sum(x) > 0:
y = yubound
else:
y = ylbound
return f7(x, y)
assert funcid >= 2 and funcid <= 7
f = [None, None, f2, f3, f4, f5, f6, f7][funcid]
fmax = [None, None, f2max, f3max, f4max, f5max, f6max, f7max][funcid]
# Optimization
maxeval = 1e6
logpre = 'advcma_f{}'.format(str(funcid))
logpath = logpre + '.csv'
logpathx = logpre + '_x.csv'
logpathy = logpre + '_y.csv'
logpathworst = logpre + '_worst_y.csv'
print("Optimization has started.")
fxy, xbest, yworst, f_arr, x_arr, y_arr = minmax(f, xlbound, xubound, ylbound, yubound, maxeval, bounded, etamin=1e-4, tolgap=1e-6, tolxsigma=1e-8, tolysigma=1e-8, logpath=logpath, logchol=False)
print("Optimization has finished.")
print("best x:", xbest)
print("worst y:", yworst)
print("f(x,y):", fxy)
print(f_arr)
np.savetxt(logpathx, x_arr)
np.savetxt(logpathy, y_arr)
# Evaluation
print("Worst objective evaluation has started.")
dat = np.loadtxt(logpath)
fmax_array = np.zeros(len(dat))
for i in range(len(fmax_array)):
x = dat[i, 4:4+m]
fmax_array[i] = fmax(x)
print("Worst objective evaluation has finished.")
# Plot
fig = plt.figure()
plt.semilogy(dat[:, 0], fmax_array, label="max_y f(x, y)")
plt.semilogy(dat[:, 0], dat[:, 2], label="max_y f(x, y) - min_x f(x, y) (estimated)")
plt.semilogy(dat[:, 0], dat[:, 3], label="eta")
plt.semilogy(dat[:, 0], dat[:, -2], label="sigma_x")
plt.semilogy(dat[:, 0], dat[:, -1], label="sigma_y")
plt.grid()
plt.xlabel("#f-calls")
plt.legend()
plt.tight_layout()
plt.savefig(logpre + ".pdf")
def my_ex():
"""experiment prototype"""
def ftest(x, y, a=1.0, b=10.0, c=1.0):
"""define your objective function"""
fx = (a/2) * np.dot(x, x)
fxy = b * np.dot(x, y)
fy = (c/2) * np.dot(y, y)
return fx + fxy - fy
np.random.seed(100)
m = n = 10
a = 1
b = 4
c = 1
bounded = False
# Initialization Interval
xlbound = -np.ones(m)
xubound = np.ones(m)
ylbound = -np.ones(n)
yubound = np.ones(n)
def f(x, y):
return ftest(x, y, a, b, c)
# Optimization
maxeval = 1e6
logpre = 'advcma_ftest'
logpath = logpre + '.csv'
logpathx = logpre + '_x.csv'
logpathy = logpre + '_y.csv'
logpathworst = logpre + '_worst_y.csv'
print("Optimization has started.")
fxy, xbest, yworst, f_arr, x_arr, y_arr = minmax(f, xlbound, xubound, ylbound, yubound, maxeval, bounded, etamin=1e-4, tolgap=1e-6, tolxsigma=1e-8, tolysigma=1e-8, logpath=logpath, logchol=False)
print("Optimization has finished.")
print("best x:", xbest)
print("worst y:", yworst)
print("f(x,y):", fxy)
print(f_arr)
np.savetxt(logpathx, x_arr)
np.savetxt(logpathy, y_arr)
# Evaluation
print("Worst-case search has started.")
fy, y, f_arr, y_arr = worstsearch(f, xbest, ylbound, yubound, bounded, maxeval=100*len(ylbound), tolsigma=1e-8, n_restart=100)
print("Worst-case search has finished.")
print("worst y:", y)
print("f(x,y):", fy)
print(f_arr)
np.savetxt(logpathworst, y_arr)
# Plot
dat = np.loadtxt(logpath)
fig = plt.figure()
plt.semilogy(dat[:, 0], dat[:, 2], label="max_y f(x, y) - min_x f(x, y) (estimated)")
plt.semilogy(dat[:, 0], dat[:, 3], label="eta")
plt.semilogy(dat[:, 0], dat[:, -2], label="sigma_x")
plt.semilogy(dat[:, 0], dat[:, -1], label="sigma_y")
plt.grid()
plt.xlabel("#f-calls")
plt.legend()
plt.tight_layout()
plt.savefig("advcma_ftest.pdf")
if __name__ == "__main__":
import sys
exid = int(sys.argv[1])
if exid == 0:
my_ex()
elif exid == 1:
ex1()
elif exid in [2, 3, 4, 5, 6, 7]:
ex2(funcid=exid)
else:
raise ValueError("`exid` must be in [[0, 7]].")
@youheiakimoto
Copy link
Author

The worstsearch function now accepts an initial search point and initial step-size to perform a local search for the first run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment