Skip to content
This repository was archived by the owner on Jul 2, 2021. It is now read-only.

Improve apply_prediction #523

Merged
merged 19 commits into from
Mar 5, 2018
19 changes: 9 additions & 10 deletions chainercv/extensions/evaluator/detection_voc_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import chainer.training.extensions

from chainercv.evaluations import eval_detection_voc
from chainercv.utils import apply_prediction_to_iterator
from chainercv.utils import apply_to_batch


class DetectionVOCEvaluator(chainer.training.extensions.Evaluator):
Expand Down Expand Up @@ -72,17 +72,16 @@ def evaluate(self):
else:
it = copy.copy(iterator)

imgs, pred_values, gt_values = apply_prediction_to_iterator(
target.predict, it)
# delete unused iterator explicitly
del imgs
in_values, out_values, rest_values = apply_to_batch(target.predict, it)
# delete unused iterators explicitly
del in_values

pred_bboxes, pred_labels, pred_scores = pred_values
pred_bboxes, pred_labels, pred_scores = out_values

if len(gt_values) == 3:
gt_bboxes, gt_labels, gt_difficults = gt_values
elif len(gt_values) == 2:
gt_bboxes, gt_labels = gt_values
if len(rest_values) == 3:
gt_bboxes, gt_labels, gt_difficults = rest_values
elif len(rest_values) == 2:
gt_bboxes, gt_labels = rest_values
gt_difficults = None

