Apache MXNet - Birleşik Operatör API'si

Bu bölüm, Apache MXNet'teki birleşik operatör uygulama programlama arabirimi (API) hakkında bilgi sağlar.

SimpleOp

SimpleOp, farklı çağırma süreçlerini birleştiren yeni bir birleşik operatör API'sidir. Çağrıldığında, operatörlerin temel öğelerine geri döner. Birleşik operatör, hem tekli hem de ikili işlemler için özel olarak tasarlanmıştır. Bunun nedeni, matematiksel operatörlerin çoğunun bir veya iki işlenenle ilgilenmesi ve daha fazla işlenenin bağımlılıkla ilgili optimizasyonu yararlı kılmasıdır.

Bir örnek yardımıyla çalışan SimpleOp birleşik operatörünü anlayacağız. Bu örnekte, bir operatör olarak işlev gören bir operatör oluşturacağız.smooth l1 loss, l1 ve l2 kaybının bir karışımıdır. Kaybı aşağıdaki gibi tanımlayabilir ve yazabiliriz -

loss = outside_weight .* f(inside_weight .* (data - label))
grad = outside_weight .* inside_weight .* f'(inside_weight .* (data - label))

Burada, yukarıdaki örnekte,

  • . * eleman bazında çarpma anlamına gelir

  • f, f’ içinde olduğunu varsaydığımız pürüzsüz l1 kayıp fonksiyonudur mshadow.

Bu belirli kaybı bir tekli veya ikili operatör olarak uygulamak imkansız görünmektedir, ancak MXNet, kullanıcılarına sembolik uygulamada otomatik farklılaşma sağlar ve bu da kaybı doğrudan f ve f 'ye basitleştirir. Bu nedenle, bu belirli kaybı kesinlikle tekli bir operatör olarak uygulayabiliriz.

Şekilleri Tanımlama

MXNet'in mshadow libraryaçık bellek tahsisi gerektirir, bu nedenle herhangi bir hesaplama yapılmadan önce tüm veri şekillerini sağlamamız gerekir. Fonksiyonları ve gradyanı tanımlamadan önce, aşağıdaki gibi girdi şekli tutarlılığı ve çıktı şekli sağlamamız gerekir:

typedef mxnet::TShape (*UnaryShapeFunction)(const mxnet::TShape& src,
const EnvArguments& env);
   typedef mxnet::TShape (*BinaryShapeFunction)(const mxnet::TShape& lhs,
const mxnet::TShape& rhs,
const EnvArguments& env);

Mxnet :: Tshape işlevi, girdi veri şeklini ve belirlenen çıktı verisi şeklini kontrol etmek için kullanılır. Bu işlevi tanımlamazsanız, varsayılan çıktı şekli giriş şekli ile aynı olacaktır. Örneğin, ikili operatör durumunda, lhs ve rhs şekli varsayılan olarak aynı şekilde kontrol edilir.

Şimdi bize geçelim smooth l1 loss example. Bunun için başlık uygulamasında cpu veya gpu için bir XPU tanımlamamız gerekiyor. smooth_l1_unary-inl.h. Nedeni, aynı kodu yeniden kullanmaktır. smooth_l1_unary.cc ve smooth_l1_unary.cu.

#include <mxnet/operator_util.h>
   #if defined(__CUDACC__)
      #define XPU gpu
   #else
      #define XPU cpu
#endif

Bizimki gibi smooth l1 loss example,çıktı kaynakla aynı şekle sahipse, varsayılan davranışı kullanabiliriz. Aşağıdaki gibi yazılabilir -

inline mxnet::TShape SmoothL1Shape_(const mxnet::TShape& src,const EnvArguments& env) {
   return mxnet::TShape(src);
}

Fonksiyonları Tanımlama

Aşağıdaki gibi bir girişle tekli veya ikili bir fonksiyon oluşturabiliriz -

typedef void (*UnaryFunction)(const TBlob& src,
   const EnvArguments& env,
   TBlob* ret,
   OpReqType req,
   RunContext ctx);
typedef void (*BinaryFunction)(const TBlob& lhs,
   const TBlob& rhs,
   const EnvArguments& env,
   TBlob* ret,
   OpReqType req,
   RunContext ctx);

Aşağıdaki RunContext ctx struct yürütme için çalışma zamanında gerekli bilgileri içeren -

