欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

目錄

柚子快報(bào)激活碼778899分享:mxnet系統(tǒng)架構(gòu)

eBay跨境俠綜合2025-05-05450

柚子快報(bào)激活碼778899分享:mxnet系統(tǒng)架構(gòu)

http://yzkb.51969.com/

mxnet系統(tǒng)架構(gòu)

MXNet 是一個(gè)高性能、靈活的深度學(xué)習(xí)框架,最早由李沐(Mu Li)等人開(kāi)發(fā),并且得到了 Amazon 的支持。它支持多種語(yǔ)言(包括 Python、Scala、C++、R、Julia、Perl 等),并以其靈活的編程模型、高效的自動(dòng)微分和分布式訓(xùn)練能力而聞名。

原文鏈接

MXNet 系統(tǒng)架構(gòu)(翻譯)

這張圖展示了 MXNet 的主要模塊和組件,以及它們之間的交互。這些模塊是:

Runtime Dependency Engine: 根據(jù)運(yùn)算之間讀寫的依賴關(guān)系,調(diào)度并執(zhí)行它們。Storage Allocator: 高效地分配和回收主機(jī)(CPU)和設(shè)備(GPU)的內(nèi)存。Resource Manager: 管理全局資源,比如隨機(jī)數(shù)生成器和臨時(shí)空間。NDArray: 動(dòng)態(tài)的異步的 n-維數(shù)組,為 MXNet 提供靈活的命令式編程。Symbolic Execution: 靜態(tài)符號(hào)圖執(zhí)行器,提供高效的符號(hào)圖執(zhí)行和優(yōu)化。Operator: 定義靜態(tài)的前向和梯度計(jì)算(backprop)的運(yùn)算符。SimpleOp: 以統(tǒng)一的方式擴(kuò)展 NDArray 運(yùn)算符和符號(hào)式運(yùn)算符的運(yùn)算符。Symbol Construction: 提供創(chuàng)建計(jì)算圖(網(wǎng)絡(luò)配置)的方式。KVStore: 鍵值存儲(chǔ)接口,提供高效的參數(shù)同步機(jī)制。Data Loading (IO): 高效的數(shù)據(jù)加載和更新。

MXNet 系統(tǒng)模塊

Execution Engine (執(zhí)行引擎)

你不僅可以使用 MXNet 引擎進(jìn)行深度學(xué)習(xí),還可以使用它來(lái)解決任何專業(yè)領(lǐng)域的問(wèn)題。MXNet 的設(shè)計(jì)目標(biāo)是解決通用問(wèn)題:根據(jù)依賴關(guān)系來(lái)執(zhí)行一系列的函數(shù)。有依賴關(guān)系的任意兩個(gè)函數(shù)應(yīng)該按順序執(zhí)行。為提升性能,沒(méi)有依賴關(guān)系的函數(shù)可以被并行執(zhí)行。關(guān)于這方面的更詳細(xì)的討論,請(qǐng)見(jiàn) notes on the dependency engine。

Interface (接口)

下面這個(gè) API 是執(zhí)行引擎的核心接口:

virtual void PushSync(Fn exec_fun, Context exec_ctx,

std::vector const& const_vars,

std::vector const& mutate_vars) = 0;

這個(gè) API 允許你將一個(gè)函數(shù)(exec_fun)和它的執(zhí)行上下文(context)以及依賴關(guān)系一起推送給執(zhí)行引擎。exec_ctx 是 exec_fun 的執(zhí)行上下文,const_vars 是函數(shù)要讀取的變量,mutate_vars 是函數(shù)要修改的變量。執(zhí)行引擎提供以下保證:

任意兩個(gè)函數(shù),如果它們要修改同一個(gè)變量,則它們的執(zhí)行順序與它們被推送到引擎的順序是一致的。

Function (函數(shù))

引擎的函數(shù)的類型是:

using Fn = std::function;

RunContext 包含了運(yùn)行時(shí)的信息,這些信息由引擎來(lái)確定。

struct RunContext {

// stream pointer which could be safely cast to

// cudaStream_t* type

void* stream

};

或者,你也可以使用 mxnet::engine::DAGEngine::Fn, 它有相同的類型定義。

所有的函數(shù)都在引擎的內(nèi)部線程中執(zhí)行。在這種模型中,把會(huì)阻塞的函數(shù)(通常是處理磁盤或網(wǎng)絡(luò)之類的 I/O 任務(wù)的操作)發(fā)送給引擎通常不是個(gè)好主意,因?yàn)樗鼤?huì)占用執(zhí)行線程,并且降低吞吐量。在這種情況下,我們提供了另一個(gè)異步的函數(shù):

using Callback = std::function;

using AsyncFn = std::function;

