torchsight.experiments.retrievers.flickr32.experiment
module
A module with an experiment for the retrievers using the Flickr32 dataset.
Source code
"""A module with an experiment for the retrievers using the Flickr32 dataset."""
import os
import random
import torch
from PIL import Image
from torchsight.datasets import Flickr32Dataset
from torchsight.metrics.retrieval import AveragePrecision
from torchsight.retrievers.dldenet import DLDENetRetriever
from torchsight.utils import JsonObject, PrintMixin
class Flickr32RetrieverExperiment(PrintMixin):
"""An experiment to measure the precision, recall and F1 metrics using different retrivers over
the Flickr32 dataset.
The experiment consist that given some logo queries (images + bounding boxes) we need to retrieve
all the instances of that logo. A perfect experiment will be to retrieve all the instances of all
of the queries in the first place, without false positives.
The experiment is not very strict, it takes a true positive if the image contains an instance, so
if the retriever produced a very poor bounding box for the instance it does not matter. It focus
on the finding of the instance.
"""
def __init__(self, params=None, device=None):
"""Initialize the experiment.
Arguments:
params (dict, optional): a dict to modify the base parameters.
device (str, optional): where to run the experiments.
"""
self.params = self.get_params().merge(params)
self.device = device if device is not None else 'cuda:0' if torch.cuda.is_available() else 'cpu'
self.print('Loading dataset ...')
self.dataset = self.get_dataset()
self.print('Loading retriever ...')
self.retriver = self.get_retriver()
self.print('Loading metric ...')
self.average_precision = AveragePrecision()
###########################
### GETTERS ###
###########################
@staticmethod
def get_params():
"""Get the base parameters for the experiment.
Returns:
JsonObject: with the parameters.
"""
return JsonObject({
'dataset': {
'root': None,
'dataset': 'test',
},
'k': 500, # The amount of results to retrieve
'queries_file': './queries.csv',
'results_file': './results.csv',
'retriever': {
'use': 'dldenet',
'dldenet': {
'checkpoint': None,
'paths': None,
'extensions': None,
'batch_size': 8,
'num_workers': 8,
'verbose': True,
'params': {'transform': {}}
}
}
})
def get_dataset(self):
"""Get the Flickr32 dataset.
Returns:
Flickr32Dataset: initialized and with its attributes.
"""
return Flickr32Dataset(**self.params.dataset)
def get_retriver(self):
"""Initialize and return the retriver to use in the experiment.
Return:
InstanceRetriver: the retriever to use.
"""
retriever = self.params.retriever.use
if retriever == 'dldenet':
params = self.params.retriever.dldenet
params.paths = [t[1] for t in self.dataset.paths] # Paths are tuples like (brand, image path, boxes path)
params.device = self.device
if params.checkpoint is None:
raise ValueError('Please provide a checkpoint for the DLDENet retriever.')
params.checkpoint = torch.load(params.checkpoint, map_location=self.device)
params.params.transform = params.checkpoint['hyperparameters']['transform']
return DLDENetRetriever(**params)
raise NotImplementedError('There is no implementation for the "{}" retriever.'.format(retriever))
def generate_queries(self):
"""Generate a file with a random path to an image for each brand.
It will store the file in the self.params.queries_file path.
"""
results = []
for brand in self.dataset.brands:
if brand == 'no-logo':
continue
# Each path tuple contains (brand, image path, boxes path)
results.append(random.choice([path for path in self.dataset.paths if path[0] == brand]))
with open(self.params.queries_file, 'w') as file:
file.write('\n'.join(','.join(line) for line in results))
def load_queries(self):
"""Load the images and their bounding boxes to use as queries.
It knows which images to use using the self.params.queries_file, if no one exists it generates a new one.
Returns:
list of str: with the brand of each image.
list of PIL Image: with the images.
list of torch.Tensor: with the first bounding box only to query. Shape `(1, 4)` with the
x1, y1 for the top-left corner and the x2, y2 for the bottom-right corner.
"""
if not os.path.exists(self.params.queries_file):
self.generate_queries()
brands, images, boxes = [], [], []
with open(self.params.queries_file, 'r') as file:
for brand, image, annot in [line.split(',') for line in file.read().split('\n')]:
brands.append(brand)
images.append(Image.open(image))
with open(annot, 'r') as file:
line = file.readlines()[1] # The first line contains "x y width height"
x, y, w, h = (int(val) for val in line.split())
x1, y1 = x - 1, y - 1
x2, y2 = x1 + w, y1 + h
boxes.append(torch.Tensor([[x1, y1, x2, y2]]))
return brands, images, boxes
############################
### METHODS ###
############################
def run(self):
"""Run the experiment and compute the mean average precision over the entire test dataset."""
self.print('Loading queries ...')
brands, images, query_boxes = self.load_queries()
self.print('Retrieving instances ...')
_, _, paths, _ = self.retriver.query(images=images, boxes=query_boxes, k=self.params.k)
paths = self.unique_paths(paths)
self.print('Getting average precision for each query ...')
average_precisions = self.average_precision(*self.results_tensors(brands, paths))
self.print('Storing results ...')
self.store_results(brands, paths, average_precisions)
self.print('Mean Average Precision: {}'.format(float(average_precisions.mean())))
def unique_paths(self, paths):
"""Make the resulting paths unique.
As each image could have more than one embedding similar to the query there could be images
more than one time in the results, but we are want to count each image only one time.
For this, we are going to remove the duplicates paths for each query.
Arguments:
paths (list of list of str): with the paths of the retrieved images.
Returns:
list of list of str: with the unique paths for each query.
"""
for i in range(len(paths)):
seen = set()
paths[i] = [path for path in paths[i] if not (path in seen or seen.add(path))]
return paths
def results_tensors(self, brands, paths):
"""Generate the results tensor of the retrieval task for the metric and amount of relevant results.
Arguments:
brands (list of str): with the brand corresponding to each query.
paths (list of list of str): with the resulting paths of the retrieved images for each query.
Returns:
torch.Tensor: with the 1s indicating the true positives. Shape `(q, k)`.
Where `q` is the number of queries and `k` the number of instances retrieved.
torch.Tensor: with the amount of relevant images for each query (true positives).
"""
results = torch.zeros(len(brands), self.params.k)
for i, brand in enumerate(brands):
for j, path in enumerate(paths[i]):
if brand in path:
results[i, j] = 1
relevant = {brand: 0 for brand in brands}
for brand, *_ in self.dataset.paths:
if brand not in relevant:
continue
relevant[brand] += 1
relevant = torch.Tensor(list(relevant.values()))
return results, relevant
def store_results(self, brands, paths, average_precisions):
"""Store the results in a CSV file with columns: Brand, Average Precision, result paths.
Arguments:
brands (list of str): with the brand of each query.
paths (list of list of str): with the paths of the images retrieved.
average_precisions (torch.Tensor): with the average precision for each query.
"""
with open(self.params.results_file, 'w') as file:
for i, brand in brands:
line = '{},{},'.format(brand, float(average_precisions[i]))
line += ','.join(paths[i])
file.write(line + '\n')
Classes
class Flickr32RetrieverExperiment (ancestors: PrintMixin)
-
An experiment to measure the precision, recall and F1 metrics using different retrivers over the Flickr32 dataset.
The experiment consist that given some logo queries (images + bounding boxes) we need to retrieve all the instances of that logo. A perfect experiment will be to retrieve all the instances of all of the queries in the first place, without false positives.
The experiment is not very strict, it takes a true positive if the image contains an instance, so if the retriever produced a very poor bounding box for the instance it does not matter. It focus on the finding of the instance.
Source code
class Flickr32RetrieverExperiment(PrintMixin): """An experiment to measure the precision, recall and F1 metrics using different retrivers over the Flickr32 dataset. The experiment consist that given some logo queries (images + bounding boxes) we need to retrieve all the instances of that logo. A perfect experiment will be to retrieve all the instances of all of the queries in the first place, without false positives. The experiment is not very strict, it takes a true positive if the image contains an instance, so if the retriever produced a very poor bounding box for the instance it does not matter. It focus on the finding of the instance. """ def __init__(self, params=None, device=None): """Initialize the experiment. Arguments: params (dict, optional): a dict to modify the base parameters. device (str, optional): where to run the experiments. """ self.params = self.get_params().merge(params) self.device = device if device is not None else 'cuda:0' if torch.cuda.is_available() else 'cpu' self.print('Loading dataset ...') self.dataset = self.get_dataset() self.print('Loading retriever ...') self.retriver = self.get_retriver() self.print('Loading metric ...') self.average_precision = AveragePrecision() ########################### ### GETTERS ### ########################### @staticmethod def get_params(): """Get the base parameters for the experiment. Returns: JsonObject: with the parameters. """ return JsonObject({ 'dataset': { 'root': None, 'dataset': 'test', }, 'k': 500, # The amount of results to retrieve 'queries_file': './queries.csv', 'results_file': './results.csv', 'retriever': { 'use': 'dldenet', 'dldenet': { 'checkpoint': None, 'paths': None, 'extensions': None, 'batch_size': 8, 'num_workers': 8, 'verbose': True, 'params': {'transform': {}} } } }) def get_dataset(self): """Get the Flickr32 dataset. Returns: Flickr32Dataset: initialized and with its attributes. """ return Flickr32Dataset(**self.params.dataset) def get_retriver(self): """Initialize and return the retriver to use in the experiment. Return: InstanceRetriver: the retriever to use. """ retriever = self.params.retriever.use if retriever == 'dldenet': params = self.params.retriever.dldenet params.paths = [t[1] for t in self.dataset.paths] # Paths are tuples like (brand, image path, boxes path) params.device = self.device if params.checkpoint is None: raise ValueError('Please provide a checkpoint for the DLDENet retriever.') params.checkpoint = torch.load(params.checkpoint, map_location=self.device) params.params.transform = params.checkpoint['hyperparameters']['transform'] return DLDENetRetriever(**params) raise NotImplementedError('There is no implementation for the "{}" retriever.'.format(retriever)) def generate_queries(self): """Generate a file with a random path to an image for each brand. It will store the file in the self.params.queries_file path. """ results = [] for brand in self.dataset.brands: if brand == 'no-logo': continue # Each path tuple contains (brand, image path, boxes path) results.append(random.choice([path for path in self.dataset.paths if path[0] == brand])) with open(self.params.queries_file, 'w') as file: file.write('\n'.join(','.join(line) for line in results)) def load_queries(self): """Load the images and their bounding boxes to use as queries. It knows which images to use using the self.params.queries_file, if no one exists it generates a new one. Returns: list of str: with the brand of each image. list of PIL Image: with the images. list of torch.Tensor: with the first bounding box only to query. Shape `(1, 4)` with the x1, y1 for the top-left corner and the x2, y2 for the bottom-right corner. """ if not os.path.exists(self.params.queries_file): self.generate_queries() brands, images, boxes = [], [], [] with open(self.params.queries_file, 'r') as file: for brand, image, annot in [line.split(',') for line in file.read().split('\n')]: brands.append(brand) images.append(Image.open(image)) with open(annot, 'r') as file: line = file.readlines()[1] # The first line contains "x y width height" x, y, w, h = (int(val) for val in line.split()) x1, y1 = x - 1, y - 1 x2, y2 = x1 + w, y1 + h boxes.append(torch.Tensor([[x1, y1, x2, y2]])) return brands, images, boxes ############################ ### METHODS ### ############################ def run(self): """Run the experiment and compute the mean average precision over the entire test dataset.""" self.print('Loading queries ...') brands, images, query_boxes = self.load_queries() self.print('Retrieving instances ...') _, _, paths, _ = self.retriver.query(images=images, boxes=query_boxes, k=self.params.k) paths = self.unique_paths(paths) self.print('Getting average precision for each query ...') average_precisions = self.average_precision(*self.results_tensors(brands, paths)) self.print('Storing results ...') self.store_results(brands, paths, average_precisions) self.print('Mean Average Precision: {}'.format(float(average_precisions.mean()))) def unique_paths(self, paths): """Make the resulting paths unique. As each image could have more than one embedding similar to the query there could be images more than one time in the results, but we are want to count each image only one time. For this, we are going to remove the duplicates paths for each query. Arguments: paths (list of list of str): with the paths of the retrieved images. Returns: list of list of str: with the unique paths for each query. """ for i in range(len(paths)): seen = set() paths[i] = [path for path in paths[i] if not (path in seen or seen.add(path))] return paths def results_tensors(self, brands, paths): """Generate the results tensor of the retrieval task for the metric and amount of relevant results. Arguments: brands (list of str): with the brand corresponding to each query. paths (list of list of str): with the resulting paths of the retrieved images for each query. Returns: torch.Tensor: with the 1s indicating the true positives. Shape `(q, k)`. Where `q` is the number of queries and `k` the number of instances retrieved. torch.Tensor: with the amount of relevant images for each query (true positives). """ results = torch.zeros(len(brands), self.params.k) for i, brand in enumerate(brands): for j, path in enumerate(paths[i]): if brand in path: results[i, j] = 1 relevant = {brand: 0 for brand in brands} for brand, *_ in self.dataset.paths: if brand not in relevant: continue relevant[brand] += 1 relevant = torch.Tensor(list(relevant.values())) return results, relevant def store_results(self, brands, paths, average_precisions): """Store the results in a CSV file with columns: Brand, Average Precision, result paths. Arguments: brands (list of str): with the brand of each query. paths (list of list of str): with the paths of the images retrieved. average_precisions (torch.Tensor): with the average precision for each query. """ with open(self.params.results_file, 'w') as file: for i, brand in brands: line = '{},{},'.format(brand, float(average_precisions[i])) line += ','.join(paths[i]) file.write(line + '\n')
Static methods
def get_params()
-
Get the base parameters for the experiment.
Returns
JsonObject
- with the parameters.
Source code
@staticmethod def get_params(): """Get the base parameters for the experiment. Returns: JsonObject: with the parameters. """ return JsonObject({ 'dataset': { 'root': None, 'dataset': 'test', }, 'k': 500, # The amount of results to retrieve 'queries_file': './queries.csv', 'results_file': './results.csv', 'retriever': { 'use': 'dldenet', 'dldenet': { 'checkpoint': None, 'paths': None, 'extensions': None, 'batch_size': 8, 'num_workers': 8, 'verbose': True, 'params': {'transform': {}} } } })
Methods
def __init__(self, params=None, device=None)
-
Initialize the experiment.
Arguments
params
:dict
, optional- a dict to modify the base parameters.
device
:str
, optional- where to run the experiments.
Source code
def __init__(self, params=None, device=None): """Initialize the experiment. Arguments: params (dict, optional): a dict to modify the base parameters. device (str, optional): where to run the experiments. """ self.params = self.get_params().merge(params) self.device = device if device is not None else 'cuda:0' if torch.cuda.is_available() else 'cpu' self.print('Loading dataset ...') self.dataset = self.get_dataset() self.print('Loading retriever ...') self.retriver = self.get_retriver() self.print('Loading metric ...') self.average_precision = AveragePrecision()
def generate_queries(self)
-
Generate a file with a random path to an image for each brand.
It will store the file in the self.params.queries_file path.
Source code
def generate_queries(self): """Generate a file with a random path to an image for each brand. It will store the file in the self.params.queries_file path. """ results = [] for brand in self.dataset.brands: if brand == 'no-logo': continue # Each path tuple contains (brand, image path, boxes path) results.append(random.choice([path for path in self.dataset.paths if path[0] == brand])) with open(self.params.queries_file, 'w') as file: file.write('\n'.join(','.join(line) for line in results))
def get_dataset(self)
-
Get the Flickr32 dataset.
Returns
Flickr32Dataset
- initialized and with its attributes.
Source code
def get_dataset(self): """Get the Flickr32 dataset. Returns: Flickr32Dataset: initialized and with its attributes. """ return Flickr32Dataset(**self.params.dataset)
def get_retriver(self)
-
Initialize and return the retriver to use in the experiment.
Return
InstanceRetriver
- the retriever to use.
Source code
def get_retriver(self): """Initialize and return the retriver to use in the experiment. Return: InstanceRetriver: the retriever to use. """ retriever = self.params.retriever.use if retriever == 'dldenet': params = self.params.retriever.dldenet params.paths = [t[1] for t in self.dataset.paths] # Paths are tuples like (brand, image path, boxes path) params.device = self.device if params.checkpoint is None: raise ValueError('Please provide a checkpoint for the DLDENet retriever.') params.checkpoint = torch.load(params.checkpoint, map_location=self.device) params.params.transform = params.checkpoint['hyperparameters']['transform'] return DLDENetRetriever(**params) raise NotImplementedError('There is no implementation for the "{}" retriever.'.format(retriever))
def load_queries(self)
-
Load the images and their bounding boxes to use as queries.
It knows which images to use using the self.params.queries_file, if no one exists it generates a new one.
Returns
list of str: with the brand of each image. list of PIL Image: with the images. list of torch.Tensor: with the first bounding box only to query. Shape
(1, 4)
with the x1, y1 for the top-left corner and the x2, y2 for the bottom-right corner.Source code
def load_queries(self): """Load the images and their bounding boxes to use as queries. It knows which images to use using the self.params.queries_file, if no one exists it generates a new one. Returns: list of str: with the brand of each image. list of PIL Image: with the images. list of torch.Tensor: with the first bounding box only to query. Shape `(1, 4)` with the x1, y1 for the top-left corner and the x2, y2 for the bottom-right corner. """ if not os.path.exists(self.params.queries_file): self.generate_queries() brands, images, boxes = [], [], [] with open(self.params.queries_file, 'r') as file: for brand, image, annot in [line.split(',') for line in file.read().split('\n')]: brands.append(brand) images.append(Image.open(image)) with open(annot, 'r') as file: line = file.readlines()[1] # The first line contains "x y width height" x, y, w, h = (int(val) for val in line.split()) x1, y1 = x - 1, y - 1 x2, y2 = x1 + w, y1 + h boxes.append(torch.Tensor([[x1, y1, x2, y2]])) return brands, images, boxes
def results_tensors(self, brands, paths)
-
Generate the results tensor of the retrieval task for the metric and amount of relevant results.
Arguments
brands
:list
ofstr
- with the brand corresponding to each query.
paths
:list
oflist
ofstr
- with the resulting paths of the retrieved images for each query.
Returns
torch.Tensor: with the 1s indicating the true positives. Shape
(q, k)
. Whereq
is the number of queries andk
the number of instances retrieved. torch.Tensor: with the amount of relevant images for each query (true positives).Source code
def results_tensors(self, brands, paths): """Generate the results tensor of the retrieval task for the metric and amount of relevant results. Arguments: brands (list of str): with the brand corresponding to each query. paths (list of list of str): with the resulting paths of the retrieved images for each query. Returns: torch.Tensor: with the 1s indicating the true positives. Shape `(q, k)`. Where `q` is the number of queries and `k` the number of instances retrieved. torch.Tensor: with the amount of relevant images for each query (true positives). """ results = torch.zeros(len(brands), self.params.k) for i, brand in enumerate(brands): for j, path in enumerate(paths[i]): if brand in path: results[i, j] = 1 relevant = {brand: 0 for brand in brands} for brand, *_ in self.dataset.paths: if brand not in relevant: continue relevant[brand] += 1 relevant = torch.Tensor(list(relevant.values())) return results, relevant
def run(self)
-
Run the experiment and compute the mean average precision over the entire test dataset.
Source code
def run(self): """Run the experiment and compute the mean average precision over the entire test dataset.""" self.print('Loading queries ...') brands, images, query_boxes = self.load_queries() self.print('Retrieving instances ...') _, _, paths, _ = self.retriver.query(images=images, boxes=query_boxes, k=self.params.k) paths = self.unique_paths(paths) self.print('Getting average precision for each query ...') average_precisions = self.average_precision(*self.results_tensors(brands, paths)) self.print('Storing results ...') self.store_results(brands, paths, average_precisions) self.print('Mean Average Precision: {}'.format(float(average_precisions.mean())))
def store_results(self, brands, paths, average_precisions)
-
Store the results in a CSV file with columns: Brand, Average Precision, result paths.
Arguments
brands
:list
ofstr
- with the brand of each query.
paths
:list
oflist
ofstr
- with the paths of the images retrieved.
average_precisions
:torch.Tensor
- with the average precision for each query.
Source code
def store_results(self, brands, paths, average_precisions): """Store the results in a CSV file with columns: Brand, Average Precision, result paths. Arguments: brands (list of str): with the brand of each query. paths (list of list of str): with the paths of the images retrieved. average_precisions (torch.Tensor): with the average precision for each query. """ with open(self.params.results_file, 'w') as file: for i, brand in brands: line = '{},{},'.format(brand, float(average_precisions[i])) line += ','.join(paths[i]) file.write(line + '\n')
def unique_paths(self, paths)
-
Make the resulting paths unique.
As each image could have more than one embedding similar to the query there could be images more than one time in the results, but we are want to count each image only one time. For this, we are going to remove the duplicates paths for each query.
Arguments
paths
:list
oflist
ofstr
- with the paths of the retrieved images.
Returns
list of list of str: with the unique paths for each query.
Source code
def unique_paths(self, paths): """Make the resulting paths unique. As each image could have more than one embedding similar to the query there could be images more than one time in the results, but we are want to count each image only one time. For this, we are going to remove the duplicates paths for each query. Arguments: paths (list of list of str): with the paths of the retrieved images. Returns: list of list of str: with the unique paths for each query. """ for i in range(len(paths)): seen = set() paths[i] = [path for path in paths[i] if not (path in seen or seen.add(path))] return paths
Inherited members