MNN/docs/train/data.md

250 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 加载训练数据
该模块用于读取保存在硬盘上的数据将其包装并输出为MNN训练可用的数据类型。该模块源码位于MNN_root/tools/train/source/data/目录下。若要使用请包含DataLoader.hpp头文件即可该模块中其他组件会全部导入用于构建DataLoader。
## 相关demo
1、MNN_root/tools/train/source/demo/dataLoaderDemo.cpp
    使用MNIST数据集构建DataLoader并进行输出显示。
2、MNN_root/tools/train/source/demo/dataLoaderTest.cpp
    使用MNIST数据集构建DataLoader并测试DataLoader中一些组件。
3、MNN_root/tools/train/source/demo/ImageDatasetDemo.cpp
    读取硬盘上保存的图片数据并显示出来。显示需要用到OpenCV并在编译时打开`MNN_USE_OPENCV`宏。
## 自定义Dataset
可参考MNN_root/tools/train/source/datasets/中预置数据集的写法继承Dataset类实现两个抽象函数即可例如
```cpp
// MnistDataset.cpp
// 返回MNIST数据集中一张图片数据及其对应的label
Example MnistDataset::get(size_t index) {
auto data = _Input({1, kImageRows, kImageColumns}, NCHW, halide_type_of<uint8_t>());
auto label = _Input({}, NCHW, halide_type_of<uint8_t>());
auto dataPtr = mImagePtr + index * kImageRows * kImageColumns;
::memcpy(data->writeMap<uint8_t>(), dataPtr, kImageRows * kImageColumns);
auto labelPtr = mLabelsPtr + index;
::memcpy(label->writeMap<uint8_t>(), labelPtr, 1);
auto returnIndex = _Const(index);
// return the index for test
return {{data, returnIndex}, {label}};
}
// 返回数据集大小对于MNIST训练集是60000测试集是10000
size_t MnistDataset::size() {
return mImages->getInfo()->dim[0];
}
```
## DataLoader使用示例
使用流程自定义Dataset构造DataLoader读取数据DataLoader->reset();
```cpp
//
// ImageDatasetDemo.cpp
// MNN
//
// Created by MNN on 2019/11/20.
// Copyright © 2018, Alibaba Group Holding Limited
//
#include <iostream>
#include "DataLoader.hpp"
#include "DemoUnit.hpp"
#include "ImageDataset.hpp"
#include "RandomSampler.hpp"
#include "Sampler.hpp"
#include "Transform.hpp"
#include "TransformDataset.hpp"
#ifdef MNN_USE_OPENCV
#include <opencv2/opencv.hpp> // use opencv to show pictures
using namespace cv;
#endif
using namespace std;
/*
* this is an demo for how to use the ImageDataset and DataLoader
*/
class ImageDatasetDemo : public DemoUnit {
public:
// this function is an example to use the lambda transform
// here we use lambda transform to normalize data from 0~255 to 0~1
static Example func(Example example) {
// // an easier way to do this
auto cast = _Cast(example.first[0], halide_type_of<float>());
example.first[0] = _Multiply(cast, _Const(1.0f / 255.0f));
return example;
}
virtual int run(int argc, const char* argv[]) override {
if (argc != 3) {
cout << "usage: ./runTrainDemo.out ImageDatasetDemo path/to/images/ path/to/image/txt\n" << endl;
// ImageDataset的数据格式采用的是ImageNet数据集的格式你也可以写一个自己的数据集自定义格式
cout << "the ImageDataset read stored images as input data.\n"
"use 'pathToImages' and a txt file to construct a ImageDataset.\n"
"the txt file should use format as below:\n"
" image1.jpg label1,label2,...\n"
" image2.jpg label3,label4,...\n"
" ...\n"
"the ImageDataset would read images from:\n"
" pathToImages/image1.jpg\n"
" pathToImages/image2.jpg\n"
" ...\n"
<< endl;
return 0;
}
std::string pathToImages = argv[1];
std::string pathToImageTxt = argv[2];
// ImageDataset可配置数据预处理
auto converImagesToFormat = ImageDataset::DestImageFormat::RGB;
int resizeHeight = 224;
int resizeWidth = 224;
std::vector<float> scales = {1/255.0, 1/255.0, 1/255.0};
auto config = ImageDataset::ImageConfig(converImagesToFormat, resizeHeight, resizeWidth, scales);
bool readAllImagesToMemory = false;
// 构建ImageDataset
auto dataset = std::make_shared<ImageDataset>(pathToImages, pathToImageTxt, config, readAllImagesToMemory);
const int batchSize = 1;
const int numWorkers = 1;
// 构建DataLoader这里会将一个batch数据stack为一个VARP(Tensor)
auto dataLoader = std::shared_ptr<DataLoader>(DataLoader::makeDataLoader(dataset, batchSize, true, false, numWorkers));
const size_t iterations = dataset->size() / batchSize;
for (int i = 0; i < iterations; i++) {
// 读取数据
auto trainData = dataLoader->next();
auto data = trainData[0].first[0]->readMap<float_t>();
auto label = trainData[0].second[0]->readMap<int32_t>();
cout << "index: " << i << " label: " << int(label[0]) << endl;
#ifdef MNN_USE_OPENCV
// only show the first picture in the batch
Mat image = Mat(resizeHeight, resizeWidth, CV_32FC(3), (void*)data);
imshow("image", image);
waitKey(-1);
#endif
}
// 每完整过一次数据集必须重置DataLoader
// this will reset the sampler's internal state
dataLoader->reset();
return 0;
}
};
DemoUnitSetRegister(ImageDatasetDemo, "ImageDatasetDemo");
```
## 相关类和概念
### VARP
MNN动态图中的变量类似于pytorch中的Tensor
### Example
DataLoader输出数据的最小单位
```cpp
/**
First: data: a vector of input tensors (for single input dataset is only one)
Second: target: a vector of output tensors (for single output dataset is only one)
*/
typedef std::pair<std::vector<VARP>, std::vector<VARP>> Example;
```
可以看到一个Example是一个数据对其first部分是输入second部分是target。由于网络有可能有多个输入和多个target所以first和second都是vector结构。
### RandomSampler : public Sampler
随机采样序列生成器例如图片数据集中有1000张图片则生成采样序列0~999根据配置指定是否进行shuffle
```cpp
public:
// size: 采样序列长度
// shuffle: 是否生成随机采样序列
explicit RandomSampler(size_t size, bool shuffle = true);
// 重置采样器内部状态
void reset(size_t size) override;
// 采样器长度
size_t size() override;
// 返回内部生成的采样序列
const std::vector<size_t> indices();
// 返回已经使用的采样序列数量
size_t index();
// 获取下一个长度为batchSize的采样序列
std::vector<size_t> next(size_t batchSize) override;
private:
std::vector<size_t> mIndices;
size_t mIndex = 0;
bool mShuffle;
```
### Dataset
数据集抽象基类用户自定义数据集需继承此基类并实现抽象函数可参考MNN_root/tools/train/source/datasets/中预置数据集的写法
```cpp
// 返回数据集的大小例如1000张图片的数据集其大小为1000
virtual size_t size() = 0;
// 返回数据集中指定index的数据如给定123返回第123张图片数据
virtual Example get(size_t index) = 0;
// 返回数据集中指定index的一批数据为一个batch
std::vector<Example> getBatch(std::vector<size_t> indices);
```
### Transform
抽象基类对一个batch中的每一个数据进行某个变换可以是一些预处理等
### BatchTransform
抽象基类对一个batch的数据进行某个变换可以是一些预处理等
### StackTransform : public BatchTransform
将一个Dataset输出的vector<Example>表示的一个batch数据合成一个VARP
Stack( (c, h, w), (c, h, w), (c, h, w)... ) --> (n, c, h, w)
### LambdaTransform : public Transform
对Dataset输出的每一个Example进行单独处理例如中心化归一化等
### TransformDataset : public Dataset
对Dataset进行某种Transform仍是一个Dataset用于输出数据
### DataLoaderConfig
对DataLoader进行配置可配置项为
> batchSize: 指定batch大小
> numWorkers: 多线程预读取的线程数
### DataLoader
根据采样器生成的采样序列到对应的Dataset中取得对应的数据并输出
```cpp
// 构造函数
DataLoader(std::shared_ptr<BatchDataset> dataset, std::shared_ptr<Sampler> sampler,
std::shared_ptr<DataLoaderConfig> config);
// 构造DataLoader无Transform
static DataLoader* makeDataLoader(std::shared_ptr<BatchDataset> dataset,
const int batchSize,
const bool stack = true, // 是否将一个batch数据叠加为一个VARP(Tensor)
const bool shuffle = true,
const int numWorkers = 0);
// 构造DataLoader有TransformTransform可多个叠加
static DataLoader* makeDataLoader(std::shared_ptr<BatchDataset> dataset,
std::vector<std::shared_ptr<BatchTransform>> transforms,
const int batchSize,
const bool shuffle = true,
const int numWorkers = 0);
// 指定batch size后迭代多少次用完全部数据最后一个batch不足batchsize也会输出
size_t iterNumber() const;
// 数据集大小
size_t size() const;
// 输出一个batch的数据
std::vector<Example> next();
// 清空内部数据队列,重置内部采样器
void clean();
// clean()并重新预读取Dataset每次数据全部输出完毕必须reset
void reset();
```