Skip to content

Commit a763b94

Browse files
Implemented DBSCAN, fixed globalVariables
1 parent af11c69 commit a763b94

15 files changed

+303
-111
lines changed

DESCRIPTION

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@ Package: ParBayesianOptimization
22
Title: Parallel Bayesian Optimization of Hyperparameters
33
Version: 0.0.1
44
Authors@R: person("Samuel", "Wilson", email = "[email protected]", role = c("aut", "cre"))
5-
Description: Converge to best hyperparameter set by iteratively fitting gaussian processes.
6-
Depends: R (>= 3.5.0), data.table (>= 1.11.8), GauPro, stats, foreach
5+
Description: Fast, flexible framework for implementing Bayesian optimization of model
6+
hyperparameters according to the methods described in Snoek et al. <arXiv:1206.2944>.
7+
The package allows the user to run scoring function in parallel, save intermediary
8+
results, and tweak other aspects of the process to fully utilize the computing resources
9+
available to the user.
10+
URL: https://github.com/AnotherSamWilson/ParBayesianOptimization
11+
BugReports: https://github.com/AnotherSamWilson/ParBayesianOptimization/issues
12+
Depends: R (>= 3.5.0), data.table (>= 1.11.8), GauPro, stats, foreach, dbscan
713
License: GPL-2
814
Encoding: UTF-8
915
LazyData: true

NAMESPACE

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# Generated by roxygen2: do not edit by hand
22

3-
export(ApplyNoise)
43
export(BayesianOptimization)
54
export(CheckBounds)
65
export(MinMaxScale)
76
export(RandParams)
87
export(UnMMScale)
8+
export(applyCluster)
9+
export(applyNoise)
910
export(assignKern)
1011
export(calcAcq)
1112
export(maxAcq)
@@ -17,11 +18,15 @@ importFrom(GauPro,Gaussian)
1718
importFrom(GauPro,Matern32)
1819
importFrom(GauPro,Matern52)
1920
importFrom(data.table,":=")
21+
importFrom(data.table,.I)
2022
importFrom(data.table,as.data.table)
2123
importFrom(data.table,between)
24+
importFrom(data.table,copy)
2225
importFrom(data.table,data.table)
26+
importFrom(data.table,fintersect)
2327
importFrom(data.table,setDT)
2428
importFrom(data.table,setcolorder)
29+
importFrom(dbscan,dbscan)
2530
importFrom(stats,dnorm)
2631
importFrom(stats,optim)
2732
importFrom(stats,pnorm)

