自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Paddle Fluid 開發(fā)者指南

企業(yè)動(dòng)態(tài)
本文將提高對(duì)各類機(jī)器學(xué)習(xí)任務(wù)的描述能力:能夠描述潛在出現(xiàn)的任意機(jī)器學(xué)習(xí)模型。代碼結(jié)構(gòu)邏輯清晰,各模塊充分解耦:內(nèi)外部貢獻(xiàn)者能夠?qū)W⒂谧约核璧墓δ苣K,基于框架進(jìn)行再次開發(fā)……

Paddle Fluid 開發(fā)者指南

==1==. 為什么需要 PaddlePaddle Fluid?

兩個(gè)基礎(chǔ)問題

  1. 如何描述機(jī)器學(xué)習(xí)模型和優(yōu)化過程?
    • 完備自洽,表達(dá)能力足以支持潛在出現(xiàn)的各種計(jì)算需求
  2. 如何充分利用資源高效計(jì)算?
    • 支持異步設(shè)備、多卡、分布式計(jì)算
    • 降低計(jì)算/計(jì)算優(yōu)化的開發(fā)成本
    • ……

如何描述模型和優(yōu)化過程?

  一組連續(xù)執(zhí)行的layers variable和operator構(gòu)成的計(jì)算圖 不再有模型的概念
2013 Caffe,Theano, Torch, PaddlePaddle    
2015   Caffe, Theano, Torch, PaddlePaddles  
2016     PyTorch, TensorFlow Eager Execution, ==PaddlePaddle Fluid==

目標(biāo) 😄

  • 提高對(duì)各類機(jī)器學(xué)習(xí)任務(wù)的描述能力:能夠描述潛在出現(xiàn)的任意機(jī)器學(xué)習(xí)模型。
  • 代碼結(jié)構(gòu)邏輯清晰,各模塊充分解耦:內(nèi)外部貢獻(xiàn)者能夠?qū)W⒂谧约核璧墓δ苣K,基于框架進(jìn)行再次開發(fā)。
  • 從設(shè)計(jì)上,留下技術(shù)優(yōu)化的空間和潛力。
  • 代碼解耦后降低多設(shè)備支持、計(jì)算優(yōu)化等的開發(fā)成本。
  • 在統(tǒng)一的設(shè)計(jì)理念下,實(shí)現(xiàn)自動(dòng)可伸縮,自動(dòng)容錯(cuò)的分布式計(jì)算。

==2.== Design Overview

Fluid: 系統(tǒng)形態(tài)

 

100%

讓我們?cè)贔luid程序?qū)嵗?,區(qū)分編譯時(shí)和運(yùn)行時(shí)

Fluid 編譯時(shí)

  • ==定義前向計(jì)算==
  • x = fluid.layers.data(name='x',shape=[13], dtype='float32')
  • y_predict = fluid.layers.fc(input=x, size=1, act=None)
  • y = fluid.layers.data(name='y', shape=[1], dtype='float32')
  • cost = fluid.layers.square_error_cost(input=y_predict, label=y)

avg_cost = fluid.layers.mean(x=cost)

  • ==添加反向、正則、優(yōu)化==
  • learning_rate = 0.01
  • sgd_optimizer = fluid.optimizer.SGD(learning_rate)

sgd_optimizer.minimize(avg_cost)

Program vs. 計(jì)算圖

  • 在科學(xué)計(jì)算領(lǐng)域,計(jì)算圖是一種描述計(jì)算的經(jīng)典方式。下圖展示了從前向計(jì)算圖(藍(lán)色)開始,通過添加反向(紅色)和優(yōu)化算法相關(guān)(綠色)操作,構(gòu)建出整個(gè)計(jì)算圖的過程:

60% center

  • Fluid ==使用Program而不是計(jì)算圖==來描述模型和優(yōu)化過程。Program由Block、Operator和Variable構(gòu)成,相關(guān)概念會(huì)在后文詳細(xì)展開。
  • 編譯時(shí) Fluid 接受前向計(jì)算(這里可以先簡(jiǎn)單的理解為是一段有序的計(jì)算流)Program,為這段前向計(jì)算按照:前向 ➡反向 ➡ 梯度 clip ➡ 正則 ➡ 優(yōu)化 的順序,添加相關(guān) Operator和Variable到Program到完整的計(jì)算。

Fluid 運(yùn)行時(shí)

  • ==讀入數(shù)據(jù)==
  • train_reader = paddle.batch(
  • paddle.reader.shuffle(paddle.dataset.uci_housing.train(), buf_size=500),
  • batch_size=20)

feeder = fluid.DataFeeder(place=place, feed_list=[x, y])

  • ==定義執(zhí)行程序的設(shè)備==
  • place = fluid.CPUPlace()

feeder = fluid.DataFeeder(place=place,feed_list=[x, y])

  • ==創(chuàng)建執(zhí)行器(Executor),執(zhí)行初始化 Program和訓(xùn)練Program==
  • exe = fluid.Executor(place)
  • exe.run(fluid.default_startup_program())
  • PASS_NUM = 100
  • for pass_id in range(PASS_NUM):
  • for data in train_reader():
  • avg_loss_value, = exe.run(fluid.default_main_program(),
  • feed=feeder.feed(data),
  • fetch_list=[avg_cost])

print(avg_loss_value)

總結(jié):框架做什么?用戶做什么?

構(gòu)建訓(xùn)練 執(zhí)行訓(xùn)練
用戶:描述前向運(yùn)算
框架:添加反向運(yùn)算
框架:添加優(yōu)化運(yùn)算
框架:添加內(nèi)存優(yōu)化
框架:添加并行/多設(shè)備/分布式相關(guān)的計(jì)算單元
框架:創(chuàng)建Operator(計(jì)算)+ Variable(數(shù)據(jù))
框架:創(chuàng)建Block
框架:內(nèi)存管理/設(shè)備管理
框架:執(zhí)行計(jì)算

總結(jié):編譯時(shí)

用戶編寫一段Python程序,描述模型的前向計(jì)算

  1. 創(chuàng)建變量描述 VarDesc
  2. 創(chuàng)建operators的描述 OpDesc
  3. 創(chuàng)建operators的屬性
  4. 推斷變量的類型和形狀,進(jìn)行靜態(tài)檢查:inferShape
  5. 規(guī)劃變量的內(nèi)存復(fù)用
  6. 創(chuàng)建反向計(jì)算
  7. 添加優(yōu)化相關(guān)的Operators
  8. (可選)添加多卡/多機(jī)相關(guān)的Operator,生成在多卡/多機(jī)上運(yùn)行的程序

總結(jié):運(yùn)行時(shí)

執(zhí)行規(guī)劃好的計(jì)算

  1. 創(chuàng)建Executor
  2. 為將要執(zhí)行的一段計(jì)算,在層級(jí)式的Scope空間中創(chuàng)建Scope
  3. 創(chuàng)建Block,依次執(zhí)行Block

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/compile_run_time.png
Figure. 編譯時(shí)運(yùn)行時(shí)概覽

==3==. 用戶如何描述計(jì)算?

