Closed salmiachraf closed 3 years ago
Hi. You can put your image folder anywhere you want, in your implementation you just need to point to the correct location. Let's go through this step by step. Consider your dataset being named foodata.
fcdd.datasets
. Implement a typical torchvision-style dataset for your image folder. You can reuse the PyTorch default implementation torchvision.datasets.ImageFolder
. Read https://pytorch.org/docs/1.4.0/torchvision/datasets.html#datasetfolder for how your folder needs to be structured for this. So I imagine something like this:import random
import torch
import torchvision.transforms as transforms
from torchvision.datasets import ImageFolder
from typing import Tuple
class FooData(ImageFolder):
def __init__(self, root, transform=None, target_transform=None,
normal_classes=None, all_transform=None):
super().__init__(root, transform=transform, target_transform=target_transform)
self.normal_classes = normal_classes
self.all_transform = all_transform # contains the OnlineSupervisor
def __getitem__(self, index: int) -> Tuple[torch.Tensor, int]:
target = self.targets[index]
if self.target_transform is not None: # transforms labels (e.g. class labels to AD labels)
target = self.target_transform(target)
if self.all_transform is not None: # replaces image and label with artificial anomalies
replace = random.random() < 0.5
if replace:
img, _, target = self.all_transform(None, None, target, replace=replace)
img = transforms.ToPILImage()(img)
else:
path, _ = self.samples[index]
img = self.loader(path)
else:
path, _ = self.samples[index]
img = self.loader(path)
if self.transform is not None: # image transformation, e.g. augmentations
img = self.transform(img)
return img, target
import os.path as pt
import torchvision.transforms as transforms
from fcdd.datasets.bases import TorchvisionDataset
from fcdd.datasets.online_supervisor import OnlineSupervisor
from fcdd.util.logging import Logger
class ADFooData(TorchvisionDataset): base_folder = 'foodata'
def __init__(self, root: str, preproc: str, supervise_mode: str,
noise_mode: str, logger: Logger = None):
root = pt.join(root, self.base_folder) # assuming your data is in "root"/foodata
trainpath = pt.join(root, 'train') # assuming your train data is in a subfolder train
testpath = pt.join(root, 'test') # assuming your test data is in a subfolder test
super().__init__(root, logger=logger)
self.n_classes = 2 # 0: normal, 1: outlier
self.raw_shape = (?, ?, ?) # shape of your data samples in channels x height x width
self.shape = (3, 224, 224) # shape of your data samples in channels x height x width after image preprocessing
self.normal_classes = (0, )
self.outlier_classes = (1, )
self.nominal_label = 0
self.anomalous_label = 1
# precomputed mean and std of your training data
mean = (?, ?, ?)
std = (?, ?, ?)
if preproc in ['', None, 'default', 'none']:
test_transform = transform = transforms.Compose([
transforms.Resize((self.shape[-2], self.shape[-1])),
transforms.ToTensor(),
transforms.Normalize(mean, std)
])
# here you could define other pipelines with augmentations
else:
raise ValueError('Preprocessing pipeline {} is not known.'.format(preproc))
target_transform = transforms.Lambda(
lambda x: self.anomalous_label if x in self.outlier_classes else self.nominal_label
)
if supervise_mode not in ['unsupervised']:
all_transform = OnlineSupervisor(self, supervise_mode, noise_mode)
else:
all_transform = None
self._train_set = FooData(
root=trainpath, normal_classes=self.normal_classes,
transform=transform, target_transform=target_transform, all_transform=all_transform,
)
self._test_set = FooData(
root=testpath, normal_classes=self.normal_classes,
transform=test_transform, target_transform=target_transform,
)
3. Add your dataset to `fcdd.datasets.__init__` so that it can be loaded by the trainer.
[...] from fcdd.datasets.foodata import ADFooData DS_CHOICES = ('mnist', 'cifar10', 'fmnist', 'mvtec', 'imagenet', 'pascalvoc', 'foodata') [...] elif dataset_name == 'foodata': dataset = ADFooData( root=data_path, preproc=preproc, supervise_mode=supervise_mode, noise_mode=noise_mode, logger=logger, ) [...] def no_classes(dataset_name: str) -> int: return { 'cifar10': 10, 'fmnist': 10, 'mvtec': 15, 'imagenet': 30, 'pascalvoc': 1, 'foodata': 1, }[dataset_name] [...] def str_labels(dataset_name: str) -> List[str]: return { 'cifar10': ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'], 'fmnist': [ 't-shirt/top', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot' ], 'mvtec': [ 'bottle', 'cable', 'capsule', 'carpet', 'grid', 'hazelnut', 'leather', 'metal_nut', 'pill', 'screw', 'tile', 'toothbrush', 'transistor', 'wood', 'zipper' ], 'imagenet': deepcopy(ADImageNet.ad_classes), 'pascalvoc': ['horse'], 'foodata': ['foo'], }[dataset_name]
I simplified things here a bit and ignored some optional arguments (such as oe_limit).
To train on your new data, just run `python runners/run_imagenet.py --dataset foodata --net FCDD_CNN224_VGG --datadir PATH_TO_YOUR_DATA`. The --datadir argument should point to your data (for example --datadir /home/salmiachraf/fcdd/data/datasets for your data being in /home/salmiachraf/fcdd/data/datasets/foodata/train/class_0/sample_x). The runner uses an imagenet-pre-trained network applicable for input images of shape 224x224 (i.e., you need to have set shape in ADFooData to 224x224) and imagenet21k data as outlier exposure.
Using the imagenet runner might seem a bit confusing here, but the runners are all the same, they just use a different set of default parameters.
However, you can just add your own runner with your own default parameters for more convenience.
Does this help you? Otherwise, feel free to provide more information. I'm glad to help.
Hi, I just read and run you code, I'd say that your framework is nice, but it costs me very much to custom my own data. And I wonder why I can't debug it, I want to figure out its training and testing flow, but I just can't. PS: if I have the same data structure as MVtec_AD, is there a faster way to run it, you know, just change the path and class name and things like that? @liznerski
Thanks for your feedback. Can you elaborate a bit more on why you can't debug it? If you literally refer to the debug mode of IDEs: you need to set the number of data loader workers to 0 so that it uses the main processes instead of spawning new ones. That's at least how I do it. The first data loader that is created is for previewing the data, which means you need to change https://github.com/liznerski/fcdd/blob/master/python/fcdd/datasets/bases.py#L90 for that.
Regarding the PS: in this case, it might be easier to adapt the MVTec-AD code. However, there are still a few things that need to be set; for example, the precomputed mean and std of the dataset. Considering the interest in using FCDD on custom data, I will set myself to work and implement some more general data loader for "image folder datasets" in the upcoming weeks. Stay tuned ;)
Thank you @liznerski for the explanation. It's very clear and I could follow it. Although, I get this error:
(one_class) C:\Users\salmiachraf \Desktop\FCDD\python\fcdd>python run_imagenet.py --dataset region3 --net FCDD_CNN224_VGG --datadir C:\Users\salmiachraf\Downloads\datasets
Plotting Many ROC for completed classes up to 0...
Traceback (most recent call last):
File "run_imagenet.py", line 19, in <module>
runner.run()
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\runners\bases.py", line 203, in run
self.run_classes(**vars(self.args))
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\runners\bases.py", line 222, in run_classes
it, **kwargs
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\runners\bases.py", line 182, in run_seeds
this_viz_ids, **kwargs
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\runners\bases.py", line 101, in run_one
**kwargs
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\training\setup.py", line 106, in trainer_setup
noise_mode, online_supervision, nominal_label, oe_limit, logger=logger
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\datasets\__init__.py", line 63, in load_dataset
supervise_mode=supervise_mode, noise_mode=noise_mode, logger=logger, )
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\datasets\region3.py", line 81, in __init__
all_transform = OnlineSupervisor(self, supervise_mode, noise_mode)
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\datasets\online_supervisor.py", line 52, in __init__
root=ds.root
File "C:\Users\salmiachraf \Desktop\FCDD\python\fcdd\datasets\outlier_exposure\imagenet.py", line 260, in __init__
assert len(size) == 4 and size[2] == size[3]
AssertionError
Thank you for your time.
Thanks for your feedback. Can you elaborate a bit more on why you can't debug it? If you literally refer to the debug mode of IDEs: you need to set the number of data loader workers to 0 so that it uses the main processes instead of spawning new ones. That's at least how I do it. The first data loader that is created is for previewing the data, which means you need to change https://github.com/liznerski/fcdd/blob/master/python/fcdd/datasets/bases.py#L90 for that.
Regarding the PS: in this case, it might be easier to adapt the MVTec-AD code. However, there are still a few things that need to be set; for example, the precomputed mean and std of the dataset. Considering the interest in using FCDD on custom data, I will set myself to work and implement some more general data loader for "image folder datasets" in the upcoming weeks. Stay tuned ;)
That will be nicer to provide a more general data loader. And I think an external config file will be more convenient, all the variable parameters in this config file can be modified to complete our customized experiments. Because sometimes, I just want to test the algorithm on my dataset quikly to get a rough conclusion. Consider me as an user, please.
@salmiachraf This assertion makes sure that the imagenet21k outliers are generated with the correct shape. The shape is automatically extracted from the raw_shape of your dataset (online_supervisor.py#L51). It seems that you didn't set the raw_shape attribute properly. It should be something like (3, 256, 256). The preprocessing pipeline will resize it to (3, 224, 224) later on. The distinction of raw_shape and shape is due to pipelines containing crop augmentations.
PS: Since you changed --datadir, make sure that your imagenet21k data is also located there (i.e., C:\Users\salmiachraf\Downloads\datasets\imagenet22k\fall11_whole_extracted...) instead of the default location.
I've just pushed a custom dataset implementation (b2fc0fee466305494e07d7e83475dc8694e0d26b). Feel free to try it and report any problems.
Would you please tell me how to compute the mean and std of training data? @liznerski
Look here for a generic version of how to do that (with the custom dataset implementation provided in this git repo). Depending on your implementation, you can probably also do something like data.permute(1, 0, 2, 3).flatten(1).mean(1), data.permute(1, 0, 2, 3).flatten(1).std(1)
.
Hello! Thank you for the code.
I am trying to train the model on my own custom dataset (folder of images). But it's a bit hard to do so. I tried to follow this:
` Create a new python script in the datasets package. Implement a dataset that inherits the fcdd.datasets.bases.TorchvisionDataset class. Your implementation needs to process all parameters of the fcdd.datasets.bases.load_dataset function in its initialization. You can use the preproc parameter to switch between different data preprocessing pipelines (augmentation, etc). In the end, your implementation needs to have at least all attributes defined in fcdd.datasets.bases.BaseADDataset class. Most importantly, the _train_set attribute and the _test_set attribute containing the corresponding torchvision-style datasets. Have a look at the already available implementations.
Add a name for your dataset to the fcdd.datasets.init.DS_CHOICES variable. Add your dataset to the "switch-case" in the fcdd.datasets.init.load_dataset function. Add the number of available class for your dataset to the fcdd.datasets.init.no_classes function and add the class names to fcdd.datasets.init.str_labels.
`
But I am finding it hard to do. I cant see where shall I put the images folder exactly and how to prepare those scripts. Can anyone help?