数据处理与数据增强模块总结

数据处理与数据增强模块

这个模块分为俩种,一种是dataset,一种是MindRecord,他们的最终目的都是为了加载和读取数据。

  • dataset可以加载常见的数据集、特定数据格式的数据集或自定义的数据集,一般在加载的时候要导入依赖的mindspore.dataset库

  • MindRecord是一种基于MIndSpore平台专门设计的一种加载数据集的库,MindSpore天然支持读取MindSpore数据格式(MindRecord存储的数据集),其在性能和特性上有更好的支持

可相互转化

MindSpore数据格式支持非标准的数据集和常见的数据集的转化,由此更加方便地加载到MindSpore中进行训练。

其实,MindSpore在部分场景做了性能优化,使用MindSpore数据格式可以获得更好的性能体验,所以推荐将数据转换成MindSpore数据格式(MindRecord存储的数据集),如果不想转化的话,就可以采用dataset模块去加载数据集,效果是等同的。

MindSpore数据格式具备的特征

  • 实现多变的用户数据统一存储、访问,训练数据读取更简便。

  • 数据聚合存储,高效读取,且方便管理、移动。

  • 高效数据编解码操作,对用户透明、无感知。

  • 灵活控制分区大小,实现分布式训练。

dataset支持的数据集

image.png

MindRecord支持的数据集

image.png

MindSpore数据格式的目标是归一化用户的数据集,并进一步通过MindDataset实现数据的读取,并用于训练过程

image.png

MindRecord文件的组成

一个MindRecord文件由数据文件和索引文件组成,且数据文件及索引文件暂不支持重命名操作:

  • 数据文件

    包含文件头、标量数据页、块数据页,用于存储用户归一化后的训练数据,且单个MindRecord文件建议小于20G,用户可将大数据集进行分片存储为多个MindRecord文件。

  • 索引文件

    包含基于标量数据(如图像Label、图像文件名等)生成的索引信息,用于方便的检索、统计数据集信息。

数据文件组成

  • 文件头

    文件头主要用来存储文件头大小、标量数据页大小、块数据页大小、Schema信息、索引字段、统计信息、文件分区信息、标量数据与块数据对应关系等,是MindRecord文件的元信息。

  • 标量数据页

    标量数据页主要用来存储整型、字符串、浮点型数据,如图像的Label、图像的文件名、图像的长宽等信息,即适合用标量来存储的信息会保存在这里。

  • 块数据页

    块数据页主要用来存储二进制串、NumPy数组等数据,如二进制图像文件本身、文本转换成的字典等。

格式转换可以参考:https://www.mindspore.cn/doc/programming_guide/zh-CN/r1.0/dataset_conversion.html

优秀代码解析

    // 名称:计算()
    // 描述:这个 Compute() 取 1 个 Tensor 并产生 1 个 Tensor。
    // 派生类应该覆盖这个函数,否则会出错。
Status TensorOp::Compute(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output) {
  IO_CHECK(input, output);
  if (!OneToOne()) {
      // 调用了错误的 Compute() 函数。 这不是 1-1 TensorOp
    return Status(StatusCode::kMDUnexpectedError, "Wrong Compute() function is called. This is not 1-1 TensorOp.");
  } else {
      // 这是 TensorOp 1-1 吗? 如果是,请在派生类中实现这个 Compute()。
    return Status(StatusCode::kMDUnexpectedError,
                  "Is this TensorOp 1-1? If yes, please implement this Compute() in the derived class.");
  }
}

// 名称:计算()
// 描述:这个 Compute() 从不同的列中获取多个张量并产生多个张量。
// 派生类应该覆盖这个函数,否则会出错。
Status TensorOp::Compute(const TensorRow &input, TensorRow *output) {
  IO_CHECK_VECTOR(input, output);
  if (OneToOne()) {
      // op是OneToOne,只能接受一个张量作为输入
    CHECK_FAIL_RETURN_UNEXPECTED(input.size() == 1, "The op is OneToOne, can only accept one tensor as input.");
    output->resize(1);
    return Compute(input[0], &(*output)[0]);
  }
  // 这是 TensorOp 一对一吗? 如果不是,请在派生类中实现这个 Compute()。
  return Status(StatusCode::kMDUnexpectedError,
                "Is this TensorOp oneToOne? If no, please implement this Compute() in the derived class.");
}

Status TensorOp::Compute(const std::shared_ptr<DeviceTensor> &input, std::shared_ptr<DeviceTensor> *output) {
  IO_CHECK(input, output);
  // “调用了错误的 Compute() 函数。这是一个可以执行的运算符的函数”
 //  "不同的设备。如果是,请在派生类中实现。
  return Status(StatusCode::kMDUnexpectedError,
                "Wrong Compute() function is called. This is a function for operators which can be executed by"
                "different device. If so, please implement it in the derived class.");
}