Fluid:==像寫程序一樣==定義計(jì)算

  • 順序執(zhí)行
  • x = fluid.layers.data(name='x',shape=[13], dtype='float32')
  • y_predict = fluid.layers.fc(input=x, size=1, act=None)
  • y = fluid.layers.data(name='y', shape=[1], dtype='float32')

cost = fluid.layers.square_error_cost(input=y_predict, label=y)

  • 條件分支: swith、ifelse
  • a = fluid.Var(10)
  • b = fluid.Var(0)
  • switch = fluid.switch()
  • with switch.block():
  • with switch.case(fluid.less_equal(a, 10)):
  • fluid.print("Case 1")
  • with switch.case(fluid.larger(a, 0)):
  • fluid.print("Case 2")
  • with switch.default():

fluid.print("Case 3")

A Lisp cond form may be compared to a continued if-then-else as found in many algebraic programming languages.

Fluid: ==像寫程序一樣==定義計(jì)算

  • 循環(huán):while
  • d0 = layers.data("d0", shape=[10], dtype='float32')
  • data_array = layers.array_write(x=d0, i=i)
  • array_len = layers.fill_constant(shape=[1],dtype='int64', value=3)
  • cond = layers.less_than(x=i, y=array_len)
  • while_op = layers.While(cond=cond)
  • with while_op.block():
  • d = layers.array_read(array=data_array, i=i)
  • i = layers.increment(x=i, in_place=True)
  • layers.array_write(result, i=i, array=d)

layers.less_than(x=i, y=array_len, cond=cond)

  • 完整實(shí)例請(qǐng)點(diǎn)查看 
  • beam search 

總結(jié)

  1. 用戶層提供的描述語法具有完備性、自洽性,有能力支持對(duì)復(fù)雜計(jì)算過程描述
  2. 使用方式和核心概念可以類比編程語言,認(rèn)知能夠直接遷移
  3. 能夠支持:定義問題,逐步求解

==3.== 核心概念

編譯時(shí)概念 :==變量和計(jì)算的描述==

  • VarDesc + TensorDesc + OpDesc ➡ BlockDesc ➡ ProgramDesc
  • 什么是 Fluid Program
    • 在Fluid中,一個(gè)神經(jīng)網(wǎng)絡(luò)任務(wù)(訓(xùn)練/預(yù)測(cè))被描述為一段Program
    • Program包含對(duì)Variable(數(shù)據(jù))和 Operator(對(duì)數(shù)據(jù)的操作)的描述
    • Variable 和 Operator 被組織為多個(gè)可以嵌套的Block,構(gòu)成一段完整的Fluid Program

編譯階段最終,經(jīng)過 Transpiler 的執(zhí)行規(guī)劃,變換處理,生成使用protobuf序列化后的ProgramDesc??梢园l(fā)送給多卡或者網(wǎng)絡(luò)中的其它計(jì)算節(jié)點(diǎn)執(zhí)行

編譯時(shí)概念 :==Transpiler==

  1. 接受一段ProgramDesc作為輸入,生成一段新的ProgramDesc
    • Memory optimization transpiler:向原始ProgramDesc 中插入 FreeMemoryOps,在一次迭代優(yōu)化結(jié)束前提前釋放內(nèi)存,使得能夠維持較小的 memory footprint
    • Distributed training transpiler:將原始的ProgramDesc中轉(zhuǎn)化為對(duì)應(yīng)的分布式版本,生成兩段新的ProgramDesc:
      1. trainer進(jìn)程執(zhí)行的ProgramDesc
      2. parameter server執(zhí)行的ProgramDesc
  2. ==WIP==: 接受一段ProgramDesc,生成可直接被gcc, nvcc, icc等編譯的代碼,編譯后得到可執(zhí)行文件

Transplier

70% center

打印 ProgramDesc

85% center

  • default_startup_program:創(chuàng)建可學(xué)習(xí)參數(shù),對(duì)參數(shù)進(jìn)行初始化
  • default_main_program:由用戶定義的模型,包括了前向、反向、優(yōu)化及所有必要的計(jì)算
  • 打印可讀的 Program
  • from paddle.v2.fluid import debuger

print debuger.pprint_program_codes(framework.default_main_program().desc)

輸出效果

variable in block 0 variable in block 0
73% 75%

運(yùn)行時(shí)概念

  • 數(shù)據(jù)相關(guān)
    • Tensor / LoDTensor / Variable
    • Scope
  • 計(jì)算相關(guān)
    • Block
    • Kernel、OpWithKernel、OpWithoutKernel
  protobuf messages C++ class objects
Data VarDesc Variable
Operation OpDesc Operator
Block BlockDesc Block
  • 執(zhí)行相關(guān) :Executor

Tensor 和 LoD(Level-of-Detail) Tensor

  • Tensor 是$n$-dimensional arry的推廣,LoDTensor是在Tensor基礎(chǔ)上附加了序列信息
  • Fluid中輸入、輸出,網(wǎng)絡(luò)中的可學(xué)習(xí)參數(shù)全部統(tǒng)一使用LoDTensor(n-dimension array)表示
  • 一個(gè)mini-batch輸入數(shù)據(jù)是一個(gè)LoDTensor
    • 在Fluid中,RNN 處理變長序列無需padding,得益于 LoDTensor表示
    • 可以簡(jiǎn)單將 LoD 理解為:std::vector<std::vector<int>>
    • 對(duì)非序列數(shù)據(jù),LoD 信息為空
  TensorFlow PaddlePaddle
RNN Support Support
recursive RNN Support Support
padding zeros Must No need
blob data type Tensor LoDTensor

LoD 信息實(shí)例

43% center

  • 圖(a)的LoD 信息

[0, 5, 8, 10, 14]

  • 圖(b)的 LoD 信息

[[0, 5, 8, 10, 14] /*level=1*/, [0, 2, 3, 5, 7, 8, 10, 13, 14] /*level=2*/]

Tensor, Variable, Scope 之間的關(guān)系

40% center

  1. Block 是一個(gè)實(shí)現(xiàn)層的概念,不在應(yīng)用層暴露給用戶。目前用戶無法自行創(chuàng)建并利用Block,用戶能夠感知的只有Program這個(gè)概念。
  2. 邏輯上,可以將 Block 類比為編程語言中的大括號(hào):定義了一段作用域,其中運(yùn)行一段代碼
  3. Executor會(huì)為每一個(gè)Block創(chuàng)建一個(gè)Scope,Block是可嵌套的,因此Scope也是可嵌套的

Executor

接口 說明
66% center 輸入
1. ProgramDesc
2. Scope
3.block_id解釋執(zhí)行步驟
1. 創(chuàng)建所有 Variables
2. 逐一創(chuàng)建 Operator 并運(yùn)行

Operator/OpWithKernel/Kernel

50% center

  • operator 無狀態(tài),Operator的核心是==Run==方法
  • 一個(gè)operator可以注冊(cè)多個(gè)kernel
  • operator 可以無 kernel:while_op 、ifelse op

Fluid Operator vs. PaddlePaddle layers

Layer Operator
70% center 73% center
1. 內(nèi)部維護(hù)狀態(tài)
2. 包含forward和backward方法
1. 內(nèi)部無狀態(tài)
2. 只有Run方法

==4.== 內(nèi)存管理

