ApacheMXNet-Unified Operator API
この章では、Apache MXNetの統合オペレーターアプリケーションプログラミングインターフェイス(API)について説明します。
SimpleOp
SimpleOpは、さまざまな呼び出しプロセスを統合する新しい統合演算子APIです。呼び出されると、演算子の基本要素に戻ります。統合演算子は、単項演算と二項演算用に特別に設計されています。これは、ほとんどの数学演算子が1つまたは2つのオペランドに対応し、より多くのオペランドが依存関係に関連する最適化を有用にするためです。
例を使用して、SimpleOp統合オペレーターを理解します。この例では、として機能する演算子を作成しますsmooth l1 loss、これはl1とl2の損失の混合です。以下のように損失を定義して書き込むことができます-
loss = outside_weight .* f(inside_weight .* (data - label))
grad = outside_weight .* inside_weight .* f'(inside_weight .* (data - label))
ここで、上記の例では、
。*は要素ごとの乗算を表します
f, f’ は、私たちが想定している滑らかなl1損失関数です。 mshadow。
この特定の損失を単項演算子または二項演算子として実装することは不可能に見えますが、MXNetはユーザーにシンボリック実行の自動微分を提供し、fとf 'への損失を直接単純化します。そのため、この特定の損失を単項演算子として確実に実装できます。
形状の定義
私たちが知っているように、MXNetは mshadow library明示的なメモリ割り当てが必要なため、計算を行う前にすべてのデータ形状を提供する必要があります。関数と勾配を定義する前に、次のように入力形状の一貫性と出力形状を提供する必要があります。
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は、入力データの形状と指定された出力データの形状をチェックするために使用されます。この関数を定義しない場合、デフォルトの出力形状は入力形状と同じになります。たとえば、二項演算子の場合、lhsとrhsの形状はデフォルトで同じとしてチェックされます。
それでは、次に進みましょう。 smooth l1 loss example. このために、ヘッダー実装でxPUをcpuまたはgpuに定義する必要があります smooth_l1_unary-inl.h. 理由は、で同じコードを再利用することです smooth_l1_unary.cc そして smooth_l1_unary.cu.
#include <mxnet/operator_util.h>
#if defined(__CUDACC__)
#define XPU gpu
#else
#define XPU cpu
#endif
私たちのように smooth l1 loss example,出力はソースと同じ形状であるため、デフォルトの動作を使用できます。次のように書くことができます-
inline mxnet::TShape SmoothL1Shape_(const mxnet::TShape& src,const EnvArguments& env) {
return mxnet::TShape(src);
}
関数の定義
次のように、1つの入力で単項関数または二項関数を作成できます。
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);
以下は RunContext ctx struct 実行時に実行に必要な情報が含まれています-
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
それでは、計算結果を次のように書く方法を見てみましょう。 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
};
それでは、次に進みましょう smooth l1 loss example。このために、UnaryFunctionを使用して、この演算子の関数を次のように定義します。
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)));
});
}
グラデーションの定義
を除いて Input, TBlob, そして OpReqType二項演算子の勾配関数は2倍になり、同様の構造になります。以下をチェックしてみましょう。ここでは、さまざまなタイプの入力を使用して勾配関数を作成しました。
// 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);
上で定義したように Input0, Input, OutputValue, そして OutputGrad すべてがの構造を共有しています GradientFunctionArgument. それは次のように定義されます-
struct GradFunctionArgument {
TBlob data;
}
それでは、次に進みましょう。 smooth l1 loss example。これを勾配の連鎖律を有効にするには、乗算する必要がありますout_grad 上から結果まで 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をMXNetに登録する
形状、関数、グラデーションを作成したら、それらをNDArray演算子とシンボリック演算子の両方に復元する必要があります。このために、登録マクロを次のように使用できます-
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 次のように定義できます-
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)
};
それでは、次に進みましょう。 smooth l1 loss example。このために、入力データに依存する勾配関数があり、関数を所定の位置に書き込むことができません。
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のSimpleOp
私たちが知っているように、いくつかの操作は以下を必要とするかもしれません-
グラデーションスケールなどの入力としてのスカラー
動作を制御するキーワード引数のセット
計算を高速化するための一時的なスペース。
EnvArgumentsを使用する利点は、計算をよりスケーラブルで効率的にするための追加の引数とリソースを提供することです。
例
まず、構造体を次のように定義しましょう-
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
};
次に、次のような追加のリソースをリクエストする必要があります mshadow::Random<xpu> およびからの一時的なメモリスペース EnvArguments.resource. それは次のように行うことができます-
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
};
これで、登録は宣言されたリソース要求をから要求します mxnet::ResourceManager. その後、リソースをに配置します std::vector<Resource> resource in EnvAgruments.
次のコードを使用してリソースにアクセスできます-
auto tmp_space_res = env.resources[0].get_space(some_shape, some_stream);
auto rand_res = env.resources[0].get_random(some_stream);
スムーズなl1損失の例を見ると、損失関数のターニングポイントをマークするためにスカラー入力が必要です。そのため、登録プロセスでは、set_enable_scalar(true)、および env.scalar 関数と勾配の宣言で。
テンソル演算の構築
ここで、なぜテンソル演算を作成する必要があるのかという疑問が生じます。その理由は以下のとおりです。
計算はmshadowライブラリを利用しており、すぐに利用できる機能がない場合があります。
ソフトマックス損失や勾配などの要素ごとの方法で操作が行われない場合。
例
ここでは、上記のスムーズなl1損失の例を使用しています。2つのマッパー、つまり滑らかなl1損失と勾配のスカラーケースを作成します。
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;
}
}
};
}