旷视MegEngine基本概念
曠視MegEngine基本概念
MegEngine 是基于計(jì)算圖的深度神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)框架。 本文簡(jiǎn)要介紹計(jì)算圖及其相關(guān)基本概念,以及它們?cè)?MegEngine 中的實(shí)現(xiàn)。
計(jì)算圖(Computational Graph)
下面通過(guò)一個(gè)簡(jiǎn)單的數(shù)學(xué)表達(dá)式 y=(w?x)+by=(w?x)+b 來(lái)介紹計(jì)算圖的基本概念,如下圖所示:
圖1
從中可以看到,計(jì)算圖中存在:
? 數(shù)據(jù)節(jié)點(diǎn)(圖中的實(shí)心圈):如輸入數(shù)據(jù) xx 、 ww 、 bb ,運(yùn)算得到的中間數(shù)據(jù) pp ,以及最終的運(yùn)算輸出 yy ;
? 計(jì)算節(jié)點(diǎn)(圖中的空心圈):圖中 * 和 + 分別表示計(jì)算節(jié)點(diǎn) 乘法 和 加法,是施加在數(shù)據(jù)節(jié)點(diǎn)上的運(yùn)算;
? 邊(圖中的箭頭):表示數(shù)據(jù)的流向,體現(xiàn)了數(shù)據(jù)節(jié)點(diǎn)和計(jì)算節(jié)點(diǎn)之間的依賴(lài)關(guān)系;
如上,便是一個(gè)簡(jiǎn)單的計(jì)算圖示例。計(jì)算圖是一個(gè)包含數(shù)據(jù)節(jié)點(diǎn)和計(jì)算節(jié)點(diǎn)的有向圖(可以是有環(huán)的,也可以是無(wú)環(huán)的), 是數(shù)學(xué)表達(dá)式的形象化表示。在深度學(xué)習(xí)領(lǐng)域,任何復(fù)雜的深度神經(jīng)網(wǎng)絡(luò)本質(zhì)上都可以用一個(gè)計(jì)算圖表示出來(lái)。
前向傳播
計(jì)算由計(jì)算圖表示的數(shù)學(xué)表達(dá)式的值的過(guò)程。在圖1中,變量 xx 和 ww ,從左側(cè)輸入,首先經(jīng)過(guò)乘法運(yùn)算得到中間結(jié)果 pp , 接著,pp 和輸入變量 bb 經(jīng)過(guò)加法運(yùn)算,得到右側(cè)最終的輸出 yy ,這就是一個(gè)完整的前向傳播過(guò)程。
在 MegEngine 中,用張量(Tensor)表示計(jì)算圖中的數(shù)據(jù)節(jié)點(diǎn),用算子(Operator)實(shí)現(xiàn)數(shù)據(jù)節(jié)點(diǎn)之間的運(yùn)算。
張量(Tensor)
與 PyTorch,TensorFlow 等深度學(xué)習(xí)框架類(lèi)似,MegEngine 使用張量(Tensor)來(lái)表示計(jì)算圖中的數(shù)據(jù)。 張量(Tensor)可以看做 NumPy 中的數(shù)組,可以是標(biāo)量、向量、矩陣或者多維數(shù)組。 可以通過(guò) NumPy 或者 Python List 來(lái)創(chuàng)建一個(gè) Tensor 。
import numpy as np
import megengine as mge
初始化一個(gè)維度為 (2, 5) 的 ndarray,并轉(zhuǎn)化成 MegEngine 的 Tensor
注:目前 MegEngine Tensor 不支持 float64 數(shù)值類(lèi)型,所以這里顯式指定了 ndarray 的數(shù)值類(lèi)型
a = mge.tensor(np.random.random((2,5)).astype(‘float32’))
print(a)
初始化一個(gè)長(zhǎng)度為3的列表,并轉(zhuǎn)化成 Tensor
b = mge.tensor([1., 2., 3.])
print(b)
輸出:
Tensor([[0.2976 0.4078 0.5957 0.3945 0.9413]
[0.7519 0.3313 0.0913 0.3345 0.3256]], device=xpux:0)
Tensor([1. 2. 3.], device=xpux:0)
通過(guò) dtype 屬性,可以獲取 Tensor 的數(shù)據(jù)類(lèi)型;
通過(guò) astype() 方法可以拷貝,創(chuàng)建一個(gè)指定數(shù)據(jù)類(lèi)型的新Tensor ,原Tensor 不變。
print(a.dtype)
d = a.astype(“float16”)
print(d.dtype)
輸出:
<class ‘numpy.float32’>
<class ‘numpy.float16’>
通過(guò) shape 屬性,可以獲取 Tensor 的形狀:
print(a.shape)
輸出為一個(gè)Tuple:
(2, 5)
通過(guò) numpy() 方法,可以將 Tensor 轉(zhuǎn)換為 numpy.ndarray:
a = mge.tensor(np.arange(12)).reshape(2, 6).astype(“float32”)
print(a)
b = a.numpy()
print(b)
輸出:
Tensor([[ 0. 1. 2. 3. 4. 5.]
[ 6. 7. 8. 9. 10. 11.]], device=xpux:0)
[[ 0. 1. 2. 3. 4. 5.]
[ 6. 7. 8. 9. 10. 11.]]
通過(guò) device 屬性,可以查詢(xún)當(dāng)前 Tensor 所在的設(shè)備。創(chuàng)建的 Tensor 可以位于不同 device,這根據(jù)當(dāng)前的環(huán)境決定。一般地,如果在創(chuàng)建 Tensor 時(shí)不指定 device,其 device 屬性默認(rèn)為 xpux,表示當(dāng)前任意一個(gè)可用的設(shè)備。如果存在 GPU 則優(yōu)先使用 GPU,否則為 CPU。
print(a.device)
輸出:
xpux:0
可以在創(chuàng)建 Tensor 時(shí),指定 device 為 cpu0, cpu1, …, gpu0, gpu1, … ,也可以是 cpux 或 gpux,表示當(dāng)前任意一個(gè)可用的 CPU 或 GPU。
通過(guò) to() 方法可以在另一個(gè) device 上生成當(dāng)前 Tensor 的拷貝,比如將剛剛創(chuàng)建的 Tensor a 遷移到 CPU 上,再遷移到 GPU 上:
下面代碼是否能正確執(zhí)行取決于你當(dāng)前所在的環(huán)境
b = a.to(“cpu0”)
print(b.device)
c = b.to(“gpu0”)
print(c.device)
輸出:
cpu0:0
gpu0:0
GPU 和 CPU 切換
MegEngine 在 GPU 和 CPU 同時(shí)存在時(shí)默認(rèn)使用 GPU 進(jìn)行訓(xùn)練。用戶可以調(diào)用 set_default_device() 來(lái)根據(jù)自身需求設(shè)置默認(rèn)計(jì)算設(shè)備。
如下代碼設(shè)置默認(rèn)設(shè)備為 CPU:
import megengine as mge
默認(rèn)使用 CPU
mge.set_default_device(‘cpux’)
如下代碼設(shè)置默認(rèn)設(shè)備為GPU:
默認(rèn)使用 GPU
mge.set_default_device(‘gpux’)
如果不想修改代碼,用戶也可通過(guò)環(huán)境變量 MGE_DEFAULT_DEVICE 來(lái)設(shè)置默認(rèn)計(jì)算設(shè)備:
默認(rèn)使用 CPU
export MGE_DEFAULT_DEVICE=‘cpux’
默認(rèn)使用 GPU
export MGE_DEFAULT_DEVICE=‘gpux’
算子(Operator)
MegEngine 中通過(guò)算子 (Operator) 來(lái)表示運(yùn)算。 類(lèi)似于 NumPy,MegEngine 中的算子支持基于 Tensor 的常見(jiàn)數(shù)學(xué)運(yùn)算和操作。 下面介紹幾個(gè)簡(jiǎn)單示例:
Tensor 的加法:
a = mge.tensor([[1., 2., 2.], [5., 1., 8.]])
print(a)
b = mge.tensor([[1., 9., 1.], [1., 7., 9.]])
print(b)
print(a + b)
輸出:
Tensor([[1. 2. 2.]
[5. 1. 8.]], device=xpux:0)
Tensor([[1. 9. 1.]
[1. 7. 9.]], device=xpux:0)
Tensor([[ 2. 11. 3.]
[ 6. 8. 17.]], device=xpux:0)
Tensor 的切片:
print(a[1, :])
輸出:
Tensor([5. 1. 8.], device=xpux:0)
Tensor 形狀的更改:
a.reshape(3, 2)
輸出:
Tensor([[1. 2.]
[2. 5.]
[1. 8.]], device=xpux:0)
reshape() 的參數(shù)允許存在單個(gè)維度的缺省值,用 -1 表示。此時(shí),reshape 會(huì)自動(dòng)推理該維度的值:
原始維度是 (2, 3),當(dāng)給出 -1 的缺省維度值時(shí),可以推理出另一維度為 6
a = a.reshape(1, -1)
print(a.shape)
輸出:
(1, 6)
MegEngine 的 functional 提供了更多的算子,比如深度學(xué)習(xí)中常用的矩陣乘操作、卷積操作等。
Tensor 的矩陣乘:
import megengine as mge
import megengine.functional as F
a = mge.tensor(np.arange(6).reshape(2, 3)).astype(‘float32’)
print(a)
b = mge.tensor(np.arange(6, 12).reshape(3, 2)).astype(‘float32’)
print(b)
c = F.matmul(a, b)
print?
輸出:
Tensor([[0. 1. 2.]
[3. 4. 5.]], device=xpux:0)
Tensor([[ 6. 7.]
[ 8. 9.]
[10. 11.]], device=xpux:0)
Tensor([[ 28. 31.]
[100. 112.]], device=xpux:0)
更多算子可以參見(jiàn) functional 部分的文檔。
求導(dǎo)器(Grad Manager)
神經(jīng)網(wǎng)絡(luò)的優(yōu)化,通常通過(guò)隨機(jī)梯度下降來(lái)進(jìn)行。需要根據(jù)計(jì)算圖的輸出,通過(guò)鏈?zhǔn)角髮?dǎo)法則,對(duì)所有的中間數(shù)據(jù)節(jié)點(diǎn)求梯度,這一過(guò)程被稱(chēng)之為 “反向傳播”。 例如,為了得到圖1中 yy 關(guān)于輸入 ww 的梯度,反向傳播的過(guò)程如下圖所示:
圖2
首先 y=p+by=p+b ,因此 ?y/?p=1?y/?p=1 ; 接著,反向追溯,p=w?xp=w?x ,因此,?p/?w=x?p/?w=x 。 根據(jù)鏈?zhǔn)角髮?dǎo)法則,?y/?w=(?y/?p)?(?p/?w)?y/?w=(?y/?p)?(?p/?w) , 因此最終 yy 關(guān)于輸入 ww 的梯度為 xx 。
MegEngine 為計(jì)算圖中的張量提供了自動(dòng)求導(dǎo)功能,以上圖的例子說(shuō)明: 假設(shè)圖中的 xx 是 shape 為 (1, 3) 的張量, ww 是 shape 為 (3, 1) 的張量, bb 是一個(gè)標(biāo)量。 利用MegEngine 計(jì)算 y=x?w+by=x?w+b 的過(guò)程如下:
import megengine as mge
import megengine.functional as F
from megengine.autodiff import GradManager
x = mge.tensor([1., 3., 5.]).reshape(1, 3)
w = mge.tensor([2., 4., 6.]).reshape(3, 1)
b = mge.tensor(-1.)
gm = GradManager().attach([w, b]) # 新建一個(gè)求導(dǎo)器,綁定需要求導(dǎo)的變量
with gm: # 開(kāi)始記錄計(jì)算圖
p = F.matmul(x, w)
y = p + b
gm.backward(y) # 計(jì)算 y 的導(dǎo)數(shù)
print(w.grad)
print(b.grad)
輸出:
Tensor([[1.]
[3.]
[5.]], device=xpux:0)
Tensor([1.], device=xpux:0)
可以看到,求出的梯度本身也是 Tensor。
with 代碼段中的前向運(yùn)算都會(huì)被求導(dǎo)器記錄??梢杂?record() 和 release() 來(lái)替代 with,分別控制求導(dǎo)器的開(kāi)啟和關(guān)閉(不推薦),代碼如下所示。
gm = GradManager().attach([w, b]) # 新建一個(gè)求導(dǎo)器,綁定需要求導(dǎo)的變量
gm.record() # 開(kāi)始記錄計(jì)算圖
p = F.matmul(x, w)
y = p + b
gm.backward(y) # 計(jì)算 y 的導(dǎo)數(shù)
gm.release() # 停止記錄計(jì)算圖并釋放資源
此外,可以使用 detach 方法,把 Tensor 當(dāng)作一個(gè)常量,這樣求導(dǎo)器將不會(huì)對(duì)其求導(dǎo)。如下所示:
gm = GradManager().attach([w, b]) # 新建一個(gè)求導(dǎo)器,綁定需要求導(dǎo)的變量
with gm: # 開(kāi)始記錄計(jì)算圖
p = F.matmul(x, w)
y = p + b.detach() # 停止對(duì) b 求導(dǎo)
gm.backward(y) # 計(jì)算 y 的導(dǎo)數(shù)
print(b.grad)
輸出:
None
總結(jié)
以上是生活随笔為你收集整理的旷视MegEngine基本概念的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 微调torchvision 0.3的目标
- 下一篇: 旷视MegEngine网络搭建