result = eval_detection_voc(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import chainer.training.extensions

from chainercv.evaluations import eval_semantic_segmentation
from chainercv.utils import apply_prediction_to_iterator
from chainercv.utils import apply_to_batch


class SemanticSegmentationEvaluator(chainer.training.extensions.Evaluator):
Expand Down Expand Up @@ -79,13 +79,12 @@ def evaluate(self):
else:
it = copy.copy(iterator)

imgs, pred_values, gt_values = apply_prediction_to_iterator(
target.predict, it)
# delete unused iterator explicitly
del imgs
in_values, out_values, rest_values = apply_to_batch(target.predict, it)
# delete unused iterators explicitly
del in_values

pred_labels, = pred_values
gt_labels, = gt_values
pred_labels, = out_values
gt_labels, = rest_values

result = eval_semantic_segmentation(pred_labels, gt_labels)

Expand Down
2 changes: 1 addition & 1 deletion chainercv/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from chainercv.utils.image import read_image # NOQA
from chainercv.utils.image import tile_images # NOQA
from chainercv.utils.image import write_image # NOQA
from chainercv.utils.iterator import apply_prediction_to_iterator # NOQA
from chainercv.utils.iterator import apply_to_batch # NOQA
from chainercv.utils.iterator import ProgressHook # NOQA
from chainercv.utils.iterator import unzip # NOQA
from chainercv.utils.testing import assert_is_bbox # NOQA
Expand Down
2 changes: 1 addition & 1 deletion chainercv/utils/iterator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from chainercv.utils.iterator.apply_prediction_to_iterator import apply_prediction_to_iterator # NOQA
from chainercv.utils.iterator.apply_to_batch import apply_to_batch # NOQA
from chainercv.utils.iterator.progress_hook import ProgressHook # NOQA
from chainercv.utils.iterator.unzip import unzip # NOQA
141 changes: 0 additions & 141 deletions chainercv/utils/iterator/apply_prediction_to_iterator.py

This file was deleted.

153 changes: 153 additions & 0 deletions chainercv/utils/iterator/apply_to_batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from chainercv.utils.iterator.unzip import unzip


def apply_to_batch(func, iterator, n_input=1, hook=None):
"""Apply a function/method to an iterator of batches.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not directly related to the change proposed in this PR, but this sentence is incorrect.
We apply a function to batches and not an iterator.
Thus, Apply a function/method to batches of data from an iterator is more precise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


This function applies a function/method to an iterator of batches.
It assumes that the iterator returns a batch of input data or
a batch of tuples whose first elements ara input data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was better to describe the internals of this function more in the beginning.
I wrote an alternative docstring. What do you think?


This function assumes that the iterator iterates over a collection of tuples that contain inputs to func.
Additionally, the tuples may contain values that are not used by func.
For convenience, we allow the iterator to iterate over a collection of inputs that are not tuple.
t
Conceptually, this function does two operations.
First, it splits the iterator into two iterators. The first iterator contains the inputs to func and the second iterator contains the rest of the data from iterator.
Second, it creates another iterator that iterates over a collection of the outputs of the func.

Here is an illustration of the expected behavior of iterator.
M is the number of arguments for func, which is defined by an argument n_input. N is the number of elements in a tuple from iterator that are not used by func. Note that N can be 0.

>>> batch = next(iterator)
>>> # batch:  [(in_val0, ..., in_val{M-1}, rest_val0, ..., rest_val{N-1})]
or
>>> # batch:  [in_val]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, it splits the iterator into two iterators. The first iterator contains the inputs to func and the second iterator contains the rest of the data from iterator.
Second, it creates another iterator that iterates over a collection of the outputs of the func.

This explanation sounds this function returns three iterators. However, it is not correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. That is my mistake.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will try to merge your solution into docstring.


>>> batch = next(iterator)
>>> # batch: [in_val]
or
>>> # batch: [(in_val0, in_val1, ...)]
or
>>> # batch: [(in_val0, in_val1, ..., rest_val0, rest_val1, ...)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n_input should appear at this part of the documentation.


This function applies :func:`func` to batch(es) of input data and gets
computed value(s). :func:`func` should take a batch of data and
return a batch of computed values
or a tuple of batches of computed values.

>>> out_vals = func(in_val0, in_val1, ...)
>>> # out_vals: [out_val]
or
>>> out_vals0, out_vals1, ... = func(in_val0, in_val1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in_val0, in_val1 --> in_val0, in_val1, ... for consistency

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these examples are not necessary.

Copy link
Member Author

@Hakuyume Hakuyume Mar 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to show the returned values should be ([out_val0], [out_val1], ...) and not [(out_val0, out_val1, ...)]. I feel it unclear without examples.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I see. So you wanted to emphasize that the function returns tuple of lists.

BTW, shouldn't function take list of values?
So out_vals0, out_vals1, ... = func(in_val0, in_val1) --> out_vals0, out_vals1, ... = func(in_vals0, in_vals1).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. That's my mistake.

>>> # out_vals0: [out_val0]
>>> # out_vals1: [out_val1]

Here is an exmple, which applies a pretrained Faster R-CNN to
PASCAL VOC dataset.

>>> from chainer import iterators
>>>
>>> from chainercv.datasets import VOCDetectionDataset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VOCBBoxDataset. This is my mistake. Sorry.

>>> from chainercv.links import FasterRCNNVGG16
>>> from chainercv.utils import apply_to_batch
>>>
>>> dataset = VOCDetectionDataset(year='2007', split='test')
>>> # next(iterator) -> [(img, gt_bbox, gt_label)]
>>> iterator = iterators.SerialIterator(
... dataset, 2, repeat=False, shuffle=False)
>>>
>>> # model.predict([img]) -> ([pred_bbox], [pred_label], [pred_score])
>>> model = FasterRCNNVGG16(pretrained_model='voc07')
>>>
>>> in_values, out_values, rest_values = apply_to_batch(
... model.predict, iterator)
>>>
>>> # in_values contains one iterator
>>> imgs, = in_values
>>> # out_values contains three iterators
>>> pred_bboxes, pred_labels, pred_scores = out_values
>>> # rest_values contains two iterators
>>> gt_bboxes, gt_labels = rest_values

Args:
func: A callable that takes batch(es) of input data and returns
computed data.
iterator (chainer.Iterator): An iterator of batch.
The first :obj:`n_input` elements in each sample are
treated as input values. They are passed to :obj:`func`.
n_input (int): The number of input data. The default value is :obj:`1`.
hook: A callable that is called after each iteration.
:obj:`in_values`, :obj:`out_values`, and :obj:`rest_values`
are passed as arguments.
Note that these values do not contain data from the previous
iterations.

Returns:
Three tuples of iterators:
This function returns three tuples of iterators:
:obj:`in_values`, :obj:`out_values` and :obj:`rest_values`.

* :obj:`in_values`: A tuple of iterators. Each iterator \
returns a corresponding input value. \
For example, if :func:`func` takes \
:obj:`[in_val0], [in_val1]`, :obj:`next(in_values[0])` \
and :obj:`next(in_values[1])` will be \
:obj:`in_val0` and :obj:`in_val1`.
* :obj:`out_values`: A tuple of iterators. Each iterator \
returns a corresponding computed value. \
For example, if :func:`func` returns \
:obj:`([out_val0], [out_val1])`, :obj:`next(out_values[0])` \
and :obj:`next(out_values[1])` will be \
:obj:`out_val0` and :obj:`out_val1`.
* :obj:`rest_values`: A tuple of iterators. Each iterator \
returns a corresponding rest value. \
For example, if the :obj:`iterator` returns \
:obj:`[(in_val0, in_val1, rest_val0, rest_val1)]`, \
:obj:`next(rest_values[0])` \
and :obj:`next(rest_values[1])` will be \
:obj:`rest_val0` and :obj:`rest_val1`. \
If the input \
iterator does not give any rest values, this tuple \
will be empty.
"""

in_values, out_values, rest_values = unzip(
_apply(func, iterator, n_input, hook))

# in_values: iter of ([in_val0], [in_val1], ...)
# -> (iter of in_val0, iter of in_val1, ...)
in_values = tuple(map(_flatten, unzip(in_values)))

# out_values: iter of ([out_val0], [out_val1], ...)
# -> (iter of out_val0, iter of out_val1, ...)
out_values = tuple(map(_flatten, unzip(out_values)))

# rest_values: iter of ([rest_val0], [rest_val1], ...)
# -> (iter of rest_val0, iter of rest_val1, ...)
rest_values = tuple(map(_flatten, unzip(rest_values)))

return in_values, out_values, rest_values


def _apply(func, iterator, n_input, hook):
for batch in iterator:
# batch: [(in_val0, in_val1, ... , rest_val0, rest_val1, ...)] or
# [in_val]

in_values = []
rest_values = []
for sample in batch:
if isinstance(sample, tuple):
in_values.append(sample[0:n_input])
rest_values.append(sample[n_input:])
else:
in_values.append((sample,))
rest_values.append(tuple())

# in_values: [(in_val0, in_val1, ...)]
# -> ([in_val0], [in_val1], ...)
in_values = tuple(list(v) for v in zip(*in_values))

# rest_values: [(rest_val0, rest_val1, ...)]
# -> ([rest_val0], [rest_val1], ...)
rest_values = tuple(list(v) for v in zip(*rest_values))

# out_values: ([out_val0], [out_val1], ...) or [out_val]
out_values = func(*in_values)
if not isinstance(out_values, tuple):
# pred_values: [out_val] -> ([out_val],)
out_values = out_values,

if hook:
hook(in_values, out_values, rest_values)

yield in_values, out_values, rest_values


def _flatten(iterator):
return (sample for batch in iterator for sample in batch)
8 changes: 4 additions & 4 deletions chainercv/utils/iterator/progress_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ def __init__(self, n_total=None):
self.start = time.time()
self.n_processed = 0

def __call__(self, imgs, pred_values, gt_values):
self.n_processed += len(imgs)
def __call__(self, in_values, out_values, rest_values):
self.n_processed += len(in_values[0])
fps = self.n_processed / (time.time() - self.start)
if self.n_total is not None:
sys.stdout.write(
'\r{:d} of {:d} images, {:.2f} FPS'.format(
'\r{:d} of {:d} samples, {:.2f} samples/sec'.format(
self.n_processed, self.n_total, fps))
else:
sys.stdout.write(
'\r{:d} images, {:.2f} FPS'.format(
'\r{:d} samples, {:.2f} samples/sec'.format(
self.n_processed, fps))

sys.stdout.flush()
Loading