struct RunContext {
   void *stream; // the stream of the device, can be NULL or Stream<gpu>* in GPU mode
   template<typename xpu> inline mshadow::Stream<xpu>* get_stream() // get mshadow stream from Context
} // namespace mxnet

Şimdi, hesaplama sonuçlarını nasıl yazabileceğimize bakalım. ret.

enum OpReqType {
   kNullOp, // no operation, do not write anything
   kWriteTo, // write gradient to provided space
   kWriteInplace, // perform an in-place write
   kAddTo // add to the provided space
};

Şimdi, devam edelim smooth l1 loss example. Bunun için, bu operatörün işlevini aşağıdaki gibi tanımlamak için UnaryFunction kullanacağız:

template<typename xpu>
void SmoothL1Forward_(const TBlob& src,
   const EnvArguments& env,
   TBlob *ret,
   OpReqType req,
RunContext ctx) {
   using namespace mshadow;
   using namespace mshadow::expr;
   mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
   real_t sigma2 = env.scalar * env.scalar;
   MSHADOW_TYPE_SWITCH(ret->type_flag_, DType, {
      mshadow::Tensor<xpu, 2, DType> out = ret->get<xpu, 2, DType>(s);
      mshadow::Tensor<xpu, 2, DType> in = src.get<xpu, 2, DType>(s);
      ASSIGN_DISPATCH(out, req,
      F<mshadow_op::smooth_l1_loss>(in, ScalarExp<DType>(sigma2)));
   });
}

Degradeleri Tanımlama

Dışında Input, TBlob, ve OpReqTypeiki katına çıkarıldığında, ikili operatörlerin gradyan fonksiyonları benzer yapıya sahiptir. Aşağıda, çeşitli girdi türleriyle bir gradyan işlevi oluşturduğumuza bir göz atalım:

// depending only on out_grad
typedef void (*UnaryGradFunctionT0)(const OutputGrad& out_grad,
   const EnvArguments& env,
   TBlob* in_grad,
   OpReqType req,
   RunContext ctx);
// depending only on out_value
typedef void (*UnaryGradFunctionT1)(const OutputGrad& out_grad,
   const OutputValue& out_value,
   const EnvArguments& env,
   TBlob* in_grad,
   OpReqType req,
   RunContext ctx);
// depending only on in_data
typedef void (*UnaryGradFunctionT2)(const OutputGrad& out_grad,
   const Input0& in_data0,
   const EnvArguments& env,
   TBlob* in_grad,
   OpReqType req,
   RunContext ctx);

Yukarıda tanımlandığı gibi Input0, Input, OutputValue, ve OutputGrad hepsi yapısını paylaşıyor GradientFunctionArgument. Aşağıdaki gibi tanımlanır -

struct GradFunctionArgument {
   TBlob data;
}

Şimdi bize geçelim smooth l1 loss example. Bunun gradyan zincir kuralını etkinleştirmek için çarpmamız gerekiyorout_grad en baştan sonucuna in_grad.

template<typename xpu>
void SmoothL1BackwardUseIn_(const OutputGrad& out_grad, const Input0& in_data0,
   const EnvArguments& env,
   TBlob *in_grad,
   OpReqType req,
   RunContext ctx) {
   using namespace mshadow;
   using namespace mshadow::expr;
   mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
   real_t sigma2 = env.scalar * env.scalar;
      MSHADOW_TYPE_SWITCH(in_grad->type_flag_, DType, {
      mshadow::Tensor<xpu, 2, DType> src = in_data0.data.get<xpu, 2, DType>(s);
      mshadow::Tensor<xpu, 2, DType> ograd = out_grad.data.get<xpu, 2, DType>(s);
      mshadow::Tensor<xpu, 2, DType> igrad = in_grad->get<xpu, 2, DType>(s);
      ASSIGN_DISPATCH(igrad, req,
      ograd * F<mshadow_op::smooth_l1_gradient>(src, ScalarExp<DType>(sigma2)));
   });
}

SimpleOp'u MXNet'e Kaydet

Şekli, işlevi ve gradyanı oluşturduktan sonra, onları hem bir NDArray operatörüne hem de sembolik bir operatöre geri yüklememiz gerekir. Bunun için kayıt makrosunu şu şekilde kullanabiliriz -