目標(biāo)

  • 為異構(gòu)設(shè)備提供統(tǒng)一的內(nèi)存分配、回收接口
  • 最小化管理內(nèi)存所需的時(shí)間,最小化管理開銷
  • 減少內(nèi)存碎片
  • 將內(nèi)存管理與計(jì)算(Operators/Kernels)完全剝離
  • 統(tǒng)一內(nèi)存管理是內(nèi)存優(yōu)化的基礎(chǔ)

Memory 接口

  • 內(nèi)存管理模塊向上層應(yīng)用邏輯提供三個(gè)基礎(chǔ)接口:
  • template <typename Place>
  • void* Alloc(Place place, size_t size);
  • template <typename Place>
  • void Free(Place place, void* ptr);
  • template <typename Place>
  • size_t Used(Place place);
  • struct Usage : public boost::static_visitor<size_t> {
  • size_t operator()(const platform::CPUPlace& cpu) const;
  • size_t operator()(const platform::CUDAPlace& gpu) const;

};

  • 模板參數(shù) Place 指示內(nèi)存分配發(fā)生的設(shè)備
  • 實(shí)現(xiàn)時(shí),需特化支持的 Place, 提供以上三個(gè)接口的實(shí)現(xiàn)

代碼結(jié)構(gòu)

內(nèi)存管理模塊可以理解為由以下兩部分構(gòu)成:

  1. SystemAllocator:實(shí)際從物理設(shè)備上分配、釋放的內(nèi)存的接口
  2. BuddyAllocator:內(nèi)存管理算法

System Allocator

  • SystemAllocator 是實(shí)現(xiàn)物理內(nèi)存分配、回收的基類
    • 不同設(shè)備上的內(nèi)存分配和回收終將轉(zhuǎn)化為標(biāo)準(zhǔn)接口調(diào)用
    • 為不同設(shè)備實(shí)現(xiàn)MemoryAllocator,繼承自SystemAllocator
  • class SystemAllocator {
  • public:
  • virtual ~SystemAllocator() {}
  • virtual void* Alloc(size_t& index, size_t size) = 0;
  • virtual void Free(void* p, size_t size, size_t index) = 0;
  • virtual bool UseGpu() const = 0;

};

CPU/GPU Allocator

class CPUAllocator : public SystemAllocator {

public:

virtual void* Alloc(size_t& index, size_t size);

virtual void Free(void* p, size_t size, size_t index);

virtual bool UseGpu() const;

};

#ifdef PADDLE_WITH_CUDA

class GPUAllocator : public SystemAllocator {

public:

virtual void* Alloc(size_t& index, size_t size);

virtual void Free(void* p, size_t size, size_t index);

virtual bool UseGpu() const;

private:

size_t gpu_alloc_size_ = 0;

size_t fallback_alloc_size_ = 0;

};

#endif

  • CPUAllocator和GPUAllocator分別繼承自SystemAllocator,分別調(diào)用相應(yīng)的標(biāo)準(zhǔn)庫函數(shù)實(shí)現(xiàn)物理內(nèi)存的分配和釋放。
  • 一旦大塊、連續(xù)的物理內(nèi)存分配之后,將通過內(nèi)存管理算法實(shí)現(xiàn)內(nèi)存的按塊分配、回收、重用等。

CPU Allocator

  • CPU 內(nèi)存的分配提供兩種選項(xiàng):
    1. non-pinned memory:可分頁內(nèi)存
    2. pinned memory:頁鎖定內(nèi)存
      • 分配過大的頁鎖定內(nèi)存有可能因?yàn)橄到y(tǒng)可使用的分頁內(nèi)存減少,影響系統(tǒng)性能,默認(rèn)CPU下分配的是可分頁內(nèi)存
  • 通過gflags進(jìn)行設(shè)置一次性分配內(nèi)存的大小以及是否使用頁鎖定內(nèi)存。
  • DEFINE_bool(use_pinned_memory, true, "If set, allocate cpu pinned memory.");
  • DEFINE_double(fraction_of_cpu_memory_to_use, 1,
  • "Default use 100% of CPU memory for PaddlePaddle,"

"reserve the rest for page tables, etc");

GPU Allocator

  • 通過 cudaMalloc 分配GPU顯存
  • GPUAllocator::Alloc 首先會(huì)計(jì)算指定GPU device上的可用顯存
    • 如果可用顯存小于請(qǐng)求分配大小,調(diào)用cudaMalloc進(jìn)行分配
    • 如果可用顯存不足,目前會(huì)報(bào)錯(cuò)退出。
  • 通過gflags控制GPU下一次性分配顯存的大小:
  • DEFINE_double(fraction_of_gpu_memory_to_use, 0.92,
  • "Default use 92% of GPU memory for PaddlePaddle,"

"reserve the rest for page tables, etc");

內(nèi)存管理算法: Buddy Memory Allocation

  • Memory Arena:一次性分配大塊連續(xù)內(nèi)存,之后會(huì)基于這塊內(nèi)存進(jìn)行內(nèi)存管理:動(dòng)態(tài)分配、釋放、重用內(nèi)存塊。
  • 伙伴內(nèi)存分配:
    • 將內(nèi)存劃分為 2 的冪次方個(gè)分區(qū),使用 best-fit 方法來分配內(nèi)存請(qǐng)求。
    • 當(dāng)釋放內(nèi)存時(shí),檢查 buddy 塊,查看相鄰的內(nèi)存塊是否也已被釋放。如果是,將內(nèi)存塊合并,以最小化內(nèi)存碎片。
    • 分配的內(nèi)存在物理內(nèi)存的自然邊界對(duì)齊,提高內(nèi)存訪問效率。
    • 算法的時(shí)間效率高,單使用 best-fit 方法的緣故,會(huì)產(chǎn)生一定的內(nèi)存浪費(fèi)

Buddy Allocator

  • BuddyAllocator 是一個(gè)單例,每個(gè)設(shè)備(如: GPU/CPU(0)/GPU(1)) 擁有一個(gè)BuddyAllocator
  • BuddyAllocator 內(nèi)部擁有一個(gè)私有成員變量 SystemAllocator
  • 當(dāng)請(qǐng)求的內(nèi)存超過BuddyAllocator管理的空余內(nèi)存時(shí),將會(huì)調(diào)用SystemAllocator去指定的設(shè)備上分配物理內(nèi)存

實(shí)例:CPU 下內(nèi)存管理接口的實(shí)現(xiàn)

  • 對(duì)上層應(yīng)用,統(tǒng)一通過BuddyAllocator來實(shí)現(xiàn)內(nèi)存的分配、釋放以及用量查詢
  • template <>
  • void* Alloc<platform::CPUPlace>(platform::CPUPlace place, size_t size) {
  • VLOG(10) << "Allocate " << size << " bytes on " << platform::Place(place);
  • void* p = GetCPUBuddyAllocator()->Alloc(size);
  • VLOG(10) << " pointer=" << p;
  • return p;
  • }
  • template <>
  • void Free<platform::CPUPlace>(platform::CPUPlace place, void* p) {
  • VLOG(10) << "Free pointer=" << p << " on " << platform::Place(place);
  • GetCPUBuddyAllocator()->Free(p);
  • }
  • template <>
  • size_t Used<platform::CPUPlace>(platform::CPUPlace place) {
  • return GetCPUBuddyAllocator()->Used();

}

