# -*- coding: utf-8 -*-
"""Interface to Lightnet object proposals."""
import logging
from os.path import expanduser, join
from wbia import constants as const
import utool as ut
import numpy as np
import cv2
import random
import tqdm
import time
import os
import copy
import PIL
(print, rrr, profile) = ut.inject2(__name__, '[densenet]')
logger = logging.getLogger('wbia')
PARALLEL = not const.CONTAINERIZED
INPUT_SIZE = 224
ARCHIVE_URL_DICT = {
'canonical_zebra_grevys_v1': 'https://wildbookiarepository.azureedge.net/models/classifier.canonical.zebra_grevys.v1.zip',
'canonical_zebra_grevys_v2': 'https://wildbookiarepository.azureedge.net/models/classifier.canonical.zebra_grevys.v2.zip',
'canonical_zebra_grevys_v3': 'https://wildbookiarepository.azureedge.net/models/classifier.canonical.zebra_grevys.v3.zip',
'canonical_zebra_grevys_v4': 'https://wildbookiarepository.azureedge.net/models/classifier.canonical.zebra_grevys.v4.zip',
'canonical_giraffe_reticulated_v1': 'https://wildbookiarepository.azureedge.net/models/classifier.canonical.giraffe_reticulated.v1.zip',
'ryan_densenet_v1': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.ryan.densenet.v1.zip',
'ryan_densenet_v2': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.ryan.densenet.v2.zip',
'megan_argentina_v1': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.megan.argentina.densenet.v1.zip',
'megan_kenya_v1': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.megan.kenya.densenet.v1.zip',
'megan_kenya_v2': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.megan.kenya.densenet.v2.zip',
'laterals_v0': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.laterals.densenet.v0.zip',
'belly_v0': 'https://wildbookiarepository.azureedge.net/models/classifier.cameratrap.right_whale_belly.densenet.v0.zip',
'zebra_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.zebra_grevys-zebra_plains.v1.zip',
'zebra_mountain_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.zebra_mountain.v0.zip',
'giraffe_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.giraffe.v1.zip',
'jaguar_v3': 'https://wildbookiarepository.azureedge.net/models/labeler.jaguar.v3.zip',
'lynx_v3': 'https://wildbookiarepository.azureedge.net/models/labeler.lynx.v3.zip',
'manta_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.manta_ray_giant.v1.zip',
'seaturtle_v3': 'https://wildbookiarepository.azureedge.net/models/labeler.seaturtle.v3.zip',
'hendrik_dorsal_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.hendrik_dorsal.v2.zip',
'spotted_dolphin_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.dolphin_spotted.v0.zip',
'spotted_skunk_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.skunk_spotted.v0.zip',
'humpback_dorsal': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_humpback.dorsal.v0.zip',
'orca_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_orca.v0.zip',
'whale_sperm_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_sperm.v0.zip',
'fins_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.fins.v0.zip',
'fins_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.fins.v1.1.zip',
'wilddog_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.wild_dog.v0.zip',
'wilddog_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.wild_dog.v1.zip',
'wilddog_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.wild_dog.v2.zip',
'wilddog_v3': 'https://wildbookiarepository.azureedge.net/models/labeler.wild_dog.v3.zip',
'leopard_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.leopard.v0.zip',
'cheetah_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.cheetah.v1.zip',
'cheetah_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.cheetah.v2.zip',
'hyaena_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.hyaena.v0.zip',
'wild_horse_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.wild_horse.v0.zip',
'seadragon_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.seadragon.v0.zip',
'seadragon_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.seadragon.v1.zip',
'seadragon_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.seadragon.v2.zip',
'jaguar_v4': 'https://wildbookiarepository.azureedge.net/models/labeler.jaguar.v4.zip',
'lynx_v4': 'https://wildbookiarepository.azureedge.net/models/labeler.lynx.v4.zip',
'manta_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.manta_ray_giant.v2.zip',
'seaturtle_v4': 'https://wildbookiarepository.azureedge.net/models/labeler.seaturtle.v4.zip',
'nassau_grouper_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.grouper_nassau.v0.zip',
'nassau_grouper_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.grouper_nassau.v1.zip',
'nassau_grouper_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.grouper_nassau.v2.zip',
'nassau_grouper_v3': 'https://wildbookiarepository.azureedge.net/models/labeler.grouper_nassau.v3.zip',
'salanader_fire_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.salamander_fire.v0.zip',
'salanader_fire_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.salamander_fire.v1.zip',
'salamander_fire_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.salamander_fire.v0.zip',
'salamander_fire_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.salamander_fire.v1.zip',
'salamander_fire_v2': 'https://wildbookiarepository.azureedge.net/models/labeler.salamander_fire.v2.zip',
'spotted_dolphin_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.dolphin_spotted.v1.zip',
'spotted_skunk_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.skunk_spotted.v1.zip',
'monk_seal_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.mediterranean_monk_seal.v0.zip',
'iot_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.iot.v0.zip',
'right_whale_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.rightwhale.v0.zip',
'whale_shark_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_shark.v0.zip',
'flukebook_v1': 'https://wildbookiarepository.azureedge.net/models/classifier2.flukebook.v1.zip',
'rightwhale_v5': 'https://wildbookiarepository.azureedge.net/models/labeler.rightwhale.v5.zip',
'snow_leopard_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.snow_leopard.v0.zip',
'grey_whale_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_grey.v0.zip',
'beluga_whale_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_beluga.v0.zip',
'beluga_whale_v1': 'https://wildbookiarepository.azureedge.net/models/labeler.whale_beluga.v1.zip',
'sea_turtle_v4': 'https://wildbookiarepository.azureedge.net/models/labeler.sea_turtle.v4.zip',
'seals_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.seals.v0.zip',
'spotted_eagle_ray_v0': 'https://wildbookiarepository.azureedge.net/models/labeler.spotted_eagle_ray.v0.zip',
}
if not ut.get_argflag('--no-pytorch'):
try:
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
logger.info('PyTorch Version: %s' % (torch.__version__,))
logger.info('Torchvision Version: %s' % (torchvision.__version__,))
except ImportError:
logger.info('WARNING Failed to import pytorch. PyTorch is unavailable')
if ut.SUPER_STRICT:
raise
try:
import imgaug # NOQA
[docs] class Augmentations(object):
def __call__(self, img):
img = np.array(img)
return self.aug.augment_image(img)
[docs] class TrainAugmentations(Augmentations):
def __init__(self, blur=True, flip=False, rotate=10, shear=10, **kwargs):
from imgaug import augmenters as iaa
sequence = []
sequence += [
iaa.Scale((INPUT_SIZE, INPUT_SIZE)),
iaa.ContrastNormalization((0.75, 1.25)),
iaa.AddElementwise((-10, 10), per_channel=0.5),
iaa.AddToHueAndSaturation(value=(-20, 20), per_channel=True),
iaa.Multiply((0.75, 1.25)),
]
sequence += [
iaa.PiecewiseAffine(scale=(0.0005, 0.005)),
iaa.Affine(
rotate=(-rotate, rotate), shear=(-shear, shear), mode='symmetric'
),
iaa.Grayscale(alpha=(0.0, 0.5)),
]
if flip:
sequence += [
iaa.Fliplr(0.5),
]
if blur:
sequence += [
iaa.Sometimes(0.01, iaa.GaussianBlur(sigma=(0, 1.0))),
]
self.aug = iaa.Sequential(sequence)
[docs] class ValidAugmentations(Augmentations):
def __init__(self, **kwargs):
from imgaug import augmenters as iaa
self.aug = iaa.Sequential([iaa.Scale((INPUT_SIZE, INPUT_SIZE))])
AUGMENTATION = {
'train': TrainAugmentations,
'val': ValidAugmentations,
'test': ValidAugmentations,
}
except ImportError:
AUGMENTATION = {}
logger.info(
'WARNING Failed to import imgaug. '
'install with pip install git+https://github.com/aleju/imgaug'
)
if ut.SUPER_STRICT:
raise
def _init_transforms(**kwargs):
TRANSFORMS = {
phase: torchvision.transforms.Compose(
[
AUGMENTATION[phase](**kwargs),
torchvision.transforms.Lambda(PIL.Image.fromarray),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
),
]
)
for phase in AUGMENTATION.keys()
}
return TRANSFORMS
[docs]class ImageFilePathList(torch.utils.data.Dataset):
def __init__(self, filepaths, targets=None, transform=None, target_transform=None):
from torchvision.datasets.folder import default_loader
self.targets = targets is not None
args = (filepaths, targets) if self.targets else (filepaths,)
self.samples = list(zip(*args))
if self.targets:
self.classes = sorted(set(ut.take_column(self.targets, 1)))
self.class_to_idx = {self.classes[i]: i for i in range(len(self.classes))}
else:
self.classes, self.class_to_idx = None, None
self.loader = default_loader
self.transform = transform
self.target_transform = target_transform
def __getitem__(self, index):
"""
Args:
index (int): Index
Returns:
tuple: (sample, target) where target is class_index of the target class.
"""
sample = self.samples[index]
if self.targets:
path, target = sample
else:
path = sample[0]
target = None
sample = self.loader(path)
if self.transform is not None:
sample = self.transform(sample)
if self.target_transform is not None:
target = self.target_transform(target)
result = (sample, target) if self.targets else (sample,)
return result
def __len__(self):
return len(self.samples)
def __repr__(self):
fmt_str = 'Dataset ' + self.__class__.__name__ + '\n'
fmt_str += ' Number of samples: {}\n'.format(self.__len__())
tmp = ' Transforms (if any): '
fmt_str += '{0}{1}\n'.format(
tmp, self.transform.__repr__().replace('\n', '\n' + ' ' * len(tmp))
)
tmp = ' Target Transforms (if any): '
fmt_str += '{0}{1}'.format(
tmp, self.target_transform.__repr__().replace('\n', '\n' + ' ' * len(tmp))
)
return fmt_str
[docs]class StratifiedSampler(torch.utils.data.sampler.Sampler):
def __init__(self, dataset, phase, multiplier=1.0):
self.dataset = dataset
self.phase = phase
self.training = self.phase == 'train'
self.labels = np.array(ut.take_column(dataset.samples, 1))
self.classes = set(self.labels)
self.indices = {
cls: list(np.where(self.labels == cls)[0]) for cls in self.classes
}
self.counts = {cls: len(self.indices[cls]) for cls in self.classes}
self.min = min(self.counts.values())
self.min = int(np.around(multiplier * self.min))
if self.training:
self.total = 0
for cls in self.indices:
num_in_class = len(self.indices[cls])
num_samples = min(self.min, num_in_class)
self.total += num_samples
else:
self.total = len(self.labels)
args = (
self.phase,
len(self.labels),
len(self.classes),
self.min,
self.total,
multiplier,
)
logger.info(
'Initialized Sampler for %r (sampling %d for %d classes | min %d per class, %d total, %0.02f multiplier)'
% args
)
def __iter__(self):
if self.training:
ret_list = []
for cls in self.indices:
num_in_class = len(self.indices[cls])
num_samples = min(self.min, num_in_class)
ret_list += random.sample(self.indices[cls], num_samples)
random.shuffle(ret_list)
else:
ret_list = range(self.total)
assert len(ret_list) == self.total
return iter(ret_list)
def __len__(self):
return self.total
[docs]def finetune(model, dataloaders, criterion, optimizer, scheduler, device, num_epochs=128):
phases = ['train', 'val']
start = time.time()
best_accuracy = 0.0
best_model_state = copy.deepcopy(model.state_dict())
last_loss = {}
best_loss = {}
for epoch in range(num_epochs):
start_batch = time.time()
lr = optimizer.param_groups[0]['lr']
logger.info('Epoch {}/{} (lr = {:0.06f})'.format(epoch, num_epochs - 1, lr))
logger.info('-' * 10)
# Each epoch has a training and validation phase
for phase in phases:
if phase == 'train':
model.train() # Set model to training mode
else:
model.eval() # Set model to evaluate mode
running_loss = 0.0
running_corrects = 0
# Iterate over data.
seen = 0
for inputs, labels in tqdm.tqdm(dataloaders[phase], desc=phase):
inputs = inputs.to(device)
labels = labels.to(device)
# zero the parameter gradients
optimizer.zero_grad()
# forward
# track history if only in train
with torch.set_grad_enabled(phase == 'train'):
# Get model outputs and calculate loss
outputs = model(inputs)
loss = criterion(outputs, labels)
_, preds = torch.max(outputs, 1)
# backward + optimize only if in training phase
if phase == 'train':
loss.backward()
optimizer.step()
# statistics
seen += len(inputs)
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / seen
epoch_acc = running_corrects.double() / seen
last_loss[phase] = epoch_loss
if phase not in best_loss:
best_loss[phase] = np.inf
flag = epoch_loss < best_loss[phase]
if flag:
best_loss[phase] = epoch_loss
logger.info(
'{:<5} Loss: {:.4f} Acc: {:.4f} {}'.format(
phase, epoch_loss, epoch_acc, '!' if flag else ''
)
)
# deep copy the model
if phase == 'val' and epoch_acc > best_accuracy:
best_accuracy = epoch_acc
logger.info('\tFound better model!')
best_model_state = copy.deepcopy(model.state_dict())
if phase == 'val':
scheduler.step(epoch_loss)
time_elapsed_batch = time.time() - start_batch
logger.info(
'time: {:.0f}m {:.0f}s'.format(
time_elapsed_batch // 60, time_elapsed_batch % 60
)
)
ratio = last_loss['train'] / last_loss['val']
logger.info('ratio: {:.04f}'.format(ratio))
logger.info('\n')
time_elapsed = time.time() - start
logger.info(
'Training complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60, time_elapsed % 60
)
)
logger.info('Best val Acc: {:4f}'.format(best_accuracy))
# load best model weights
model.load_state_dict(best_model_state)
return model
[docs]def visualize_augmentations(dataset, augmentation, tag, num_per_class=10, **kwargs):
import matplotlib.pyplot as plt
samples = dataset.samples
flags = np.array(ut.take_column(samples, 1))
logger.info('Dataset %r has %d samples' % (tag, len(flags)))
indices = []
for flag in set(flags):
index_list = list(np.where(flags == flag)[0])
random.shuffle(index_list)
indices += index_list[:num_per_class]
samples = ut.take(samples, indices)
paths = ut.take_column(samples, 0)
flags = ut.take_column(samples, 1)
images = [np.array(cv2.imread(path)) for path in paths]
images = [image[:, :, ::-1] for image in images]
images_ = []
for image, flag in zip(images, flags):
image_ = image.copy()
color = (0, 255, 0) if flag else (255, 0, 0)
cv2.rectangle(image_, (1, 1), (INPUT_SIZE - 1, INPUT_SIZE - 1), color, 3)
images_.append(image_)
canvas = np.hstack(images_)
canvas_list = [canvas]
augment = augmentation(**kwargs)
for index in range(len(indices) - 1):
logger.info(index)
images_ = [augment(image.copy()) for image in images]
canvas = np.hstack(images_)
canvas_list.append(canvas)
canvas = np.vstack(canvas_list)
canvas_filepath = expanduser(
join('~', 'Desktop', 'densenet-augmentation-%s.png' % (tag,))
)
plt.imsave(canvas_filepath, canvas)
[docs]def train(
data_path,
output_path,
batch_size=48,
class_weights={},
multi=PARALLEL,
sample_multiplier=4.0,
allow_missing_validation_classes=False,
**kwargs,
):
# Detect if we have a GPU available
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
using_gpu = str(device) != 'cpu'
phases = ['train', 'val']
logger.info('Initializing Datasets and Dataloaders...')
# Create training and validation datasets
transforms = _init_transforms(**kwargs)
datasets = {
phase: torchvision.datasets.ImageFolder(
os.path.join(data_path, phase), transforms[phase]
)
for phase in phases
}
# Create training and validation dataloaders
dataloaders = {
phase: torch.utils.data.DataLoader(
datasets[phase],
sampler=StratifiedSampler(
datasets[phase], phase, multiplier=sample_multiplier
),
batch_size=batch_size,
num_workers=batch_size // 8,
pin_memory=using_gpu,
)
for phase in phases
}
train_classes = datasets['train'].classes
val_classes = datasets['val'].classes
if not allow_missing_validation_classes:
assert len(train_classes) == len(val_classes)
num_classes = len(train_classes)
logger.info('Initializing Model...')
# Initialize the model for this run
model = torchvision.models.densenet201(pretrained=True)
num_ftrs = model.classifier.in_features
model.classifier = nn.Linear(num_ftrs, num_classes)
# Send the model to GPU
model = model.to(device)
# Multi-GPU
if multi:
logger.info('USING MULTI-GPU MODEL')
model = nn.DataParallel(model)
logger.info('Print Examples of Training Augmentation...')
# for phase in phases:
# visualize_augmentations(datasets[phase], AUGMENTATION[phase], phase, **kwargs)
logger.info('Initializing Optimizer...')
# logger.info('Params to learn:')
params_to_update = []
for name, param in model.named_parameters():
if param.requires_grad:
params_to_update.append(param)
# logger.info('\t', name)
# Observe that all parameters are being optimized
optimizer = optim.SGD(params_to_update, lr=0.001, momentum=0.9)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer, 'min', factor=0.5, patience=10, min_lr=1e-6
)
# Get weights for the class
class_index_list = list(dataloaders['train'].dataset.class_to_idx.items())
index_class_list = [class_index[::-1] for class_index in class_index_list]
weight = torch.tensor(
[class_weights.get(class_, 1.0) for index, class_ in sorted(index_class_list)]
)
weight = weight.to(device)
# Setup the loss fxn
criterion = nn.CrossEntropyLoss(weight=weight)
logger.info('Start Training...')
# Train and evaluate
model = finetune(model, dataloaders, criterion, optimizer, scheduler, device)
ut.ensuredir(output_path)
weights_path = os.path.join(output_path, 'classifier.densenet.weights')
weights = {
'state': copy.deepcopy(model.state_dict()),
'classes': train_classes,
}
torch.save(weights, weights_path)
return weights_path
[docs]def test_single(filepath_list, weights_path, batch_size=1792, multi=PARALLEL, **kwargs):
# Detect if we have a GPU available
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
using_gpu = str(device) != 'cpu'
logger.info('Initializing Datasets and Dataloaders...')
# Create training and validation datasets
transforms = _init_transforms(**kwargs)
dataset = ImageFilePathList(filepath_list, transform=transforms['test'])
# Create training and validation dataloaders
dataloader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, num_workers=0, pin_memory=using_gpu
)
logger.info('Initializing Model...')
try:
weights = torch.load(weights_path)
except RuntimeError:
weights = torch.load(weights_path, map_location='cpu')
state = weights['state']
classes = weights['classes']
num_classes = len(classes)
# Initialize the model for this run
model = torchvision.models.densenet201()
num_ftrs = model.classifier.in_features
model.classifier = nn.Linear(num_ftrs, num_classes)
# Convert any weights to non-parallel version
from collections import OrderedDict
new_state = OrderedDict()
for k, v in state.items():
k = k.replace('module.', '')
new_state[k] = v
# Load state without parallel
model.load_state_dict(new_state)
# Add softmax
model.classifier = nn.Sequential(model.classifier, nn.LogSoftmax(), nn.Softmax())
# Make parallel at end
if multi:
logger.info('USING MULTI-GPU MODEL')
model = nn.DataParallel(model)
# Send the model to GPU
model = model.to(device)
model.eval()
start = time.time()
counter = 0
outputs = []
for (inputs,) in tqdm.tqdm(dataloader, desc='test'):
logger.info('Loading batch %d from disk' % (counter,))
inputs = inputs.to(device)
logger.info('Moving batch %d to GPU' % (counter,))
with torch.set_grad_enabled(False):
logger.info('Pre-model inference %d' % (counter,))
output = model(inputs)
logger.info('Post-model inference %d' % (counter,))
outputs += output.tolist()
logger.info('Outputs done %d' % (counter,))
counter += 1
time_elapsed = time.time() - start
logger.info(
'Testing complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60, time_elapsed % 60
)
)
result_list = []
for output in outputs:
result = dict(zip(classes, output))
result_list.append(result)
return result_list
[docs]def test_ensemble(
filepath_list,
weights_path_list,
classifier_weight_filepath,
ensemble_index,
ibs=None,
gid_list=None,
multiclass=False,
**kwargs,
):
if ensemble_index is not None:
assert 0 <= ensemble_index and ensemble_index < len(weights_path_list)
weights_path_list = [weights_path_list[ensemble_index]]
assert len(weights_path_list) > 0
cached = False
try:
assert ensemble_index is None, 'Do not use depc on individual model computation'
assert None not in [ibs, gid_list], 'Needs to have access to depc'
assert len(filepath_list) == len(gid_list)
results_list = []
for model_index in range(len(weights_path_list)):
if multiclass:
classifier_two_weight_filepath_ = '%s:%d' % (
classifier_weight_filepath,
model_index,
)
config = {
'classifier_two_algo': 'densenet',
'classifier_two_weight_filepath': classifier_two_weight_filepath_,
}
scores_list = ibs.depc_image.get_property(
'classifier_two', gid_list, 'scores', config=config
)
result_list = []
for score_dict in scores_list:
result = score_dict
result_list.append(result)
else:
classifier_weight_filepath_ = '%s:%d' % (
classifier_weight_filepath,
model_index,
)
config = {
'classifier_algo': 'densenet',
'classifier_weight_filepath': classifier_weight_filepath_,
}
prediction_list = ibs.depc_image.get_property(
'classifier', gid_list, 'class', config=config
)
confidence_list = ibs.depc_image.get_property(
'classifier', gid_list, 'score', config=config
)
result_list = []
for prediction, confidence in zip(prediction_list, confidence_list):
# DO NOT REMOVE THIS ASSERT
assert prediction in set(
['negative', 'positive']
), 'Cannot use this method, need to implement classifier_two in depc'
if prediction == 'positive':
pscore = confidence
nscore = 1.0 - pscore
else:
nscore = confidence
pscore = 1.0 - nscore
result = {
'positive': pscore,
'negative': nscore,
}
result_list.append(result)
assert len(result_list) == len(gid_list)
results_list.append(result_list)
assert len(results_list) == len(weights_path_list)
cached = True
except AssertionError:
cached = False
if not cached:
# Use local implementation, due to error or not valid config
results_list = []
for weights_path in weights_path_list:
result_list = test_single(filepath_list, weights_path)
results_list.append(result_list)
for result_list in zip(*results_list):
merged = {}
for result in result_list:
for key in result:
if key not in merged:
merged[key] = []
merged[key].append(result[key])
for key in merged:
value_list = merged[key]
merged[key] = sum(value_list) / len(value_list)
yield merged
[docs]def test(
gpath_list,
classifier_weight_filepath=None,
return_dict=False,
multiclass=False,
**kwargs,
):
from wbia.detecttools.directory import Directory
# Get correct weight if specified with shorthand
archive_url = None
ensemble_index = None
if classifier_weight_filepath is not None and ':' in classifier_weight_filepath:
assert classifier_weight_filepath.count(':') == 1
classifier_weight_filepath, ensemble_index = classifier_weight_filepath.split(':')
ensemble_index = int(ensemble_index)
if classifier_weight_filepath in ARCHIVE_URL_DICT:
archive_url = ARCHIVE_URL_DICT[classifier_weight_filepath]
archive_path = ut.grab_file_url(archive_url, appname='wbia', check_hash=True)
else:
logger.info(
'classifier_weight_filepath %r not recognized' % (classifier_weight_filepath,)
)
raise RuntimeError
assert os.path.exists(archive_path)
archive_path = ut.truepath(archive_path)
ensemble_path = archive_path.strip('.zip')
if not os.path.exists(ensemble_path):
ut.unarchive_file(archive_path, output_dir=ensemble_path)
assert os.path.exists(ensemble_path)
direct = Directory(ensemble_path, include_file_extensions=['weights'], recursive=True)
weights_path_list = direct.files()
weights_path_list = sorted(weights_path_list)
assert len(weights_path_list) > 0
kwargs.pop('classifier_algo', None)
logger.info(
'Using weights in the ensemble, index %r: %s '
% (ensemble_index, ut.repr3(weights_path_list))
)
result_list = test_ensemble(
gpath_list,
weights_path_list,
classifier_weight_filepath,
ensemble_index,
multiclass=multiclass,
**kwargs,
)
for result in result_list:
best_key = None
best_score = -1.0
for key, score in result.items():
if score > best_score:
best_key = key
best_score = score
assert best_score >= 0.0 and best_key is not None
if return_dict:
yield best_score, best_key, result
else:
yield best_score, best_key
[docs]def test_dict(gpath_list, classifier_weight_filepath=None, return_dict=None, **kwargs):
result_gen = test(
gpath_list,
classifier_weight_filepath=classifier_weight_filepath,
return_dict=True,
**kwargs,
)
for result in result_gen:
best_score, best_key, result_dict = result
best_key = best_key.split(':')
if len(best_key) == 1:
best_species = best_key
best_viewpoint = None
elif len(best_key) == 2:
best_species, best_viewpoint = best_key
else:
raise ValueError('Invalid key %r' % (best_key,))
yield (
best_score,
best_species,
best_viewpoint,
'UNKNOWN',
0.0,
result_dict,
)
[docs]def features(filepath_list, batch_size=512, multi=PARALLEL, **kwargs):
# Detect if we have a GPU available
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
using_gpu = str(device) != 'cpu'
logger.info('Initializing Datasets and Dataloaders...')
# Create training and validation datasets
transforms = _init_transforms(**kwargs)
dataset = ImageFilePathList(filepath_list, transform=transforms['test'])
# Create training and validation dataloaders
dataloader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, num_workers=0, pin_memory=using_gpu
)
# Initialize the model for this run
model = torchvision.models.densenet201(pretrained=True)
# Send the model to GPU
model = model.to(device)
if multi:
logger.info('USING MULTI-GPU MODEL')
model = nn.DataParallel(model)
model.eval()
start = time.time()
outputs = []
for (inputs,) in tqdm.tqdm(dataloader, desc='test'):
inputs = inputs.to(device)
with torch.set_grad_enabled(False):
output = model(inputs)
outputs += output.tolist()
outputs = np.array(outputs, dtype=np.float32)
time_elapsed = time.time() - start
logger.info(
'Testing complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60, time_elapsed % 60
)
)
return outputs