R/BayesianOptimization.R

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
#' Increase this for a higher chance to find global optimum, at the expense of more time.
5757
#' @param convThresh convergence threshold passed to \code{factr} when the \code{optim} function (L-BFGS-B) is called.
5858
#' Lower values will take longer to converge, but may be more accurate.
59+
#' @param minClusterUtility the minimum percentage of the optimal utility required for a less optimal local
60+
#' maximum to be included as a candidate parameter set in the next scoring function. If \code{NULL},
61+
#' only the global optimum will be used as a candidate parameter set.
5962
#' @param noiseAdd if bulkNew > 1, specifies how much noise to add to the optimal candidate parameter set
6063
#' to obtain the other \code{bulkNew-1} candidate parameter sets. New random draws are pulled from
6164
#' a shape(4,4) beta distribution centered at the optimal candidate parameter set
@@ -103,29 +106,31 @@
103106
#'
104107
#' return(list(Score = max(xgbcv$evaluation_log$test_auc_mean)
105108
#' , nrounds = xgbcv$best_iteration
106-
#' )
107109
#' )
110+
#' )
111+
#' }
108112
#'
109-
#' bounds <- list( max_depth = c(2L, 10L)
110-
#' , min_child_weight = c(1L, 100L)
111-
#' , subsample = c(0.25, 1))
112113
#'
113-
#' kern <- "Matern52"
114+
#' bounds <- list( max_depth = c(2L, 10L)
115+
#' , min_child_weight = c(1, 100)
116+
#' , subsample = c(0.25, 1))
114117
#'
115-
#' acq <- "ei"
118+
#' kern <- "Matern52"
116119
#'
117-
#' ScoreResult <- BayesianOptimization(FUN = scoringFunction
118-
#' , bounds = bounds
119-
#' , initPoints = 10
120-
#' , bulkNew = 1
121-
#' , nIters = 12
122-
#' , kern = kern
123-
#' , acq = acq
124-
#' , kappa = 2.576
125-
#' , verbose = 1
126-
#' , parallel = FALSE)
120+
#' acq <- "ei"
121+
#'
122+
#' ScoreResult <- BayesianOptimization(FUN = scoringFunction
123+
#' , bounds = bounds
124+
#' , initPoints = 10
125+
#' , bulkNew = 1
126+
#' , nIters = 12
127+
#' , kern = kern
128+
#' , acq = acq
129+
#' , kappa = 2.576
130+
#' , verbose = 1
131+
#' , parallel = FALSE)
127132
#' }
128-
#' @importFrom data.table data.table setDT setcolorder := as.data.table
133+
#' @importFrom data.table data.table setDT setcolorder := as.data.table copy .I
129134
#' @importFrom utils head
130135
#' @importFrom GauPro GauPro_kernel_model Matern52 Matern32 Exponential Gaussian
131136
#' @export
@@ -151,6 +156,7 @@ BayesianOptimization <- function(
151156
, eps = 0.0
152157
, gsPoints = 100
153158
, convThresh = 1e7
159+
, minClusterUtility = NULL
154160
, noiseAdd = 0.25
155161
, verbose = 1
156162
) {
@@ -165,6 +171,13 @@ BayesianOptimization <- function(
165171
Overwrites <- 0
166172
Iter <- 0
167173
kern <- assignKern(kern,beta)
174+
boundsDT <- data.table( N = ParamNames
175+
, L = sapply(bounds, function(x) x[1])
176+
, U = sapply(bounds, function(x) x[2])
177+
, C = sapply(bounds, function(x) class(x))
178+
)
179+
180+
168181

169182
# Define processing function
170183
ParMethod <- function(x) if(x) {`%dopar%`} else {`%do%`}
@@ -197,7 +210,7 @@ BayesianOptimization <- function(
197210
if (nrow(initGrid)>0){
198211
InitFeedParams <- initGrid
199212
} else{
200-
InitFeedParams <- data.table(sapply(ParamNames,RandParams,initPoints, bounds))
213+
InitFeedParams <- data.table(sapply(ParamNames,RandParams,initPoints, boundsDT))
201214
}
202215

203216
if (verbose > 0) cat("\nRunning initial scoring function",nrow(InitFeedParams),"times in",Workers,"thread(s).\n")
@@ -212,7 +225,7 @@ BayesianOptimization <- function(
212225
, .export = export
213226
) %op% {
214227

215-
Params <- InitFeedParams[iter,]
228+
Params <- InitFeedParams[get("iter"),]
216229
Elapsed <- system.time(Result <- do.call(what = FUN, args = as.list(Params)))
217230
data.table(Params,Elapsed = Elapsed[[3]],as.data.table(Result))
218231

@@ -251,15 +264,14 @@ BayesianOptimization <- function(
251264

252265
# Setup for iterations
253266
GPlist <- list()
267+
acqMaximums <- data.table()
254268
scaleList <- list(score = max(abs(ScoreDT$Score)), elapsed = max(ScoreDT$Elapsed))
255269
GP <- NULL
256-
OptParDT <- data.table(ScoreDT[0,c("Iteration",ParamNames), with = F])
257-
BestPars <- data.table( Iteration = Iter
258-
, ScoreDT[which.max(Score),c(ParamNames,"Score",extraRet),with = F]
270+
BestPars <- data.table( "Iteration" = Iter
271+
, ScoreDT[which.max(get("Score")),c(ParamNames,"Score",extraRet),with = F]
259272
, elapsedSecs = round(difftime(Time,StartT,units = "secs"),0)
260273
)
261274

262-
263275
# Start the iterative GP udpates
264276
while(nrow(ScoreDT) < nIters){
265277

@@ -276,29 +288,26 @@ BayesianOptimization <- function(
276288
acq <- stopImpatient$newAcq
277289
}
278290

279-
280291
# Fit GP
281-
newD <- ScoreDT[Iteration == Iter-1,]
292+
newD <- ScoreDT[get("Iteration") == Iter-1,]
282293
if (verbose > 0) cat("\n 1) Fitting Gaussian process...")
283294
GP <- updateGP( GP = GP
284295
, kern = kern
285-
, X = matrix(sapply(ParamNames, MinMaxScale, newD, bounds), nrow = nrow(newD))
286-
, Z = matrix(as.matrix(newD[,.(Score/scaleList$score,Elapsed/scaleList$elapsed)]), nrow = nrow(newD))
296+
, X = matrix(sapply(ParamNames, MinMaxScale, newD, boundsDT), nrow = nrow(newD))
297+
, Z = matrix(as.matrix(newD[,.(get("Score")/scaleList$score,Elapsed/scaleList$elapsed)]), nrow = nrow(newD))
287298
, acq = acq
288299
, scaleList = scaleList
289300
, parallel = parallel)
290301

291-
292-
293302
# Store GP in list
294303
GPlist[[Iter]] <- GP
295304

296305
# Create random points to initialize local maximum search.
297-
LocalTries <- data.table(sapply(ParamNames,RandParams,gsPoints,bounds))
298-
LocalTryMM <- data.table(sapply(ParamNames,MinMaxScale,LocalTries,bounds))
306+
LocalTries <- data.table(sapply(ParamNames,RandParams,gsPoints,boundsDT))
307+
LocalTryMM <- data.table(sapply(ParamNames,MinMaxScale,LocalTries,boundsDT))
299308

300309
# Try gsPoints starting points to find parameter set that optimizes Acq
301-
if (verbose > 0) cat("\n 2) Running global optimum search...")
310+
if (verbose > 0) cat("\n 2) Running local optimum search...")
302311
LocalOptims <- maxAcq( GP = GP
303312
, TryOver = LocalTryMM
304313
, acq = acq
@@ -310,24 +319,21 @@ BayesianOptimization <- function(
310319
, convThresh = convThresh
311320
)
312321

322+
if (sum(LocalOptims$gradCount > 2) == 0) cat("\n 2a) WARNING - No initial points converged.\n Process may just be sampling random points.\n Try decreasing convThresh.")
313323

314-
# Extract the best GP_Utility, and then reverse scaling
315-
NewP <- LocalOptims[order(-GP_Utility)]
316-
NewPUMM <- data.table(sapply(ParamNames, UnMMScale, NewP, bounds))[rep(1,runNew)]
317-
318-
319-
# Save the best parameters for this Gaussian Process
320-
OptParDT <- rbind(OptParDT, data.table(Iteration = Iter, NewPUMM[1,]))
321-
322-
# Add noise to optimal parameter set.
323-
NoisyP <- sapply(ParamNames, ApplyNoise, Table = NewPUMM, bounds = bounds, saveFirst = TRUE, noiseAdd = noiseAdd)
324-
if (runNew == 1) {NoisyP <- as.data.table(as.list(NoisyP))
325-
} else NoisyP <- data.table(NoisyP)
324+
fromCluster <- applyCluster()
325+
acqMaximums <- rbind(acqMaximums, data.table("Iteration" = Iter, fromCluster$clusterPoints))
326+
newScorePars <- sapply(ParamNames, UnMMScale, fromCluster$newSet, boundsDT)
326327

328+
if (runNew < 2) {
329+
newScorePars <- as.data.table(as.list(newScorePars))
330+
} else{
331+
newScorePars <- data.table(newScorePars)
332+
}
327333

328-
if (verbose > 0) cat("\n 3) Running scoring function",nrow(NoisyP),"times in",Workers,"thread(s)...\n")
334+
if (verbose > 0) cat("\n 3) Running scoring function",nrow(newScorePars),"times in",Workers,"thread(s)...\n")
329335
sink("NUL")
330-
NewResults <- foreach( iter = 1:nrow(NoisyP)
336+
NewResults <- foreach( iter = 1:nrow(newScorePars)
331337
, .combine = rbind
332338
, .multicombine = TRUE
333339
, .inorder = FALSE
@@ -337,9 +343,9 @@ BayesianOptimization <- function(
337343
, .export = export
338344
) %op% {
339345

340-
Params <- NoisyP[iter,]
346+
Params <- newScorePars[get("iter"),]
341347
Elapsed <- system.time(Result <- do.call(what = FUN, args = as.list(Params)))
342-
data.table(NoisyP[iter,], Elapsed = Elapsed[[3]], as.data.table(Result))
348+
data.table(newScorePars[get("iter"),], Elapsed = Elapsed[[3]], as.data.table(Result))
343349

344350
}
345351
sink()
@@ -354,22 +360,22 @@ BayesianOptimization <- function(
354360

355361
if (max(NewResults$Score) > max(ScoreDT$Score)) {
356362
cat("\nNew best parameter set found:\n")
357-
print(NewResults[which.max(Score),], row.names = FALSE)
363+
print(NewResults[which.max(get("Score")),], row.names = FALSE)
358364
} else {
359365
cat("\nMaximum score was not raised this round. Best score is still:\n")
360-
print(ScoreDT[which.max(Score),], row.names = FALSE)
366+
print(ScoreDT[which.max(get("Score")),], row.names = FALSE)
361367
}
362368
}
363369

364370
ScoreDT <- rbind(ScoreDT
365-
,data.table(Iteration = rep(Iter,nrow(NewResults))
371+
,data.table("Iteration" = rep(Iter,nrow(NewResults))
366372
,NewResults
367373
)
368374
)
369375
BestPars <- rbind(BestPars
370-
,data.table( Iteration = Iter
371-
, ScoreDT[which.max(Score),c(ParamNames,"Score",extraRet),with = F]
372-
, elapsedSecs = round(difftime(Time,StartT,units = "secs"),0)
376+
,data.table("Iteration" = Iter
377+
, ScoreDT[which.max(get("Score")),c(ParamNames,"Score",extraRet),with = F]
378+
, "elapsedSecs" = round(difftime(Time,StartT,units = "secs"),0)
373379
)
374380
)
375381

@@ -390,12 +396,12 @@ BayesianOptimization <- function(
390396
RetList <- list()
391397
names(GPlist) <- paste0("GP_", 1:Iter)
392398
RetList$GPlist <- GPlist
393-
RetList$OptParDT <- OptParDT
399+
RetList$acqMaximums <- acqMaximums
394400
RetList$ScoreDT <- ScoreDT
395401
RetList$BestPars <- BestPars
396402

397403
return(RetList)
398404

399405

400406
}
401-
utils::globalVariables(c("iter","Iteration","Score",".","GauPro_kernel_beta","GauPro_kernel_model","GP_Utility"))
407+
utils::globalVariables(c("."))

R/SmallFuncs.R

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ CheckBounds <- function(x, Table, bounds) sum(!between(Table[[x]], lower = bound
3030
#' @return a data.table of random parameters
3131
#' @keywords internal
3232
#' @export
33-
RandParams <- function(x, Rpoints, bounds) {
33+
RandParams <- function(x, Rpoints, boundsDT) {
3434

35-
if (class(bounds[[x]]) == "integer"){
35+
B <- boundsDT[get("N") == x,]
3636

37-
base::sample(bounds[[x]][[1]]:bounds[[x]][[2]], size = Rpoints, replace = TRUE)
37+
if (B$C == "integer"){
38+
39+
base::sample(B$L:B$U, size = Rpoints, replace = TRUE)
3840

3941
} else {
4042

41-
runif(Rpoints, min = bounds[[x]][[1]], max = bounds[[x]][[2]])
43+
runif(Rpoints, min = B$L, max =B$U)
4244

4345
}
4446
}
@@ -49,13 +51,14 @@ RandParams <- function(x, Rpoints, bounds) {
4951
#' Scales a data.table of parameter sets to a 0-1 range
5052
#'
5153
#' @param x Parameter Name
52-
#' @param Table A data.table of parameter sets
54+
#' @param table A data.table of parameter sets
5355
#' @param bounds the original bounds list
54-
#' @return a data.table the same length as Table with scaled parameters
56+
#' @return a data.table the same length as \code{table} with scaled parameters
5557
#' @keywords internal
5658
#' @export
57-
MinMaxScale <- function(x, Table, bounds) {
58-
(Table[[x]]-bounds[[x]][[1]]) / (bounds[[x]][[2]]-bounds[[x]][[1]])
59+
MinMaxScale <- function(x, table, boundsDT) {
60+
B <- boundsDT[get("N") == x,]
61+
(table[[x]]-B$L) / (B$U-B$L)
5962
}
6063

6164

@@ -65,23 +68,28 @@ MinMaxScale <- function(x, Table, bounds) {
6568
#' Un-scales a data.table of parameter sets from a 0-1 range
6669
#'
6770
#' @param x Parameter Name
68-
#' @param Table A data.table of scaled parameter sets
71+
#' @param table A data.table of scaled parameter sets
6972
#' @param bounds the original bounds list
70-
#' @return a data.table the same length as Table with un-scaled parameters
73+
#' @return a data.table the same length as \code{table} with un-scaled parameters
7174
#' @keywords internal
7275
#' @export
73-
UnMMScale <- function(x, Table, bounds) {
74-
(bounds[[x]][[2]]-bounds[[x]][[1]])*Table[[x]]+bounds[[x]][[1]]
76+
UnMMScale <- function(x, table, boundsDT) {
77+
B <- boundsDT[get("N") == x,]
78+
79+
if (B$C == "integer") {
80+
return(round((B$U-B$L)*table[[x]]+B$L,0))
81+
} else return((B$U-B$L)*table[[x]]+B$L)
7582
}
7683

7784

7885
#' @title Assign Kernel
7986
#'
8087
#' @description
8188
#' This function exists so GauPro doesn't have to be loaded to run BayesianOptimization
89+
#'
8290
#' @param kern a kernel
8391
#' @param beta the log10(theta) the lengthscale parameter
84-
#' @return a data.table the same length as Table with un-scaled parameters
92+
#' @return an GauPro_kernel_beta R6 class
8593
#' @keywords internal
8694
#' @export
8795
assignKern <- function(kern,beta) {

0 commit comments

Comments
 (0)