如何在用户定义的tensorflow op中使用随机数? [英] How to use random number in user defined tensorflow op?

查看:56
本文介绍了如何在用户定义的tensorflow op中使用随机数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在用户定义的tensorflow op中使用随机数?

How to use random number in user defined tensorflow op?

我正在cpp中编写一个需要在Compute函数中使用随机数的op。

I am writing a op in cpp which need random number in the Compute function.

但是似乎我不应该直接使用cpp随机库,因为它不能由 tf.set_random_seed 控制。

but It seems I should not use cpp random library directly, since that cannot control by tf.set_random_seed.

我当前的代码如下所示,我应该在函数 some_interesting_random_function 中做什么?

My current code is something like the following, what should I do in function some_interesting_random_function ?

#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/common_shape_fns.h"
#include <iostream>
#include <typeinfo>
#include <random>

using namespace tensorflow;

REGISTER_OP("MyRandom")
.Output("random: int32")
.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
        c->set_output(0, c->Scalar());
        return Status::OK();
});

int some_interesting_random_function(){
    return 10;
}

class MyRandomOp : public OpKernel {
    public:
        explicit MyRandomOp(OpKernelConstruction* context) : OpKernel(context) {}
        void Compute(OpKernelContext* context) override {
            Tensor* res;
            TensorShape shape;
            int dims[] = {};
            TensorShapeUtils::MakeShape(dims, 0, &shape);
            OP_REQUIRES_OK(context, context->allocate_output(0, shape,
                        &res));
            auto out1 = res->flat<int32>();
            out1(0) = some_interesting_random_function();
        }
};

REGISTER_KERNEL_BUILDER(Name("MyRandom").Device(DEVICE_CPU), MyRandomOp);


推荐答案

TensorFlow中所有随机数生成的核心是< a href = https://github.com/tensorflow/tensorflow/blob/v1.11.0/tensorflow/core/lib/random/philox_random.h rel = nofollow noreferrer> PhiloxRandom ,通常通过其包装器 GuardedPhiloxRandom 。如 tf.set_random_seed ,有图级别和操作级别的种子,它们都可以设置或可以不设置。如果您也想在操作中使用此功能,则需要做几件事。首先,应该使用两个可选属性声明您的操作, seed seed2 ;请参阅 random_ops.cc 。然后,在Python中,您可以使用一些用户API来包装您的操作,从而使用 tensorflow.python.framework.random_seed 来生成这两个值,您必须将其导入为 tensorflow.python.framework导入random_seed 并执行 seed1,seed2 = random_seed.get_seed(seed);这将使用图形的种子和函数的可选 seed 参数正确创建两个种子值(请参见 random_ops.py )。然后将这些 seed1 seed2 值作为 seed 传递, seed2 显然是您的操作的属性。如果完成所有操作,则 GuardedPhiloxRandom 将负责使用正确的种子正确初始化随机数生成器。

The core of all random number generation in TensorFlow is PhiloxRandom, generally accessed through its wrapper GuardedPhiloxRandom. As explained in tf.set_random_seed, there are graph-level and op-level seeds, both of which may or may not be set. If you want to have this in your op too, you need to do a couple of things. First, your op should be declared with two optional attributes, seed and seed2; see the existing ops in random_ops.cc. Then, in Python, you have some user API wrapping your op that makes these two values using tensorflow.python.framework.random_seed, which you have to import as tensorflow.python.framework import random_seed, and do seed1, seed2 = random_seed.get_seed(seed); this will correctly create the two seed values using the graph's seed and an optional seed parameter to the function (see random_ops.py). These seed1 and seed2 values are then passed as seed and seed2 attributes to your op, obviously. If you do all that, then GuardedPhiloxRandom will take care of properly initializing the random number generator using the right seeds.

现在,到内核实施。除了我上面提到的内容外,您还需要结合两件事:在FillPhiloxRandom tensorflow / tensorflow / blob / master / tensorflow / core / kernels / random_op.h rel = nofollow noreferrer> core / kernels / random_op.h ,这将帮助您用随机数据填充张量;和 Distribution ,它只是可以用随机数生成器调用以生成值的对象(请参阅 core / lib / random / random_distributions.h )。现在主要是在 core / kernels / random_op.cc ,然后复制所需的位。其中的大多数内核都基于 PhiloxRandomOp (未公开声明,但您可以复制或改编)。这实质上是一个随机数生成器,在输出张量中分配空间(假定第一个输入是所需的形状),然后调用 FillPhiloxRandom 进行工作。如果您要尝试创建这种操作(根据某种分布生成一些数据),那么一切就绪!您的代码可能看起来像这样:

Now, to the kernel implementation. In addition to the things I mentioned above, you will need to combine two things: the struct template FillPhiloxRandom, declared in core/kernels/random_op.h, which will help you fill a tensor with random data; and a Distribution, which is just an object that can be called with a random number generator to produce a value (see existing implementations in core/lib/random/random_distributions.h). Now it is mostly a matter of looking at how it is done in core/kernels/random_op.cc, and copy the bits you need. Most kernels in there are based on PhiloxRandomOp (which is not publicly declared, but you can copy or adapt). This essentially holds a random number generator, allocates space in the output tensor (it assumes the first input is the desired shape) and calls FillPhiloxRandom to do the work. If this is the kind of op you are trying to create (generate some data according to some distribution), then you are all set! Your code could look something like this:

// Required for thread pool device
#define EIGEN_USE_THREADS

#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/kernels/random_op.h"
#include "tensorflow/core/util/guarded_philox_random.h"

// Helper function to convert an 32-bit integer to a float between [0..1).
// Copied from core/lib/random/random_distributions.h
PHILOX_DEVICE_INLINE float Uint32ToFloat(uint32 x) {
  // IEEE754 floats are formatted as follows (MSB first):
  //    sign(1) exponent(8) mantissa(23)
  // Conceptually construct the following:
  //    sign == 0
  //    exponent == 127  -- an excess 127 representation of a zero exponent
  //    mantissa == 23 random bits
  const uint32 man = x & 0x7fffffu;  // 23 bit mantissa
  const uint32 exp = static_cast<uint32>(127);
  const uint32 val = (exp << 23) | man;

  // Assumes that endian-ness is same for float and uint32.
  float result;
  memcpy(&result, &val, sizeof(val));
  return result - 1.0f;
}

// Template class for your custom distribution
template <class Generator, typename RealType>
class MyDistribution;

// Implementation for tf.float32
template <class Generator>
class MyDistribution<Generator, float> {
 public:
  // The number of elements that will be returned (see below).
  static const int kResultElementCount = Generator::kResultElementCount;
  // Cost of generation of a single element (in cycles) (see below).
  static const int kElementCost = 3;
  // Indicate that this distribution may take variable number of samples
  // during the runtime (see below).
  static const bool kVariableSamplesPerOutput = false;
  typedef Array<float, kResultElementCount> ResultType;
  typedef float ResultElementType;

  PHILOX_DEVICE_INLINE
  ResultType operator()(Generator* gen) {
    typename Generator::ResultType sample = (*gen)();
    ResultType result;
    for (int i = 0; i < kResultElementCount; ++i) {
      float r = Uint32ToFloat(sample[i]);
      // Example distribution logic: produce 1 or 0 with 50% probability
      result[i] = 1.0f * (r < 0.5f);
    }
    return result;
  }
};

// Could add implementations for other data types...

// Base kernel
// Copied from core/kernels/random_op.cc
static Status AllocateOutputWithShape(OpKernelContext* ctx, const Tensor& shape,
                                      int index, Tensor** output) {
  TensorShape tensor_shape;
  TF_RETURN_IF_ERROR(ctx->op_kernel().MakeShape(shape, &tensor_shape));
  return ctx->allocate_output(index, tensor_shape, output);
}

template <typename Device, class Distribution>
class PhiloxRandomOp : public OpKernel {
 public:
  typedef typename Distribution::ResultElementType T;
  explicit PhiloxRandomOp(OpKernelConstruction* ctx) : OpKernel(ctx) {
    OP_REQUIRES_OK(ctx, generator_.Init(ctx));
  }
  void Compute(OpKernelContext* ctx) override {
    const Tensor& shape = ctx->input(0);
    Tensor* output;
    OP_REQUIRES_OK(ctx, AllocateOutputWithShape(ctx, shape, 0, &output));
    auto output_flat = output->flat<T>();
    tensorflow::functor::FillPhiloxRandom<Device, Distribution>()(
        ctx, ctx->eigen_device<Device>(),
        // Multiplier 256 is the same as in FillPhiloxRandomTask; do not change
        // it just here.
        generator_.ReserveRandomOutputs(output_flat.size(), 256),
        output_flat.data(), output_flat.size(), Distribution());
  }
 private:
  GuardedPhiloxRandom generator_;
};

// Register kernel
typedef Eigen::ThreadPoolDevice CPUDevice;
template struct functor::FillPhiloxRandom<
    CPUDevice, MyDistribution<tensorflow::random::PhiloxRandom, float>>;
REGISTER_KERNEL_BUILDER(
    Name("MyDistribution")
        .Device(DEVICE_CPU)
        .HostMemory("shape")
        .TypeConstraint<float>("dtype"),
    PhiloxRandomOp<CPUDevice, MyDistribution<tensorflow::random::PhiloxRandom, float>>);

// Register kernels for more types, can use macros as in core/kernels/random_op.cc...

这里有一些额外的点点滴滴。首先,您需要了解 PhiloxRandom 通常在每个步骤上生成四个无符号的32位整数,并且您必须从中获得随机值。 Uint32ToFloat 是从该数字之一获得零到一之间的 float 的助手。也有一些常数。 kResultElementCount 是分布在每个步骤中产生的值的数量。如果您通过生成器为每个随机数生成一个值,则也可以将其设置为 Generator :: kResultElementCount ,如下所示(为4)。但是,例如,如果要生成 double 值(即 tf.float64 ),则可能需要每个值使用两个32位整数,因此在这种情况下,您可能会生成 Generator :: kResultElementCount / 2 kElementCost 应该用来指示您的分布产生一个元素需要多少个周期。我不知道TensorFlow团队是如何衡量的,但这只是在任务之间分配生成工作的提示(由 FillPhiloxRandom 使用),因此您可以猜测或从类似的昂贵发行版中复制。 kVariableSamplesPerOutput 确定对您的发行版的每次调用是否可能产生不同数量的输出;再次,当这是 false (应该是常见的情况)时, FillPhiloxRandom 将使值生成更加有效。 PHILOX_DEVICE_INLINE (在 core / lib / random / philox_random.h )是用于内联函数的编译器提示。然后可以为其他数据类型添加其他实现和内核注册,如果支持的话,还可以为 DEVICE_GPU GPUDevice 添加(使用 typedef Eigen :: GpuDevice GPUDevice )甚至什至 DEVICE_SYCL (使用 typedef Eigen :: SyclDevice SYCLDevice )(如果需要)。与此相关的是, EIGEN_USE_THREADS 只是为了启用Eigen中的线程池执行设备,使CPU实现成为多线程。

There are a few extra bits and pieces here. First you need to understand that PhiloxRandom generally produces four unsigned 32-bit integers on each step, and you have to make your random values from these. Uint32ToFloat is a helper to get a float between zero and one from one of this numbers. There are a few constants in there too. kResultElementCount is the number of values your distribution produces on each step. If you produce one value per random number form the generator, you can set it too Generator::kResultElementCount, like here (which is 4). However, for example if you want to produce double values (that is, tf.float64), you may want to use two 32-bit integers per value, so maybe you would produce Generator::kResultElementCount / 2 in that case. kElementCost is supposed to indicate how many cycles it takes your distribution to produce an element. I do not know how this is measured by the TensorFlow team, but it is just a hint to distribute the generation work among tasks (used by FillPhiloxRandom), so you can just guess something, or copy it from a similarly expensive distribution. kVariableSamplesPerOutput determines whether each call to your distribution may produce a different number of outputs; again, when this is false (which should be the common case), FillPhiloxRandom will make the value generation more efficient. PHILOX_DEVICE_INLINE (defined in core/lib/random/philox_random.h) is a compiler hint to inline the function. You can add then additional implementations and kernel registrations for other data types and, if you are supporting it, for DEVICE_GPU GPUDevice (with typedef Eigen::GpuDevice GPUDevice) or even DEVICE_SYCL (with typedef Eigen::SyclDevice SYCLDevice), if you want. And about that, EIGEN_USE_THREADS is just to enable the thread pool execution device in Eigen, to make CPU implementation multi-threaded.

但是,如果您的用例不同(例如,您想生成一些随机数并进行其他计算),则 FillPhiloxRandom 可能不是对您有用(或者可能有用,但随后您还需要执行其他操作)。看看 core / kernels / random_op.cc 和不同类的标题应该有助于您弄清楚如何使用它们解决问题。

If your use case is different, though (for example, you want to generate some random numbers and do some other computation in addition to that), FillPhiloxRandom may not be useful to you (or it may be, but then you also need to do something else). Having a look at core/kernels/random_op.cc and the headers of the different classes should help you figure out how to use them for your problem.

这篇关于如何在用户定义的tensorflow op中使用随机数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