@@ -321,11 +321,10 @@ If a single measure or non-empty vector of measures is specified by
321321written to the training report (call `report` on the trained
322322machine wrapping the ensemble model).
323323
324- *Important:* If sample weights `w` (not to be confused with atomic
325- weights) are specified when constructing a machine for the ensemble
326- model, as in `mach = machine(ensemble_model, X, y, w)`, then `w` is
327- used by any measures specified in `out_of_bag_measure` that support
328- sample weights.
324+ *Important:* If per-observation or class weights `w` (not to be confused with atomic
325+ weights) are specified when constructing a machine for the ensemble model, as in `mach =
326+ machine(ensemble_model, X, y, w)`, then `w` is used by any measures specified in
327+ `out_of_bag_measure` that support them.
329328
330329"""
331330function EnsembleModel (
@@ -395,34 +394,56 @@ function _fit(res::CPUProcesses, func, verbosity, stuff)
395394 if i != nworkers ()
396395 func (atom, 0 , chunk_size, n_patterns, n_train, rng, progress_meter, args... )
397396 else
398- func (atom, 0 , chunk_size + left_over, n_patterns, n_train, rng, progress_meter, args... )
397+ func (
398+ atom,
399+ 0 ,
400+ chunk_size + left_over,
401+ n_patterns,
402+ n_train,
403+ rng,
404+ progress_meter,
405+ args... ,
406+ )
399407 end
400408 end
401409end
402410
403- @static if VERSION >= v " 1.3.0-DEV.573"
404- function _fit (res:: CPUThreads , func, verbosity, stuff)
405- atom, n, n_patterns, n_train, rng, progress_meter, args = stuff
406- if verbosity > 0
407- println (" Ensemble-building in parallel on $(Threads. nthreads ()) threads." )
408- end
409- nthreads = Threads. nthreads ()
410- chunk_size = div (n, nthreads)
411- left_over = mod (n, nthreads)
412- resvec = Vector (undef, nthreads) # FIXME : Make this type-stable?
413-
414- Threads. @threads for i = 1 : nthreads
415- resvec[i] = if i != nworkers ()
416- func (atom, 0 , chunk_size, n_patterns, n_train, rng, progress_meter, args... )
417- else
418- func (atom, 0 , chunk_size + left_over, n_patterns, n_train, rng, progress_meter, args... )
419- end
420- end
411+ function _fit (res:: CPUThreads , func, verbosity, stuff)
412+ atom, n, n_patterns, n_train, rng, progress_meter, args = stuff
413+ if verbosity > 0
414+ println (" Ensemble-building in parallel on $(Threads. nthreads ()) threads." )
415+ end
416+ nthreads = Threads. nthreads ()
417+ chunk_size = div (n, nthreads)
418+ left_over = mod (n, nthreads)
419+ resvec = Vector (undef, nthreads) # FIXME : Make this type-stable?
421420
422- return reduce (_reducer, resvec)
421+ Threads. @threads for i = 1 : nthreads
422+ resvec[i] = if i != nworkers ()
423+ func (atom, 0 , chunk_size, n_patterns, n_train, rng, progress_meter, args... )
424+ else
425+ func (
426+ atom,
427+ 0 ,
428+ chunk_size + left_over,
429+ n_patterns,
430+ n_train,
431+ rng,
432+ progress_meter,
433+ args... ,
434+ )
435+ end
423436 end
437+
438+ return reduce (_reducer, resvec)
424439end
425440
441+ # for subsampling weights, which could be `nothing`, per-observation weights, or
442+ # class_weights:
443+ _view (class_weights:: AbstractDict , rows) = class_weights
444+ _view (:: Nothing , rows) = nothing
445+ _view (weights, rows) = view (weights, rows)
446+
426447function MMI. fit (
427448 model:: EitherEnsembleModel{Atom} , verbosity:: Int , args...
428449) where Atom<: Supervised
@@ -446,10 +467,14 @@ function MMI.fit(
446467 acceleration = CPU1 ()
447468 end
448469
470+ # we wrap the measures in `robust_measure` so they can be called with weights, even
471+ # when they don't support them, and just ignore them silently.
449472 if model. out_of_bag_measure isa Vector
450- out_of_bag_measure = model. out_of_bag_measure
473+ out_of_bag_measure =
474+ StatisticalMeasuresBase. robust_measure .(model. out_of_bag_measure)
451475 else
452- out_of_bag_measure = [model. out_of_bag_measure,]
476+ out_of_bag_measure =
477+ [StatisticalMeasuresBase. robust_measure (model. out_of_bag_measure),]
453478 end
454479
455480 if model. rng isa Integer
@@ -484,7 +509,7 @@ function MMI.fit(
484509
485510 if ! isempty (out_of_bag_measure)
486511
487- metrics = zeros (length (ensemble),length (out_of_bag_measure))
512+ measurements = zeros (length (ensemble),length (out_of_bag_measure))
488513 for i= 1 : length (ensemble)
489514 # oob indices
490515 ooB_indices= setdiff (1 : n_patterns, ensemble_indices[i])
@@ -493,42 +518,44 @@ function MMI.fit(
493518 " Data size too small or " *
494519 " bagging_fraction too close to 1.0. " )
495520 end
496- yhat = predict (atom, ensemble[i], selectrows (atom, ooB_indices, atom_specific_X)... )
521+ yhat = predict (
522+ atom,
523+ ensemble[i],
524+ selectrows (atom, ooB_indices, atom_specific_X)... ,
525+ )
497526 Xtest = selectrows (X, ooB_indices)
498527 ytest = selectrows (y, ooB_indices)
499528
500- if w === nothing
501- wtest = nothing
502- else
503- wtest = selectrows (w, ooB_indices)
504- end
529+ # this could be class weights OR per-observation weights, OR `nothing`:
530+ wtest = _view (w, ooB_indices)
505531
506532 for k in eachindex (out_of_bag_measure)
507533 m = out_of_bag_measure[k]
508- if MMI. reports_each_observation (m)
509- s = MLJBase. aggregate (
510- MLJBase. value (m, yhat, Xtest, ytest, wtest),
511- m
512- )
513- else
514- s = MLJBase. value (m, yhat, Xtest, ytest, wtest)
515- end
516- metrics[i,k] = s
534+ s = m (yhat, ytest, wtest)
535+ measurements[i,k] = s
517536 end
518537 end
519538
520- # aggregate metrics across the ensembles:
521- aggregated_metrics = map (eachindex (out_of_bag_measure)) do k
522- MLJBase. aggregate (metrics[:,k], out_of_bag_measure[k])
539+ # aggregate measurements across the ensembles:
540+ aggregated_measurements = map (eachindex (out_of_bag_measure)) do k
541+ StatisticalMeasuresBase. aggregate (
542+ measurements[:,k],
543+ mode= StatisticalMeasuresBase. external_aggregation_mode (
544+ out_of_bag_measure[k],
545+ )
546+ )
523547 end
524548
525549 names = Symbol .(string .(out_of_bag_measure))
526550
527551 else
528- aggregated_metrics = missing
552+ aggregated_measurements = missing
529553 end
530554
531- report= (measures= out_of_bag_measure, oob_measurements= aggregated_metrics,)
555+ report= (
556+ measures= out_of_bag_measure,
557+ oob_measurements= aggregated_measurements,
558+ )
532559 cache = deepcopy (model)
533560
534561 return fitresult, cache, report
@@ -542,7 +569,7 @@ function MMI.update(model::EitherEnsembleModel,
542569
543570 n = model. n
544571
545- if MLJBase . is_same_except (model. model, old_model. model,
572+ if MMI . is_same_except (model. model, old_model. model,
546573 :n , :atomic_weights , :acceleration )
547574 if n > old_model. n
548575 verbosity < 1 ||
0 commit comments