Skip to content

Add pytorch style dataloader - #463

Merged
NiklasGustafsson merged 45 commits into
dotnet:mainfrom
dayo05:main
Jan 12, 2022
Merged

Add pytorch style dataloader#463
NiklasGustafsson merged 45 commits into
dotnet:mainfrom
dayo05:main

Conversation

@dayo05

@dayo05dayo05 commented Nov 27, 2021

Copy link
Copy Markdown
Contributor

In pytorch there are DataLoader on torch.utils.data, Torchsharp has DataIterator on TorchSharp.Data.

In pytorch, Creating Dataset is easy.

classCustomImageDataset(Dataset):
def__init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels=pd.read_csv(annotations_file)
self.img_dir=img_dirself.transform=transformself.target_transform=target_transformdef__len__(self):
returnlen(self.img_labels)
def__getitem__(self, idx):
img_path=os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image=read_image(img_path)
label=self.img_labels.iloc[idx, 1]
ifself.transform:
image=self.transform(image)
ifself.target_transform:
label=self.target_transform(label)
returnimage, label

This is sample code that I found on pytorch official tutorial.
But in DataIterator, I don't know how to use that, It is not similar as pytorch style, I cannot found tutorial about it.

So I make dataloader using like this.
This style is very similar as what pytorch does.

This will make iterating data more easier.

@nietras

nietras commented Nov 29, 2021

Copy link
Copy Markdown
Contributor

This is "hard-coded" to only two tensors which doesn't fit general case scenarios. We have scenarios when many input and many output tensors. In python this is handled by the dynamic type system and being able to handle tuples etc. I think :) This cannot be mirrored with a good experience in C# in my view. The best generalization is to base this on Dictionary<string, Tensor> (or Dictionary<object, Tensor>) if generalizing key. Or we need to go down a generic, reflection based path which is problematic in my view.

Sorry, editing old comment, since DataLoader is present here but wanted to say that, since we need to batch we need to be able to iterator over Tensors and produce the same structured output which is why Dictionary<string, Tensor> seems apt.

However, we would then also very much like to be able to run data loading threaded, which involves handling reproducibility in the face of global variables, ensuring randomized sampling is reproducible. So shuflling should be fully reproducible and seeded. Hence, random created at ctor. While we are at then, do fischer-yates shuffle and not use object index, but use statically typed stuff. Hence, it should be GetTensor(int index). Or this could probably be Dictionary<string, Tensor> Get(int index) or even just an indexer.

I also don't think Dataset should have a GetDataEnumerable(), Dataset should not be iterable, but only be indexable. Since that is how DataLoader can do parallel, shuffled loading.

I had been thinking about adding this myself, so very nice to see it being worked on 👍 However, as I see there are some fundamental issues that need to be addressed regarding multi-threading loading and reproducibility that need to be taken into account.

@nietrasnietras left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've made some review comments too. :) I have more but just some quick remarks.

Comment threadsrc/TorchSharp/Data/Dataset.cs Outdated
Comment threadsrc/TorchSharp/Data/Dataset.cs Outdated
Comment threadsrc/TorchSharp/Data/Dataset.cs Outdated
Comment threadsrc/TorchSharp/Data/Dataset.cs Outdated
Comment threadsrc/TorchSharp/Data/DataLoader.cs Outdated
Comment threadsrc/TorchSharp/Data/DataLoader.cs Outdated
Comment threadsrc/TorchSharp/Data/DataLoader.cs Outdated
@dayo05

Copy link
Copy Markdown
ContributorAuthor

TODO: Write test about it

Comment threadsrc/TorchSharp/Data/Dataset.cs Outdated
Comment threadsrc/TorchSharp/Data/Dataset.cs Outdated
@NiklasGustafsson

Copy link
Copy Markdown

This is such an important addition, and we must make sure to get it right. I would like to see some more extensive code examples on how to use this -- the user experience is essential.

@dayo05

Copy link
Copy Markdown
ContributorAuthor

I'm making a new example for reflection about review

@NiklasGustafsson

NiklasGustafsson commented Dec 1, 2021

Copy link
Copy Markdown