在 AsyncFn 中,你可以把阻塞的部分傳回到你自己的線程,然后退出函數(shù)。引擎在調(diào)用 Callback 之后才會(huì)認(rèn)為 AsyncFn 函數(shù)執(zhí)行完成。

Context (上下文)

你可以指定函數(shù)執(zhí)行的上下文。這通常包括函數(shù)該在 CPU 還是 GPU 上運(yùn)行,如果指定使用 GPU,還可以指定哪個(gè) GPU。Context 和 RunContext 不同,Context 包含設(shè)備類型 (GPU/CPU) 和設(shè)備 id,而 RunContext 包含只有運(yùn)行時(shí)才能確定的信息,比如函數(shù)應(yīng)該在哪個(gè)流上執(zhí)行。

VarHandle

VarHandle 是用來(lái)指定函數(shù)之間的依賴關(guān)系的。MXNet 引擎被設(shè)計(jì)為與其他模塊之間是松耦合的,因此 VarHandle 就像一個(gè)引擎提供的標(biāo)記,你可以用它來(lái)代表函數(shù)能使用或修改的外部資源。VarHandle 很輕量,所以創(chuàng)建、刪除或復(fù)制都只有很小的開(kāi)銷。在把函數(shù)推送給引擎時(shí),你需要在 const_vars 向量中指定函數(shù)將要使用(只讀)的變量,在 mutate_vars 向量中指定函數(shù)將要修改的變量。引擎使用以下規(guī)則來(lái)解析函數(shù)之間的依賴關(guān)系:

如果兩個(gè)函數(shù)修改至少一個(gè)共同的變量,那么它們的執(zhí)行順序和它們被推送的順序是一致的。

舉個(gè)例子,如果 Fn1 和 Fn2 都要修改 V2,并且 Fn2 是在 Fn1 之后被推送的,則執(zhí)行引擎保證 Fn2 在 Fn1 之后執(zhí)行。如果 Fn1 和 Fn2 都使用(只讀)V2,則它們的執(zhí)行順序是隨機(jī)的。

這樣的設(shè)計(jì)允許引擎最小化內(nèi)存分配的方式來(lái)調(diào)度運(yùn)算。例如,DNN 的權(quán)重更新函數(shù)可以使用 += 操作來(lái)進(jìn)行原地操作,而不是每次都生成新的數(shù)組。

你可以使用 NewVar() 來(lái)創(chuàng)建變量,用 PushDelete() 來(lái)刪除變量。

Push and Wait (推送和等待)

所有的 Push API 都是異步的,函數(shù)調(diào)用會(huì)立即返回,不管 Fn 是否執(zhí)行完成。這允許引擎在用戶線程推送函數(shù)的時(shí)候并行開(kāi)始執(zhí)行計(jì)算。Push API 不是線程安全的,在同一個(gè)時(shí)刻,只有一個(gè)線程可以調(diào)用 Push API。

如果你想要等待某個(gè)特定的 Fn 執(zhí)行完成,你可以傳入一個(gè) Callback,并且在你的 Fn 結(jié)束的時(shí)候調(diào)用它。

如果你想等待與某個(gè)變量相關(guān)的所有 Fn 都結(jié)束,可以使用 WaitForVar(var) API。

如果你想等待所有已推送的 Fn 都結(jié)束,可以使用 WaitForAll() API。

Save Object Creation Cost (減少創(chuàng)建對(duì)象的開(kāi)銷)

在某些情況下,你需要在較長(zhǎng)一段時(shí)間內(nèi)把多個(gè)函數(shù)推送到引擎。如果這些函數(shù)的計(jì)算量不大,那么復(fù)制匿名函數(shù)和創(chuàng)建變量的開(kāi)銷就會(huì)變得相對(duì)比較高。這種情況下,我們提供了 API 來(lái)提前創(chuàng)建 OprHandle:

virtual OprHandle NewOperator(AsyncFn fn,

std::vector const& const_vars,

std::vector const& mutate_vars) = 0;

你可以連續(xù)推送 OprHandle 而不用重復(fù)創(chuàng)建它們:

virtual void Push(OprHandle op, Context exec_ctx) = 0;

要?jiǎng)h除它,調(diào)用 DeleteOperator(OprHandle op) API。要保證在調(diào)用這個(gè) API 之前,運(yùn)算符已經(jīng)完成計(jì)算。

API Reference

略。

Operators (運(yùn)算符) in MXNet

在 MXNet 中,運(yùn)算符是一個(gè)類,包括了實(shí)際的計(jì)算邏輯和一些幫助系統(tǒng)進(jìn)行優(yōu)化的輔助信息,比如原地更新和自動(dòng)求導(dǎo)之類的。要理解這篇文檔余下的部分,我們建議你熟悉一下 mshadow 庫(kù),因?yàn)樗械倪\(yùn)算符都是在系統(tǒng)運(yùn)行時(shí)提供的類似張量(tensor-like)的數(shù)據(jù)結(jié)構(gòu) mshadow::TBlob 上進(jìn)行計(jì)算。

MXNet 的運(yùn)算符接口允許你:

通過(guò)指定原地更新來(lái)減少內(nèi)存分配。對(duì) Python 接口隱藏一些內(nèi)部參數(shù),使接口更簡(jiǎn)潔。定義輸入張量和輸出張量之間的關(guān)系,允許系統(tǒng)為你檢查它們的形狀(shape)。為執(zhí)行計(jì)算(如調(diào)用 cudnn 的函數(shù))向系統(tǒng)請(qǐng)求額外的臨時(shí)空間。

Operator Interface (運(yùn)算符接口)

Forward 是核心的運(yùn)算符接口:

virtual void Forward(const OpContext &ctx,

const std::vector &in_data,

const std::vector &req,

const std::vector &out_data,

const std::vector &aux_states) = 0;

OpContext 結(jié)構(gòu)體:

struct OpContext {

int is_train;

RunContext run_ctx;

std::vector requested;

}

它描述了此運(yùn)算符是在訓(xùn)練階段還是測(cè)試階段,它應(yīng)該運(yùn)行在哪個(gè)設(shè)備上(run_ctx),以及已經(jīng)請(qǐng)求的資源(這個(gè)會(huì)在之后的章節(jié)討論)。

in_data 和 out_data 分別代表輸入和輸出張量。系統(tǒng)已經(jīng)為所有張量分配好了空間。 req 表示計(jì)算結(jié)果如何寫入 out_data。換句話說(shuō),req.size() == out_data.size(),并且 req[i] 對(duì)應(yīng)于 out_data[i] 的寫入類型。 OpReqType 的定義為: enum OpReqType {

kNullOp,

kWriteTo,

kWriteInplace,

kAddTo

};

通常情況下,所有 out_data 的類型應(yīng)該為 kWriteTo,表明所提供的 out_data 張量是原始的內(nèi)存塊,運(yùn)算符應(yīng)當(dāng)直接向它里面寫入數(shù)據(jù)。但是在某些情況下,比如在計(jì)算梯度張量時(shí),我們最好對(duì)結(jié)果進(jìn)行累加,而不是直接覆蓋張量原有的內(nèi)容,這樣我們就不用每次分配額外的內(nèi)存。在這種情況下,相對(duì)應(yīng)的 req 類型應(yīng)被設(shè)置為 kAddTo,表示應(yīng)當(dāng)調(diào)用 +=。 aux_states 被設(shè)計(jì)為輔助計(jì)算的張量,目前沒(méi)有用到。

除了 Foward 操作,你可以視需要選擇實(shí)現(xiàn) Backward 接口:

virtual void Backward(const OpContext &ctx,

const std::vector &out_grad,

const std::vector &in_data,

const std::vector &out_data,

const std::vector &req,

const std::vector &in_grad,

const std::vector &aux_states);

這個(gè)接口遵循與 Forward 相同的設(shè)計(jì)原則,不同之處是接口中 out_grad, in_data 和 out_data 是給定的,需要計(jì)算 in_grad 作為結(jié)果。這里的命名規(guī)則與 Torch 類似,可以總結(jié)為下圖:

[input/output semantics figure]

某些運(yùn)算符可能可以省略某個(gè)參數(shù):out_grad, in_data, 和 out_data。你可以用 OperatorProperty 類的 DeclareBackwardDependency 接口來(lái)指定它們的依賴關(guān)系。

Operator Property (運(yùn)算符屬性)

卷積可以有多種實(shí)現(xiàn)方式,你可能想在各種方式之間切換以實(shí)現(xiàn)最佳性能。因此,我們把運(yùn)算符的語(yǔ)義接口從他的實(shí)現(xiàn)接口(Operator 類)中剝離出來(lái),放到 OperatorProperty 類中。OperatorProperty 接口包含:

InferShape virtual bool InferShape(std::vector *in_shape,

std::vector *out_shape,

std::vector *aux_shape) const = 0;

這個(gè)接口有兩個(gè)目的:

告訴系統(tǒng)每個(gè)輸入張量和輸出張量的形狀,以便在調(diào)用 Forward 和 Backward 之前分配空間。在執(zhí)行之前做檢查,保證沒(méi)有明顯的錯(cuò)誤。in_shape 中指定的形狀是系統(tǒng)設(shè)置的(從前一個(gè)操作的 out_shape 中得到)。當(dāng)已知的信息不足以推斷出形狀時(shí),InferShape 返回 false,并且當(dāng)參數(shù)形狀不一致時(shí)會(huì)拋出異常。請(qǐng)求資源: 像 cudnnConvolutionForward 之類的運(yùn)算符在計(jì)算時(shí)需要一個(gè)工作區(qū)(workspace)。如果系統(tǒng)能夠管理工作區(qū),就可以對(duì)它進(jìn)行優(yōu)化,比如重用空間等。為此,MXNet 定義了兩個(gè)接口: virtual std::vector ForwardResource(const std::vector &in_shape) const;

virtual std::vector BackwardResource(const std::vector &in_shape) const;

ResourceRequest 結(jié)構(gòu)體(在 resource.h 中)目前僅包含一個(gè)類型標(biāo)志: struct ResourceRequest {

enum Type {

kRandom, // get a mshadow::Random object

kTempSpace, // request temporary space

};

Type type;

};

如果 ForwardResource 和 BackwardResource 返回非空的數(shù)組,系統(tǒng)會(huì)通過(guò) Operator 類的 Forward 和 Backward 接口的 ctx 參數(shù),來(lái)提供相應(yīng)的資源。大體上說(shuō),要訪問(wèn)這些資源,使用: auto tmp_space_res = ctx.requested[kTempSpace].get_space(some_shape, some_stream);

auto rand_res = ctx.requested[kRandom].get_random(some_stream);

示例請(qǐng)見(jiàn) src/operator/cudnn_convolution-inl.h Backward dependency (反向依賴): 讓我們看一下兩個(gè)不同的運(yùn)算符的的函數(shù)簽名(為方便展示,我們給每個(gè)參數(shù)加上了名字): void FullyConnectedForward(TBlob weight, TBlob in_data, TBlob out_data);

void FullyConnectedBackward(TBlob weight, TBlob in_data, TBlob out_grad, TBlob in_grad);

void PoolingForward(TBlob in_data, TBlob out_data);

void PoolingBackward(TBlob in_data, TBlob out_data, TBlob out_grad, TBlob in_grad);

注意 FullyConnectedForward 中的 out_data 沒(méi)有在 FullyConnectedBackward 中被用到,而 PoolingBackward 用到了 PoolingForward 的所有參數(shù)。因此對(duì)于 FullyConnectedForward,out_data 張量在使用完之后馬上可以釋放空間,因?yàn)橄鄳?yīng)的反向函數(shù)不需要它。這給系統(tǒng)提供了機(jī)會(huì)來(lái)盡早做垃圾回收。我們提供了一個(gè)接口來(lái)指定: virtual std::vector DeclareBackwardDependency(

const std::vector &out_grad,

const std::vector &in_data,

const std::vector &out_data) const;

參數(shù) vector 中的 int 是 ID,用來(lái)區(qū)分不同的數(shù)組。讓我們看一下這個(gè)接口是如何為 FullyConnected 和 Pooling 指定不同的依賴關(guān)系: std::vector FullyConnectedProperty::DeclareBackwardDependency(

const std::vector &out_grad,

const std::vector &in_data,

const std::vector &out_data) const {

return {out_grad[0], in_data[0]}; // NOTE: out_data[0] is NOT included

}

std::vector PoolingProperty::DeclareBackwardDependency(

const std::vector &out_grad,

const std::vector &in_data,

const std::vector &out_data) const {

return {out_grad[0], in_data[0], out_data[0]};

}

In place Option (原地更新選項(xiàng)):為了節(jié)省更多的內(nèi)存,你可以使用原地更新(in-place updates)。它們適用于輸入張量和輸出張量有相同形狀時(shí)的元素操作(element-wise operations)。你使用以下接口來(lái)指定原地更新: virtual std::vector> ElewiseOpProperty::ForwardInplaceOption(

const std::vector &in_data,

const std::vector &out_data) const {

return { {in_data[0], out_data[0]} };

}

virtual std::vector> ElewiseOpProperty::BackwardInplaceOption(

const std::vector &out_grad,

const std::vector &in_data,

const std::vector &out_data,

const std::vector &in_grad) const {

return { {out_grad[0], in_grad[0]} }

}

這段代碼告訴系統(tǒng),在 Forward 中,in_data[0] 和 out_data[0] 張量可以共用同一塊內(nèi)存空間,而在 Backward 中,out_grad[0] 和 in_grad[0] 可以共用空間。

重要: 即使你按照以上代碼指定了共享選項(xiàng),系統(tǒng)也不保證輸入和輸出張量會(huì)共享同一塊空間。實(shí)際上,這只是給系統(tǒng)一個(gè)建議,最終還是系統(tǒng)自己來(lái)決定是否要共用空間。不管怎樣,這個(gè)決定對(duì)你來(lái)說(shuō)是透明的,所以在實(shí)現(xiàn) Forward 和 Backward 的時(shí)候,不需要考慮這些。

Expose Operator to Python (將運(yùn)算符暴露給 Python): 因?yàn)?C++ 的限制,你需要實(shí)現(xiàn)以下接口: // initial the property class from a list of key-value string pairs

virtual void Init(const vector> &kwargs) = 0;

// return the parameters in a key-value string map

virtual map GetParams() const = 0;

// return the name of arguments (for generating signature in python)

virtual vector ListArguments() const;

// return the name of output values

virtual vector ListOutputs() const;

// return the name of auxiliary states

virtual vector ListAuxiliaryStates() const;

// return the number of output values

virtual int NumOutputs() const;

// return the number of visible outputs

virtual int NumVisibleOutputs() const;

從 Operator Property 創(chuàng)建 Operator

OperatorProperty 包含了 Operator 的所有的語(yǔ)義屬性。它也負(fù)責(zé)為實(shí)際計(jì)算創(chuàng)建 Operator。

創(chuàng)建 Operator

實(shí)現(xiàn) OperatorProperty 中的如下這個(gè)接口:

virtual Operator* CreateOperator(Context ctx) const = 0;

例如:

class ConvolutionOp {

public:

void Forward( ... ) { ... }

void Backward( ... ) { ... }

};

class ConvolutionOpProperty : public OperatorProperty {

public:

Operator* CreateOperator(Context ctx) const {

return new ConvolutionOp;

}

};

Operator 的參數(shù)化

當(dāng)你實(shí)現(xiàn)一個(gè)卷積運(yùn)算符時(shí),你需要知道核的大小 (kernel size),步長(zhǎng)的大小 (stride size),填充的大小 (padding size),等等。這些參數(shù)應(yīng)當(dāng)在 Forward 和 Backward 接口被調(diào)用之前傳給 Operator。你可以定義一個(gè) ConvolutionParam 結(jié)構(gòu),如下:

#include

struct ConvolutionParam : public dmlc::Parameter {

TShape kernel, stride, pad;

uint32_t num_filter, num_group, workspace;

bool no_bias;

};

把它放在 ConvolutionOpProperty 中,并且在創(chuàng)建 Operator 時(shí)傳進(jìn)去:

class ConvolutionOp {

public:

ConvolutionOp(ConvolutionParam p): param_(p) {}

void Forward( ... ) { ... }

void Backward( ... ) { ... }

private:

ConvolutionParam param_;

};

class ConvolutionOpProperty : public OperatorProperty {

public:

void Init(const vector& kwargs) {

// initialize param_ using kwargs

}

Operator* CreateOperator(Context ctx) const {

return new ConvolutionOp(param_);

}

private:

ConvolutionParam param_;

};

使用以下宏來(lái)把 Operator 的 Property 類和 Parameter 類注冊(cè)到 MXNet:

DMLC_REGISTER_PARAMETER(ConvolutionParam);

MXNET_REGISTER_OP_PROPERTY(Convolution, ConvolutionOpProperty);

第一個(gè)參數(shù)是名字,第二個(gè)參數(shù)是 Property 的類名。

接口總結(jié)

到目前為止,我們基本上涵蓋了定義一個(gè) Operator 的全部接口。讓我們回顧一下:

使用 Operator 接口來(lái)實(shí)現(xiàn)你的計(jì)算邏輯 (Forward 和 Backward)。使用 OperatorProperty 接口來(lái):

向運(yùn)算符類傳遞參數(shù)(可以使用 Init 接口)使用 CreateOperator 接口來(lái)創(chuàng)建運(yùn)算符正確地實(shí)現(xiàn)描述操作符的接口,例如參數(shù)名,等等正確地實(shí)現(xiàn) InferShape 接口來(lái)設(shè)置輸出張量的形狀[可選] 如果需要額外的資源,檢查 ForwardResource 和 BackwardResource[可選] 如果 Backward 不需要用到 Forward 的所有輸入和輸出,檢查 DeclareBackwardDependency[可選] 如果支持原地更新,檢查 ForwardInplaceOption 和 BackwardInplaceOption 將 OperatorProperty 類和參數(shù)類注冊(cè)到 MXNet

統(tǒng)一 NDArray 運(yùn)算符和符號(hào)運(yùn)算符

