簡體   English   中英

將 Eigen MatrixXd 拆分為具有隨機打亂行的固定大小批次的最佳方法

[英]The optimal way to split Eigen MatrixXd into fixed-size batches with randomly shuffled rows

我有輸入和目標數據表示為 MatrixXd (N x M) 和 VectorXd (N)。 目標是創建大小為 K 的小批量,其中包含以相同方式混洗的輸入和目標數據的子集。 然后,ML model 將循環處理這些小批量。 您能否推薦如何通過盡可能少的復制來實現這一目標(也許,使用代碼示例)?

我嘗試實現這種批處理

#include <algorithm>
#include <numeric>
#include <random>

#include <Eigen/Dense>

using Eigen::MatrixXd;
using Eigen::Ref;
using Eigen::VectorXd;

struct Batch {
    const Ref<const MatrixXd> input;
    const Ref<const VectorXd> target;
};

std::vector<Batch> generate_batches(const Ref<const MatrixXd> input, const Ref<const VectorXd> target, unsigned batch_size)
{
    unsigned num_samples = input.rows();
    unsigned num_batches = ceil(num_samples / (float)batch_size);

    static std::default_random_engine engine;
    std::vector<unsigned> idxs(num_samples);
    std::iota(idxs.begin(), idxs.end(), 0);
    std::shuffle(idxs.begin(), idxs.end(), engine);

    std::vector<Batch> batches;
    batches.reserve(num_batches);

    auto idxs_begin = std::make_move_iterator(idxs.begin());
    for (unsigned idx = 0; idx < num_batches; ++idx) {
        int start = idx * batch_size;
        int end = std::min(start + batch_size, num_samples);

        std::vector<unsigned> batch_idxs(std::next(idxs_begin, start), std::next(idxs_begin, end));
        batches.push_back({ input(batch_idxs, Eigen::all), target(batch_idxs) });
    }
    return batches;
}

Eigen 帶有一個Transpositions類型,它就是這樣做的。 它通過交換行或列就地工作。 所以你可以一遍又一遍地改組同一個矩陣。

#include <Eigen/Dense>

#include <algorithm>
// using std::min
#include <cassert>
#include <random>
// using std::default_random_engine, std::uniform_int_distribution


void shuffle_apply(Eigen::Ref<Eigen::MatrixXd> mat,
                   Eigen::Ref<Eigen::VectorXd> vec,
                   int generations, int batchsize)
{
  // colwise is faster than rowwise
  const Eigen::Index size = mat.cols();
  assert(vec.size() == size);
  using Transpositions = Eigen::Transpositions<
    Eigen::Dynamic, Eigen::Dynamic, Eigen::Index>;
  Transpositions transp(size);
  Eigen::Index* transp_indices = transp.indices().data();
  std::default_random_engine rng; // seed appropriately!
  for(int gen = 0; gen < generations; ++gen) {
    for(Eigen::Index i = 0; i < size; ++i) {
      std::uniform_int_distribution<Eigen::Index> distr(i, size - 1);
      transp_indices[i] = distr(rng);
    }
    mat = mat * transp; // operates in-place
    vec = transp * vec; // transp on left side to shuffle rows, not cols
    for(Eigen::Index start = 0; start < size; start += batchsize) {
      const Eigen::Index curbatch = std::min<Eigen::Index>(
            batchsize, size - start);
      const auto mat_batch = mat.middleCols(start, curbatch);
      const auto vec_batch = vec.segment(start, curbatch);
    }
  }
}

另請參閱特征矩陣中的置換列和類似問題。

編輯:這個的舊版本通過 std::shuffle 初始化了索引,我認為這是錯誤的

這是第二個版本,可能會提供更友好的界面。 特別是,可以在不復制的情況下恢復原始矩陣和向量。

class BatchShuffle
{
  using Transpositions = Eigen::Transpositions<
    Eigen::Dynamic, Eigen::Dynamic, Eigen::Index>;
  using Permutations = Eigen::PermutationMatrix<
    Eigen::Dynamic, Eigen::Dynamic, Eigen::Index>;

  Eigen::MatrixXd mat_;
  Eigen::VectorXd vec_;
  Transpositions cur_transp;
  Permutations aggregated_permut;
public:
  BatchShuffle(Eigen::MatrixXd mat, Eigen::VectorXd vec)
    : mat_(std::move(mat)),
      vec_(std::move(vec)),
      cur_transp(this->mat_.cols()),
      aggregated_permut(this->mat_.cols())
  {
    assert(this->vec_.size() == this->mat_.cols());
    aggregated_permut.setIdentity();
  }
  Eigen::Index totalsize() const noexcept
  { return mat_.cols(); }

  const Eigen::MatrixXd& mat() const noexcept
  { return mat_; }

  const Eigen::VectorXd& vec() const noexcept
  { return vec_; }
  
  template<class RandomNumberEngine>
  void shuffle(RandomNumberEngine& rng)
  {
    Eigen::Index* indices = cur_transp.indices().data();
    for(Eigen::Index i = 0, n = totalsize(); i < n; ++i) {
      std::uniform_int_distribution<Eigen::Index> distr(i, n - 1);
      indices[i] = distr(rng);
    }
    Permutations::IndicesType& aggregated = aggregated_permut.indices();
    aggregated = cur_transp * aggregated;
    mat_ = mat_ * cur_transp;
    vec_ = cur_transp * vec_;
  }
  void BatchShuffle::restore_original()
  {
    const auto& inverse = aggregated_permut.inverse().eval();
    mat_ = mat_ * inverse;
    vec_ = inverse * vec_;
    aggregated_permut.setIdentity();
  }
};

void apply(const Eigen::Ref<const Eigen::MatrixXd>& mat,
           const Eigen::Ref<const Eigen::VectorXd>& vec);

int main()
{
  int rows = 1000, cols = 10000, batchsize = 100;
  BatchShuffle batch(Eigen::MatrixXd::Random(rows, cols),
                     Eigen::VectorXd::Random(cols));
  std::default_random_engine rng;
  for(int i = 0; i < 100; ++i) {
    batch.shuffle(rng);
    for(Eigen::Index j = 0; j < batch.totalsize(); j += batchsize) {
      Eigen::Index cursize =
        std::min<Eigen::Index>(batchsize, batch.totalsize() - j);
      apply(batch.mat().middleCols(j, cursize),
            batch.vec().segment(j, cursize));
    }
  }
  batch.restore_original();
}

同樣,我選擇按列使用矩陣,這與您采用行的代碼嘗試不同。 Eigen 以列優先順序(又名 Fortran 順序)存儲其矩陣。 采用行切片而不是列切片會顯着減慢您對數據所做的幾乎所有操作。 因此,如果可能的話,我強烈建議您相應地轉置您的輸入生成和矩陣使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM