Beispiel #1
0
    def _choose_action(file_infos):
        import kwarray
        file_infos = kwarray.shuffle(file_infos, rng=0)
        n_keep = (len(file_infos) // factor) + 1

        for info in file_infos[:n_keep]:
            info['action'] = 'keep'
        for info in file_infos[n_keep:]:
            info['action'] = 'delete'
def the_core_dataset_backend():

    import kwcoco
    dset = kwcoco.CocoDataset.demo('shapes2')

    # Make data slightly tider for display
    for ann in dset.dataset['annotations']:
        ann.pop('segmentation', None)
        ann.pop('keypoints', None)
        ann.pop('area', None)

    for cat in dset.dataset['categories']:
        cat.pop('keypoints', None)

    for img in dset.dataset['images']:
        img.pop('channels', None)

    dset.reroot(dirname(dset.fpath), absolute=False)
    import kwarray
    dset.remove_annotations(kwarray.shuffle(list(dset.anns.keys()))[10:])

    print('dset.dataset = {}'.format(ub.repr2(dset.dataset, nl=2)))
Beispiel #3
0
def _coerce_datasets(config):
    import netharn as nh
    import ndsampler
    import numpy as np
    from torchvision import transforms
    coco_datasets = nh.api.Datasets.coerce(config)
    print('coco_datasets = {}'.format(ub.repr2(coco_datasets, nl=1)))
    for tag, dset in coco_datasets.items():
        dset._build_hashid(hash_pixels=False)

    workdir = ub.ensuredir(ub.expandpath(config['workdir']))
    samplers = {
        tag: ndsampler.CocoSampler(dset, workdir=workdir, backend=config['sampler_backend'])
        for tag, dset in coco_datasets.items()
    }

    for tag, sampler in ub.ProgIter(list(samplers.items()), desc='prepare frames'):
        sampler.frames.prepare(workers=config['workers'])

    # TODO: basic ndsampler torch dataset, likely has to support the transforms
    # API, bleh.

    transform = transforms.Compose([
        transforms.Resize(config['input_dims']),
        transforms.CenterCrop(config['input_dims']),
        transforms.ToTensor(),
        transforms.Lambda(lambda x: x.mul(255))
    ])

    torch_datasets = {
        key: SamplerDataset(
            sapmler, transform=transform,
            # input_dims=config['input_dims'],
            # augmenter=config['augmenter'] if key == 'train' else None,
        )
        for key, sapmler in samplers.items()
    }
    # self = torch_dset = torch_datasets['train']

    if config['normalize_inputs']:
        # Get stats on the dataset (todo: turn off augmentation for this)
        import kwarray
        _dset = torch_datasets['train']
        stats_idxs = kwarray.shuffle(np.arange(len(_dset)), rng=0)[0:min(1000, len(_dset))]
        stats_subset = torch.utils.data.Subset(_dset, stats_idxs)

        cacher = ub.Cacher('dset_mean', cfgstr=_dset.input_id + 'v3')
        input_stats = cacher.tryload()

        from netharn.data.channel_spec import ChannelSpec
        channels = ChannelSpec.coerce(config['channels'])

        if input_stats is None:
            # Use parallel workers to load data faster
            from netharn.data.data_containers import container_collate
            from functools import partial
            collate_fn = partial(container_collate, num_devices=1)

            loader = torch.utils.data.DataLoader(
                stats_subset,
                collate_fn=collate_fn,
                num_workers=config['workers'],
                shuffle=True,
                batch_size=config['batch_size'])

            # Track moving average of each fused channel stream
            channel_stats = {key: nh.util.RunningStats()
                             for key in channels.keys()}
            assert len(channel_stats) == 1, (
                'only support one fused stream for now')
            for batch in ub.ProgIter(loader, desc='estimate mean/std'):
                if isinstance(batch, (tuple, list)):
                    inputs = {'rgb': batch[0]}  # make assumption
                else:
                    inputs = batch['inputs']

                for key, val in inputs.items():
                    try:
                        for part in val.numpy():
                            channel_stats[key].update(part)
                    except ValueError:  # final batch broadcast error
                        pass

            perchan_input_stats = {}
            for key, running in channel_stats.items():
                running = ub.peek(channel_stats.values())
                perchan_stats = running.simple(axis=(1, 2))
                perchan_input_stats[key] = {
                    'std': perchan_stats['mean'].round(3),
                    'mean': perchan_stats['std'].round(3),
                }

            input_stats = ub.peek(perchan_input_stats.values())
            cacher.save(input_stats)
    else:
        input_stats = {}

    torch_loaders = {
        tag: dset.make_loader(
            batch_size=config['batch_size'],
            num_batches=config['num_batches'],
            num_workers=config['workers'],
            shuffle=(tag == 'train'),
            balance=(config['balance'] if tag == 'train' else None),
            pin_memory=True)
        for tag, dset in torch_datasets.items()
    }

    dataset_info = {
        'torch_datasets': torch_datasets,
        'torch_loaders': torch_loaders,
        'input_stats': input_stats
    }
    return dataset_info
Beispiel #4
0
def setup_harn(cmdline=True, **kw):
    """
    Ignore:
        >>> from object_detection import *  # NOQA
        >>> cmdline = False
        >>> kw = {
        >>>     'train_dataset': '~/data/VOC/voc-trainval.mscoco.json',
        >>>     'vali_dataset': '~/data/VOC/voc-test-2007.mscoco.json',
        >>> }
        >>> harn = setup_harn(**kw)
    """
    import ndsampler
    from ndsampler import coerce_data
    # Seed other global rngs just in case something uses them under the hood
    kwarray.seed_global(1129989262, offset=1797315558)

    config = DetectFitConfig(default=kw, cmdline=cmdline)

    nh.configure_hacks(config)  # fix opencv bugs
    ub.ensuredir(config['workdir'])

    # Load ndsampler.CocoDataset objects from info in the config
    subsets = coerce_data.coerce_datasets(config)

    samplers = {}
    for tag, subset in subsets.items():
        print('subset = {!r}'.format(subset))
        sampler = ndsampler.CocoSampler(subset, workdir=config['workdir'])
        samplers[tag] = sampler

    torch_datasets = {
        tag: DetectDataset(
            sampler,
            input_dims=config['input_dims'],
            augment=config['augment'] if (tag == 'train') else False,
        )
        for tag, sampler in samplers.items()
    }

    print('make loaders')
    loaders_ = {
        tag:
        torch.utils.data.DataLoader(dset,
                                    batch_size=config['batch_size'],
                                    num_workers=config['workers'],
                                    shuffle=(tag == 'train'),
                                    collate_fn=nh.data.collate.padded_collate,
                                    pin_memory=True)
        for tag, dset in torch_datasets.items()
    }
    # for x in ub.ProgIter(loaders_['train']):
    #     pass

    if config['normalize_inputs']:
        # Get stats on the dataset (todo: turn off augmentation for this)
        _dset = torch_datasets['train']
        stats_idxs = kwarray.shuffle(np.arange(len(_dset)),
                                     rng=0)[0:min(1000, len(_dset))]
        stats_subset = torch.utils.data.Subset(_dset, stats_idxs)
        cacher = ub.Cacher('dset_mean', cfgstr=_dset.input_id + 'v2')
        input_stats = cacher.tryload()
        if input_stats is None:
            # Use parallel workers to load data faster
            loader = torch.utils.data.DataLoader(
                stats_subset,
                collate_fn=nh.data.collate.padded_collate,
                num_workers=config['workers'],
                shuffle=True,
                batch_size=config['batch_size'])
            # Track moving average
            running = nh.util.RunningStats()
            for batch in ub.ProgIter(loader, desc='estimate mean/std'):
                try:
                    running.update(batch['im'].numpy())
                except ValueError:  # final batch broadcast error
                    pass
            input_stats = {
                'std': running.simple(axis=None)['mean'].round(3),
                'mean': running.simple(axis=None)['std'].round(3),
            }
            cacher.save(input_stats)
    else:
        input_stats = None
    print('input_stats = {!r}'.format(input_stats))

    initializer_ = nh.Initializer.coerce(config, leftover='kaiming_normal')
    print('initializer_ = {!r}'.format(initializer_))

    arch = config['arch']
    if arch == 'yolo2':

        if False:
            dset = samplers['train'].dset
            print('dset = {!r}'.format(dset))
            # anchors = yolo2.find_anchors(dset)

        anchors = np.array([(1.3221, 1.73145), (3.19275, 4.00944),
                            (5.05587, 8.09892), (9.47112, 4.84053),
                            (11.2364, 10.0071)])

        classes = samplers['train'].classes
        model_ = (yolo2.Yolo2, {
            'classes': classes,
            'anchors': anchors,
            'conf_thresh': 0.001,
            'nms_thresh': 0.5 if not ub.argflag('--eav') else 0.4
        })
        model = model_[0](**model_[1])
        model._initkw = model_[1]

        criterion_ = (
            yolo2.YoloLoss,
            {
                'coder': model.coder,
                'seen': 0,
                'coord_scale': 1.0,
                'noobject_scale': 1.0,
                'object_scale': 5.0,
                'class_scale': 1.0,
                'thresh': 0.6,  # iou_thresh
                # 'seen_thresh': 12800,
            })
    else:
        raise KeyError(arch)

    scheduler_ = nh.Scheduler.coerce(config)
    print('scheduler_ = {!r}'.format(scheduler_))

    optimizer_ = nh.Optimizer.coerce(config)
    print('optimizer_ = {!r}'.format(optimizer_))

    dynamics_ = nh.Dynamics.coerce(config)
    print('dynamics_ = {!r}'.format(dynamics_))

    xpu = nh.XPU.coerce(config['xpu'])
    print('xpu = {!r}'.format(xpu))

    import sys

    hyper = nh.HyperParams(
        **{
            'nice':
            config['nice'],
            'workdir':
            config['workdir'],
            'datasets':
            torch_datasets,
            'loaders':
            loaders_,
            'xpu':
            xpu,
            'model':
            model,
            'criterion':
            criterion_,
            'initializer':
            initializer_,
            'optimizer':
            optimizer_,
            'dynamics':
            dynamics_,

            # 'optimizer': (torch.optim.SGD, {
            #     'lr': lr_step_points[0],
            #     'momentum': 0.9,
            #     'dampening': 0,
            #     # multiplying by batch size was one of those unpublished details
            #     'weight_decay': decay * simulated_bsize,
            # }),
            'scheduler':
            scheduler_,
            'monitor': (
                nh.Monitor,
                {
                    'minimize': ['loss'],
                    # 'maximize': ['mAP'],
                    'patience': config['patience'],
                    'max_epoch': config['max_epoch'],
                    'smoothing': .6,
                }),
            'other': {
                # Other params are not used internally, so you are free to set any
                # extra params specific to your algorithm, and still have them
                # logged in the hyperparam structure. For YOLO this is `ovthresh`.
                'batch_size': config['batch_size'],
                'nice': config['nice'],
                'ovthresh': config['ovthresh'],  # used in mAP computation
            },
            'extra': {
                'config': ub.repr2(config.asdict()),
                'argv': sys.argv,
            }
        })
    print('hyper = {!r}'.format(hyper))
    print('make harn')
    harn = DetectHarn(hyper=hyper)
    harn.preferences.update({
        'num_keep': 2,
        'keep_freq': 30,
        'export_modules': ['netharn'],  # TODO
        'prog_backend': 'progiter',  # alternative: 'tqdm'
        'keyboard_debug': True,
    })
    harn.intervals.update({
        'log_iter_train': 50,
    })
    harn.fit_config = config
    print('harn = {!r}'.format(harn))
    print('samplers = {!r}'.format(samplers))
    return harn
Beispiel #5
0
    def mine_negatives(self, dvecs, labels, num=1, mode='hardest', eps=1e-9):
        """
        triplets =
             are a selection of anchor, positive, and negative annots
             chosen to be the hardest for each annotation (with a valid
             pos and neg partner) in the batch.

        Args:
            dvecs (Tensor): descriptor vectors for each item
            labels (Tensor): id-label for each descriptor vector
            num (int, default=1): number of negatives per positive combination
            mode (str, default='hardest'): method for selecting negatives
            eps (float, default=1e9): distance threshold for near duplicates

        Returns:
            Dict: info: containing neg_dists, pos_dists, triples, and dist

        CommandLine:
            xdoctest -m netharn.criterions.triplet TripletLoss.mine_negatives:1 --profile

        Example:
            >>> from netharn.criterions.triplet import *
            >>> dvecs = torch.FloatTensor([
            ...     # Individual 1
            ...     [1.0, 0.0, 0.0, ],
            ...     [0.9, 0.1, 0.0, ],  # Looks like 2 [1]
            ...     # Individual 2
            ...     [0.0, 1.0, 0.0, ],
            ...     [0.0, 0.9, 0.1, ],  # Looks like 3 [3]
            ...     # Individual 3
            ...     [0.0, 0.0, 1.0, ],
            ...     [0.1, 0.0, 0.9, ],  # Looks like 1 [5]
            >>> ])
            >>> import itertools as it
            >>> labels = torch.LongTensor([0, 0, 1, 1, 2, 2])
            >>> num = 1
            >>> info = TripletLoss().mine_negatives(dvecs, labels, num)
            >>> print('info = {!r}'.format(info))
            >>> assert torch.all(info['pos_dists'] < info['neg_dists'])

        Example:
            >>> # xdoxctest: +SKIP
            >>> import itertools as it
            >>> p = 3
            >>> k = 10
            >>> d = p
            >>> mode = 'consistent'
            >>> mode = 'hardest'
            >>> for p, k in it.product(range(3, 13), range(2, 13)):
            >>>     d = p
            >>>     def make_individual_dvecs(i):
            >>>         vecs = torch.zeros((k, d))
            >>>         vecs[:, i] = torch.linspace(0.9, 1.0, k)
            >>>         return vecs
            >>>     dvecs = torch.cat([make_individual_dvecs(i) for i in range(p)], dim=0)
            >>>     labels = torch.LongTensor(np.hstack([[i] * k for i in range(p)]))
            >>>     num = 1
            >>>     info = TripletLoss().mine_negatives(dvecs, labels, num, mode=mode)
            >>>     if mode.startswith('hard'):
            >>>         assert torch.all(info['pos_dists'] < info['neg_dists'])
            >>>     base = k
            >>>     for a, p, n in info['triples']:
            >>>         x = a // base
            >>>         y = p // base
            >>>         z = n // base
            >>>         assert x == y, str([a, p, n])
            >>>         assert x != z, str([a, p, n])
        """
        import kwarray
        dist = all_pairwise_distances(dvecs, squared=True, approx=True)

        with torch.no_grad():
            labels_ = labels.numpy()

            symmetric = False
            pos_adjm = labels_to_adjacency_matrix(labels_, symmetric=symmetric,
                                                  diagonal=False)

            if symmetric:
                neg_adjm = 1 - pos_adjm
            else:
                neg_adjm = 1 - pos_adjm - pos_adjm.T
            np.fill_diagonal(neg_adjm, 0)

            # ignore near duplicates
            dist_ = dist.data.cpu().numpy()
            is_near_dup = np.where(dist_ < eps)
            neg_adjm[is_near_dup] = 0
            pos_adjm[is_near_dup] = 0

            # Filter out any anchor row that does not have both a positive and
            # a negative match
            flags = np.any(pos_adjm, axis=1) & np.any(neg_adjm, axis=1)

            anchors_idxs = np.where(flags)[0]
            pos_adjm_ = pos_adjm[flags].astype(bool)
            neg_adjm_ = neg_adjm[flags].astype(bool)

            if mode == 'hardest':
                # Order each anchors positives and negatives by increasing distance
                sortx_ = dist_[flags].argsort(axis=1)
                pos_cands_list = [x[m[x]] for x, m in zip(sortx_, pos_adjm_)]
                neg_cands_list = [x[n[x]] for x, n in zip(sortx_, neg_adjm_)]
                triples = []
                backup = []
                _iter = zip(anchors_idxs, pos_cands_list, neg_cands_list)
                for (anchor_idx, pos_cands, neg_cands) in _iter:
                    # Take `num` hardest negative pairs for each positive pair
                    num_ = min(len(neg_cands), len(pos_cands), num)
                    pos_idxs = pos_cands
                    neg_idxs = neg_cands[:num_]

                    anchor_dists = dist_[anchor_idx]

                    if True:
                        pos_dists = anchor_dists[pos_idxs]
                        neg_dists = anchor_dists[neg_idxs]

                        # Ignore any triple that satisfies the margin
                        # constraint
                        losses = pos_dists[:, None] - neg_dists[None, :] + self.margin
                        ilocs, jlocs = np.where(losses > 0)

                        if len(ilocs) > 0:
                            valid_pos_idxs = pos_idxs[ilocs].tolist()
                            valid_neg_idxs = neg_idxs[jlocs].tolist()

                            for pos_idx, neg_idx in zip(valid_pos_idxs, valid_neg_idxs):
                                triples.append((anchor_idx, pos_idx, neg_idx))
                        elif len(triples) == 0:
                            # Take everything because we might need a backup
                            for pos_idx, neg_idx in it.product(pos_idxs, neg_idxs):
                                backup.append((anchor_idx, pos_idx, neg_idx))
                    else:
                        for pos_idx, neg_idx in it.product(pos_idxs, neg_idxs):
                            # Only take items that will contribute positive loss
                            d_ap = anchor_dists[pos_idx]
                            d_an = anchor_dists[neg_idx]
                            loss = d_ap - d_an + self.margin
                            if loss > 0:
                                triples.append((anchor_idx, pos_idx, neg_idx))
                            elif len(triples) == 0:
                                backup.append((anchor_idx, pos_idx, neg_idx))

            elif mode == 'moderate':
                pos_cands_list = [np.where(m)[0].tolist() for m in pos_adjm_]
                neg_cands_list = [np.where(n)[0].tolist() for n in neg_adjm_]
                triples = []
                backup = []
                _iter = zip(anchors_idxs, pos_cands_list, neg_cands_list)
                for (anchor_idx, pos_cands, neg_cands) in _iter:
                    # Take `num` moderate negative pairs for each positive pair
                    # Only take items that will contribute positive loss
                    # but try not to take any that are too hard.
                    anchor_dists = dist_[anchor_idx]
                    neg_dists = anchor_dists[neg_cands]

                    for pos_idx in pos_cands:
                        pos_dist = anchor_dists[pos_idx]
                        losses = pos_dist - neg_dists + self.margin

                        # valid_negs = np.where((losses < margin) & (losses > 0))[0]
                        valid_negs = np.where(losses > 0)[0]
                        if len(valid_negs):
                            neg_idx = neg_cands[np.random.choice(valid_negs)]
                            triples.append((anchor_idx, pos_idx, neg_idx))
                        elif len(triples) == 0:
                            # We try to always return valid triples so, create
                            # a backup set in case we cant find any valid
                            # candidates
                            neg_idx = neg_cands[0]
                            backup.append((anchor_idx, pos_idx, neg_idx))

            elif mode == 'consistent':
                # Choose the same triples every time
                rng = kwarray.ensure_rng(0)
                pos_cands_list = [kwarray.shuffle(np.where(m)[0], rng=rng)
                                  for m in pos_adjm_]
                neg_cands_list = [kwarray.shuffle(np.where(n)[0], rng=rng)
                                  for n in neg_adjm_]
                triples = []
                _iter = zip(anchors_idxs, pos_cands_list, neg_cands_list)
                for (anchor_idx, pos_cands, neg_cands) in _iter:
                    num_ = min(len(neg_cands), len(pos_cands), num)
                    pos_idxs = pos_cands
                    for pos_idx in pos_idxs:
                        neg_idx = rng.choice(neg_cands)
                        triples.append((anchor_idx, pos_idx, neg_idx))
                rng.shuffle(triples)
            else:
                raise KeyError(mode)

            if len(triples) == 0:
                triples = backup
                if len(backup) == 0:
                    raise RuntimeError('unable to mine triples')

            triples = np.array(triples)
            A, P, N = triples.T

            if 0 and __debug__:
                if labels is not None:
                    for a, p, n in triples:
                        na_ = labels[a]
                        np_ = labels[p]
                        nn_ = labels[n]
                        assert na_ == np_
                        assert np_ != nn_

        # Note these distances are approximate distances, but they should be
        # good enough to backprop through (if not see alternate commented code).
        pos_dists = dist[A, P]
        neg_dists = dist[A, N]
        # pos_dists = (dvecs[A] - dvecs[P]).pow(2).sum(1)
        # neg_dists = (dvecs[A] - dvecs[N]).pow(2).sum(1)

        info = {
            'pos_dists': pos_dists,
            'neg_dists': neg_dists,
            'triples': triples,
            'dist': dist,
        }
        return info
Beispiel #6
0
def setup_harn(cmdline=True, **kw):
    """
    CommandLine:
        xdoctest -m netharn.examples.segmentation setup_harn

    Example:
        >>> # xdoctest: +REQUIRES(--slow)
        >>> kw = {'workers': 0, 'xpu': 'cpu', 'batch_size': 2}
        >>> cmdline = False
        >>> # Just sets up the harness, does not do any heavy lifting
        >>> harn = setup_harn(cmdline=cmdline, **kw)
        >>> #
        >>> harn.initialize()
        >>> #
        >>> batch = harn._demo_batch(tag='train')
        >>> epoch_metrics = harn._demo_epoch(tag='vali', max_iter=2)
    """
    import sys
    import ndsampler
    import kwarray
    # kwarray.seed_global(2108744082)

    config = SegmentationConfig(default=kw)
    config.load(cmdline=cmdline)
    nh.configure_hacks(config)  # fix opencv bugs

    coco_datasets = nh.api.Datasets.coerce(config)
    print('coco_datasets = {}'.format(ub.repr2(coco_datasets)))
    for tag, dset in coco_datasets.items():
        dset._build_hashid(hash_pixels=False)

    workdir = ub.ensuredir(ub.expandpath(config['workdir']))
    samplers = {
        tag: ndsampler.CocoSampler(dset,
                                   workdir=workdir,
                                   backend=config['backend'])
        for tag, dset in coco_datasets.items()
    }

    for tag, sampler in ub.ProgIter(list(samplers.items()),
                                    desc='prepare frames'):
        try:
            sampler.frames.prepare(workers=config['workers'])
        except AttributeError:
            pass

    torch_datasets = {
        tag: SegmentationDataset(
            sampler,
            config['input_dims'],
            input_overlap=((tag == 'train') and config['input_overlap']),
            augmenter=((tag == 'train') and config['augmenter']),
        )
        for tag, sampler in samplers.items()
    }
    torch_loaders = {
        tag: torch_data.DataLoader(dset,
                                   batch_size=config['batch_size'],
                                   num_workers=config['workers'],
                                   shuffle=(tag == 'train'),
                                   drop_last=True,
                                   pin_memory=True)
        for tag, dset in torch_datasets.items()
    }

    if config['class_weights']:
        mode = config['class_weights']
        dset = torch_datasets['train']
        class_weights = _precompute_class_weights(dset,
                                                  mode=mode,
                                                  workers=config['workers'])
        class_weights = torch.FloatTensor(class_weights)
        class_weights[dset.classes.index('background')] = 0
    else:
        class_weights = None

    if config['normalize_inputs']:
        stats_dset = torch_datasets['train']
        stats_idxs = kwarray.shuffle(np.arange(len(stats_dset)),
                                     rng=0)[0:min(1000, len(stats_dset))]
        stats_subset = torch.utils.data.Subset(stats_dset, stats_idxs)
        cacher = ub.Cacher('dset_mean', cfgstr=stats_dset.input_id + 'v3')
        input_stats = cacher.tryload()
        if input_stats is None:
            loader = torch.utils.data.DataLoader(
                stats_subset,
                num_workers=config['workers'],
                shuffle=True,
                batch_size=config['batch_size'])
            running = nh.util.RunningStats()
            for batch in ub.ProgIter(loader, desc='estimate mean/std'):
                try:
                    running.update(batch['im'].numpy())
                except ValueError:  # final batch broadcast error
                    pass
            input_stats = {
                'std': running.simple(axis=None)['mean'].round(3),
                'mean': running.simple(axis=None)['std'].round(3),
            }
            cacher.save(input_stats)
    else:
        input_stats = {}

    print('input_stats = {!r}'.format(input_stats))

    # TODO: infer numbr of channels
    model_ = (SegmentationModel, {
        'arch': config['arch'],
        'input_stats': input_stats,
        'classes': torch_datasets['train'].classes.__json__(),
        'in_channels': 3,
    })

    initializer_ = nh.Initializer.coerce(config)
    # if config['init'] == 'cls':
    #     initializer_ = model_[0]._initializer_cls()

    # Create hyperparameters
    hyper = nh.HyperParams(
        nice=config['nice'],
        workdir=config['workdir'],
        xpu=nh.XPU.coerce(config['xpu']),
        datasets=torch_datasets,
        loaders=torch_loaders,
        model=model_,
        initializer=initializer_,
        scheduler=nh.Scheduler.coerce(config),
        optimizer=nh.Optimizer.coerce(config),
        dynamics=nh.Dynamics.coerce(config),
        criterion=(
            nh.criterions.FocalLoss,
            {
                'focus': config['focus'],
                'weight': class_weights,
                # 'reduction': 'none',
            }),
        monitor=(nh.Monitor, {
            'minimize': ['loss'],
            'patience': config['patience'],
            'max_epoch': config['max_epoch'],
            'smoothing': .6,
        }),
        other={
            'batch_size': config['batch_size'],
        },
        extra={
            'argv': sys.argv,
            'config': ub.repr2(config.asdict()),
        })

    # Create harness
    harn = SegmentationHarn(hyper=hyper)
    harn.classes = torch_datasets['train'].classes
    harn.preferences.update({
        'num_keep': 2,
        'keyboard_debug': True,
        # 'export_modules': ['netharn'],
    })
    harn.intervals.update({
        'vali': 1,
        'test': 10,
    })
    harn.script_config = config
    return harn
Beispiel #7
0
 def refresh(self):
     import kwarray
     self._pos = 0
     if self.shuffle:
         self.indices = kwarray.shuffle(self.indices, rng=self.rng)
Beispiel #8
0
def setup_harn(cmdline=True, **kw):
    """
    This creates the "The Classification Harness" (i.e. core ClfHarn object).
    This is where we programmatically connect our program arguments with the
    netharn HyperParameter standards. We are using :module:`scriptconfig` to
    capture these, but you could use click / argparse / etc.

    This function has the responsibility of creating our torch datasets,
    lazy computing input statistics, specifying our model architecture,
    schedule, initialization, optimizer, dynamics, XPU etc. These can usually
    be coerced using netharn API helpers and a "standardized" config dict. See
    the function code for details.

    Args:
        cmdline (bool, default=True):
            if True, behavior will be modified based on ``sys.argv``.
            Note this will activate the scriptconfig ``--help``, ``--dump`` and
            ``--config`` interactions.

    Kwargs:
        **kw: the overrides the default config for :class:`ClfConfig`.
            Note, command line flags have precedence if cmdline=True.

    Returns:
        ClfHarn: a fully-defined, but uninitialized custom :class:`FitHarn`
            object.

    Example:
        >>> # xdoctest: +SKIP
        >>> kw = {'datasets': 'special:shapes256'}
        >>> cmdline = False
        >>> harn = setup_harn(cmdline, **kw)
        >>> harn.initialize()
    """
    import ndsampler
    config = ClfConfig(default=kw)
    config.load(cmdline=cmdline)
    print('config = {}'.format(ub.repr2(config.asdict())))

    nh.configure_hacks(config)
    coco_datasets = nh.api.Datasets.coerce(config)

    print('coco_datasets = {}'.format(ub.repr2(coco_datasets, nl=1)))
    for tag, dset in coco_datasets.items():
        dset._build_hashid(hash_pixels=False)

    workdir = ub.ensuredir(ub.expandpath(config['workdir']))
    samplers = {
        tag: ndsampler.CocoSampler(dset,
                                   workdir=workdir,
                                   backend=config['sampler_backend'])
        for tag, dset in coco_datasets.items()
    }

    for tag, sampler in ub.ProgIter(list(samplers.items()),
                                    desc='prepare frames'):
        sampler.frames.prepare(workers=config['workers'])

    torch_datasets = {
        'train':
        ClfDataset(
            samplers['train'],
            input_dims=config['input_dims'],
            augmenter=config['augmenter'],
        ),
        'vali':
        ClfDataset(samplers['vali'],
                   input_dims=config['input_dims'],
                   augmenter=False),
    }

    if config['normalize_inputs']:
        # Get stats on the dataset (todo: turn off augmentation for this)
        _dset = torch_datasets['train']
        stats_idxs = kwarray.shuffle(np.arange(len(_dset)),
                                     rng=0)[0:min(1000, len(_dset))]
        stats_subset = torch.utils.data.Subset(_dset, stats_idxs)

        cacher = ub.Cacher('dset_mean', cfgstr=_dset.input_id + 'v3')
        input_stats = cacher.tryload()

        channels = ChannelSpec.coerce(config['channels'])

        if input_stats is None:
            # Use parallel workers to load data faster
            from netharn.data.data_containers import container_collate
            from functools import partial
            collate_fn = partial(container_collate, num_devices=1)

            loader = torch.utils.data.DataLoader(
                stats_subset,
                collate_fn=collate_fn,
                num_workers=config['workers'],
                shuffle=True,
                batch_size=config['batch_size'])

            # Track moving average of each fused channel stream
            channel_stats = {
                key: nh.util.RunningStats()
                for key in channels.keys()
            }
            assert len(channel_stats) == 1, (
                'only support one fused stream for now')
            for batch in ub.ProgIter(loader, desc='estimate mean/std'):
                for key, val in batch['inputs'].items():
                    try:
                        for part in val.numpy():
                            channel_stats[key].update(part)
                    except ValueError:  # final batch broadcast error
                        pass

            perchan_input_stats = {}
            for key, running in channel_stats.items():
                running = ub.peek(channel_stats.values())
                perchan_stats = running.simple(axis=(1, 2))
                perchan_input_stats[key] = {
                    'std': perchan_stats['mean'].round(3),
                    'mean': perchan_stats['std'].round(3),
                }

            input_stats = ub.peek(perchan_input_stats.values())
            cacher.save(input_stats)
    else:
        input_stats = {}

    torch_loaders = {
        tag: dset.make_loader(
            batch_size=config['batch_size'],
            num_batches=config['num_batches'],
            num_workers=config['workers'],
            shuffle=(tag == 'train'),
            balance=(config['balance'] if tag == 'train' else None),
            pin_memory=True)
        for tag, dset in torch_datasets.items()
    }

    initializer_ = None
    classes = torch_datasets['train'].classes

    modelkw = {
        'arch': config['arch'],
        'input_stats': input_stats,
        'classes': classes.__json__(),
        'channels': channels,
    }
    model = ClfModel(**modelkw)
    model._initkw = modelkw

    if initializer_ is None:
        initializer_ = nh.Initializer.coerce(config)

    hyper = nh.HyperParams(name=config['name'],
                           workdir=config['workdir'],
                           xpu=nh.XPU.coerce(config['xpu']),
                           datasets=torch_datasets,
                           loaders=torch_loaders,
                           model=model,
                           criterion=None,
                           optimizer=nh.Optimizer.coerce(config),
                           dynamics=nh.Dynamics.coerce(config),
                           scheduler=nh.Scheduler.coerce(config),
                           initializer=initializer_,
                           monitor=(nh.Monitor, {
                               'minimize': ['loss'],
                               'patience': config['patience'],
                               'max_epoch': config['max_epoch'],
                               'smoothing': 0.0,
                           }),
                           other={
                               'name': config['name'],
                               'batch_size': config['batch_size'],
                               'balance': config['balance'],
                           },
                           extra={
                               'argv': sys.argv,
                               'config': ub.repr2(config.asdict()),
                           })
    harn = ClfHarn(hyper=hyper)
    harn.preferences.update({
        'num_keep': 3,
        'keep_freq': 10,
        'tensorboard_groups': ['loss'],
        'eager_dump_tensorboard': True,
    })
    harn.intervals.update({})
    harn.script_config = config
    return harn
Beispiel #9
0
def perterb_coco(coco_dset, **kwargs):
    """
    Perterbs a coco dataset

    Args:
        rng (int, default=0):
        box_noise (int, default=0):
        cls_noise (int, default=0):
        null_pred (bool, default=False):
        with_probs (bool, default=False):
        score_noise (float, default=0.2):
        hacked (int, default=1):

    Example:
        >>> from kwcoco.demo.perterb import *  # NOQA
        >>> from kwcoco.demo.perterb import _demo_construct_probs
        >>> import kwcoco
        >>> coco_dset = true_dset = kwcoco.CocoDataset.demo('shapes8')
        >>> kwargs = {
        >>>     'box_noise': 0.5,
        >>>     'n_fp': 3,
        >>>     'with_probs': 1,
        >>> }
        >>> pred_dset = perterb_coco(true_dset, **kwargs)
        >>> pred_dset._check_json_serializable()

    Ignore:
        import xdev
        from kwcoco.demo.perterb import perterb_coco  # NOQA
        defaultkw = xdev.get_func_kwargs(perterb_coco)
        for k, v in defaultkw.items():
            desc = ''
            print('{} ({}, default={}): {}'.format(k, type(v).__name__, v, desc))

    """
    import kwimage
    import kwarray
    # Parse kwargs
    rng = kwarray.ensure_rng(kwargs.get('rng', 0))

    box_noise = kwargs.get('box_noise', 0)
    cls_noise = kwargs.get('cls_noise', 0)

    null_pred = kwargs.get('null_pred', False)
    with_probs = kwargs.get('with_probs', False)

    # specify an amount of overlap between true and false scores
    score_noise = kwargs.get('score_noise', 0.2)

    # Build random variables
    from kwarray import distributions
    DiscreteUniform = distributions.DiscreteUniform.seeded(rng=rng)

    def _parse_arg(key, default):
        value = kwargs.get(key, default)
        try:
            low, high = value
            return (low, high + 1)
        except Exception:
            return (value, value + 1)

    n_fp_RV = DiscreteUniform(*_parse_arg('n_fp', 0))
    n_fn_RV = DiscreteUniform(*_parse_arg('n_fn', 0))

    box_noise_RV = distributions.Normal(0, box_noise, rng=rng)
    cls_noise_RV = distributions.Bernoulli(cls_noise, rng=rng)

    # the values of true and false scores starts off with no overlap and
    # the overlap increases as the score noise increases.
    def _interp(v1, v2, alpha):
        return v1 * alpha + (1 - alpha) * v2

    mid = 0.5
    # true_high = 2.0
    true_high = 1.0
    false_low = 0.0
    true_low = _interp(0, mid, score_noise)
    false_high = _interp(true_high, mid - 1e-3, score_noise)
    true_mean = _interp(0.5, .8, score_noise)
    false_mean = _interp(0.5, .2, score_noise)

    true_score_RV = distributions.TruncNormal(mean=true_mean,
                                              std=.5,
                                              low=true_low,
                                              high=true_high,
                                              rng=rng)
    false_score_RV = distributions.TruncNormal(mean=false_mean,
                                               std=.5,
                                               low=false_low,
                                               high=false_high,
                                               rng=rng)

    # Create the category hierarcy
    classes = coco_dset.object_categories()

    cids = coco_dset.cats.keys()
    cidxs = [classes.id_to_idx[c] for c in cids]

    frgnd_cx_RV = distributions.CategoryUniform(cidxs, rng=rng)

    new_dset = coco_dset.copy()
    remove_aids = []
    false_anns = []

    index_invalidated = False

    for gid in coco_dset.imgs.keys():
        # Sample random variables
        n_fp_ = n_fp_RV()
        n_fn_ = n_fn_RV()

        true_annots = coco_dset.annots(gid=gid)
        aids = true_annots.aids
        for aid in aids:
            # Perterb box coordinates
            ann = new_dset.anns[aid]

            new_bbox = (np.array(ann['bbox']) + box_noise_RV(4)).tolist()
            new_x, new_y, new_w, new_h = new_bbox
            allow_neg_boxes = 0
            if not allow_neg_boxes:
                new_w = max(new_w, 0)
                new_h = max(new_h, 0)
            ann['bbox'] = [new_x, new_y, new_w, new_h]
            ann['score'] = float(true_score_RV(1)[0])

            if cls_noise_RV():
                # Perterb class predictions
                ann['category_id'] = classes.idx_to_id[frgnd_cx_RV()]
                index_invalidated = True

        # Drop true positive boxes
        if n_fn_:
            import kwarray
            drop_idxs = kwarray.shuffle(np.arange(len(aids)), rng=rng)[0:n_fn_]
            remove_aids.extend(list(ub.take(aids, drop_idxs)))

        # Add false positive boxes
        if n_fp_:
            try:
                img = coco_dset.imgs[gid]
                scale = (img['width'], img['height'])
            except KeyError:
                scale = 100
            false_boxes = kwimage.Boxes.random(num=n_fp_,
                                               scale=scale,
                                               rng=rng,
                                               format='cxywh')
            false_cxs = frgnd_cx_RV(n_fp_)
            false_scores = false_score_RV(n_fp_)
            false_dets = kwimage.Detections(
                boxes=false_boxes,
                class_idxs=false_cxs,
                scores=false_scores,
                classes=classes,
            )
            for ann in list(false_dets.to_coco('new')):
                ann['category_id'] = classes.node_to_id[ann.pop(
                    'category_name')]
                ann['image_id'] = gid
                false_anns.append(ann)

        if null_pred:
            raise NotImplementedError

    if index_invalidated:
        new_dset.index.clear()
        new_dset._build_index()
    new_dset.remove_annotations(remove_aids)

    for ann in false_anns:
        new_dset.add_annotation(**ann)

    # Hack in the probs
    if with_probs:
        annots = new_dset.annots()
        pred_cids = annots.lookup('category_id')
        pred_cxs = np.array([classes.id_to_idx[cid] for cid in pred_cids])
        pred_scores = np.array(annots.lookup('score'))
        # Transform the scores for the assigned class into a predicted
        # probability for each class. (Currently a bit hacky).
        pred_probs = _demo_construct_probs(pred_cxs,
                                           pred_scores,
                                           classes,
                                           rng,
                                           hacked=kwargs.get('hacked', 1))

        for aid, prob in zip(annots.aids, pred_probs):
            new_dset.anns[aid]['prob'] = prob.tolist()
    return new_dset