NDArray 運(yùn)算符和符號(hào)運(yùn)算符類似,區(qū)別是在沒(méi)有完整的依賴關(guān)系圖時(shí),有時(shí)你不能原地更新。然而,NDArray 運(yùn)算符和符號(hào)運(yùn)算符的底層邏輯是一樣的。SimpleOp 是一個(gè)新的統(tǒng)一的運(yùn)算符 API,統(tǒng)一了不同的方式。因?yàn)槎鄶?shù)數(shù)學(xué)運(yùn)算符有一個(gè)或兩個(gè)操作數(shù),而更多個(gè)操作數(shù)使得和依賴關(guān)系相關(guān)的優(yōu)化有用,統(tǒng)一的運(yùn)算符是被專門設(shè)計(jì)用于一元和二元運(yùn)算。

讓我們考慮運(yùn)算符的基本元素。理想情況下,你只需要用函數(shù)和導(dǎo)數(shù)來(lái)描述一個(gè)運(yùn)算符。讓我們將討論限制在一元和二元運(yùn)算符。我們?cè)撊绾畏诸愃胁僮鞣?,?lái)使原地更新的可能性最大?注意你可以按照操作數(shù)的數(shù)量來(lái)對(duì)函數(shù)進(jìn)行分類。而導(dǎo)數(shù)更復(fù)雜一些。要?jiǎng)?chuàng)建一個(gè)依賴關(guān)系圖,你需要知道輸出值,輸入數(shù)據(jù)是否在之后的梯度中被用到。統(tǒng)一 API 中的梯度函數(shù)是用操作數(shù)的類型來(lái)區(qū)分的。

在我們繼續(xù)了解 SimpleOp 接口之前,我們建議你瀏覽 mshadow library guide,因?yàn)橛?jì)算是在 mshadow:TBlob 中進(jìn)行的。

在以下例子中,我們會(huì)創(chuàng)建一個(gè)實(shí)現(xiàn) smooth l1 loss 函數(shù)的運(yùn)算符,這是 l1 loss 和 l2 loss 的混合體。這個(gè)函數(shù)可以被寫成如下形式:

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

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

.* 代表元素乘,f 和 f’ 是 smooth l1 loss 函數(shù),我們先假設(shè)它們?cè)?mshadow 中可以找到。乍看起來(lái),不可能把它實(shí)現(xiàn)成一元或二元操作。但是我們有符號(hào)的自動(dòng)微分,這簡(jiǎn)化了 f 和 f’ 的損失函數(shù)。這個(gè)損失函數(shù)并不比 sin 或者 abs 函數(shù)復(fù)雜,我們可以將它實(shí)現(xiàn)為一元操作符。

SimpleOp: 統(tǒng)一的 Operator API

Define Shapes (定義形狀)

mshadow 庫(kù)需要顯式地分配內(nèi)存,因此在計(jì)算開(kāi)始前,就需要定義好所有數(shù)據(jù)的形狀。在定義函數(shù)和導(dǎo)數(shù)之前,先讓我們檢查輸入的形狀并且提供輸出的形狀。

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

你可以使用 mshadow::TShape 來(lái)檢查輸入形狀并且指定輸出形狀。如果你不定義這個(gè)函數(shù),則默認(rèn)的輸出形狀和輸入形狀相同。如果是二元運(yùn)算符,默認(rèn)情況下,系統(tǒng)會(huì)檢查 lhs 和 rhs 的形狀是否相同。

你還可以用這些函數(shù)來(lái)檢查是否有額外的參數(shù)和資源??梢詤⒖?EnvArguments 的用法。

在我們開(kāi)始 smooth l1 loss 的例子之前,我們?cè)陬^文件 smooth_l1_unary-inl.h 中定義了 XPU,值為 cpu 或者 gpu,以便于我們可以在 smooth_l1_unary.cc 和 smoth_l1_unary.cu 中使用相同的代碼。

#include

#if defined(__CUDACC__)

#define XPU gpu

#else

#define XPU cpu

#endif

在 smooth l1 loss 的例子中,因?yàn)檩斎胼敵鲇邢嗤男螤?,我們就直接用默認(rèn)行為:

inline TShape SmoothL1Shape_(const TShape& src, const EnvArguments& env) {

return TShape(src);

}

定義函數(shù)

創(chuàng)建有一個(gè)輸出 (mshadow::TBlob) 的一元或二元函數(shù)。

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);

函數(shù)按照輸入?yún)?shù)的類型區(qū)分。 RunContext ctx 包含運(yùn)行時(shí)所需要的信息。 struct RunContext {

void *stream; // the stream of the device, can be NULL or Stream* in GPU mode

template inline mshadow::Stream* get_stream() // get mshadow stream from Context

} // namespace mxnet

從 ctx 獲取一個(gè)流的例子: mshadow::stream *s = ctx.get_stream();

OpReqType req 指明計(jì)算結(jié)果該如何寫入 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

};

ASSIGN_DISPATH(out, req, exp) 是 operator_util.h 中定義的一個(gè)宏,用來(lái)簡(jiǎn)化 OpReqType 的使用,它會(huì)檢查 req 并且進(jìn)行賦值。

