1+ from typing import Tuple
2+
13import torch
2- from torch import nn
4+ from torch import Tensor , nn
5+
6+ from .utils import get_device
37
48# We use standard deviation to measure uncertainity since entropy is not
59# defined for continuous variables and differential entropy is not ideal.
610# In case all predictions are identical, std is 0. If 50% are 0 and 50% are
711# one, it is maximal, i.e. 0.5.
812MAX_STD = 0.5
9- MIN_STD = 0.
13+ MIN_STD = 0.0
14+
15+ DEVICE = get_device ()
16+
17+
18+ def map_to_device (inputs : Tuple [Tensor , ...]) -> Tuple [Tensor , ...]:
19+ return tuple (x .to (DEVICE ) for x in inputs )
1020
1121
1222def monte_carlo_dropout (
13- model , regime = ' loader' , loader = None , tensors = None , repetitions = 20
23+ model , regime = " loader" , loader = None , tensors = None , repetitions = 20
1424):
1525 """
1626 Attempts to approximate epistemic uncertainity through MC dropout.
17- Performs Monte Carlo dropout for a given model and returns a list of
27+ Performs Monte Carlo dropout for a given model and returns a list of
1828 sample-wise confidence estimates.
1929 This method can be used in two regimes, either by passing a dataloader
2030 or by passing a tensor with the raw input to the model.
@@ -25,7 +35,7 @@ def monte_carlo_dropout(
2535
2636
2737 Arguments:
28- model (torch.nn.Module): The torch network to be investigated.
38+ model (torch.nn.Module): The torch network to be investigated.
2939 NOTE: Model is assumed to return either a single tensor of
3040 predictions or a n-tupel with the first part being a tensor
3141 of predictions. They need to be [0, 1] where 0 and 1 represent
@@ -50,7 +60,7 @@ def monte_carlo_dropout(
5060 Contains the averaged predictions across all MC dropout estimates.
5161 """
5262
53- if regime != ' loader' and regime != ' tensors' :
63+ if regime != " loader" and regime != " tensors" :
5464 raise ValueError ("Choose regime from {'loader', 'tensors'}" )
5565
5666 # Activate dropout layers while keeping other rest in eval mode.
@@ -61,53 +71,48 @@ def enable_dropout(m):
6171 model .eval ()
6272 model .apply (enable_dropout )
6373
64- if regime == ' loader' :
74+ if regime == " loader" :
6575
6676 # Error handling
67- if not isinstance (
68- loader .sampler , torch .utils .data .sampler .SequentialSampler
69- ):
77+ if not isinstance (loader .sampler , torch .utils .data .sampler .SequentialSampler ):
7078 raise AttributeError (
71- ' Data loader does not use sequential sampling. Consider set'
72- ' ting shuffle=False when instantiating the data loader.'
79+ " Data loader does not use sequential sampling. Consider set"
80+ " ting shuffle=False when instantiating the data loader."
7381 )
7482
7583 # Run over all batches in the loader
7684
7785 def call_fn ():
7886 preds = []
79- for ind , inputs in enumerate ( loader ) :
87+ for inputs in loader :
8088 # inputs is a tuple with the last element being the labels
8189 # outs can be a n-tuple returned by the model
82- outs = model (* inputs [:- 1 ])
83- preds .append (outs [0 ] if isinstance (outs , tuple ) else outs )
90+ outs = model (* map_to_device (inputs [:- 1 ]))
91+ preds .append (
92+ outs [0 ].detach ().cpu ()
93+ if isinstance (outs , tuple )
94+ else outs .detach ().cpu ()
95+ )
8496
8597 return torch .cat (preds )
8698
87- elif regime == ' tensors' :
99+ elif regime == " tensors" :
88100
89- if (
90- not isinstance (tensors , tuple )
91- and not isinstance (tensors , torch .Tensor )
92- ):
93- raise ValueError ('Tensor needs to either tuple or torch.Tensor' )
101+ if not isinstance (tensors , tuple ) and not isinstance (tensors , torch .Tensor ):
102+ raise ValueError ("Tensor needs to either tuple or torch.Tensor" )
94103
95- inputs = tensors if isinstance (tensors , tuple ) else (tensors , )
104+ inputs = tensors if isinstance (tensors , tuple ) else (tensors ,)
96105
97106 def call_fn ():
98- outs = model (* inputs )
107+ outs = model (* map_to_device ( inputs ) )
99108 return outs [0 ] if isinstance (outs , tuple ) else outs
100109
101110 with torch .no_grad ():
102- predictions = [
103- torch .unsqueeze (call_fn (), - 1 ) for _ in range (repetitions )
104- ]
111+ predictions = [torch .unsqueeze (call_fn (), - 1 ) for _ in range (repetitions )]
105112 predictions = torch .cat (predictions , dim = - 1 )
106113
107114 # Scale confidences to [0, 1]
108- confidences = - 1 * (
109- (predictions .std (dim = - 1 ) - MIN_STD ) / (MAX_STD - MIN_STD )
110- ) + 1
115+ confidences = - 1 * ((predictions .std (dim = - 1 ) - MIN_STD ) / (MAX_STD - MIN_STD )) + 1
111116
112117 model .eval ()
113118
@@ -116,12 +121,12 @@ def call_fn():
116121
117122def test_time_augmentation (
118123 model ,
119- regime = ' loader' ,
124+ regime = " loader" ,
120125 loader = None ,
121126 tensors = None ,
122127 repetitions = 20 ,
123128 augmenter = None ,
124- tensors_to_augment = None
129+ tensors_to_augment = None ,
125130):
126131 """
127132 Attempts to measure aleatoric uncertainity through augmentation during test
@@ -135,7 +140,7 @@ def test_time_augmentation(
135140 classification like MNIST.
136141
137142 Arguments:
138- model (torch.nn.Module): The torch network to be investigated.
143+ model (torch.nn.Module): The torch network to be investigated.
139144 NOTE: Model is assumed to return either a single tensor of
140145 predictions or a n-tupel with the first part being a tensor
141146 of predictions. They need to be [0, 1] where 0 and 1 represent
@@ -173,86 +178,80 @@ def test_time_augmentation(
173178 Contains the averaged predictions across estimates.
174179 """
175180
176- if regime != ' loader' and regime != ' tensors' :
181+ if regime != " loader" and regime != " tensors" :
177182 raise ValueError ("Choose regime from {'loader', 'tensors'}" )
178183
179184 model .eval ()
180185
181- if regime == ' loader' :
186+ if regime == " loader" :
182187
183188 # Error handling
184- if not isinstance (
185- loader .sampler , torch .utils .data .sampler .SequentialSampler
186- ):
189+ if not isinstance (loader .sampler , torch .utils .data .sampler .SequentialSampler ):
187190 raise AttributeError (
188- ' Data loader does not use sequential sampling. Consider set'
189- ' ting shuffle=False when instantiating the data loader.'
191+ " Data loader does not use sequential sampling. Consider set"
192+ " ting shuffle=False when instantiating the data loader."
190193 )
191194
192195 # Run over all batches in the loader
193196
194197 def call_fn ():
195198 preds = []
196- for ind , inputs in enumerate ( loader ) :
199+ for inputs in loader :
197200 # inputs is a tuple with the last element being the labels
198201 # outs can be a n-tuple returned by the model
199- outs = model (* inputs [:- 1 ])
202+ outs = model (* map_to_device ( inputs [:- 1 ]) )
200203 preds .append (outs [0 ] if isinstance (outs , tuple ) else outs )
201204
202205 return torch .cat (preds )
203206
204- elif regime == ' tensors' :
207+ elif regime == " tensors" :
205208
206- if (
207- not isinstance (tensors , tuple )
208- and not isinstance (tensors , torch .Tensor )
209- ):
210- raise ValueError ('Tensor needs to either tuple or torch.Tensor' )
211- if (
212- not isinstance (tensors_to_augment , list )
213- and not isinstance (tensors_to_augment , int )
209+ if not isinstance (tensors , tuple ) and not isinstance (tensors , torch .Tensor ):
210+ raise ValueError ("Tensor needs to either tuple or torch.Tensor" )
211+ if not isinstance (tensors_to_augment , list ) and not isinstance (
212+ tensors_to_augment , int
214213 ):
215- raise ValueError (' tensors_to_augment needs to be list or int' )
214+ raise ValueError (" tensors_to_augment needs to be list or int" )
216215
217216 # Convert input to common formats (tuples and lists)
218217 tensors_to_augment = (
219218 [tensors_to_augment ]
220- if isinstance (tensors_to_augment , int ) else tensors_to_augment
219+ if isinstance (tensors_to_augment , int )
220+ else tensors_to_augment
221221 )
222- inputs = tensors if isinstance (tensors , tuple ) else (tensors , )
223- aug_fns = augmenter if isinstance (augmenter , tuple ) else (augmenter , )
222+ inputs = tensors if isinstance (tensors , tuple ) else (tensors ,)
223+ aug_fns = augmenter if isinstance (augmenter , tuple ) else (augmenter ,)
224224
225225 # Error handling
226226 if not len (aug_fns ) == len (tensors_to_augment ):
227227 raise ValueError (
228- ' Provide one augmenter for each tensor you want to augment.'
228+ " Provide one augmenter for each tensor you want to augment."
229229 )
230230 if max (tensors_to_augment ) > len (inputs ):
231231 raise ValueError (
232- ' tensors_to_augment should be indexes to the tensors used for '
233- f' augmentation. { max (tensors_to_augment )} is larger than '
234- f' length of inputs ({ len (inputs )} ).'
232+ " tensors_to_augment should be indexes to the tensors used for "
233+ f" augmentation. { max (tensors_to_augment )} is larger than "
234+ f" length of inputs ({ len (inputs )} )."
235235 )
236236
237237 def call_fn ():
238238 # Perform augmentation on all designated functions
239239 augmented_inputs = [
240- aug_fns [tensors_to_augment [tensors_to_augment == ind ]](tensor )
241- if ind in tensors_to_augment else tensor
240+ (
241+ aug_fns [tensors_to_augment [tensors_to_augment == ind ]](tensor )
242+ if ind in tensors_to_augment
243+ else tensor
244+ )
242245 for ind , tensor in enumerate (tensors )
243246 ]
244- outs = model (* augmented_inputs )
247+ outs = model (* map_to_device ( augmented_inputs ) )
245248 return outs [0 ] if isinstance (outs , tuple ) else outs
246249
247250 with torch .no_grad ():
248- predictions = [
249- torch .unsqueeze (call_fn (), - 1 ) for _ in range (repetitions )
250- ]
251+ predictions = [torch .unsqueeze (call_fn (), - 1 ) for _ in range (repetitions )]
251252 predictions = torch .cat (predictions , dim = - 1 )
252253
253254 # Scale confidences to [0, 1]
254- confidences = - 1 * (
255- (predictions .std (dim = - 1 ) - MIN_STD ) / (MAX_STD - MIN_STD )
256- ) + 1
255+ confidences = - 1 * ((predictions .std (dim = - 1 ) - MIN_STD ) / (MAX_STD - MIN_STD )) + 1
257256
258257 return torch .clamp (confidences , min = 0 ), torch .mean (predictions , - 1 )
0 commit comments