Status TensorOp::OutputShape(const std::vector<TensorShape> &inputs, std::vector<TensorShape> &outputs) {
  if (inputs.size() != NumInput())
    return Status(StatusCode::kMDUnexpectedError,
    // 输入参数向量的大小与输入数量不匹配
                  "The size of the input argument vector does not match the number of inputs");
  outputs = inputs;
  return Status::OK();
}

Status TensorOp::OutputType(const std::vector<DataType> &inputs, std::vector<DataType> &outputs) {
  if (inputs.size() != NumInput())
    return Status(StatusCode::kMDUnexpectedError,
        // 输入参数向量的大小与输入数量不匹配
                  "The size of the input argument vector does not match the number of inputs");
  outputs = inputs;
  return Status::OK();
}

Status TensorOp::SetAscendResource(const std::shared_ptr<DeviceResource> &resource) {
  return Status(StatusCode::kMDUnexpectedError,
      // 这是一个没有 Ascend Resource 的 CPU 操作符。 请验证您的上下文
                "This is a CPU operator which doesn't have Ascend Resource. Please verify your context");
}
const bool DecodeOp::kDefRgbFormat = true; // 实例化对象

DecodeOp::DecodeOp(bool is_rgb_format) : is_rgb_format_(is_rgb_format) {
  // 解码颜色模式为RGB
  if (is_rgb_format_) {  // RGB色彩模式
    MS_LOG(DEBUG) << "Decode colour mode is RGB.";
  } else {
    MS_LOG(DEBUG) << "Decode colour mode is BGR.";
  }
}

Status DecodeOp::Compute(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output) {
  IO_CHECK(input, output);
  // 检查输入张量形状
  if (input->Rank() != 1) {
      // 解码:无效输入形状,仅支持一维输入
    RETURN_STATUS_UNEXPECTED("Decode: invalid input shape, only support 1D input.");
  }
  if (is_rgb_format_) {  // RGB色彩模式
    return Decode(input, output);
  } else {  // RGB色彩模式
      // 解码:仅支持RGB图像
    RETURN_STATUS_UNEXPECTED("Decode: only support RGB image.");
  }
}
Status DecodeOp::OutputShape(const std::vector<TensorShape> &inputs, std::vector<TensorShape> &outputs) {
  RETURN_IF_NOT_OK(TensorOp::OutputShape(inputs, outputs));
  outputs.clear();
  TensorShape out({-1, -1, 3});  // 我们不知道什么是输出图像大小,但我们知道它应该是 3 个通道
  if (inputs[0].Rank() == 1) outputs.emplace_back(out);
  if (!outputs.empty()) return Status::OK();
  // 解码:无效的输入形状
  return Status(StatusCode::kMDUnexpectedError, "Decode: invalid input shape.");
}

Status DecodeOp::OutputType(const std::vector<DataType> &inputs, std::vector<DataType> &outputs) {
  // 返回结果是否ok
  RETURN_IF_NOT_OK(TensorOp::OutputType(inputs, outputs));
  outputs[0] = DataType(DataType::DE_UINT8);
  return Status::OK();
}
MixUpBatchOp::MixUpBatchOp(float alpha) : alpha_(alpha) { rnd_.seed(GetSeed()); } // 实例化函数

