pb2_utils.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import numpy as np
  2. from scipy.optimize import minimize
  3. from sklearn.gaussian_process import GaussianProcessRegressor
  4. from sklearn.gaussian_process.kernels import Hyperparameter, Kernel
  5. from sklearn.metrics import pairwise_distances
  6. from sklearn.metrics.pairwise import euclidean_distances
  7. class TV_SquaredExp(Kernel):
  8. """Time varying squared exponential kernel.
  9. For more info see the TV-GP-UCB paper:
  10. http://proceedings.mlr.press/v51/bogunovic16.pdf
  11. """
  12. def __init__(
  13. self,
  14. variance=1.0,
  15. lengthscale=1.0,
  16. epsilon=0.1,
  17. variance_bounds=(1e-5, 1e5),
  18. lengthscale_bounds=(1e-5, 1e5),
  19. epsilon_bounds=(1e-5, 0.5),
  20. ):
  21. self.variance = variance
  22. self.lengthscale = lengthscale
  23. self.epsilon = epsilon
  24. self.variance_bounds = variance_bounds
  25. self.lengthscale_bounds = lengthscale_bounds
  26. self.epsilon_bounds = epsilon_bounds
  27. @property
  28. def hyperparameter_variance(self):
  29. return Hyperparameter("variance", "numeric", self.variance_bounds)
  30. @property
  31. def hyperparameter_lengthscale(self):
  32. return Hyperparameter("lengthscale", "numeric", self.lengthscale_bounds)
  33. @property
  34. def hyperparameter_epsilon(self):
  35. return Hyperparameter("epsilon", "numeric", self.epsilon_bounds)
  36. def __call__(self, X, Y=None, eval_gradient=False):
  37. X = np.atleast_2d(X)
  38. if Y is None:
  39. Y = X
  40. epsilon = np.clip(self.epsilon, 1e-5, 0.5)
  41. # Time must be in the first column
  42. T1 = X[:, 0].reshape(-1, 1)
  43. T2 = Y[:, 0].reshape(-1, 1)
  44. dists = pairwise_distances(T1, T2, "cityblock")
  45. timekernel = (1 - epsilon) ** (0.5 * dists)
  46. # RBF kernel on remaining features
  47. X_spatial = X[:, 1:]
  48. Y_spatial = Y[:, 1:]
  49. rbf = self.variance * np.exp(
  50. -np.square(euclidean_distances(X_spatial, Y_spatial)) / self.lengthscale
  51. )
  52. K = rbf * timekernel
  53. if eval_gradient:
  54. K_gradient_variance = K
  55. dist2 = np.square(euclidean_distances(X_spatial, Y_spatial))
  56. K_gradient_lengthscale = K * dist2 / self.lengthscale
  57. n = dists / 2
  58. K_gradient_epsilon = -K * n * epsilon / (1 - epsilon)
  59. return K, np.dstack(
  60. [K_gradient_variance, K_gradient_lengthscale, K_gradient_epsilon]
  61. )
  62. return K
  63. def diag(self, X):
  64. return np.full(X.shape[0], self.variance, dtype=np.float64)
  65. def is_stationary(self):
  66. return False
  67. @property
  68. def theta(self):
  69. return np.log([self.variance, self.lengthscale, self.epsilon])
  70. @theta.setter
  71. def theta(self, theta):
  72. self.variance = np.exp(theta[0])
  73. self.lengthscale = np.exp(theta[1])
  74. self.epsilon = np.exp(theta[2])
  75. @property
  76. def bounds(self):
  77. return np.log(
  78. [
  79. list(self.variance_bounds),
  80. list(self.lengthscale_bounds),
  81. list(self.epsilon_bounds),
  82. ]
  83. )
  84. def normalize(data, wrt):
  85. """Normalize data to be in range (0,1), with respect to (wrt) boundaries,
  86. which can be specified.
  87. """
  88. return (data - np.min(wrt, axis=0)) / (
  89. np.max(wrt, axis=0) - np.min(wrt, axis=0) + 1e-8
  90. )
  91. def standardize(data):
  92. """Standardize to be Gaussian N(0,1). Clip final values."""
  93. data = (data - np.mean(data, axis=0)) / (np.std(data, axis=0) + 1e-8)
  94. return np.clip(data, -2, 2)
  95. def UCB(m, m1, x, fixed, kappa=None):
  96. """UCB acquisition function. Interesting points to note:
  97. 1) We concat with the fixed points, because we are not optimizing wrt
  98. these. This is the Reward and Time, which we can't change. We want
  99. to find the best hyperparameters *given* the reward and time.
  100. 2) We use m to get the mean and m1 to get the variance. If we already
  101. have trials running, then m1 contains this information. This reduces
  102. the variance at points currently running, even if we don't have
  103. their label.
  104. Ref: https://jmlr.org/papers/volume15/desautels14a/desautels14a.pdf
  105. """
  106. c1 = 0.2
  107. c2 = 0.4
  108. beta_t = c1 + max(0, np.log(c2 * m.X_train_.shape[0]))
  109. kappa = np.sqrt(beta_t) if kappa is None else kappa
  110. xtest = np.concatenate((fixed.reshape(-1, 1), np.array(x).reshape(-1, 1))).T
  111. try:
  112. mean = m.predict(xtest)[0]
  113. except ValueError:
  114. mean = -9999
  115. try:
  116. _, std = m1.predict(xtest, return_std=True)
  117. var = std[0] ** 2
  118. except ValueError:
  119. var = 0
  120. return mean + kappa * var
  121. def optimize_acq(func, m, m1, fixed, num_f):
  122. """Optimize acquisition function."""
  123. opts = {"maxiter": 200, "maxfun": 200, "disp": False}
  124. T = 10
  125. best_value = -999
  126. best_theta = m1.X_train_[0, :]
  127. bounds = [(0, 1) for _ in range(m.X_train_.shape[1] - num_f)]
  128. for ii in range(T):
  129. x0 = np.random.uniform(0, 1, m.X_train_.shape[1] - num_f)
  130. res = minimize(
  131. lambda x: -func(m, m1, x, fixed),
  132. x0,
  133. bounds=bounds,
  134. method="L-BFGS-B",
  135. options=opts,
  136. )
  137. val = func(m, m1, res.x, fixed)
  138. if val > best_value:
  139. best_value = val
  140. best_theta = res.x
  141. return np.clip(best_theta, 0, 1)
  142. def select_length(Xraw, yraw, bounds, num_f):
  143. """Select the number of datapoints to keep, using cross validation"""
  144. min_len = 200
  145. if Xraw.shape[0] < min_len:
  146. return Xraw.shape[0]
  147. else:
  148. length = min_len - 10
  149. scores = []
  150. while length + 10 <= Xraw.shape[0]:
  151. length += 10
  152. base_vals = np.array(list(bounds.values())).T
  153. X_len = Xraw[-length:, :]
  154. y_len = yraw[-length:]
  155. oldpoints = X_len[:, :num_f]
  156. old_lims = np.concatenate(
  157. (np.max(oldpoints, axis=0), np.min(oldpoints, axis=0))
  158. ).reshape(2, oldpoints.shape[1])
  159. limits = np.concatenate((old_lims, base_vals), axis=1)
  160. X = normalize(X_len, limits)
  161. y = standardize(y_len).reshape(y_len.size, 1)
  162. kernel = TV_SquaredExp(variance=1.0, lengthscale=1.0, epsilon=0.1)
  163. m = GaussianProcessRegressor(kernel=kernel, optimizer="fmin_l_bfgs_b")
  164. m.fit(X, y)
  165. scores.append(m.log_marginal_likelihood_value_)
  166. idx = np.argmax(scores)
  167. length = (idx + int((min_len / 10))) * 10
  168. return length