Unverified Commit 3dacd474 authored by Jekaterina Jaroslavceva's avatar Jekaterina Jaroslavceva Committed by GitHub
Browse files

File movings (#9899)

* Dataset utilities added.

* Global model definition

* Dataset modules added.

* Dataset modules fix.

* global features model training added

* global features fix

* Test dataset update

* PR fixes

* repo sync

* repo sync

* modules moving

* Removed unnecessary modules

* Removed unnecessary files
parent b1aa44d9
......@@ -35,5 +35,7 @@ from delf.python.examples import extractor
from delf.python import detect_to_retrieve
from delf.python import training
from delf.python.training import model
from delf.python.training import datasets
from delf.python import datasets
from delf.python.datasets import google_landmarks_dataset
from delf.python.datasets import revisited_op
# pylint: enable=unused-import
......@@ -18,7 +18,7 @@ For more details on the dataset, please refer to its
### Install DELF library
To be able to use this code, please follow
[these instructions](../../../INSTALL_INSTRUCTIONS.md) to properly install the
[these instructions](../../../../INSTALL_INSTRUCTIONS.md) to properly install the
DELF library.
### Download Revisited Oxford/Paris datasets
......@@ -48,7 +48,7 @@ wget http://cmp.felk.cvut.cz/revisitop/data/datasets/rparis6k/gnd_rparis6k.mat
### Download model
```bash
# From models/research/delf/delf/python/google_landmarks_dataset
# From models/research/delf/delf/python/datasets/google_landmarks_dataset
mkdir parameters && cd parameters
# RN101-ArcFace model trained on GLDv2-clean.
......@@ -72,8 +72,8 @@ datasets, and not required for the GLDv2 retrieval/recognition datasets.
Run query feature extraction as follows:
```bash
# From models/research/delf/delf/python/google_landmarks_dataset
python3 ../delg/extract_features.py \
# From models/research/delf/delf/python/datasets/google_landmarks_dataset
python3 ../../delg/extract_features.py \
--delf_config_path rn101_af_gldv2clean_config.pbtxt \
--dataset_file_path ~/revisitop/data/gnd_roxford5k.mat \
--images_dir ~/revisitop/data/oxford5k_images \
......@@ -86,8 +86,8 @@ python3 ../delg/extract_features.py \
Run index feature extraction as follows:
```bash
# From models/research/delf/delf/python/google_landmarks_dataset
python3 ../delg/extract_features.py \
# From models/research/delf/delf/python/datasets/google_landmarks_dataset
python3 ../../delg/extract_features.py \
--delf_config_path rn101_af_gldv2clean_config.pbtxt \
--dataset_file_path ~/revisitop/data/gnd_roxford5k.mat \
--images_dir ~/revisitop/data/oxford5k_images \
......@@ -100,8 +100,8 @@ python3 ../delg/extract_features.py \
To run retrieval on `roxford5k`, the following command can be used:
```bash
# From models/research/delf/delf/python/google_landmarks_dataset
python3 ../delg/perform_retrieval.py \
# From models/research/delf/delf/python/datasets/google_landmarks_dataset
python3 ../../delg/perform_retrieval.py \
--dataset_file_path ~/revisitop/data/gnd_roxford5k.mat \
--query_features_dir ~/revisitop/data/oxford5k_features/query \
--index_features_dir ~/revisitop/data/oxford5k_features/index \
......
# Copyright 2021 The TensorFlow Authors All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Module exposing Google Landmarks dataset for training."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# pylint: disable=unused-import
from delf.python.datasets.google_landmarks_dataset import googlelandmarks
# pylint: enable=unused-import
......@@ -25,8 +25,8 @@ import argparse
import sys
from tensorflow.python.platform import app
from delf.python.google_landmarks_dataset import dataset_file_io
from delf.python.google_landmarks_dataset import metrics
from delf.python.datasets.google_landmarks_dataset import dataset_file_io
from delf.python.datasets.google_landmarks_dataset import metrics
cmd_args = None
......
......@@ -25,8 +25,8 @@ import argparse
import sys
from tensorflow.python.platform import app
from delf.python.google_landmarks_dataset import dataset_file_io
from delf.python.google_landmarks_dataset import metrics
from delf.python.datasets.google_landmarks_dataset import dataset_file_io
from delf.python.datasets.google_landmarks_dataset import metrics
cmd_args = None
......
......@@ -23,7 +23,7 @@ import os
from absl import flags
import tensorflow as tf
from delf.python.google_landmarks_dataset import dataset_file_io
from delf.python.datasets.google_landmarks_dataset import dataset_file_io
FLAGS = flags.FLAGS
......
......@@ -20,7 +20,7 @@ from __future__ import print_function
import tensorflow as tf
from delf.python.google_landmarks_dataset import metrics
from delf.python.datasets.google_landmarks_dataset import metrics
def _CreateRecognitionSolution():
......
# Copyright 2020 The TensorFlow Authors All Rights Reserved.
# Copyright 2021 The TensorFlow Authors All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -12,11 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Module exposing datasets for training."""
"""Module for revisited Oxford and Paris datasets."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# pylint: disable=unused-import
from delf.python.training.datasets import googlelandmarks
from delf.python.datasets.revisited_op import dataset
# pylint: enable=unused-import
......@@ -18,6 +18,9 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import pickle
import numpy as np
from scipy.io import matlab
import tensorflow as tf
......@@ -102,14 +105,14 @@ def ParseEasyMediumHardGroundTruth(ground_truth):
hard_ground_truth = []
for i in range(num_queries):
easy_ground_truth.append(
_ParseGroundTruth([ground_truth[i]['easy']],
[ground_truth[i]['junk'], ground_truth[i]['hard']]))
_ParseGroundTruth([ground_truth[i]['easy']],
[ground_truth[i]['junk'], ground_truth[i]['hard']]))
medium_ground_truth.append(
_ParseGroundTruth([ground_truth[i]['easy'], ground_truth[i]['hard']],
[ground_truth[i]['junk']]))
_ParseGroundTruth([ground_truth[i]['easy'], ground_truth[i]['hard']],
[ground_truth[i]['junk']]))
hard_ground_truth.append(
_ParseGroundTruth([ground_truth[i]['hard']],
[ground_truth[i]['junk'], ground_truth[i]['easy']]))
_ParseGroundTruth([ground_truth[i]['hard']],
[ground_truth[i]['junk'], ground_truth[i]['easy']]))
return easy_ground_truth, medium_ground_truth, hard_ground_truth
......@@ -213,13 +216,13 @@ def ComputePRAtRanks(positive_ranks, desired_pr_ranks):
positive_ranks_one_indexed = positive_ranks + 1
for i, desired_pr_rank in enumerate(desired_pr_ranks):
recalls[i] = np.sum(
positive_ranks_one_indexed <= desired_pr_rank) / num_expected_positives
positive_ranks_one_indexed <= desired_pr_rank) / num_expected_positives
# If `desired_pr_rank` is larger than last positive's rank, only compute
# precision with respect to last positive's position.
precision_rank = min(max(positive_ranks_one_indexed), desired_pr_rank)
precisions[i] = np.sum(
positive_ranks_one_indexed <= precision_rank) / precision_rank
positive_ranks_one_indexed <= precision_rank) / precision_rank
return precisions, recalls
......@@ -269,8 +272,8 @@ def ComputeMetrics(sorted_index_ids, ground_truth, desired_pr_ranks):
if sorted_desired_pr_ranks[-1] > num_index_images:
raise ValueError(
'Requested PR ranks up to %d, however there are only %d images' %
(sorted_desired_pr_ranks[-1], num_index_images))
'Requested PR ranks up to %d, however there are only %d images' %
(sorted_desired_pr_ranks[-1], num_index_images))
# Instantiate all outputs, then loop over each query and gather metrics.
mean_average_precision = 0.0
......@@ -292,7 +295,7 @@ def ComputeMetrics(sorted_index_ids, ground_truth, desired_pr_ranks):
continue
positive_ranks = np.arange(num_index_images)[np.in1d(
sorted_index_ids[i], ok_index_images)]
sorted_index_ids[i], ok_index_images)]
junk_ranks = np.arange(num_index_images)[np.in1d(sorted_index_ids[i],
junk_index_images)]
......@@ -332,9 +335,9 @@ def SaveMetricsFile(mean_average_precision, mean_precisions, mean_recalls,
with tf.io.gfile.GFile(output_path, 'w') as f:
for k in sorted(mean_average_precision.keys()):
f.write('{}\n mAP={}\n mP@k{} {}\n mR@k{} {}\n'.format(
k, np.around(mean_average_precision[k] * 100, decimals=2),
np.array(pr_ranks), np.around(mean_precisions[k] * 100, decimals=2),
np.array(pr_ranks), np.around(mean_recalls[k] * 100, decimals=2)))
k, np.around(mean_average_precision[k] * 100, decimals=2),
np.array(pr_ranks), np.around(mean_precisions[k] * 100, decimals=2),
np.array(pr_ranks), np.around(mean_recalls[k] * 100, decimals=2)))
def _ParseSpaceSeparatedStringsInBrackets(line, prefixes, ind):
......@@ -375,8 +378,8 @@ def _ParsePrRanks(line):
ValueError: If input line is malformed.
"""
return [
int(pr_rank) for pr_rank in _ParseSpaceSeparatedStringsInBrackets(
line, [' mP@k['], 0) if pr_rank
int(pr_rank) for pr_rank in _ParseSpaceSeparatedStringsInBrackets(
line, [' mP@k['], 0) if pr_rank
]
......@@ -394,8 +397,8 @@ def _ParsePrScores(line, num_pr_ranks):
ValueError: If input line is malformed.
"""
pr_scores = [
float(pr_score) for pr_score in _ParseSpaceSeparatedStringsInBrackets(
line, (' mP@k[', ' mR@k['), 1) if pr_score
float(pr_score) for pr_score in _ParseSpaceSeparatedStringsInBrackets(
line, (' mP@k[', ' mR@k['), 1) if pr_score
]
if len(pr_scores) != num_pr_ranks:
......@@ -427,8 +430,8 @@ def ReadMetricsFile(metrics_path):
if len(file_contents_stripped) % 4:
raise ValueError(
'Malformed input %s: number of lines must be a multiple of 4, '
'but it is %d' % (metrics_path, len(file_contents_stripped)))
'Malformed input %s: number of lines must be a multiple of 4, '
'but it is %d' % (metrics_path, len(file_contents_stripped)))
mean_average_precision = {}
pr_ranks = []
......@@ -439,13 +442,13 @@ def ReadMetricsFile(metrics_path):
protocol = file_contents_stripped[i]
if protocol in protocols:
raise ValueError(
'Malformed input %s: protocol %s is found a second time' %
(metrics_path, protocol))
'Malformed input %s: protocol %s is found a second time' %
(metrics_path, protocol))
protocols.add(protocol)
# Parse mAP.
mean_average_precision[protocol] = float(
file_contents_stripped[i + 1].split('=')[1]) / 100.0
file_contents_stripped[i + 1].split('=')[1]) / 100.0
# Parse (or check consistency of) pr_ranks.
parsed_pr_ranks = _ParsePrRanks(file_contents_stripped[i + 2])
......@@ -458,12 +461,74 @@ def ReadMetricsFile(metrics_path):
# Parse mean precisions.
mean_precisions[protocol] = np.array(
_ParsePrScores(file_contents_stripped[i + 2], len(pr_ranks)),
dtype=float) / 100.0
_ParsePrScores(file_contents_stripped[i + 2], len(pr_ranks)),
dtype=float) / 100.0
# Parse mean recalls.
mean_recalls[protocol] = np.array(
_ParsePrScores(file_contents_stripped[i + 3], len(pr_ranks)),
dtype=float) / 100.0
_ParsePrScores(file_contents_stripped[i + 3], len(pr_ranks)),
dtype=float) / 100.0
return mean_average_precision, pr_ranks, mean_precisions, mean_recalls
def create_config_for_test_dataset(dataset, dir_main):
"""Creates the configuration dictionary for the test dataset.
Args:
dataset: String, dataset name: either 'roxford5k' or 'rparis6k'.
dir_main: String, path to the folder containing ground truth files.
Returns:
cfg: Dataset configuration in a form of dictionary. The configuration
includes:
`gnd_fname` - path to the ground truth file for teh dataset,
`ext` and `qext` - image extentions for the images in the test dataset
and the query images,
`dir_data` - path to the folder containing ground truth files,
`dir_images` - path to the folder containing images,
`n` and `nq` - number of images and query images in the dataset
respectively,
`im_fname` and `qim_fname` - functions providing paths for the dataset
and query images respectively,
`dataset` - test dataset name.
Raises:
ValueError: If an unknown dataset name is provided as an argument.
"""
DATASETS = ['roxford5k', 'rparis6k']
dataset = dataset.lower()
def _config_imname(cfg, i):
return os.path.join(cfg['dir_images'], cfg['imlist'][i] + cfg['ext'])
def _config_qimname(cfg, i):
return os.path.join(cfg['dir_images'], cfg['qimlist'][i] + cfg['qext'])
if dataset not in DATASETS:
raise ValueError('Unknown dataset: {}!'.format(dataset))
# Loading imlist, qimlist, and gnd in configuration as a dictionary.
gnd_fname = os.path.join(dir_main, 'gnd_{}.pkl'.format(dataset))
with tf.io.gfile.GFile(gnd_fname, 'rb') as f:
cfg = pickle.load(f)
cfg['gnd_fname'] = gnd_fname
if dataset == 'rparis6k':
dir_images = 'paris6k_images'
elif dataset == 'roxford5k':
dir_images = 'oxford5k_images'
cfg['ext'] = '.jpg'
cfg['qext'] = '.jpg'
cfg['dir_data'] = os.path.join(dir_main)
cfg['dir_images'] = os.path.join(cfg['dir_data'], dir_images)
cfg['n'] = len(cfg['imlist'])
cfg['nq'] = len(cfg['qimlist'])
cfg['im_fname'] = _config_imname
cfg['qim_fname'] = _config_qimname
cfg['dataset'] = dataset
return cfg
......@@ -22,14 +22,15 @@ import tensorflow as tf
from delf.python import utils as image_loading_utils
def pil_imagenet_loader(path, imsize, bounding_box=None, normalize=True):
def pil_imagenet_loader(path, imsize, bounding_box=None, preprocess=True):
"""Pillow loader for the images.
Args:
path: Path to image to be loaded.
imsize: Integer, defines the maximum size of longer image side.
bounding_box: (x1,y1,x2,y2) tuple to crop the query image.
normalize: Bool, whether to normalize the image.
preprocess: Bool, whether to preprocess the images in respect to the
ImageNet dataset.
Returns:
image: `Tensor`, image in ImageNet suitable format.
......@@ -47,7 +48,7 @@ def pil_imagenet_loader(path, imsize, bounding_box=None, normalize=True):
img.thumbnail((imsize, imsize), Image.ANTIALIAS)
img = np.array(img)
if normalize:
if preprocess:
# Preprocessing for ImageNet data. Converts the images from RGB to BGR,
# then zero-centers each color channel with respect to the ImageNet
# dataset, without scaling.
......@@ -56,16 +57,18 @@ def pil_imagenet_loader(path, imsize, bounding_box=None, normalize=True):
return img
def default_loader(path, imsize, bounding_box=None, normalize=True):
def default_loader(path, imsize, bounding_box=None, preprocess=True):
"""Default loader for the images is using Pillow.
Args:
path: Path to image to be loaded.
imsize: Integer, defines the maximum size of longer image side.
bounding_box: (x1,y1,x2,y2) tuple to crop the query image.
preprocess: Bool, whether to preprocess the images in respect to the
ImageNet dataset.
Returns:
image: `Tensor`, image in ImageNet suitable format.
"""
img = pil_imagenet_loader(path, imsize, bounding_box, normalize)
img = pil_imagenet_loader(path, imsize, bounding_box, preprocess)
return img
......@@ -44,7 +44,7 @@ class UtilsTest(tf.test.TestCase):
max_img_size = 1024
# Load the saved dummy image.
img = image_loading_utils.default_loader(filename, imsize=max_img_size,
normalize=False)
preprocess=False)
# Make sure the values are the same before and after loading.
self.assertAllEqual(np.array(img_out), img)
......@@ -62,13 +62,12 @@ class UtilsTest(tf.test.TestCase):
max_img_size = 1024
# Load the saved dummy image.
expected_size = 400
img = image_loading_utils.default_loader(filename, imsize=max_img_size,
bounding_box=[120, 120,
120 + expected_size,
120 + expected_size],
normalize=False)
img = image_loading_utils.default_loader(
filename, imsize=max_img_size,
bounding_box=[120, 120, 120 + expected_size, 120 + expected_size],
preprocess=False)
# Check the shape.
# Check that the final shape is as expected.
self.assertAllEqual(tf.shape(img), [expected_size, expected_size, 3])
......
......@@ -34,6 +34,8 @@ mv paris6k_images_tmp/paris/*/*.jpg paris6k_images/
# Revisited annotations.
wget http://cmp.felk.cvut.cz/revisitop/data/datasets/roxford5k/gnd_roxford5k.mat
wget http://cmp.felk.cvut.cz/revisitop/data/datasets/rparis6k/gnd_rparis6k.mat
wget http://cmp.felk.cvut.cz/cnnimageretrieval/data/test/roxford5k/gnd_roxford5k.pkl
wget http://cmp.felk.cvut.cz/cnnimageretrieval/data/test/rparis6k/gnd_rparis6k.pkl
```
### Download model
......
......@@ -20,5 +20,4 @@ from __future__ import print_function
# pylint: disable=unused-import
from delf.python.detect_to_retrieve import aggregation_extraction
from delf.python.detect_to_retrieve import boxes_and_features_extraction
from delf.python.detect_to_retrieve import dataset
# pylint: enable=unused-import
......@@ -20,9 +20,9 @@ import tensorflow as tf
class MAC(tf.keras.layers.Layer):
"""Global max pooling (MAC) layer.
Maximum Activations of Convolutions (MAC) is simply constructed by
max-pooling over all dimensions per feature map. See
https://arxiv.org/abs/1511.05879 for a reference.
Maximum Activations of Convolutions (MAC) is simply constructed by
max-pooling over all dimensions per feature map. See
https://arxiv.org/abs/1511.05879 for a reference.
"""
def call(self, x, axis=None):
......@@ -31,7 +31,6 @@ class MAC(tf.keras.layers.Layer):
Args:
x: [B, H, W, D] A float32 Tensor.
axis: Dimensions to reduce. By default, dimensions [1, 2] are reduced.
Returns:
output: [B, D] A float32 Tensor.
"""
......@@ -99,6 +98,47 @@ class GeM(tf.keras.layers.Layer):
return gem(x, power=self.power, eps=self.eps, axis=axis)
class GeMPooling2D(tf.keras.layers.Layer):
def __init__(self, power=20., pool_size=(2, 2), strides=None,
padding='valid', data_format='channels_last'):
"""Generalized mean pooling (GeM) pooling operation for spatial data.
Args:
power: Float, power > 0. is an inverse exponent parameter (GeM power).
pool_size: Integer or tuple of 2 integers, factors by which to
downscale (vertical, horizontal)
strides: Integer, tuple of 2 integers, or None. Strides values.
If None, it will default to `pool_size`.
padding: One of `valid` or `same`. `valid` means no padding.
`same` results in padding evenly to the left/right or up/down of
the input such that output has the same height/width dimension as the
input.
data_format: A string, one of `channels_last` (default) or
`channels_first`. The ordering of the dimensions in the inputs.
`channels_last` corresponds to inputs with shape `(batch, height,
width, channels)` while `channels_first` corresponds to inputs with
shape `(batch, channels, height, width)`.
"""
super(GeMPooling2D, self).__init__()
self.power = power
self.eps = 1e-6
self.pool_size = pool_size
self.strides = strides
self.padding = padding.upper()
data_format_conv = {'channels_last': 'NHWC',
'channels_first': 'NCHW',
}
self.data_format = data_format_conv[data_format]
def call(self, x):
tmp = tf.pow(x, self.power)
tmp = tf.nn.avg_pool(tmp, self.pool_size, self.strides,
self.padding, self.data_format)
out = tf.pow(tmp, 1. / self.power)
return out
def mac(x, axis=None):
"""Performs global max pooling (MAC).
......@@ -137,7 +177,7 @@ def gem(x, axis=None, power=3., eps=1e-6):
axis: Dimensions to reduce. By default, dimensions [1, 2] are reduced.
power: Float, power > 0 is an inverse exponent parameter (GeM power).
eps: Float, parameter for numerical stability.
Returns:
output: [B, D] A float32 Tensor.
"""
......
......@@ -48,6 +48,37 @@ class PoolingsTest(tf.test.TestCase):
# Compare actual and expected.
self.assertAllClose(exp_output, result)
def testGeMPooling2D(self):
# Create a testing tensor.
x = tf.constant([[[1., 2., 3.],
[4., 5., 6.],
[7., 8., 9.]]])
x = tf.reshape(x, [1, 3, 3, 1])
# Checking GeMPooling2D relation to MaxPooling2D for the large values of
# `p`.
max_pool_2d = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(1, 1), padding='valid')
out_max = max_pool_2d(x)
gem_pool_2d = pooling.GeMPooling2D(power=30., pool_size=(2, 2),
strides=(1, 1), padding='valid')
out_gem_max = gem_pool_2d(x)
# Check that for large `p` GeMPooling2D is close to MaxPooling2D.
self.assertAllEqual(out_max, tf.round(out_gem_max))
# Checking GeMPooling2D relation to AveragePooling2D for the value
# of `p` = 1.
avg_pool_2d = tf.keras.layers.AveragePooling2D(pool_size=(2, 2),
strides=(1, 1),
padding='valid')
out_avg = avg_pool_2d(x)
gem_pool_2d = pooling.GeMPooling2D(power=1., pool_size=(2, 2),
strides=(1, 1), padding='valid')
out_gem_avg = gem_pool_2d(x)
# Check that for `p` equals 1., GeMPooling2D becomes AveragePooling2D.
self.assertAllEqual(out_avg, out_gem_avg)
if __name__ == '__main__':
tf.test.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment