Apache MXNet - API Operator Terpadu
Bab ini memberikan informasi tentang antarmuka pemrograman aplikasi operator (API) terpadu di Apache MXNet.
SimpleOp
SimpleOp adalah API operator terpadu baru yang menyatukan berbagai proses pemanggilan. Setelah dipanggil, ia kembali ke elemen dasar operator. Operator terpadu dirancang khusus untuk operasi unary serta biner. Itu karena sebagian besar operator matematika memperhatikan satu atau dua operan dan lebih banyak operan membuat pengoptimalan, terkait dengan ketergantungan, berguna.
Kami akan memahami operator terpadu SimpleOp yang bekerja dengan bantuan sebuah contoh. Dalam contoh ini, kami akan membuat operator yang berfungsi sebagaismooth l1 loss, yang merupakan campuran dari kerugian l1 dan l2. Kita dapat mendefinisikan dan menulis kerugian seperti yang diberikan di bawah ini -
loss = outside_weight .* f(inside_weight .* (data - label))
grad = outside_weight .* inside_weight .* f'(inside_weight .* (data - label))
Di sini, pada contoh di atas,
. * adalah singkatan dari perkalian berdasarkan elemen
f, f’ adalah fungsi kerugian l1 halus yang kita asumsikan berada mshadow.
Tampaknya tidak mungkin untuk menerapkan kerugian khusus ini sebagai operator unary atau biner, tetapi MXNet menyediakan diferensiasi otomatis kepada penggunanya dalam eksekusi simbolik yang menyederhanakan kerugian ke f dan f 'secara langsung. Itu sebabnya kami pasti dapat menerapkan kerugian khusus ini sebagai operator unary.
Mendefinisikan Bentuk
Seperti yang kita ketahui tentang MXNet mshadow librarymembutuhkan alokasi memori eksplisit sehingga kita perlu menyediakan semua bentuk data sebelum perhitungan apapun terjadi. Sebelum mendefinisikan fungsi dan gradien, kita perlu memberikan konsistensi bentuk masukan dan bentuk keluaran sebagai berikut:
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);
Fungsi mxnet :: Tshape digunakan untuk memeriksa bentuk data masukan dan bentuk data keluaran yang ditentukan. Dalam kasus ini, jika Anda tidak mendefinisikan fungsi ini maka bentuk keluaran default akan sama dengan bentuk masukan. Misalnya, dalam kasus operator biner, bentuk lhs dan rhs secara default dicentang sama.
Sekarang mari kita lanjutkan ke smooth l1 loss example. Untuk ini, kita perlu mendefinisikan XPU ke cpu atau gpu dalam implementasi header smooth_l1_unary-inl.h. Alasannya adalah untuk menggunakan kembali kode yang sama di smooth_l1_unary.cc dan smooth_l1_unary.cu.
#include <mxnet/operator_util.h>
#if defined(__CUDACC__)
#define XPU gpu
#else
#define XPU cpu
#endif
Seperti di kami smooth l1 loss example,Outputnya memiliki bentuk yang sama dengan sumbernya, kita dapat menggunakan perilaku default. Itu dapat ditulis sebagai berikut -
inline mxnet::TShape SmoothL1Shape_(const mxnet::TShape& src,const EnvArguments& env) {
return mxnet::TShape(src);
}
Mendefinisikan Fungsi
Kita dapat membuat fungsi unary atau binary dengan satu input sebagai berikut -
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);
Berikut ini adalah RunContext ctx struct yang berisi informasi yang diperlukan selama runtime untuk eksekusi -
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
Sekarang, mari kita lihat bagaimana kita bisa menulis hasil komputasi 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
};
Sekarang, mari kita lanjutkan ke smooth l1 loss example. Untuk ini, kami akan menggunakan UnaryFunction untuk mendefinisikan fungsi operator ini sebagai berikut:
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)));
});
}
Mendefinisikan Gradien
Kecuali Input, TBlob, dan OpReqTypedigandakan, fungsi Gradien dari operator biner memiliki struktur yang serupa. Mari kita lihat di bawah, di mana kami membuat fungsi gradien dengan berbagai jenis input:
// 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);
Seperti yang didefinisikan di atas Input0, Input, OutputValue, dan OutputGrad semua berbagi struktur GradientFunctionArgument. Ini didefinisikan sebagai berikut -
struct GradFunctionArgument {
TBlob data;
}
Sekarang mari kita lanjutkan ke smooth l1 loss example. Untuk mengaktifkan aturan rantai gradien kita perlu mengalikanout_grad dari atas ke hasil 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)));
});
}
Daftarkan SimpleOp ke MXNet
Setelah kita membuat bentuk, fungsi, dan gradien, kita perlu mengembalikannya menjadi operator NDArray dan juga menjadi operator simbolik. Untuk ini, kita dapat menggunakan makro registrasi sebagai berikut -
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");
Itu SimpleOpInplaceOption dapat didefinisikan sebagai berikut -
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)
};
Sekarang mari kita lanjutkan ke smooth l1 loss example. Untuk ini, kami memiliki fungsi gradien yang bergantung pada data masukan sehingga fungsi tersebut tidak dapat ditulis pada tempatnya.
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)");
SimpleOp di EnvArguments
Seperti yang kita ketahui, beberapa operasi mungkin memerlukan yang berikut -
Skalar sebagai masukan seperti skala gradien
Seperangkat argumen kata kunci yang mengontrol perilaku
Ruang sementara untuk mempercepat penghitungan.
Manfaat menggunakan EnvArguments adalah menyediakan argumen dan sumber daya tambahan untuk membuat penghitungan lebih skalabel dan efisien.
Contoh
Pertama mari kita definisikan struct seperti di bawah ini -
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
};
Selanjutnya, kita perlu meminta sumber daya tambahan seperti mshadow::Random<xpu> dan ruang memori sementara dari EnvArguments.resource. Itu dapat dilakukan sebagai berikut -
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
};
Sekarang, pendaftaran akan meminta permintaan sumber daya yang dideklarasikan dari mxnet::ResourceManager. Setelah itu, itu akan menempatkan sumber daya std::vector<Resource> resource in EnvAgruments.
Kami dapat mengakses sumber daya dengan bantuan kode berikut -
auto tmp_space_res = env.resources[0].get_space(some_shape, some_stream);
auto rand_res = env.resources[0].get_random(some_stream);
Jika Anda melihat contoh kerugian l1 halus kami, masukan skalar diperlukan untuk menandai titik balik fungsi kerugian. Karena itulah dalam proses registrasi, kami menggunakanset_enable_scalar(true), dan env.scalar dalam deklarasi fungsi dan gradien.
Membangun Operasi Tensor
Di sini muncul pertanyaan mengapa kita perlu membuat operasi tensor? Alasannya adalah sebagai berikut -
Komputasi menggunakan pustaka mshadow dan terkadang kami tidak memiliki fungsi yang tersedia.
Jika sebuah operasi tidak dilakukan dengan cara yang bijaksana elemen seperti kehilangan dan gradien softmax.
Contoh
Di sini, kami menggunakan contoh kerugian l1 halus di atas. Kami akan membuat dua pembuat peta yaitu kasus skalar kerugian l1 halus dan gradien:
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;
}
}
};
}