-
Notifications
You must be signed in to change notification settings - Fork 146
Feature/pubfig83 #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feature/pubfig83 #46
Changes from 4 commits
41d0948
87ba532
16eb447
09f8f5b
62443e2
708e034
cf73248
94c4c56
5c9c94e
7fe4454
38c329c
6597105
524ac52
65f302b
573a4a1
bc20c27
f5743a9
808aab6
15b3369
3bdcfdf
fb46221
1a5b30e
2f42a23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,4 @@ | |
| *.tar | ||
| *.tgz | ||
| *.egg-info | ||
| *.DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| # Dan Yamins <yamins@mit.edu> | ||
| # James Bergstra <bergstra@rowland.harvard.edu> | ||
| # Nicolas Pinto <pinto@rowland.harvard.edu> | ||
| # Giovani Chiachia <chiachia@rowland.harvard.edu> | ||
|
|
||
| # License: Simplified BSD | ||
|
|
||
|
|
@@ -30,11 +31,15 @@ | |
| from glob import glob | ||
| import hashlib | ||
|
|
||
| import larray | ||
| from data_home import get_data_home | ||
| from utils import download, extract | ||
| from utils import download, extract, int_labels | ||
| import utils | ||
| import utils.image | ||
| from utils.image import ImgLoader | ||
|
|
||
| from sklearn import cross_validation | ||
| import numpy as np | ||
|
|
||
| class PubFig83(object): | ||
| """PubFig83 Face Dataset | ||
|
|
@@ -82,16 +87,8 @@ class PubFig83(object): | |
| def __init__(self, meta=None): | ||
| if meta is not None: | ||
| self._meta = meta | ||
|
|
||
| self.name = self.__class__.__name__ | ||
|
|
||
| try: | ||
| from joblib import Memory | ||
| mem = Memory(cachedir=self.home('cache')) | ||
| self._get_meta = mem.cache(self._get_meta) | ||
| except ImportError: | ||
| pass | ||
|
|
||
| def home(self, *suffix_paths): | ||
| return path.join(get_data_home(), self.name, *suffix_paths) | ||
|
|
||
|
|
@@ -129,29 +126,71 @@ def fetch(self, download_if_missing=True): | |
|
|
||
| @property | ||
| def meta(self): | ||
| if hasattr(self, '_meta'): | ||
| return self._meta | ||
| else: | ||
| if not hasattr(self, '_meta'): | ||
| self.fetch(download_if_missing=True) | ||
| self._meta = self._get_meta() | ||
| return self._meta | ||
| return self._meta | ||
|
|
||
| def _get_meta(self): | ||
| names = sorted(os.listdir(self.home('pubfig83'))) | ||
| names2 = sorted(os.listdir(self.home('pubfig83'))) | ||
| genders = self._GENDERS | ||
| assert len(names) == len(genders) | ||
| assert len(names2) == len(genders) | ||
| meta = [] | ||
| ind = 0 | ||
| for gender, name in zip(genders, names): | ||
| for gender, name in zip(genders, names2): | ||
| img_filenames = sorted(glob(self.home('pubfig83', name, '*.jpg'))) | ||
| for img_filename in img_filenames: | ||
| img_data = open(img_filename, 'rb').read() | ||
| sha1 = hashlib.sha1(img_data).hexdigest() | ||
| meta.append(dict(gender=gender, name=name, id=ind, | ||
| filename=img_filename, sha1=sha1)) | ||
| ind += 1 | ||
|
|
||
| return meta | ||
|
|
||
| @property | ||
| def names(self): | ||
| if not hasattr(self, '_names'): | ||
| self._names = np.array([self.meta[ind]['name'] for ind in xrange(len(self.meta))]) | ||
| return self._names | ||
|
|
||
| @property | ||
| def classification_splits(self): | ||
| """ | ||
| generates splits and attaches them in the "splits" attribute | ||
|
|
||
| """ | ||
| if not hasattr(self, '_classification_splits'): | ||
| self._classification_splits = self._generate_classification_splits() | ||
| return self._classification_splits | ||
|
|
||
| def _generate_classification_splits(self): | ||
| meta = self.meta | ||
| rng = np.random.RandomState(0) | ||
| classification_splits = {} | ||
|
|
||
| splits = {} | ||
| labels = np.unique(self.names) | ||
| for label in labels: | ||
| samples_to_consider = (self.names == label) | ||
| samples_to_consider = np.where(samples_to_consider)[0] | ||
| assert len(samples_to_consider) >= 100 | ||
| p = rng.permutation(len(samples_to_consider)) | ||
| if 'Test' not in splits: | ||
| splits['Test'] = [] | ||
| splits['Test'].extend(samples_to_consider[p[:10]]) | ||
| remainder = samples_to_consider[p[10:]] | ||
| for _ind in range(5): | ||
| p = rng.permutation(len(remainder)) | ||
| if 'Train%d' % _ind not in splits: | ||
| splits['Train%d' % _ind] = [] | ||
| splits['Train%d' % _ind].extend(remainder[p[:80]].copy()) | ||
| if 'Validate%d' % _ind not in splits: | ||
| splits['Validate%d' % _ind] = [] | ||
| splits['Validate%d' % _ind].extend(remainder[p[80:90]].copy()) | ||
|
|
||
| return splits | ||
|
|
||
| # ------------------------------------------------------------------------ | ||
| # -- Dataset Interface: clean_up() | ||
| # ------------------------------------------------------------------------ | ||
|
|
@@ -165,23 +204,42 @@ def clean_up(self): | |
| # ------------------------------------------------------------------------ | ||
|
|
||
| def image_path(self, m): | ||
| return self.home('pubfig83', m['name'], m['jpgfile']) | ||
| return self.home('pubfig83', m['name'], m['filename']) | ||
| #return self.home('pubfig83', m['name'], m['jpgfile']) | ||
|
|
||
| # ------------------------------------------------------------------------ | ||
| # -- Standard Tasks | ||
| # ------------------------------------------------------------------------ | ||
|
|
||
| def raw_recognition_task(self): | ||
| names = [m['name'] for m in self.meta] | ||
| paths = [self.image_path(m) for m in self.meta] | ||
| labels = utils.int_labels(names) | ||
| return paths, labels | ||
| def raw_classification_task(self, split=None): | ||
| """ | ||
| :param split: an integer from 0 to 9 inclusive. | ||
| :param split_role: either 'train' or 'test' | ||
|
|
||
| :returns: either all samples (when split_k=None) or the specific | ||
| train/test split | ||
| """ | ||
|
|
||
| if split is not None: | ||
| inds = self.classification_splits[split] | ||
| else: | ||
| inds = range(len(self.meta)) | ||
| names = self.names[inds] | ||
| paths = [self.meta[ind]['filename'] for ind in inds] | ||
| labels = int_labels(names) | ||
| return paths, labels, inds | ||
|
|
||
| def raw_gender_task(self): | ||
| genders = [m['gender'] for m in self.meta] | ||
| paths = [self.image_path(m) for m in self.meta] | ||
| return paths, utils.int_labels(genders) | ||
|
|
||
| def img_classification_task(self, dtype='uint8', split=None): | ||
| img_paths, labels, inds = self.raw_classification_task(split=split) | ||
| imgs = larray.lmap(ImgLoader(shape=(100, 100, 3), dtype=dtype, mode='RGB'), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question here, why hardcoding 100, 100 ? this could be done upstream
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again ... this is a "standard" issue; This basically checks that the images are in the right format and gives people a standard way to load them. I agree it could be handled downstream ... in fact, we overload the equivalent method in the lfw stuff we're doing now. I think the question is really how hard you want to make it to NOT use the "standard." We could not only make the size variable, but also the mode (e.g. allow people to pass a desired mode and then do the conversion for them, and also various resizing option variables, &c...) If your feeling is to give people maximum flexibility through this particular interface -- and set the "standards" via easily-changeable default values -- that's fine with me. |
||
| img_paths) | ||
| return imgs, labels | ||
|
|
||
|
|
||
| # ------------------------------------------------------------------------ | ||
| # -- Drivers for skdata/bin executables | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason to have hardcoded values here (eg 90, 10, 80) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally preferred the variable ntest, ntrain approach, like you suggest. But, on the other hand, it might be good to have "standard" splits. I've occasionally heard @zstone and @davidcox talking about standard 90/10 splits. Of course, we COULD make them variables and then just set defaults. I think it depends on how the creators of this dataset intended to be used -- @npinto, you and @zstone worked on this together (?) so if you think giving people guidance that suggests that non-standard-sized splits are "OK", then that seems fine to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I support the creation of a set of standard splits of any dataset, but I would favor canonizing a minimal, language-agnostic materialization of said standard splits rather than defining them implicitly by code in a specific language that must be run correctly in a specific environment to regenerate the right splits. Concretely, I would advocate looking for the simplest-possible JSON format that would completely specify, say, ten 90/10 splits with reference to relative image paths within a canonical archive of a dataset.
That's just my opinion, though, and it may not fit the way scikit-data already works!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zstone I'm not sure if it's obvious how to use JSON inside scikit-data in a natural way. however, could you comment on the split scheme currently proposed in the code? I just added support for non-standard splits. I put the split related data in the dataset init, because we might want to have the split data travel with the dataset instance. The current code has the idea that to change splits, you have to re-instantiate a new instance (or I guess, monkey around inside the code; probably the various attributes should be private). What do you think?