==5.== 多設(shè)備支持

多設(shè)備支持(一)

  • step 1:添加Place類型,由用戶實(shí)現(xiàn)添加到框架
    • 可以將Place類型理解為一個(gè)整數(shù)加上一個(gè)枚舉型,包括:設(shè)備號(hào) + 設(shè)備類型

40% center

  • DeviceContext
    • 不同的Place會(huì)對(duì)應(yīng)一個(gè)相應(yīng)的DeviceContext,用于組織管理與設(shè)備相關(guān)的信息
      • 例如,GpuDeviceContext中會(huì)管理Cuda stream
    • 目前實(shí)現(xiàn)中一些特殊的庫也會(huì)對(duì)應(yīng)有自己的DeviceContext:例如:

class MKLDNNDeviceContext : public CPUDeviceContext {……}

    • 每種設(shè)備對(duì)應(yīng)的DeviceContext需要管理的內(nèi)容不盡相同,視具體需求來實(shí)現(xiàn)

多設(shè)備支持(二)

  • step 2: 增加KernelType,為相應(yīng)的KernelType注冊(cè)Kernel對(duì)象,由用戶實(shí)現(xiàn)注冊(cè)給框架 可以按照:
    1. Place 執(zhí)行設(shè)備
    2. DataType 執(zhí)行數(shù)據(jù)類型 FP32/FP64/INT32/INT64
    3. Memory layout: 運(yùn)行時(shí) Tensor 在內(nèi)存中的排布格式 NCHW、 NHWC
    4. 使用的庫

來區(qū)分Kernel,為同一個(gè)operator注冊(cè)多個(gè) Kernel。

struct OpKernelType {

proto::DataType data_type_;

DataLayout data_layout_;

platform::Place place_;

LibraryType library_type_;

}

多設(shè)備支持(三)

step 3: 運(yùn)行時(shí)的 KernelType 推斷和Kernel切換,按需要修改Kernel推斷和Kernel切換規(guī)則

  • Expected Kernel:期待調(diào)用的Kernel:由(1)Place和計(jì)算精度決定;或(2)用戶在配置中顯示指定使用的計(jì)算庫,如cudnn、mkldnn等。
  • Actual Kernel:運(yùn)行時(shí)從Operator的輸入(Variable)可以推斷出實(shí)際需要的KernelType
  • 當(dāng)Expected Kernel和Actual Kernel不一致的時(shí)候,框架會(huì)插入data_transformer或者data_layerout_transform等,保證Expected Kernel可以執(zhí)行,包括:
    • CPUPlace ➡ GPUPlace :跨設(shè)備內(nèi)存復(fù)制
    • NCHW ➡ nChw8c :Layout轉(zhuǎn)換
    • FP32 ➡ FP16 :精度轉(zhuǎn)換 尚未支持
    • ……
  • 以上過程實(shí)現(xiàn)在OperatorWithKernel類的Run方法中 

==6.== while_op

while_op

  • 循環(huán)執(zhí)行一段Program,直到條件operator判斷循環(huán)條件不滿足時(shí)終止循環(huán)
  • while_op 的特殊之處:
    1. while_op 沒有 kernel
    2. while_op 擁有自己的Block,會(huì)形成一段嵌套的Block
    3. ==while_op 內(nèi)部創(chuàng)建了一個(gè) Executor,來循環(huán)執(zhí)行Block==
  • while_op 輸入輸出 : LoDTensorArray
  • namespace paddle {
  • namespace framework {
  • using LoDTensorArray = std::vector<LoDTensor>;
  • }

}

    1. 每一次循環(huán),從原始輸入中“切出”一個(gè)片段
    2. LoDTensorArray 在Python端暴露,是Fluid支持的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)之一,用戶可以直接創(chuàng)建并使用

while_op Run 方法概覽

void Run(const framework::Scope &scope,

const platform::Place &dev_place) const override {

PADDLE_ENFORCE_NOT_NULL(scope.FindVar(Input(kCondition)));

auto &cond = scope.FindVar(Input(kCondition))->Get<LoDTensor>();

PADDLE_ENFORCE_EQ(cond.dims(), paddle::framework::make_ddim({1}));

framework::Executor executor(dev_place);

auto *block = Attr<framework::BlockDesc *>(kStepBlock);

auto *program = block->Program();

auto step_scopes =

scope.FindVar(Output(kStepScopes))->GetMutable<StepScopeVar>();

while (cond.data<bool>()[0]) {

auto &current_scope = scope.NewScope();

step_scopes->push_back(&current_scope);

executor.Run(*program, &current_scope, block->ID(),

false /*create_local_scope*/);

}

}

 

while_op 的重要應(yīng)用:Dynamic RNN

什么是 dynamicRNN ?

 

  1. 用戶可以自定義在一個(gè)時(shí)間步之內(nèi)的計(jì)算, 框架接受序列輸入數(shù)據(jù),在其上循環(huán)調(diào)用用戶定義的單步計(jì)算
  2. 可學(xué)習(xí)參數(shù)在多個(gè)時(shí)間步之間共享
  3. dynamicRNN 由 while_op 實(shí)現(xiàn)
  4. 如果dynamicRNN中定義了memory,將會(huì)構(gòu)成一個(gè)循環(huán)神經(jīng)網(wǎng)絡(luò),否則其行為就等于在輸入序列上循環(huán)調(diào)用預(yù)定義的單步計(jì)算

dynamic RNN 用戶接口

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/user_interface.png

  • dynamicRNN 中的重要元素
    1. step input: dynamicRNN 每個(gè)時(shí)間步的輸入
    2. step function: 用戶定義的單步計(jì)算
    3. memory: 用于形成循環(huán)連接
    4. external/static memory:?jiǎn)尾接?jì)算的每一步都可以全部讀取到的外部輸入

dynamicRNN 中的 Memory

dynamicRNN中memory的行為非常類似于 C++ 中的引用變量

  • memory “指向” 一個(gè)operator的輸出變量,記作: A
  • memory 可以被 LoDTensor 初始化(當(dāng)LoD信息為空時(shí),為非序列,否則為序列),默認(rèn)memory被初始化為零
  • memory 在 operator A 前向計(jì)算之后,進(jìn)行前向計(jì)算
  • 當(dāng) memory 的前向計(jì)算會(huì) "指向" A 的輸出 LoDTensor
  • memory 的輸出可以是另一個(gè) operator 的輸入,于是形成了“循環(huán)”連接

DynamicRNN 實(shí)現(xiàn)細(xì)節(jié)

  • while_op 無法獨(dú)立構(gòu)成dynamicRNN,必須和一組相關(guān)的 operator 及數(shù)據(jù)結(jié)構(gòu)配合
    • 依賴的 operators (這里僅列出最重要的,并非全部):
      • lod_rank_table operator
      • lod_tensor_to_array operator
      • array_to_lod_tensor operator
      • shrink_memory operator
    • 依賴的數(shù)據(jù)結(jié)構(gòu)
      • TensorArray
      • LoDRankTable
  • 在Fluid中,RNN接受變長序列輸入,無需填充,以上數(shù)據(jù)結(jié)構(gòu)和相關(guān)的operator配合工作,實(shí)現(xiàn)了對(duì)變長輸入以batch計(jì)算