Status MixUpBatchOp::Compute(const TensorRow &input, TensorRow *output) {
    // MixUpBatch:输入缺少图像或标签
  if (input.size() < 2) {
    RETURN_STATUS_UNEXPECTED("MixUpBatch: input lack of images or labels");
  }
  // 实例化列表
  std::vector<std::shared_ptr<CVTensor>> images;
  std::vector<int64_t> image_shape = input.at(0)->shape().AsVector();
  std::vector<int64_t> label_shape = input.at(1)->shape().AsVector();

  // 检查输入
  if (image_shape.size() != 4 || image_shape[0] != label_shape[0]) {
      // 确保图像是 HWC 或 CHW 并在调用 MixUpBatch 之前进行批处理
    RETURN_STATUS_UNEXPECTED(
      "MixUpBatch: "
      "please make sure images are HWC or CHW and batched before calling MixUpBatch.");
  }
  if (!input.at(1)->type().IsInt()) {
      // 第二列(标签)必须只包含 int 类型
    RETURN_STATUS_UNEXPECTED(
      "MixUpBatch: wrong labels type. "
      "The second column (labels) must only include int types.");
  }
  // MixUpBatch:错误的标签形状。 ”
  //“第二列(标签)的形状必须为 NC 或 NLC其中 N 是批量大小,”
   // "L 是每行标签的数量,C 是类的数量。"
    // "标签必须是单热格式和批处理。
  if (label_shape.size() != 2 && label_shape.size() != 3) {
    RETURN_STATUS_UNEXPECTED(
      "MixUpBatch: wrong labels shape. "
      "The second column (labels) must have a shape of NC or NLC where N is the batch size, "
      "L is the number of labels in each row, and C is the number of classes. "
      "labels must be in one-hot format and in a batch.");
  }
  // MixUpBatch:图像必须是 HWC 或 CHW 的形状
  if ((image_shape[1] != 1 && image_shape[1] != 3) && (image_shape[3] != 1 && image_shape[3] != 3)) {
    RETURN_STATUS_UNEXPECTED("MixUpBatch: images must be in the shape of HWC or CHW.");
  }

  // 将图像移动到 CVTensors 向量中
  RETURN_IF_NOT_OK(BatchTensorToCVTensorVector(input.at(0), &images));

  // 计算 lambda
   // 如果 x1 是来自 Gamma(a1, 1) 的随机变量,而 x2 是来自 Gamma(a2, 1) 的随机变量
   // 那么 x = x1 / (x1+x2) 是来自 Beta(a1, a2) 的随机变量
  std::gamma_distribution<float> distribution(alpha_, 1);
  float x1 = distribution(rnd_);
  float x2 = distribution(rnd_);
  float lam = x1 / (x1 + x2);

  // 计算随机标签
  std::vector<int64_t> rand_indx;
  for (int64_t i = 0; i < images.size(); i++) rand_indx.push_back(i);
  std::shuffle(rand_indx.begin(), rand_indx.end(), rnd_);

  // 计算标签
  std::shared_ptr<Tensor> out_labels;
  RETURN_IF_NOT_OK(TypeCast(std::move(input.at(1)), &out_labels, DataType(DataType::DE_FLOAT32)));

  int64_t row_labels = label_shape.size() == 3 ? label_shape[1] : 1;
  int64_t num_classes = label_shape.size() == 3 ? label_shape[2] : label_shape[1];
  // 三重循环用三目运算符为列表插入数据
  for (int64_t i = 0; i < label_shape[0]; i++) {
    for (int64_t j = 0; j < row_labels; j++) {
      for (int64_t k = 0; k < num_classes; k++) {
        std::vector<int64_t> first_index = label_shape.size() == 3 ? std::vector{i, j, k} : std::vector{i, k};
        std::vector<int64_t> second_index =
          label_shape.size() == 3 ? std::vector{rand_indx<i>, j, k} : std::vector{rand_indx<i>, k};
        if (input.at(1)->type().IsSignedInt()) {
          int64_t first_value, second_value;
          RETURN_IF_NOT_OK(input.at(1)->GetItemAt(&first_value, first_index));
          RETURN_IF_NOT_OK(input.at(1)->GetItemAt(&second_value, second_index));
          RETURN_IF_NOT_OK(out_labels->SetItemAt(first_index, lam * first_value + (1 - lam) * second_value));
        } else {
          uint64_t first_value, second_value;
          RETURN_IF_NOT_OK(input.at(1)->GetItemAt(&first_value, first_index));
          RETURN_IF_NOT_OK(input.at(1)->GetItemAt(&second_value, second_index));
          RETURN_IF_NOT_OK(out_labels->SetItemAt(first_index, lam * first_value + (1 - lam) * second_value));
        }
      }
    }
  }
  // 计算图像
  for (int64_t i = 0; i < images.size(); i++) {
    TensorShape remaining({-1});
    uchar *start_addr_of_index = nullptr;
    // 张量的智能指针
    std::shared_ptr<Tensor> out;
    // 判断结果是否ok
    RETURN_IF_NOT_OK(input.at(0)->StartAddrOfIndex({rand_indx<i>, 0, 0, 0}, &start_addr_of_index, &remaining));
    RETURN_IF_NOT_OK(input.at(0)->CreateFromMemory(TensorShape({image_shape[1], image_shape[2], image_shape[3]}),
                                                   input.at(0)->type(), start_addr_of_index, &out));
    std::shared_ptr<CVTensor> rand_image = CVTensor::AsCVTensor(std::move(out));
    // M分配内存失败
    if (!rand_image->mat().data) {
      RETURN_STATUS_UNEXPECTED("MixUpBatch: allocate memory failed.");
    }
    images<i>->mat() = lam * images<i>->mat() + (1 - lam) * rand_image->mat();
  }

  // 将输出移动到 TensorRow
  std::shared_ptr<Tensor> output_image;
  RETURN_IF_NOT_OK(Tensor::CreateEmpty(input.at(0)->shape(), input.at(0)->type(), &output_image));
  for (int64_t i = 0; i < images.size(); i++) { // 遍历images
    RETURN_IF_NOT_OK(output_image->InsertTensor({i}, images<i>));
  }
  output->push_back(output_image); // 尾插output_image
  output->push_back(out_labels);

  return Status::OK();
}

void MixUpBatchOp::Print(std::ostream &out) const {
  out << "MixUpBatchOp: " // 混合批处理操作
      << "alpha: " << alpha_ << "\n";
}

总结

  • MindrecordMindSpore定义的一种数据格式,是一个执行读取、写入、搜索和转换MindSpore格式数据集的模块。

  • 用户可以通过FileReaderFileWriter)加载(修改)Mindrecord数据。

  • 用户还可以通过相应的子模块将其他格式的数据集转换为Mindrecord

posted @ 2021-12-27 16:03  MS小白  阅读(68)  评论(0)    收藏  举报