在 smooth l1 loss 例子中,我們使用 UnaryFunction 來(lái)定義操作符的函數(shù)。

template

void SmoothL1Forward_(const TBlob& src,

const EnvArguments& env,

TBlob *ret,

OpReqType req,

RunContext ctx) {

using namespace mshadow;

using namespace mshadow::expr;

mshadow::Stream *s = ctx.get_stream();

real_t sigma2 = env.scalar * env.scalar;

MSHADOW_TYPE_SWITCH(ret->type_flag_, DType, {

mshadow::Tensor out = ret->get(s);

mshadow::Tensor in = src.get(s);

ASSIGN_DISPATCH(out, req,

F(in, ScalarExp(sigma2)));

});

}

從 RunContext 獲取到 mshadow::Stream 之后,我們從 mshadow::TBlob 拿到 mshadow::Tensor。mshadow::F 是創(chuàng)建一個(gè) mshadow 表達(dá)式的快捷方式。宏 MSHADOW_TYPE_SWITCH(type, DType, …) 處理不同類型的細(xì)節(jié),宏 ASSIGN_DISPATCH(out, req, exp) 檢查 OpReqType 并且執(zhí)行相應(yīng)動(dòng)作。 sigma2 是這個(gè)損失函數(shù)中的一個(gè)特殊的參數(shù),我們后面會(huì)講到。

定義梯度(可選)

// 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);

二元運(yùn)算符的梯度函數(shù)擁有相似的結(jié)構(gòu),不同之處在于 Input, TBlob 和 OpReqType 的數(shù)量翻倍。

GradFunctionArgument

Input0, Input, OutputValue 和 OutputGrad 都和 GradFunctionArgument 有相同的結(jié)構(gòu),定義如下:

struct GradFunctionArgument {

TBlob data;

}

在 smooth l1 loss 例子中,注意用到輸入來(lái)計(jì)算梯度的是 f’(x),所以 UnaryGradFunctionT2 是適用的。我們還需要用 out_grade 乘以結(jié)果的 in_grad 來(lái)用鏈?zhǔn)椒▌t計(jì)算梯度。

template

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 *s = ctx.get_stream();

real_t sigma2 = env.scalar * env.scalar;

MSHADOW_TYPE_SWITCH(in_grad->type_flag_, DType, {

mshadow::Tensor src = in_data0.data.get(s);

mshadow::Tensor ograd = out_grad.data.get(s);

mshadow::Tensor igrad = in_grad->get(s);

ASSIGN_DISPATCH(igrad, req,

ograd * F(src, ScalarExp(sigma2)));

});

}

把 SimpleOp 注冊(cè)到 MXNet

在創(chuàng)建 shape, function 和 gradient 之后,要把它們放到 NDArray 運(yùn)算符和符號(hào)運(yùn)算符中??梢允褂?operator_util.h 中定義的宏來(lái)簡(jiǎn)化這個(gè)過(guò)程。

MXNET_REGISTER_SIMPLE_OP(Name, DEV)

.set_shape_function(Shape)

.set_function(DEV::kDevMask, Function, SimpleOpInplaceOption)

.set_gradient(DEV::kDevMask, Gradient, 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)

};

在我們的例子中,我們的梯度依賴于函數(shù)的輸入,因此函數(shù)不能原地更新數(shù)據(jù)。輸出的梯度在梯度計(jì)算后就不被用到了,因此梯度可以原地更新。

MXNET_REGISTER_SIMPLE_OP(smooth_l1, XPU)

.set_function(XPU::kDevMask, SmoothL1Forward_, kNoInplace)

.set_gradient(XPU::kDevMask, SmoothL1BackwardUseIn_, kInplaceOutIn)

.set_enable_scalar(true)

.describe("Calculate Smooth L1 Loss(lhs, scalar)");

不要忘了之前的討論,在沒(méi)有用 set_shape_function 來(lái)設(shè)置 shape 的時(shí)候,默認(rèn)會(huì)強(qiáng)制輸出的 shape 和輸入的 shape 一致。我們后面會(huì)討論 set_enable_scalar。

NDArray 運(yùn)算符總結(jié)

創(chuàng)建一個(gè) shape 函數(shù)用來(lái)決定輸出 shape創(chuàng)建一個(gè)函數(shù)作為前向過(guò)程,選擇一個(gè)合適的函數(shù)類型創(chuàng)建一個(gè)梯度作為反向過(guò)程,選擇一個(gè)合適的梯度類型注冊(cè)運(yùn)算符

SimpleOp 的其他信息

在 EnvArguments 上使用 SimpleOp