MXNET_REGISTER_SIMPLE_OP(Name, DEV)
   .set_shape_function(Shape)
   .set_function(DEV::kDevMask, Function<XPU>, SimpleOpInplaceOption)
   .set_gradient(DEV::kDevMask, Gradient<XPU>, SimpleOpInplaceOption)
   .describe("description");

SimpleOpInplaceOption aşağıdaki gibi tanımlanabilir -

enum SimpleOpInplaceOption {
   kNoInplace, // do not allow inplace in arguments
   kInplaceInOut, // allow inplace in with out (unary)
   kInplaceOutIn, // allow inplace out_grad with in_grad (unary)
   kInplaceLhsOut, // allow inplace left operand with out (binary)

   kInplaceOutLhs // allow inplace out_grad with lhs_grad (binary)
};

Şimdi bize geçelim smooth l1 loss example. Bunun için, girdi verilerine dayanan bir gradyan fonksiyonumuz var, böylece fonksiyon yerinde yazılamaz.

MXNET_REGISTER_SIMPLE_OP(smooth_l1, XPU)
.set_function(XPU::kDevMask, SmoothL1Forward_<XPU>, kNoInplace)
.set_gradient(XPU::kDevMask, SmoothL1BackwardUseIn_<XPU>, kInplaceOutIn)
.set_enable_scalar(true)
.describe("Calculate Smooth L1 Loss(lhs, scalar)");

EnvArguments üzerinde SimpleOp

Bildiğimiz gibi, bazı operasyonların aşağıdakilere ihtiyacı olabilir -

  • Gradyan ölçeği gibi girdi olarak skaler

  • Davranışı kontrol eden bir dizi anahtar kelime argümanı

  • Hesaplamaları hızlandırmak için geçici bir alan.

EnvArguments kullanmanın yararı, hesaplamaları daha ölçeklenebilir ve verimli hale getirmek için ek argümanlar ve kaynaklar sağlamasıdır.

Misal

Öncelikle yapıyı aşağıdaki gibi tanımlayalım -

struct EnvArguments {
   real_t scalar; // scalar argument, if enabled
   std::vector<std::pair<std::string, std::string> > kwargs; // keyword arguments
   std::vector<Resource> resource; // pointer to the resources requested
};

Daha sonra, aşağıdaki gibi ek kaynaklar talep etmemiz gerekiyor mshadow::Random<xpu> ve geçici hafıza alanı EnvArguments.resource. Aşağıdaki gibi yapılabilir -

struct ResourceRequest {
   enum Type { // Resource type, indicating what the pointer type is
      kRandom, // mshadow::Random<xpu> object
      kTempSpace // A dynamic temp space that can be arbitrary size
   };
   Type type; // type of resources
};

Şimdi kayıt, beyan edilen kaynak talebini şuradan isteyecektir: mxnet::ResourceManager. Bundan sonra kaynakları yerleştirecek std::vector<Resource> resource in EnvAgruments.

Kaynaklara aşağıdaki kod yardımı ile erişebiliriz -

auto tmp_space_res = env.resources[0].get_space(some_shape, some_stream);
auto rand_res = env.resources[0].get_random(some_stream);

Düzgün l1 kaybı örneğimizde görüyorsanız, bir kayıp fonksiyonunun dönüm noktasını işaretlemek için bir skaler giriş gereklidir. Bu yüzden kayıt sürecinde kullanıyoruzset_enable_scalar(true), ve env.scalar fonksiyon ve gradyan bildirimlerinde.

Bina Tensörü Operasyonu

Burada şu soru ortaya çıkıyor: Neden tensör operasyonları yapmamız gerekiyor? Sebepler aşağıdaki gibidir -

  • Hesaplama, mshadow kitaplığını kullanır ve bazen kullanıma hazır işlevlerimiz olmayabilir.

  • Softmax kaybı ve gradyan gibi eleman bazında bir işlem yapılmazsa.

Misal

Burada, yukarıdaki pürüzsüz l1 kaybı örneğini kullanıyoruz. Düzgün l1 kaybı ve gradyan skaler durumları olmak üzere iki eşleyici oluşturacağız:

namespace mshadow_op {
   struct smooth_l1_loss {
      // a is x, b is sigma2
      MSHADOW_XINLINE static real_t Map(real_t a, real_t b) {
         if (a > 1.0f / b) {
            return a - 0.5f / b;
         } else if (a < -1.0f / b) {
            return -a - 0.5f / b;
         } else {
            return 0.5f * a * a * b;
         }
      }
   };
}