dynamicRNN 如何實(shí)現(xiàn) batch 計(jì)算 ?

  • 問題:
    • RNN 可以看作是一個(gè)展開的前向網(wǎng)絡(luò),前向網(wǎng)絡(luò)的深度是最長序列的長度
    • 如果不對(duì)變長序列進(jìn)行填充,將它們填充到一樣長度,每個(gè)mini-batch輸入將會(huì)不等長,每個(gè)樣本展開長度不一致,導(dǎo)致前向和反向計(jì)算實(shí)現(xiàn)困難

實(shí)例 :RNN encoder-decoder with attention

  • 以機(jī)器翻譯的RNN encoder-decoder 模型(涉及了dynamicRNN的所有設(shè)計(jì)要素)為例,下圖是 RNN encoder-decoder 的原始輸入:

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/raw_input.png
Figure. RNN encoder-decoder 原始batch 輸入數(shù)據(jù)

  • source word sequences 是encoder RNN的輸出,是一個(gè)LoDTensor
  • target word sequences 是look_uptable的輸入,是一個(gè)LoDTensor
  • 上圖中一個(gè)矩形方塊是CPU/GPU內(nèi)存中一片連續(xù)的內(nèi)存空間,表示一個(gè)dense vector

dynamicRNN 如何實(shí)現(xiàn) batch 計(jì)算 ?

  1. 對(duì)一個(gè)mini batch中不等長樣本進(jìn)行排序,最長樣本變成batch中的第一個(gè),最短樣本是batch中最后一個(gè)
    • LoDTensor ➡ LoDRankTable ➕ lod_rank_table operaator
      • 可以將LoDRankTable理解為對(duì)LoDTensor中的多個(gè)序列按照長度排序LoDRankTable 存儲(chǔ)了排序之后的index
  2. 構(gòu)建每個(gè)時(shí)間步的batch輸入:隨著時(shí)間步增加,每個(gè)時(shí)間步的batch輸入可能會(huì)逐漸縮小
    • TensorArray ➕ lod_tensor_to_array ➡ LoDTensor (without LoD)
  3. 每個(gè)時(shí)間步輸出寫入一個(gè)輸出 LoDTensorArray
  4. dynamicRNN循環(huán)結(jié)束后, 按照LoDRankTable中記錄的信息對(duì)輸出LoDTensorArray重排序,還原會(huì)原始輸入順序
    • TensorArray ➕ array_to_lod_tensor ➡ LoDTensor

運(yùn)行實(shí)例

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/sorted_input.png

運(yùn)行實(shí)例

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/1.png

  • 執(zhí)行到第5~7個(gè)batch時(shí),batch size將會(huì)縮小

運(yùn)行實(shí)例

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/1.png

  • 第5 ~ 7個(gè)batch時(shí)RNN的memory會(huì)發(fā)生什么?
    • memory 指向某個(gè)operator的輸出Tensor,在該operator前向計(jì)算之后,“取回”其計(jì)算結(jié)果
    • 5 ~ 7時(shí),遇到了序列的結(jié)束,==下一個(gè)時(shí)間步計(jì)算不再需要在已經(jīng)結(jié)束的序列上展開==
    • 在dynamicRNN中shrink_memory operator 用來縮小memory的batch輸入

運(yùn)行實(shí)例:batch 1 ~ 2

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/2.png
Figure. 第1、2個(gè)batch輸入dynamicRNN的batch輸入

運(yùn)行實(shí)例:batch 3 ~ 4

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/3.png
Figure. 第3、4個(gè)batch輸入dynamicRNN的batch輸入

運(yùn)行實(shí)例:batch 5 ~ 7

https://github.com/lcy-seso/learning_notes/raw/master/Fluid/developer's_guid_for_Fluid/images/4.png
Figure. 第5、6、7個(gè)batch輸入dynamicRNN的batch輸入

==7.== Fluid 代碼結(jié)構(gòu)

Fluid 代碼結(jié)構(gòu)

代碼結(jié)構(gòu) 模塊結(jié)構(gòu)
60% center 65% center

==8.== 文檔總結(jié)

  • 設(shè)計(jì)概覽
    • 重構(gòu)概覽 
    • fluid 
    • fluid_compiler 
  • 核心概念
  • 重要功能模塊
    • backward 
    • 內(nèi)存優(yōu)化 
    • evaluator 
    • python API 
    • regularization 
  • 開發(fā)指南
    • 支持新設(shè)硬件設(shè)備庫 
    • 添加新的Operator 
    • 添加新的Kernel 

==9.== 開發(fā)指南

建議開發(fā)環(huán)境:使用 Docker 編譯和測(cè)試

Docker編譯PaddlePaddle源碼: 

PaddlePaddle 在 Dockerhub 地址:

  1. 獲取PaddlePaddle的Docker鏡像

docker pull paddlepaddle/paddle:latest-dev

  1. 啟動(dòng) docker container

docker run -it -v $PWD/Paddle:/paddle paddlepaddle/paddle:latest-dev /bin/bash

  1. 進(jìn)入docker container后,從源碼編譯,請(qǐng)參考文檔 

一些說明

  1. PaddlePaddle的Docker鏡像為了減小體積,默認(rèn)沒有安裝vim,可以在容器中執(zhí)行apt-get install -y vim來安裝vim。
  2. 開發(fā)推薦使用tag為latest-dev的鏡像,其中打包了所有編譯依賴。latest及l(fā)astest-gpu是production鏡像,主要用于運(yùn)行PaddlePaddle程序。
  3. 在Docker中運(yùn)行GPU程序,推薦使用nvidia-docker,否則需要將CUDA庫和設(shè)備掛載到Docker容器內(nèi)。

nvidia-docker run -it -v $PWD/Paddle:/paddle paddlepaddle/paddle:latest-dev /bin/bash

如何貢獻(xiàn)

  • ==提交PullRequest前請(qǐng)務(wù)必閱讀==: 
  • 代碼要求
    1. 代碼注釋遵守 Doxygen 的樣式
    2. 確保編譯器選項(xiàng) WITH_STYLE_CHECK 已打開,并且編譯能通過代碼樣式檢查
    3. 所有代碼必須具有單元測(cè)試,且能夠通過所有單元測(cè)試
  • 使用 pre-commit 鉤子提交Pull Request
    1. 幫助格式化源代碼(C++,Python)
    2. 在提交前自動(dòng)檢查一些基本事宜:如每個(gè)文件只有一個(gè) EOL,Git 中不要添加大文件等
    3. 安裝pre-commit,并在PaddlePaddle根目錄運(yùn)行:
  • ➜ pip install pre-commit

➜ pre-commit install

==10.== 添加新的 Operator

概念簡(jiǎn)介

