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

Add sequential_feature_extractor #342

Merged
merged 37 commits into from
Jul 17, 2017

Conversation

yuyu2172
Copy link
Member

I split PR from #265.

@yuyu2172 yuyu2172 added this to the v0.7 milestone Jul 14, 2017
@yuyu2172 yuyu2172 force-pushed the sequential-feature-extractor branch from 090940e to 3c031d4 Compare July 14, 2017 07:34
Copy link
Member

@Hakuyume Hakuyume left a comment

Choose a reason for hiding this comment

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

First comments


This class is a base class that can be used for an implementation of
a feature extractor model.
The link takes as an argument :obj:`layers` that specifies the computation
Copy link
Member

Choose a reason for hiding this comment

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

takes as -> takes ?

a feature extractor model.
The link takes as an argument :obj:`layers` that specifies the computation
conducted in :meth:`__call__`.
:obj:`layers` is a list or :obj:`collections.OrderedDict` of
Copy link
Member

Choose a reason for hiding this comment

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

:obj: -> :class:

conducted in :meth:`__call__`.
:obj:`layers` is a list or :obj:`collections.OrderedDict` of
callable objects called layers, which are going to be called sequentially
starting from the top to the end.
Copy link
Member

Choose a reason for hiding this comment

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

from the top to the end -> from the top to the bottom

:obj:`layers` is a list or :obj:`collections.OrderedDict` of
callable objects called layers, which are going to be called sequentially
starting from the top to the end.
A :obj:`chainer.Link` object in the sequence will be added as
Copy link
Member

Choose a reason for hiding this comment

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

:obj: -> :class:

:meth:`__call__` returns single or multiple features that are picked up
through a stream of computation.
These features can be specified by :obj:`layer_names`, which contains
the names of the layers whose output is collected.
Copy link
Member

Choose a reason for hiding this comment

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

output is -> outputs are

the names of the layers whose output is collected.
When :obj:`layer_names` is a string, single value is returned.
When :obj:`layer_names` is an iterable of strings, a tuple of values
will be returned. The order of the values is the same as the order of
Copy link
Member

Choose a reason for hiding this comment

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

will be -> is. You used is in L. 24.

if not isinstance(layers, collections.OrderedDict):
layers = collections.OrderedDict(
[('{}_{}'.format(layer.__class__.__name__, i), layer)
for i, layer in enumerate(layers)])
Copy link
Member

Choose a reason for hiding this comment

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

I don't like layer.__class__. Do we need to support iterables? (I mean OrderedDict is enough).

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice suggestion.
I dropped support for iterables.

else:
return_tuple = False
layer_names = [layer_names]
if any([name not in self._layers for name in layer_names]):
Copy link
Member

Choose a reason for hiding this comment

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

any(name not in self._layers for name in layer_names)

layer_names = list(self._layers.keys())[-1]

if (not isinstance(layer_names, str) and
all([isinstance(name, str) for name in layer_names])):
Copy link
Member

Choose a reason for hiding this comment

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

all(isinstance(name, str) for name in layer_names)

# The biggest index among indices of the layers that are included
# in self._layer_names.
last_index = max([list(self._layers.keys()).index(name) for
name in self._layer_names])
Copy link
Member

Choose a reason for hiding this comment

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

How about last_index = max(map(list(self._layers.keys()).index, self._layer_names)) ?
This version calls list(self._layers.keys()) only once.

Copy link
Member Author

Choose a reason for hiding this comment

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

I personally find this more readable.

        layers_keys = list(self._layers.keys())
        last_index = max(
            [layer_keys.index(name) for name in self._layer_names])

Copy link
Member

@Hakuyume Hakuyume Jul 14, 2017

Choose a reason for hiding this comment

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

Making a temporal list layers_keys is good. Note that we can replace max([...]) with max(...).


if self._return_tuple:
features = tuple(
[features[name] for name in self._layer_names])
Copy link
Member

Choose a reason for hiding this comment

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

tuple([...]) -> tuple(...)

>>> feat1, feat2 = model(x)
>>> # The layer_names can be dynamically changed.
>>> model.layer_names = 'l3'
>>> # This is an output of layer l1.
Copy link
Member

Choose a reason for hiding this comment

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

l1 -> l3

>>> ('l3', L.Linear(None, 10))])
>>> model = SequentialFeatureExtractor(layers, ['l2_relu', 'l1_relu'])
>>> # These are outputs of layer l2_relu and l1_relu.
>>> feat1, feat2 = model(x)
Copy link
Member

Choose a reason for hiding this comment

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

feat1, feat2 -> feat2, feat1? (feat1 sounds a feature from l1_relu)

return features

def copy(self):
ret = super(SequentialFeatureExtractor, self).copy()
Copy link
Member

Choose a reason for hiding this comment

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

How about this?

ret = super(SequentialFeatureExtractor, self).copy()
for name in ret._layers.keys():
    if hasattr(ret, name):
        ret._layers[name] = ret[name]
return ret

out = self.link(x)

self.assertEqual(len(out), 3)
self.assertIsInstance(out[0], chainer.Variable)
Copy link
Member

Choose a reason for hiding this comment

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

How about using for?


def check_copy(self):
copied = self.link.copy()
self.assertIs(copied.l1, copied.layers['l1'])
Copy link
Member

Choose a reason for hiding this comment

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

layers -> _layers

…ure-extractor

Use init_scope in SequentialFeatureExtractor
@yuyu2172
Copy link
Member Author

Related yuyu2172#2


Examples:

>>> import collections
Copy link
Member

Choose a reason for hiding this comment

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

I forgot to remove this line. Could you remove this?

A :class:`chainer.Link` object in the sequence will be added as
a child link of this link.

:meth:`__call__` returns single or multiple features that are picked up
Copy link
Member

Choose a reason for hiding this comment

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

that -> those

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that is OK.
It works like which.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, it is OK.

@Hakuyume
Copy link
Member

How about using features instead of layer_names?

@yuyu2172
Copy link
Member Author

yuyu2172 commented Jul 15, 2017

I think feature_names is better because it is more explicit than features.
It seems that feature_names is better than layer_names.

The word layer was used because I used a variable layers previously.

@Hakuyume
Copy link
Member

Yes, feature_names is better. Could you replace layer_names with feature_names?

layer_names (string or iterable of strings):
Names of layers whose outputs will be collected in
feature_names (string or iterable of strings):
Names of feature that are collected during
Copy link
Member

Choose a reason for hiding this comment

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

feature -> features?

@Hakuyume Hakuyume force-pushed the sequential-feature-extractor branch from 9a3fdee to faa7f22 Compare July 16, 2017 01:40
@yuyu2172
Copy link
Member Author

This needs to deal with a situation when feature_names contains the same strings.

@yuyu2172
Copy link
Member Author

I mean add a test in that situation.

@Hakuyume
Copy link
Member

This needs to deal with a situation when feature_names contains the same strings.

You can add that test as follows.

...
    {'feature_names': ('l2', 'l1', 'f2')},
    {'feature_names': ('l2', 'l2')},
)

Copy link
Member

@Hakuyume Hakuyume left a comment

Choose a reason for hiding this comment

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

LGTM

@Hakuyume Hakuyume merged commit 9240a2c into chainer:master Jul 17, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants