|
a |
|
b/R/metrics.R |
|
|
1 |
#' F1 metric |
|
|
2 |
#' |
|
|
3 |
#' Compute F1 metric. If loss is `"categorical_crossentropy"`, number of targets must be 2. If |
|
|
4 |
#' loss is `"binary_crossentropy"` and number of targets > 1, will flatten `y_true` and `y_pred` matrices |
|
|
5 |
#' to a single vector (rather than computing separate F1 scores for each class). |
|
|
6 |
#' |
|
|
7 |
#' @param num_targets Size of model output. |
|
|
8 |
#' @param loss Loss function of model. |
|
|
9 |
#' @examplesIf reticulate::py_module_available("tensorflow") |
|
|
10 |
#' |
|
|
11 |
#' y_true <- c(1,0,0,1,1,0,1,0,0) |
|
|
12 |
#' y_pred <- c(0.9,0.05,0.05,0.9,0.05,0.05,0.9,0.05,0.05) |
|
|
13 |
#' \donttest{ |
|
|
14 |
#' library(keras) |
|
|
15 |
#' f1_metric <- f1_wrapper(3L, "binary_crossentropy") |
|
|
16 |
#' f1_metric$update_state(y_true, y_pred) |
|
|
17 |
#' f1_metric$result() |
|
|
18 |
#' |
|
|
19 |
#' |
|
|
20 |
#' # add metric to a model |
|
|
21 |
#' |
|
|
22 |
#' num_targets <- 1 |
|
|
23 |
#' model <- create_model_lstm_cnn(maxlen = 20, |
|
|
24 |
#' layer_lstm = 8, |
|
|
25 |
#' bal_acc = FALSE, |
|
|
26 |
#' last_layer_activation = "sigmoid", |
|
|
27 |
#' loss_fn = "binary_crossentropy", |
|
|
28 |
#' layer_dense = c(8, num_targets)) |
|
|
29 |
#' |
|
|
30 |
#' f1_metric <- f1_wrapper(num_targets, loss = model$loss) |
|
|
31 |
#' model %>% keras::compile(loss = model$loss, |
|
|
32 |
#' optimizer = model$optimizer, |
|
|
33 |
#' metrics = c(model$metrics, f1_metric)) |
|
|
34 |
#' } |
|
|
35 |
#' @returns A keras metric. |
|
|
36 |
#' @export |
|
|
37 |
f1_wrapper <- function(num_targets = 2, loss = "binary_crossentropy") { |
|
|
38 |
|
|
|
39 |
stopifnot(loss %in% c("binary_crossentropy", "categorical_crossentropy")) |
|
|
40 |
|
|
|
41 |
if (loss == "binary_crossentropy" & num_targets > 1) { |
|
|
42 |
message("Will flatten y_true and y_pred matrices to a single vector for evaluation |
|
|
43 |
rather than computing separate F1 scores for each class and taking the mean.") |
|
|
44 |
} |
|
|
45 |
|
|
|
46 |
if (loss == "categorical_crossentropy" & num_targets != 2) { |
|
|
47 |
stop("Output size must be two, when loss is categorical_crossentropy") |
|
|
48 |
} |
|
|
49 |
|
|
|
50 |
f1_stateful <- reticulate::PyClass("f1", |
|
|
51 |
inherit = tensorflow::tf$keras$metrics$Metric, |
|
|
52 |
list( |
|
|
53 |
|
|
|
54 |
`__init__` = function(self, num_targets, loss) { |
|
|
55 |
super()$`__init__`(name = "f1") |
|
|
56 |
self$num_targets <- num_targets |
|
|
57 |
self$f1_score <- 0 |
|
|
58 |
self$loss <- loss |
|
|
59 |
self$rc <- tensorflow::tf$keras$metrics$Recall() |
|
|
60 |
self$pr <- tensorflow::tf$keras$metrics$Precision() |
|
|
61 |
NULL |
|
|
62 |
}, |
|
|
63 |
|
|
|
64 |
update_state = function(self, y_true, y_pred, sample_weight = NULL) { |
|
|
65 |
if (self$loss == "binary_crossentropy") { |
|
|
66 |
self$rc$update_state(y_true, y_pred) |
|
|
67 |
self$pr$update_state(y_true, y_pred) |
|
|
68 |
} else { |
|
|
69 |
self$rc$update_state(y_true[ , 1], y_pred[ , 1]) |
|
|
70 |
self$pr$update_state(y_true[ , 1], y_pred[ , 1]) |
|
|
71 |
} |
|
|
72 |
NULL |
|
|
73 |
}, |
|
|
74 |
|
|
|
75 |
result = function(self) { |
|
|
76 |
self$f1_score <- self$compute_f1() |
|
|
77 |
return(self$f1_score) |
|
|
78 |
}, |
|
|
79 |
|
|
|
80 |
compute_f1 = function(self) { |
|
|
81 |
f1 <- (2 * self$pr$result() * self$rc$result())/(self$pr$result() + self$rc$result() + tensorflow::tf$constant(1e-15)) |
|
|
82 |
return(f1) |
|
|
83 |
}, |
|
|
84 |
|
|
|
85 |
reset_state = function(self) { |
|
|
86 |
self$rc$reset_state() |
|
|
87 |
self$pr$reset_state() |
|
|
88 |
#self$f1_score$assign(0) |
|
|
89 |
NULL |
|
|
90 |
} |
|
|
91 |
|
|
|
92 |
)) |
|
|
93 |
|
|
|
94 |
return(f1_stateful(num_targets = num_targets, loss = loss)) |
|
|
95 |
} |
|
|
96 |
|
|
|
97 |
|
|
|
98 |
#' Balanced accuracy metric |
|
|
99 |
#' |
|
|
100 |
#' Compute balanced accuracy as additional score. Useful for imbalanced data. Only implemented for |
|
|
101 |
#' model with mutually exclusive targets. |
|
|
102 |
#' |
|
|
103 |
#' @param num_targets Number of targets. |
|
|
104 |
#' @param cm_dir Directory of confusion matrix used to compute balanced accuracy. |
|
|
105 |
#' @examplesIf reticulate::py_module_available("tensorflow") |
|
|
106 |
#' |
|
|
107 |
#' y_true <- c(1,0,0,1, |
|
|
108 |
#' 0,1,0,0, |
|
|
109 |
#' 0,0,1,0) %>% matrix(ncol = 3) |
|
|
110 |
#' y_pred <- c(0.9,0.1,0.2,0.1, |
|
|
111 |
#' 0.05,0.7,0.2,0.0, |
|
|
112 |
#' 0.05,0.2,0.6,0.9) %>% matrix(ncol = 3) |
|
|
113 |
#' |
|
|
114 |
#' cm_dir <- tempfile() |
|
|
115 |
#' dir.create(cm_dir) |
|
|
116 |
#' \donttest{ |
|
|
117 |
#' bal_acc_metric <- balanced_acc_wrapper(num_targets = 3L, cm_dir = cm_dir) |
|
|
118 |
#' bal_acc_metric$update_state(y_true, y_pred) |
|
|
119 |
#' bal_acc_metric$result() |
|
|
120 |
#' as.array(bal_acc_metric$cm) |
|
|
121 |
#' } |
|
|
122 |
#' |
|
|
123 |
#' @returns A keras metric. |
|
|
124 |
#' @export |
|
|
125 |
balanced_acc_wrapper <- function(num_targets, cm_dir) { |
|
|
126 |
balanced_acc_stateful <- reticulate::PyClass("balanced_acc", |
|
|
127 |
inherit = tensorflow::tf$keras$metrics$Metric, |
|
|
128 |
list( |
|
|
129 |
|
|
|
130 |
`__init__` = function(self, num_targets, cm_dir) { |
|
|
131 |
super()$`__init__`(name = "balanced_acc") |
|
|
132 |
self$num_targets <- num_targets |
|
|
133 |
self$cm_dir <- cm_dir |
|
|
134 |
self$count <- 0 |
|
|
135 |
self$cm <- self$add_weight(name = "cm_matrix", shape = c(num_targets, num_targets), initializer="zeros") |
|
|
136 |
NULL |
|
|
137 |
}, |
|
|
138 |
|
|
|
139 |
update_state = function(self, y_true, y_pred, sample_weight = NULL) { |
|
|
140 |
self$cm$assign_add(self$compute_cm(y_true, y_pred)) |
|
|
141 |
NULL |
|
|
142 |
}, |
|
|
143 |
|
|
|
144 |
result = function(self) { |
|
|
145 |
balanced_acc <- self$compute_balanced_acc() |
|
|
146 |
#self$store_cm() |
|
|
147 |
return(balanced_acc) |
|
|
148 |
}, |
|
|
149 |
|
|
|
150 |
compute_cm = function(self, y_true, y_pred) { |
|
|
151 |
labels <- tensorflow::tf$math$argmax(y_true, axis = 1L) |
|
|
152 |
predictions <- tensorflow::tf$math$argmax(y_pred, axis = 1L) |
|
|
153 |
current_cm <- tensorflow::tf$math$confusion_matrix( |
|
|
154 |
labels = labels, predictions = predictions, |
|
|
155 |
dtype = "float32", num_classes = self$num_targets) |
|
|
156 |
current_cm <- tensorflow::tf$transpose(current_cm) |
|
|
157 |
return(current_cm) |
|
|
158 |
}, |
|
|
159 |
|
|
|
160 |
compute_balanced_acc = function(self) { |
|
|
161 |
diag <- tensorflow::tf$linalg$diag_part(self$cm) |
|
|
162 |
col_sums <- tensorflow::tf$math$reduce_sum(self$cm, axis=0L) |
|
|
163 |
average_per_class <- tensorflow::tf$math$divide(diag, col_sums) |
|
|
164 |
nan_index <- tensorflow::tf$math$logical_not(tensorflow::tf$math$is_nan(average_per_class)) |
|
|
165 |
average_per_class <- tensorflow::tf$boolean_mask(average_per_class, nan_index) |
|
|
166 |
acc_sum <- tensorflow::tf$math$reduce_sum(average_per_class) |
|
|
167 |
balanced_acc <- tensorflow::tf$math$divide(acc_sum, tensorflow::tf$math$count_nonzero(col_sums, dtype= acc_sum$dtype)) |
|
|
168 |
return(balanced_acc) |
|
|
169 |
}, |
|
|
170 |
|
|
|
171 |
reset_state = function(self) { |
|
|
172 |
self$store_cm() |
|
|
173 |
self$count <- self$count + 1 |
|
|
174 |
self$cm$assign_sub(self$cm) |
|
|
175 |
NULL |
|
|
176 |
}, |
|
|
177 |
|
|
|
178 |
store_cm = function(self) { |
|
|
179 |
#if (self$count > 0) { |
|
|
180 |
if (self$count %% 2 != 0) { |
|
|
181 |
file_name <- file.path(self$cm_dir, paste0("cm_val_", floor(self$count/2), ".rds")) |
|
|
182 |
} else { |
|
|
183 |
file_name <- file.path(self$cm_dir, paste0("cm_train_", floor(self$count/2), ".rds")) |
|
|
184 |
} |
|
|
185 |
saveRDS(keras::k_eval(self$cm), file_name) |
|
|
186 |
NULL |
|
|
187 |
#} |
|
|
188 |
} |
|
|
189 |
|
|
|
190 |
)) |
|
|
191 |
return(balanced_acc_stateful(num_targets = num_targets, cm_dir = cm_dir)) |
|
|
192 |
} |
|
|
193 |
|
|
|
194 |
|
|
|
195 |
#' Mean AUC score |
|
|
196 |
#' |
|
|
197 |
#' Compute AUC score as additional metric. If model has several output neurons with binary crossentropy loss, will use the average score. |
|
|
198 |
#' |
|
|
199 |
#' @param model_output_size Number of neurons in model output layer. |
|
|
200 |
#' @param loss Loss function of model, for which metric will be applied to; must be `"binary_crossentropy"` |
|
|
201 |
#' or `"categorical_crossentropy"`. |
|
|
202 |
#' @examplesIf reticulate::py_module_available("tensorflow") |
|
|
203 |
#' |
|
|
204 |
#' y_true <- c(1,0,0,1,1,0,1,0,0) %>% matrix(ncol = 3) |
|
|
205 |
#' y_pred <- c(0.9,0.05,0.05,0.9,0.05,0.05,0.9,0.05,0.05) %>% matrix(ncol = 3) |
|
|
206 |
#' |
|
|
207 |
#' \donttest{ |
|
|
208 |
#' library(keras) |
|
|
209 |
#' auc_metric <- auc_wrapper(3L, "binary_crossentropy") |
|
|
210 |
#' |
|
|
211 |
#' auc_metric$update_state(y_true, y_pred) |
|
|
212 |
#' auc_metric$result() |
|
|
213 |
#' |
|
|
214 |
#' # add metric to a model |
|
|
215 |
#' num_targets <- 4 |
|
|
216 |
#' model <- create_model_lstm_cnn(maxlen = 20, |
|
|
217 |
#' layer_lstm = 8, |
|
|
218 |
#' bal_acc = FALSE, |
|
|
219 |
#' last_layer_activation = "sigmoid", |
|
|
220 |
#' loss_fn = "binary_crossentropy", |
|
|
221 |
#' layer_dense = c(8, num_targets)) |
|
|
222 |
#' |
|
|
223 |
#' auc_metric <- auc_wrapper(num_targets, loss = model$loss) |
|
|
224 |
#' model %>% keras::compile(loss = model$loss, |
|
|
225 |
#' optimizer = model$optimizer, |
|
|
226 |
#' metrics = c(model$metrics, auc_metric)) |
|
|
227 |
#' } |
|
|
228 |
#' @returns A keras metric. |
|
|
229 |
#' @export |
|
|
230 |
auc_wrapper <- function(model_output_size, |
|
|
231 |
loss = "binary_crossentropy") { |
|
|
232 |
|
|
|
233 |
multi_label <- FALSE |
|
|
234 |
stopifnot(loss %in% c("binary_crossentropy", "categorical_crossentropy")) |
|
|
235 |
|
|
|
236 |
if (loss == "categorical_crossentropy" & model_output_size != 2) { |
|
|
237 |
stop("Output size must be two, when loss is categorical_crossentropy") |
|
|
238 |
} |
|
|
239 |
|
|
|
240 |
if (loss == "categorical_crossentropy") { |
|
|
241 |
label_weights <- c(1L, 0L) |
|
|
242 |
} else { |
|
|
243 |
label_weights <- NULL |
|
|
244 |
} |
|
|
245 |
|
|
|
246 |
if (loss == "binary_crossentropy" & model_output_size > 1) { |
|
|
247 |
multi_label <- TRUE |
|
|
248 |
} |
|
|
249 |
|
|
|
250 |
metric_name <- ifelse(loss == "binary_crossentropy" & model_output_size > 1, |
|
|
251 |
"mean_AUC", "AUC") |
|
|
252 |
|
|
|
253 |
auc_metric <- tensorflow::tf$keras$metrics$AUC(label_weights = label_weights, |
|
|
254 |
multi_label = multi_label) |
|
|
255 |
|
|
|
256 |
return(auc_metric) |
|
|
257 |
} |
|
|
258 |
|
|
|
259 |
#' Loss function for label noise |
|
|
260 |
#' |
|
|
261 |
#' Implements approach from this [paper](https://arxiv.org/abs/1609.03683) and code from |
|
|
262 |
#' [here](https://github.com/giorgiop/loss-correction/blob/15a79de3c67c31907733392085c333547c2f2b16/loss.py#L16-L21). |
|
|
263 |
#' Can be used if labeled data contains noise, i.e. some of the data is labeled wrong. |
|
|
264 |
#' |
|
|
265 |
#' @param noise_matrix Matrix of noise distribution. |
|
|
266 |
#' @importFrom magrittr "%>%" |
|
|
267 |
#' @examplesIf reticulate::py_module_available("tensorflow") |
|
|
268 |
#' # If first label contains 5% wrong labels and second label no noise |
|
|
269 |
#' noise_matrix <- matrix(c(0.95, 0.05, 0, 1), nrow = 2, byrow = TRUE) |
|
|
270 |
#' noisy_loss <- noisy_loss_wrapper(noise_matrix) |
|
|
271 |
#' |
|
|
272 |
#' @returns A function implementing noisy loss. |
|
|
273 |
#' @export |
|
|
274 |
noisy_loss_wrapper <- function(noise_matrix) { |
|
|
275 |
inverted_noise_matrix <- solve(noise_matrix) |
|
|
276 |
inverted_noise_matrix <- tensorflow::tf$cast(inverted_noise_matrix, dtype = "float32") |
|
|
277 |
noisy_loss <- function(y_true, y_pred) { |
|
|
278 |
y_pred <- y_pred / keras::k_sum(y_pred, axis = -1, keepdims = TRUE) |
|
|
279 |
y_pred <- keras::k_clip(y_pred, tensorflow::tf$keras$backend$epsilon(), 1.0 - tensorflow::tf$keras$backend$epsilon()) |
|
|
280 |
loss <- -1 * keras::k_sum(keras::k_dot(y_true, inverted_noise_matrix) * keras::k_log(y_pred), axis=-1) |
|
|
281 |
return(loss) |
|
|
282 |
} |
|
|
283 |
noisy_loss |
|
|
284 |
} |
|
|
285 |
|
|
|
286 |
cpcloss <- function(latents, |
|
|
287 |
context, |
|
|
288 |
target_dim = 64, |
|
|
289 |
emb_scale = 0.1 , |
|
|
290 |
steps_to_ignore = 2, |
|
|
291 |
steps_to_predict = 3, |
|
|
292 |
steps_skip = 1, |
|
|
293 |
batch_size = 32, |
|
|
294 |
k = 5, |
|
|
295 |
train_type = "cpc") { |
|
|
296 |
# define empty lists for metrics |
|
|
297 |
loss <- list() |
|
|
298 |
acc <- list() |
|
|
299 |
# create context tensor |
|
|
300 |
ctx <- context(latents) |
|
|
301 |
c_dim <- latents$shape[[2]] |
|
|
302 |
|
|
|
303 |
# loop for different distances of predicted patches |
|
|
304 |
for (i in seq(steps_to_ignore, (steps_to_predict - 1), steps_skip)) { |
|
|
305 |
# define patches to be deleted |
|
|
306 |
c_dim_i <- c_dim - i - 1 |
|
|
307 |
if (train_type == "Self-GenomeNet") { |
|
|
308 |
steps_to_ignore <- 1 |
|
|
309 |
steps_to_predict <- 2 |
|
|
310 |
steps_skip <- 1 |
|
|
311 |
target_dim <- ctx$shape[[3]] |
|
|
312 |
ctx_conv <- |
|
|
313 |
ctx %>% keras::layer_conv_1d(kernel_size = 1, filters = target_dim) |
|
|
314 |
logits <- tensorflow::tf$zeros(list(0L,as.integer(batch_size*2))) |
|
|
315 |
for (j in seq_len(c_dim - (i + 1))) { |
|
|
316 |
basepos <- ctx_conv[, j,] %>% keras::k_reshape(c(-1, target_dim)) |
|
|
317 |
targetpos <- |
|
|
318 |
ctx[, (c_dim - j - i), ] %>% keras::k_reshape(c(-1, target_dim)) |
|
|
319 |
logits_j <- tensorflow::tf$matmul(basepos, tensorflow::tf$transpose(targetpos)) |
|
|
320 |
logits <- tensorflow::tf$concat(list(logits, logits_j), axis = 0L) |
|
|
321 |
} |
|
|
322 |
# define labels |
|
|
323 |
labels <- |
|
|
324 |
rep(c(seq(batch_size, ( |
|
|
325 |
2 * batch_size - 1 |
|
|
326 |
)), (seq( |
|
|
327 |
0, (batch_size - 1) |
|
|
328 |
))), (c_dim - (i + 1))) %>% as.integer() |
|
|
329 |
} else { |
|
|
330 |
c_dim_i <- c_dim - i - 1 |
|
|
331 |
# define total number of elements in context tensor |
|
|
332 |
total_elements <- batch_size * c_dim_i |
|
|
333 |
# add conv layer and reshape tensor for matrix multiplication |
|
|
334 |
targets <- |
|
|
335 |
latents %>% keras::layer_conv_1d(kernel_size = 1, filters = target_dim) %>% keras::k_reshape(c(-1, target_dim)) |
|
|
336 |
# add conv layer and reshape for matrix multiplication |
|
|
337 |
preds_i <- |
|
|
338 |
ctx %>% keras::layer_conv_1d(kernel_size = 1, filters = target_dim) |
|
|
339 |
preds_i <- preds_i[, (1:(c_dim - i - 1)),] |
|
|
340 |
preds_i <- keras::k_reshape(preds_i, c(-1, target_dim)) * emb_scale |
|
|
341 |
|
|
|
342 |
# define logits normally |
|
|
343 |
logits <- tensorflow::tf$matmul(preds_i, tensorflow::tf$transpose(targets)) |
|
|
344 |
|
|
|
345 |
# get position of labels |
|
|
346 |
b <- floor(seq(0, total_elements - 1) / c_dim_i) |
|
|
347 |
col <- seq(0, total_elements - 1) %% c_dim_i |
|
|
348 |
|
|
|
349 |
# define labels |
|
|
350 |
labels <- b * c_dim + col + (i + 1) |
|
|
351 |
labels <- as.integer(labels) |
|
|
352 |
|
|
|
353 |
} |
|
|
354 |
# calculate loss and accuracy for each step |
|
|
355 |
loss[[length(loss) + 1]] <- |
|
|
356 |
tensorflow::tf$nn$sparse_softmax_cross_entropy_with_logits(labels, logits) %>% |
|
|
357 |
tensorflow::tf$stack(axis = 0) %>% tensorflow::tf$reduce_mean() |
|
|
358 |
acc[[length(acc) + 1]] <- |
|
|
359 |
tensorflow::tf$keras$metrics$sparse_top_k_categorical_accuracy(tensorflow::tf$cast(labels, dtype = "int64"), logits, as.integer(k)) %>% |
|
|
360 |
tensorflow::tf$stack(axis = 0) %>% tensorflow::tf$reduce_mean() |
|
|
361 |
} |
|
|
362 |
# convert to tensor for output |
|
|
363 |
loss <- loss %>% tensorflow::tf$stack(axis = 0) %>% tensorflow::tf$reduce_mean() |
|
|
364 |
acc <- acc %>% tensorflow::tf$stack(axis = 0) %>% tensorflow::tf$reduce_mean() |
|
|
365 |
return(tensorflow::tf$stack(list(loss, acc))) |
|
|
366 |
} |
|
|
367 |
|
|
|
368 |
#' Stochastic Gradient Descent with Warm Restarts |
|
|
369 |
#' |
|
|
370 |
#' Compute the learning Rate for a given epoch using Stochastic Gradient Descent with Warm Restarts. Implements approach from this [paper](https://arxiv.org/abs/1608.03983). |
|
|
371 |
#' |
|
|
372 |
#' @param lrmin Lower limit of the range for the learning rate. |
|
|
373 |
#' @param lrmax Upper limit of the range for the learning rate. |
|
|
374 |
#' @param restart Number of epochs until a restart is conducted. |
|
|
375 |
#' @param mult Factor, by which the number of epochs until a restart is increased at every restart. |
|
|
376 |
#' @param epoch Epoch, for which the learning rate shall be calculated. |
|
|
377 |
#' @examples |
|
|
378 |
#' sgdr(lrmin = 5e-10, lrmax = 5e-2, restart = 50, |
|
|
379 |
#' mult = 1, epoch = 5) |
|
|
380 |
#' |
|
|
381 |
#' @returns A numeric value. |
|
|
382 |
#' @export |
|
|
383 |
sgdr <- function(lrmin = 5e-10, |
|
|
384 |
lrmax = 5e-2, |
|
|
385 |
restart = 50, |
|
|
386 |
mult = 1, |
|
|
387 |
epoch = NULL) { |
|
|
388 |
iter <- c() |
|
|
389 |
position <- c() |
|
|
390 |
i <- 0 |
|
|
391 |
while (length(iter) < epoch) { |
|
|
392 |
iter <- c(iter, rep(i, restart * mult ^ i)) |
|
|
393 |
position <- c(position, c(1:(restart * mult ^ i))) |
|
|
394 |
i <- i + 1 |
|
|
395 |
} |
|
|
396 |
restart2 <- (restart * mult ^ iter[epoch]) |
|
|
397 |
epoch <- position[epoch] |
|
|
398 |
return(lrmin + 1 / 2 * (lrmax - lrmin) * (1 + cos((epoch / restart2) * pi))) |
|
|
399 |
} |
|
|
400 |
|
|
|
401 |
#' Step Decay |
|
|
402 |
#' |
|
|
403 |
#' Compute the learning Rate for a given epoch using Step Decay. |
|
|
404 |
#' |
|
|
405 |
#' @param lrmax Upper limit of the range for the learning rate. |
|
|
406 |
#' @param newstep Number of epochs until the learning rate is reduced. |
|
|
407 |
#' @param mult Factor, by which the number of epochs until a restart is decreased after a new step. |
|
|
408 |
#' @param epoch Epoch, for which the learning rate shall be calculated. |
|
|
409 |
#' @examples |
|
|
410 |
#' stepdecay(lrmax = 0.005, newstep = 50, |
|
|
411 |
#' mult = 0.7, epoch = 3) |
|
|
412 |
#' |
|
|
413 |
#' @returns A numeric value. |
|
|
414 |
#' @export |
|
|
415 |
stepdecay <- function(lrmax = 0.005, |
|
|
416 |
newstep = 50, |
|
|
417 |
mult = 0.7, |
|
|
418 |
epoch = NULL) { |
|
|
419 |
return(lrmax * (mult ^ (floor(( |
|
|
420 |
epoch |
|
|
421 |
) / newstep)))) |
|
|
422 |
} |
|
|
423 |
|
|
|
424 |
#' Exponential Decay |
|
|
425 |
#' |
|
|
426 |
#' Compute the learning Rate for a given epoch using Exponential Decay. |
|
|
427 |
#' |
|
|
428 |
#' @param lrmax Upper limit of the range for the learning rate. |
|
|
429 |
#' @param mult Factor, by which the number of epochs until a restart is decreased after a new step. |
|
|
430 |
#' @param epoch Epoch, for which the learning rate shall be calculated. |
|
|
431 |
#' @examples |
|
|
432 |
#' exp_decay(lrmax = 0.005, mult = 0.1, epoch = 8) |
|
|
433 |
#' |
|
|
434 |
#' @returns A numeric value. |
|
|
435 |
#' @export |
|
|
436 |
exp_decay <- function(lrmax = 0.005, |
|
|
437 |
mult = 0.1, |
|
|
438 |
epoch = NULL) { |
|
|
439 |
return(lrmax * exp(-mult * epoch)) |
|
|
440 |
} |
|
|
441 |
|
|
|
442 |
|
|
|
443 |
euclidean_distance <- function(vects) { |
|
|
444 |
x <- vects[[1]] |
|
|
445 |
y <- vects[[2]] |
|
|
446 |
sum_square <- tensorflow::tf$math$reduce_sum(tensorflow::tf$math$square(x - y), axis=1L, keepdims=TRUE) |
|
|
447 |
return(tensorflow::tf$math$sqrt(tensorflow::tf$math$maximum(sum_square, tensorflow::tf$keras$backend$epsilon()))) |
|
|
448 |
} |
|
|
449 |
|
|
|
450 |
cosine_similarity <- function(vects) { |
|
|
451 |
x <- vects[[1]] |
|
|
452 |
y <- vects[[2]] |
|
|
453 |
xy_dot <- tensorflow::tf$math$reduce_sum(x*y, axis=1L, keepdims=TRUE) |
|
|
454 |
x_norm <- tensorflow::tf$math$sqrt(tensorflow::tf$math$reduce_sum(tensorflow::tf$math$square(x), axis=1L, keepdims=TRUE)) |
|
|
455 |
y_norm <- tensorflow::tf$math$sqrt(tensorflow::tf$math$reduce_sum(tensorflow::tf$math$square(y), axis=1L, keepdims=TRUE)) |
|
|
456 |
return(xy_dot/(x_norm*y_norm)) |
|
|
457 |
} |
|
|
458 |
|
|
|
459 |
#' Contrastive loss |
|
|
460 |
#' |
|
|
461 |
#' Contrastive loss as used here: https://keras.io/examples/vision/siamese_contrastive/. |
|
|
462 |
#' |
|
|
463 |
#' @param margin Integer, baseline for distance for which pairs should be classified as dissimilar. |
|
|
464 |
#' @examplesIf reticulate::py_module_available("tensorflow") |
|
|
465 |
#' cl <- loss_cl(margin=1) |
|
|
466 |
#' |
|
|
467 |
#' @returns A function implementing contrastive loss. |
|
|
468 |
#' @export |
|
|
469 |
loss_cl <- function(margin=1) { |
|
|
470 |
|
|
|
471 |
contrastive_loss <- function(y_true, y_pred) { |
|
|
472 |
|
|
|
473 |
square_pred <- tensorflow::tf$math$square(y_pred) |
|
|
474 |
margin_square <- tensorflow::tf$math$square(tensorflow::tf$math$maximum(margin - (y_pred), 0)) |
|
|
475 |
l <- tensorflow::tf$math$reduce_mean( |
|
|
476 |
(1 - y_true) * square_pred + (y_true) * margin_square |
|
|
477 |
) |
|
|
478 |
return(l) |
|
|
479 |
} |
|
|
480 |
|
|
|
481 |
return(contrastive_loss) |
|
|
482 |
|
|
|
483 |
} |
|
|
484 |
|
|
|
485 |
#' Focal loss for two or more labels |
|
|
486 |
#' |
|
|
487 |
#' @param y_true Vector of true values. |
|
|
488 |
#' @param y_pred Vector of predicted values. |
|
|
489 |
#' @param gamma Focusing parameter. |
|
|
490 |
#' @param alpha Vector of weighting factors. |
|
|
491 |
#' @examplesIf reticulate::py_module_available("tensorflow") |
|
|
492 |
#' y_true <- matrix(c(0, 1, 0, 0, 0, 1), nrow = 2, byrow = TRUE) |
|
|
493 |
#' y_pred <- matrix(c(0.15, 0.8, 0.05, |
|
|
494 |
#' 0.08, 0.02, 0.9), nrow = 2, byrow = TRUE) |
|
|
495 |
#' fl <- focal_loss_multiclass(y_true, y_pred) |
|
|
496 |
#' fl$numpy() |
|
|
497 |
#' |
|
|
498 |
#' @returns A function implementing focal loss. |
|
|
499 |
#' @export |
|
|
500 |
focal_loss_multiclass <- function(y_true, y_pred, gamma = 2.5, alpha = c(1)) { |
|
|
501 |
y_pred <- keras::k_clip(y_pred, keras::k_epsilon(), 1.0 - keras::k_epsilon()) |
|
|
502 |
cd_loss <- -y_true * keras::k_log(y_pred) # categorical cross entropy |
|
|
503 |
fl_loss <- alpha * keras::k_pow(1. - y_pred, gamma) * cd_loss |
|
|
504 |
return(keras::k_sum(fl_loss, axis = -1)) |
|
|
505 |
} |