添加一個(gè)新的operator,會(huì)涉及實(shí)現(xiàn)以下C++類的派生類:

  1. framework::OperatorBase: Operator(簡(jiǎn)寫,Op)基類。
  2. framework::OpKernel: Op計(jì)算函數(shù)的基類,稱作Kernel。
  3. framework::OperatorWithKernel:繼承自O(shè)peratorBase,Op有計(jì)算函數(shù),稱作有Kernel。
  4. class OpProtoAndCheckerMaker:描述該Op的輸入、輸出、屬性、注釋,主要用于Python API接口生成

依據(jù)是否包含kernel,可以將Op分為兩種:

  1. 包含Kernel的Op:繼承自O(shè)peratorWithKernel,==絕大多數(shù)operator都屬于這一類==
  2. 不包含kernel的Op,繼承自O(shè)peratorBase,只有少量Op屬于這一類,例如while_op,ifelse_op

這里主要介紹帶Kernel的Op如何編寫。

添加新的Operator需要修改/添加哪些文件?

內(nèi)容 定義位置
OpProtoMake定義 .cc文件,Backward Op不需要OpProtoMaker
Op定義 .cc文件
Kernel實(shí)現(xiàn) CPU、CUDA共享Kernel實(shí)現(xiàn)在.h文件中,否則,CPU 實(shí)現(xiàn)在.cc文件中,CUDA 實(shí)現(xiàn)在.cu文件中。
注冊(cè)O(shè)p Op注冊(cè)實(shí)現(xiàn)在.cc文件;Kernel注冊(cè)CPU實(shí)現(xiàn)在.cc文件中,CUDA實(shí)現(xiàn)在.cu文件中
  • 添加 Operator 之前請(qǐng)閱讀:Operator 命名規(guī)范Operator Markdown注釋規(guī)范。
  • 實(shí)現(xiàn)新的op都添加至目錄paddle/operators下,文件命名以*_op.h(如有) 、 *_op.cc 、*_op.cu(如有)結(jié)尾。
  • 根據(jù)文件名自動(dòng)構(gòu)建op和Python端綁定,請(qǐng)務(wù)必遵守以上命名,否則需要進(jìn)一步修改PyBind相關(guān)文件及CMakeLists.txt。

實(shí)現(xiàn)帶Kernel的Operator step1: 定義ProtoMaker類

下面均以clip_op為例進(jìn)行介紹

  • clip_op計(jì)算公式:$Out = \min(\max(X, min), max)$
  • 首先定義ProtoMaker來描述該Op的輸入、輸出,并添加注釋(下面代碼段的中注釋進(jìn)行了簡(jiǎn)化,實(shí)現(xiàn)時(shí)需按照規(guī)范添加注釋):
  • template <typename AttrType>
  • class ClipOpMaker : public framework::OpProtoAndCheckerMaker {
  • public:
  • ClipOpMaker(OpProto* proto, OpAttrChecker* op_checker)
  • : OpProtoAndCheckerMaker(proto, op_checker) {
  • AddInput("X","(Tensor)The input of clip op.");
  • AddOutput("Out", "(Tensor),The output of clip op.");
  • AddAttr<AttrType>(
  • "min", "(float),Minimum value.");
  • AddAttr<AttrType>(
  • "max", "(float),Maximum value.");
  • AddComment(R"DOC(
  • ……
  • )DOC");
  • }

};

實(shí)現(xiàn)帶Kernel的Operator step2: 定義Operator類

下面的代碼段實(shí)現(xiàn)了clip_op的定義:

class ClipOp : public framework::OperatorWithKernel {

public:

using framework::OperatorWithKernel::OperatorWithKernel;

void InferShape(framework::InferShapeContext* ctx) const override {

PADDLE_ENFORCE(ctx->HasInput("X"),

"Input(X) of ClipOp should not be null.");

PADDLE_ENFORCE(ctx->HasOutput("Out"),

"Output(Out) of ClipOp should not be null.");

auto x_dims = ctx->GetInputDim("X");

auto max = ctx->Attrs().Get<float>("max");

auto min = ctx->Attrs().Get<float>("min");

PADDLE_ENFORCE_LT(min, max, "max should be greater than min.");

ctx->SetOutputDim("Out", x_dims);

ctx->ShareLoD("X", /*->*/ "Out");

}

};

Operator 類中需要完成的工作

  1. clip_op 繼承自O(shè)peratorWithKernel,

using framework::OperatorWithKernel::OperatorWithKernel;

表示使用基類OperatorWithKernel的構(gòu)造函數(shù)。

  1. 重寫InferShape接口。
    • InferShape 為const函數(shù),不能修改Op的成員變
    • InferShape 的參數(shù)為 const framework::InferShapeContext &ctx,從中可獲取到輸入輸出以及屬性
    • InferShape 會(huì)被調(diào)用兩次,一次是編譯時(shí)(創(chuàng)建op),一次是運(yùn)行時(shí)(調(diào)用op的Run方法時(shí)),需要完成以下功能:
      1. 做檢查, 盡早報(bào)錯(cuò):檢查輸入數(shù)據(jù)維度、類型等是否合法
      2. 設(shè)置輸出Tensor的形狀

通常OpProtoMaker和Op類的定義寫在.cc文件中。

補(bǔ)充說明

  1. InferShape目前支持兩種實(shí)現(xiàn)方式,二者最后都會(huì)生成一個(gè)functor注冊(cè)給OpInfo結(jié)構(gòu)體。
    1. 繼承framework::InferShapeBase,實(shí)現(xiàn)為一個(gè)functor(參考 mul_op
    2. override InferShape函數(shù)(參考 clip_op
  2. 什么是functor ?
    1. 類或結(jié)構(gòu)體僅重載了(),一般是可被多個(gè)kernel復(fù)用的計(jì)算函數(shù)。
    2. template <typename T>
    3. class CrossEntropyFunctor<platform::CPUDeviceContext, T> {
    4. public:
    5. void operator()(const platform::CPUDeviceContext& ctx,
    6. framework::Tensor* out,
    7. const framework::Tensor* prob,
    8. const framework::Tensor* labels, const bool softLabel) {
    9. ……
    10. }

};

    1. 在 clip_op 內(nèi)也會(huì)看到將一段計(jì)算函數(shù)抽象為functor的使用法: 。

實(shí)現(xiàn)帶Kernel的Operator step3: 定義OpKernel類

  • ClipKernel繼承自framework::OpKernel,帶有下面兩個(gè)模板參數(shù):
    1. typename DeviceContext: 表示設(shè)備類型,不同設(shè)備共享同一個(gè)Kernel時(shí),需添加該模板參數(shù)。不共享時(shí),需要提供針對(duì)不同設(shè)備的特化實(shí)現(xiàn)。
    2. typename T : 表示支持的數(shù)據(jù)類型,如float, double等
  • 在ClipKernel類中重寫Compute方法
    1. Compute接受輸入?yún)?shù):const framework::ExecutionContext& context
      • ExecutionContext 是從 Scope中將運(yùn)行時(shí)Op的輸入、輸出Variable組織在一起,使得Op在調(diào)用Compute方法時(shí),能夠簡(jiǎn)單地通過名字拿到需要的輸入輸出Variable
      • 與InferShapeContext相比,ExecutionContext 中增加了設(shè)備類型
    2. 在Compute函數(shù)里實(shí)現(xiàn)OpKernel的具體計(jì)算邏輯