一些操作需要一個(gè)標(biāo)量作為輸入,比如一個(gè)梯度標(biāo)量,一組用來(lái)控制算法行為的關(guān)鍵字參數(shù),或者一個(gè)用來(lái)加速計(jì)算的臨時(shí)空間。EnvArguments 提供額外的參數(shù)和資源來(lái)使計(jì)算更易擴(kuò)展和更高效。

struct EnvArguments {

real_t scalar; // scalar argument, if enabled

std::vector > kwargs; // keyword arguments

std::vector resource; // pointer to the resources requested

};

要啟用這些額外的功能,需要更多的注冊(cè)參數(shù)。為避免參數(shù)的混淆,scalar 和 kwargs 不能同時(shí)使用。要啟用 scalar,在注冊(cè)時(shí)使用 set_enable_scalar(bool enable_scalar)。之后,在前向函數(shù)和梯度中,可以用函數(shù)測(cè)參數(shù) EnvArguments env 中的 env.scalar 來(lái)訪問(wèn) scalar。

要啟用 kwargs,在注冊(cè)時(shí)使用 set_enable_kwargs(bool enable_kwargs)。在前向函數(shù)和反向梯度時(shí),額外的參數(shù)包含在 env.kwargs 中,被定義為 std::vectorstd::string。使用 DMLC 參數(shù)接口來(lái)簡(jiǎn)化關(guān)鍵字參數(shù)的解析。更多細(xì)節(jié)請(qǐng)參考 guide on parameter structure。

mshadow::Random 或者臨時(shí)內(nèi)存空間之類的額外資源可以通過(guò) EnvArguments.resoure 來(lái)請(qǐng)求和訪問(wèn)。注冊(cè)方式為 set_resource_request(ResourceRequest req) 或者 set_resource_request(const std::vector),其中 mxnet::ResourceRequest 被定義為:

struct ResourceRequest {

enum Type { // Resource type, indicating what the pointer type is

kRandom, // mshadow::Random object

kTempSpace // A dynamic temp space that can be arbitrary size

};

Type type; // type of resources

};

相關(guān)例子請(qǐng)參見(jiàn) src/operator/loss_binary_op-inl.h。

在我們的 smooth l1 loss 例子中,需要有一個(gè)標(biāo)量輸入來(lái)標(biāo)記損失函數(shù)的折點(diǎn)。因此,在注冊(cè)時(shí),我們使用 set_enable_scalar(true),并且在函數(shù)和梯度中使用 env.scalar。

實(shí)現(xiàn)一個(gè)張量運(yùn)算 (Tensor Operation)

因?yàn)槭褂?mshadow 庫(kù)來(lái)進(jìn)行計(jì)算,有時(shí)候沒(méi)有我們用到的函數(shù),我們可以在運(yùn)算符中實(shí)現(xiàn)張量運(yùn)算。如果你把函數(shù)定義為元素 (element-wise) 運(yùn)算,那么你可以把它實(shí)現(xiàn)成一個(gè) mxnet::op::mshadow_op。src/operator/mshadow_op.h 中有許多 mshadow_op 的例子。 mshadow_op 是表達(dá)式映射器。它們處理函數(shù)的標(biāo)量形式。細(xì)節(jié)請(qǐng)見(jiàn) mshadow expression API guide。

如果一個(gè)運(yùn)算不能用元素 (element-wise) 方式實(shí)現(xiàn),比如 softmax 損失函數(shù)和梯度,那么你就需要實(shí)現(xiàn)一個(gè)新的張量運(yùn)算。你需要直接創(chuàng)建 mshadow 函數(shù)和 mshadow::cuda 函數(shù)。更多例子請(qǐng)見(jiàn) src/ooperator/roi_pooling.cc。

在我們的 smooth l1 loss 例子中,我們創(chuàng)建了兩個(gè)映射器,分別是標(biāo)量情形下的 smooth l1 loss 和 gradient。

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;

}

}

};

}

梯度 (gradient) 與之相似,可以在 src/operator/smooth_l1_unary-inl.h 中找到。

兩個(gè)以上操作數(shù)

新的統(tǒng)一 API 被設(shè)計(jì)為完成運(yùn)算符的基本功能。對(duì)于有兩個(gè)以上輸入、或一個(gè)以上輸出、或是需要更多特性的運(yùn)算符,請(qǐng)見(jiàn)原始的 Operator API。

其他文章可參考

https://mli.github.io/2015/12/03/mxnet-overview/https://blog.51cto.com/u_16213409/7336190https://blog.csdn.net/huareal/article/details/72589863

柚子快報(bào)激活碼778899分享:mxnet系統(tǒng)架構(gòu)

http://yzkb.51969.com/

推薦閱讀

評(píng)論可見(jiàn),查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19564217.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問(wèn)

文章目錄