I'm going to add a few things to the code comments in a review, but I have some questions:

  1. Should we allow data loaders to accept custom shufflers? That is, if I think (I'd probably be wrong) that I have a better shuffle algorithm, shouldn't I be allowed to use that, instead?
  2. There's no way to specify the device on which the 'Current' tensors end up.
  3. Why does 'Current' return a dictionary? Is it input vs. labels?
  4. How do I split a data set into train, test, and validation data subsets?
  5. How do we add data augmentation support?
  6. When the dataset is not shuffled, we're still creating fresh batches every time through the data set. It would be great to think of some way to keep data in memory between epochs, as long as it fits, of course.

Also, it would be great to demonstrate how to use this by converting the Examples to use this instead of the ad hoc loading I added earlier.

Comment threadsrc/TorchSharp/DataLoader.cs
Comment threadsrc/TorchSharp/DataLoader.cs Outdated
Comment threadsrc/TorchSharp/DataLoader.cs Outdated
Comment threadsrc/TorchSharp/DataLoader.cs
Comment threadsrc/TorchSharp/DataLoader.cs Outdated
Comment threadsrc/TorchSharp/Utils/ShuffleGenerator.cs Outdated
Comment threadsrc/TorchSharp/Utils/ShuffleGenerator.cs Outdated
Comment threadsrc/TorchSharp/DataLoader.cs Outdated
Comment threadsrc/TorchSharp/Dataset.cs Outdated
Comment threadsrc/TorchSharp/DataLoader.cs
@NiklasGustafsson

Copy link
Copy Markdown

@dayo05 -- thanks for all the work so far. I'll be taking some time off in December, but I'm looking forward to integrating this when I come back.

Thanks,

Niklas

@dayo05

Copy link
Copy Markdown
ContributorAuthor

I'm busy now, I'll going to work on two weeks later :(

@dayo05

Copy link
Copy Markdown
ContributorAuthor

@NiklasGustafsson I finished all my job, review please.

@dayo05

Copy link
Copy Markdown
ContributorAuthor

I found shuffler is not correctly working. I'll fix ASAP.

@dayo05

dayo05 commented Dec 25, 2021

Copy link
Copy Markdown
ContributorAuthor

This is example for classifying fruits360 dataset with imagesharp. Now shuffler is not working fine so loss is big but if I use previous shuffler(which before making shufflegenerator class), It works fine.

publicclassFruits360:Dataset{privateList<string>Labels=new();privateList<string>images=new();publicFruits360(boolisTrain,Devicedevice){varroot="/home/dayo/datasets/fruits-360/"+(isTrain?"Training":"Test");//Labels.AddRange(Directory.GetDirectories(root));foreach(varxinDirectory.GetDirectories(root))images.AddRange(Directory.GetFiles(x));Labels.AddRange(Directory.GetDirectories(root).Select(x =>x.Split('/')[^1]));}publicoverridelongCount=>images.Count;publicoverrideDictionary<string,torch.Tensor>GetTensor(longindex){varimage=Image.Load<Rgb24>(images[(int)index],newJpegDecoder());usingvarr=tensor(image.GetPixelMemoryGroup()[0].Span.ToArray().Select(x =>x.R/255.0f).ToList(),newlong[]{1,100,100});usingvarg=tensor(image.GetPixelMemoryGroup()[0].Span.ToArray().Select(x =>x.G/255.0f).ToList(),newlong[]{1,100,100});usingvarb=tensor(image.GetPixelMemoryGroup()[0].Span.ToArray().Select(x =>x.B/255.0f).ToList(),newlong[]{1,100,100});returnnew(){{"image",cat(newList<Tensor>{r,g,b},0)},{"label",tensor(Labels.IndexOf(images[(int)index].Split('/')[^2]),ScalarType.Int64)}};}}
usingvartrainDataset=newFruits360(true,CUDA);usingvartestDataset=newFruits360(false,CUDA);usingvartrain=newDataLoader(trainDataset,256,true,CUDA);usingvartest=newDataLoader(testDataset,512,false,CUDA);varmodel=newFruits360Model(CUDA);varoptimizer=optim.Adam(model.parameters(),learningRate:0.01);foreach(varepochinRange(1,1000)){model.Train();varbatchId=1;Console.WriteLine($"Epoch{epoch} running");foreach(varxintrain){optimizer.zero_grad();varprediction=model.forward(x["image"]);varoutput=functional.nll_loss(reduction:Reduction.Mean)(prediction,x["label"]);output.backward();optimizer.step();Console.Write($"\rTrain: epoch {epoch}{batchId*1.0/train.Count:P2} [{batchId} / {train.Count}] Loss: {output.ToSingle():F9}");batchId++;prediction.Dispose();output.Dispose();GC.Collect();}using(no_grad()){model.Eval();vartestLoss=0.0;varcorrect=0;vartotal=0L;varidx=0;foreach(varxintest){idx++;Console.Write($"\rTest running: {idx*1.0/test.Count:P2}");varprediction=model.forward(x["image"]);varoutput=functional.nll_loss(reduction:Reduction.Sum)(prediction,x["label"]);testLoss+=output.ToSingle();varpred=prediction.argmax(1);total+=pred.size()[0];correct+=pred.eq(x["label"]).sum().ToInt32();pred.Dispose();prediction.Dispose();output.Dispose();GC.Collect();}Console.WriteLine($"\rTest set: Average loss {(testLoss/testDataset.Count):F9} | Accuracy {((double)correct/testDataset.Count):P2}");}}classFruits360Model:Module{privateModulelayer1=Sequential(Conv2d(3,32,3),ReLU(),MaxPool2d(2,2));privateModulelayer2=Sequential(Conv2d(32,64,3),ReLU(),MaxPool2d(2,2));privateModulelayer3=Sequential(Conv2d(64,64,3),ReLU(),MaxPool2d(2,2));privateModulefc=Sequential(Flatten(),Linear(6400,1024),ReLU(),Dropout(),Linear(1024,625),ReLU(),Linear(625,131));publicFruits360Model(Device?device):base("fruits360"){RegisterComponents();to(device??CPU);}publicoverrideTensorforward(Tensort){t=layer1.forward(t);t=layer2.forward(t);t=layer3.forward(t);t=fc.forward(t);returnLogSoftmax(0).forward(t);}}

@dayo05

Copy link
Copy Markdown
ContributorAuthor

I found that this shuffler is depend on seed. So, I'll change to other shuffler and enable to use custom shuffler with implementation of IEnumerable.

@NiklasGustafsson

Copy link
Copy Markdown

@dayo05 -- A couple of comments:

  1. This looks really good now. I will be happy to approve this later.
  2. There seems to be some problem with the Azure pipelines that are doing the builds for MacOS. We'll have to wait for that to go away before merging.
  3. Before we merge, I would like to see at least one of the examples in this repo modified to use this API instead of the version I put together for temporary purposes.

@dayo05

Copy link
Copy Markdown
ContributorAuthor

I make fisher yates shuffler as default because I thought that default value is friendly for beginner and previous shuffler is not good for beginner who has less amount of dataset. But, I left previous shuffler and allow to call like this

vardata=newDataLoader(dataset,batchSize,newFastShuffler(dataset.Count),torch.CUDA);

@dayo05

dayo05 commented Jan 7, 2022

Copy link
Copy Markdown
ContributorAuthor

https://github.com/dayo05/DataLoaderExample/tree/master
I write example usage for this commit
See Fruits360.cs, Program.cs

@NiklasGustafsson

Copy link
Copy Markdown

https://github.com/dayo05/DataLoaderExample/tree/master I write example usage for this commit See Fruits360.cs, Program.cs

That's great. Could you take the MNIST example in this repo and convert it to use your API?

@dayo05

Copy link
Copy Markdown
ContributorAuthor

@NiklasGustafsson I updated here https://github.com/dayo05/DataLoaderExample/tree/master
Exist on MNIST.cs, MNISTReader.cs
I edited from torchsharp main repo source

@NiklasGustafsson

NiklasGustafsson commented Jan 11, 2022

Copy link
Copy Markdown

I've been trying to restart the MacOS builds for a couple of days now. I have no idea why there are failing, but it is very early, way before it gets to building TorchSharp.

Update: @dayo05 -- the 'main' branch still builds just fine, so there must be something in your PR. I suspect it may have something to do with your changing the SDK version number, but I'm not sure.

@dayo05

Copy link
Copy Markdown
ContributorAuthor

@NiklasGustafsson I fix it

@NiklasGustafsson

Copy link
Copy Markdown

Okay, I'm going to merge this now. Let's still get some of the examples in this repository using the DataLoader API.

@NiklasGustafsson
NiklasGustafsson merged commit 25d6ad2 into dotnet:mainJan 12, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@dayo05@nietras@NiklasGustafsson@mfagerlund