ClipKernel 代碼概覽

template <typename DeviceContext, typename T>

class ClipKernel : public framework::OpKernel<T> {

public:

void Compute(const framework::ExecutionContext& context) const override {

auto max = context.Attr<T>("max");

auto min = context.Attr<T>("min");

auto* x = context.Input<Tensor>("X");

auto* out = context.Output<Tensor>("Out");

T* out_data = out->mutable_data<T>(context.GetPlace());

const T* x_data = x->data<T>();

int64_t numel = x->numel();

Transform<DeviceContext> trans;

trans(context.template device_context<DeviceContext>(), x_data,

x_data + numel, out_data, ClipFunctor<T>(min, max));

}

};

  • 為了使OpKernel的計(jì)算過程書寫更加簡(jiǎn)單,并且CPU、CUDA的代碼可以復(fù)用, Fluid 使用 Eigen 作為基礎(chǔ)的矩陣運(yùn)算庫
  • Fluid對(duì)Eigen unsupported Tensor提供了一些基本的封裝,可以在Compute接口中直接調(diào)用
    • 關(guān)于在PaddlePaddle中如何使用Eigen庫,請(qǐng)參考使用文檔。

實(shí)現(xiàn)帶Kernel的Operator step4: 實(shí)現(xiàn)反向Op

  • ==反向Op沒有ProtoMaker==,除此之外定義與實(shí)現(xiàn)方式前向Op完全一致,不再贅述
  • 這里僅對(duì)反向Op的輸入輸出進(jìn)行說明:
    1. 反向Op的輸入
      • 前向Op的輸出
      • 反向傳播過程中傳遞給當(dāng)前Op的梯度
        • 需要注意,F(xiàn)luid中,不區(qū)分Cost Op和中間層Op,所有Op都必須正確處理接收到的梯度
    2. 反向Op的輸出
      • 對(duì)可學(xué)習(xí)參數(shù)的求導(dǎo)結(jié)果
      • 對(duì)所有輸入的求導(dǎo)結(jié)果

實(shí)現(xiàn)帶Kernel的Operator step5: 注冊(cè)O(shè)p及Kernel

至此Op和Op kernel都已經(jīng)實(shí)現(xiàn)完畢,接下來,需要在.cc和cu文件中注冊(cè)op和kernel

  1. 在.cc文件中注冊(cè)前向、反向Op類,注冊(cè)CPU Kernel。
  2. namespace ops = paddle::operators;
  3. REGISTER_OP(clip, ops::ClipOp, ops::ClipOpMaker<float>, clip_grad,
  4. ops::ClipOpGrad);
  5. REGISTER_OP_CPU_KERNEL(
  6. clip, ops::ClipKernel<paddle::platform::CPUDeviceContext, float>);
  7. REGISTER_OP_CPU_KERNEL(

clip_grad, ops::ClipGradKernel<paddle::platform::CPUDeviceContext, float>);

    • 在上面的代碼片段中:
      1. REGISTER_OP : 注冊(cè)ops::ClipOp類,類型名為clip,該類的ProtoMaker為ops::ClipOpMaker,注冊(cè)ops::ClipOpGrad,類型名為clip_grad
      2. REGISTER_OP_WITHOUT_GRADIENT : 用于注冊(cè)沒有反向的Op,例如:優(yōu)化算法相關(guān)的Op
      3. REGISTER_OP_CPU_KERNEL :注冊(cè)ops::ClipKernel類,并特化模板參數(shù)為paddle::platform::CPUPlace和float類型,同理,注冊(cè)ops::ClipGradKernel類
  1. 按照同樣方法,在.cu文件中注冊(cè)GPU Kernel
    • 如果CUDA Kernel的實(shí)現(xiàn)基于Eigen,需在 .cu的開始加上宏定義 #define EIGEN_USE_GPU

編譯和Python端綁定

  • 運(yùn)行下面命令可以僅編譯新添加的Op:
  • make mul_op
    • 需注意,運(yùn)行單元測(cè)試需要編譯整個(gè)工程
  • 如果遵循前文的文件命名規(guī)則,構(gòu)建過程中,會(huì)自動(dòng)為新增的op添加Python端綁定,并鏈接到生成的lib庫中

實(shí)現(xiàn)帶Kernel的Operator step6: 添加前向單測(cè)及梯度檢測(cè)

  • 新增Op的單元測(cè)試統(tǒng)一添加至:python/paddle/v2/fluid/tests目錄
  • 前向Operator單測(cè)
    1. Op單元測(cè)試?yán)^承自O(shè)pTest,各項(xiàng)具體的單元測(cè)試在TestClipOp里完成,所有單測(cè)case都以TestXX命名
    2. 單元測(cè)試Operator,需要:
      1. 在setUp函數(shù)定義輸入、輸出,以及相關(guān)的屬性參數(shù)
      2. 生成隨機(jī)的輸入數(shù)據(jù)
      3. 在Python腳本中實(shí)現(xiàn)與前向operator相同的計(jì)算邏輯,得到輸出值,與operator前向計(jì)算的輸出進(jìn)行對(duì)比
      4. 反向梯度檢測(cè)流程測(cè)試框架已經(jīng)實(shí)現(xiàn),直接調(diào)用相應(yīng)接口check_grad即可
  • clip_op 單測(cè)代碼請(qǐng)參考 ,這里不再展開

編譯執(zhí)行單測(cè)

  • python/paddle/v2/framework/tests 目錄下新增的 test_*.py 單元測(cè)試會(huì)被自動(dòng)加入工程進(jìn)行編譯
    • 運(yùn)行單元測(cè)試測(cè)時(shí)需要編譯整個(gè)工程,并且編譯時(shí)需要打開WITH_TESTING, 即cmake paddle_dir -DWITH_TESTING=ON
  • 編譯成功后,執(zhí)行下面的命令來運(yùn)行單元測(cè)試:

make test ARGS="-R test_mul_op -V"

或者:

ctest -R test_mul_op

添加Op的一些注意事項(xiàng)

  • 為每個(gè)Op創(chuàng)建單獨(dú)的*_op.h(如有)、*_op.cc和*_op.cu(如有)。不允許一個(gè)文件中包含多個(gè)Op,將會(huì)導(dǎo)致編譯出錯(cuò)。
  • 注冊(cè)O(shè)p時(shí)的類型名,需要和該Op的名字一樣。不允許在A_op.cc里面,注冊(cè)REGISTER_OP(B, ...),會(huì)導(dǎo)致單元測(cè)試出錯(cuò)。
  • 如果Op沒有實(shí)現(xiàn)CUDA Kernel,不要?jiǎng)?chuàng)建空的*_op.cu,會(huì)導(dǎo)致單元測(cè)試出錯(cuò)。
  • 如果多個(gè)Op依賴一些共用的函數(shù),可以創(chuàng)建非*_op.*格式的文件來存放,如gather.h文件。

==10.== 使用相關(guān)問題

定義前向計(jì)算

  • 當(dāng)在python端執(zhí)行時(shí):

import paddle.v2.fluid as fluid

framework.py定義了兩個(gè)全局Program:

# program is a global instance.

_main_program_ = Program()

_startup_program_ = Program()

  • 前向定義的過程就是不斷往mian_program中添加Op和Variable
  • 如果需要執(zhí)行一個(gè)新的mian_program時(shí),可以調(diào)用調(diào)用:
  • def switch_main_program(program):
  • """
  • Switch the main program to a new program.
  • This funtion returns the previous main program.
  • """

……

自定義參數(shù)的初始化

  • 調(diào)用fluid.ParamAttr(……)接口,自定義參數(shù)的初始化
  • w_param_attrs = ParamAttr(name=None,
  • initializer=UniformInitializer(low=-1.0, high=1.0, seed=0),
  • learning_rate=1.0,
  • regularizer=L1Decay(1.0),
  • trainable=True,
  • clip=GradientClipByValue(-1.0, 1.0),
  • )

y_predict = fluid.layers.fc(input=x, size=1, param_attr=w_param_attrs)

  • 補(bǔ)充問題:如何創(chuàng)建 Variable
  • cur_program = Program()
  • cur_block = cur_program.current_block()

new_var = cur_block.create_var(name="X", shape=[-1, 16, 16], dtype="float32")

添加反向Op

  • 調(diào)用fluid.backward.append_backward(X)(X是一個(gè)Variable),來為一段前向ProgramDesc添加反Op
  • data = fluid.layers.data(name="data", shape=(2,3,4))
  • out = fluid.layers.fc(input=data,size=128,act=None)
  • loss = fluid.layers.reduce_sum(out)

fluid.backward.append_backward(loss=loss)

  • 添加優(yōu)化相關(guān)的Op
  • sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.001)

sgd_optimizer.minimize(loss)

  • 可以隨時(shí)調(diào)用print(fluid.default_main_program())來輸出當(dāng)前的main_program
  • 當(dāng)構(gòu)建完成整個(gè)Program后,調(diào)用下面的接口執(zhí)行內(nèi)存優(yōu)化:

fluid.memory_optimize(fluid.default_main_program())

    • 注:內(nèi)存優(yōu)化目前仍在持續(xù)開發(fā)中,有可能不夠穩(wěn)定。

總結(jié):編譯時(shí)執(zhí)行流程

  • 用戶定義前向計(jì)算
  • 添加反向Op到default_main_program
  • 添加 gradient clipping Op 到
  • 添加 regularization Op 到default_main_program
  • 為指定的優(yōu)化算法,添加相關(guān)的狀態(tài) variable of optimizer 到default_startup_program
    • 狀態(tài)相關(guān) variable是指如學(xué)習(xí)率, 歷史 momentum, 二階momentum等
  • 添加初始化 variable 的Op 到 default_startup_program
  • 為整個(gè)網(wǎng)絡(luò)最后一個(gè)op,添加設(shè)置其接受到的梯度的Op到default_main_program
  • 進(jìn)行內(nèi)存優(yōu)化規(guī)劃

Feed 數(shù)據(jù) (一):通過 feed 字典

  • 執(zhí)行executor的run方法時(shí),指定feed字典,feed op 會(huì)將指定的數(shù)據(jù)放到x和y兩個(gè)Variable中
  • y_data = np.random.randint(0, 8, [1]).astype("int32")
  • y_tensor = core.Tensor()
  • y_tensor.set(y_data, place)
  • x_data = np.random.uniform(0.1, 1, [11, 8]).astype("float32")
  • x_tensor = core.Tensor()
  • x_tensor.set(x_data, place)
  • ……
  • cost = exe.run(
  • fluid.default_main_program(),
  • feed={'x': x_tensor,
  • 'y': y_tensor},

fetchlist=[avg_cost])

  • 這種方法較為底層,一般用于單測(cè)中

Feed 數(shù)據(jù) (二):使用 DataFeeder接口

  • 編寫一個(gè)data_reader函數(shù),data_reader是一個(gè)Python generator
  • def demo_reader():
  • def random_generator():
  • yield np.random.uniform(0.1, 1, [4]), np.random.randint(0, 1, [1])

return random_generator

  • 在訓(xùn)練任務(wù)中使用 DataFeeder 接口
  • cost = exe.run(
  • fluid.default_main_program(),
  • feed={'x': x_tensor,
  • 'y': y_tensor},
  • fetchlist=[avg_cost])
  • train_reader = paddle.batch(
  • paddle.reader.shuffle(demo_reader(), buf_size=500), batch_size=4)
  • feeder = fluid.DataFeeder(place=place, feed_list=[x, y])
  • for data in train_reader():
  • cost = exe.run(
  • fluid.default_main_program(),
  • feed=feeder.feed(data),

fetch_list=[cost])

常見問題

  • 如何使用 evaluator ? 
  • accuracy = fluid.evaluator.Accuracy(input=predict, label=label)
  • for pass_id in range(PASS_NUM):
  • accuracy.reset()
  • for data in train_reader():
  • loss, acc = exe.run(fluid.default_main_program(),
  • feed=feeder.feed(data),
  • fetch_list=[avg_cost] + accuracy.metrics)
  • pass_acc = accuracy.eval(exe)
  • # acc 當(dāng)前一個(gè)batch 的 accuracy
  • # pass_acc 當(dāng)前batch 的 accuracy

pass_total_acc = accuracy.eval(exe) # 整個(gè)pass的accuracy

  • 如何在訓(xùn)練中測(cè)試?
  • 如何保存訓(xùn)練好的模型?
  • 如何加載訓(xùn)練好的模型進(jìn)行預(yù)測(cè)?
  • 如何在同一個(gè)訓(xùn)練任務(wù)中定義多個(gè)Program,并交替運(yùn)行? 
  • 如何profile?Fluid 實(shí)現(xiàn)了profile 工具,可以直接調(diào)用。請(qǐng)參考示例 

謝謝 

責(zé)任編輯:張燕妮 來源: github
相關(guān)推薦

2019-08-16 10:55:37

開發(fā)者技能AI

2024-05-07 08:45:16

OpenAILlamaIndex大語言模型

2024-02-01 09:37:42

Kubernetes服務(wù)網(wǎng)格? 命令

2022-01-02 23:26:08

開發(fā)SDK Sentry

2011-04-13 13:38:57

選項(xiàng)APIBlackBerry

2011-04-13 09:55:16

Mail APIBlackBerry

2022-01-11 20:42:54

開發(fā)Sentry標(biāo)志

2022-01-17 19:34:43

SentryWeb APISentry API

2022-01-15 23:33:47

SentryPyCharm配置

2009-02-19 08:46:31

Windows 7開發(fā)者指南下載

2011-07-19 09:51:32

性能優(yōu)化Designing FAndroid

2022-01-18 23:26:45

開發(fā)

2011-04-13 11:31:06

PIM APIBlackBerry

2021-12-25 22:31:55

Sentry 監(jiān)控SDK 開發(fā) 性能監(jiān)控

2021-12-15 20:06:48

ReactJSSentry開發(fā)者

2019-02-21 13:40:35

Javascript面試前端

2011-04-02 13:44:08

2017-11-27 13:09:00

AndroidGradle代碼

2022-01-21 21:33:03

開發(fā)JavaScript應(yīng)用

2022-01-03 22:59:30

開發(fā)SDK數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)