曠視MegEngine基本概念


曠視MegEngine基本概念

MegEngine 是基於計算圖的深度神經網絡學習框架。 本文簡要介紹計算圖及其相關基本概念,以及它們在 MegEngine 中的實現。

計算圖(Computational Graph

下面通過一個簡單的數學表達式 y=(w∗x)+by=(w∗x)+b 來介紹計算圖的基本概念,如下圖所示:

 

 

 圖1

從中可以看到,計算圖中存在:

  • 數據節點(圖中的實心圈):如輸入數據 xx 、 ww 、 bb ,運算得到的中間數據 pp ,以及最終的運算輸出 yy ;
  • 計算節點(圖中的空心圈):圖中 * 和 + 分別表示計算節點 乘法 和 加法,是施加在數據節點上的運算;
  • 邊(圖中的箭頭):表示數據的流向,體現了數據節點和計算節點之間的依賴關系;

如上,便是一個簡單的計算圖示例。計算圖是一個包含數據節點和計算節點的有向圖(可以是有環的,也可以是無環的), 是數學表達式的形象化表示。在深度學習領域,任何復雜的深度神經網絡本質上都可以用一個計算圖表示出來。

前向傳播 

計算由計算圖表示的數學表達式的值的過程。在圖1中,變量 xx 和 ww ,從左側輸入,首先經過乘法運算得到中間結果 pp , 接着,pp 和輸入變量 bb 經過加法運算,得到右側最終的輸出 yy ,這就是一個完整的前向傳播過程。

在 MegEngine 中,用張量(Tensor)表示計算圖中的數據節點,用算子(Operator)實現數據節點之間的運算。

張量(Tensor

與 PyTorch,TensorFlow 等深度學習框架類似,MegEngine 使用張量(Tensor)來表示計算圖中的數據。 張量(Tensor)可以看做 NumPy 中的數組,可以是標量、向量、矩陣或者多維數組。 可以通過 NumPy 或者 Python List 來創建一個 Tensor 。

import numpy as np

import megengine as mge

 

# 初始化一個維度為 (2, 5) ndarray,並轉化成 MegEngine Tensor

# 注:目前 MegEngine Tensor 不支持 float64 數值類型,所以這里顯式指定了 ndarray 的數值類型

a = mge.tensor(np.random.random((2,5)).astype('float32'))

print(a)

 

# 初始化一個長度為3的列表,並轉化成 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)

通過 dtype 屬性,可以獲取 Tensor 的數據類型;

通過 astype() 方法可以拷貝,創建一個指定數據類型的新Tensor ,原Tensor 不變。

print(a.dtype)

d = a.astype("float16")

print(d.dtype)

輸出:

<class 'numpy.float32'>

<class 'numpy.float16'>

通過 shape 屬性,可以獲取 Tensor 的形狀:

print(a.shape)

輸出為一個Tuple:

(2, 5)

通過 numpy() 方法,可以將 Tensor 轉換為 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.]]

通過 device 屬性,可以查詢當前 Tensor 所在的設備。創建的 Tensor 可以位於不同 device,這根據當前的環境決定。一般地,如果在創建 Tensor 時不指定 device,其 device 屬性默認為 xpux,表示當前任意一個可用的設備。如果存在 GPU 則優先使用 GPU,否則為 CPU。

print(a.device)

輸出:

xpux:0

可以在創建 Tensor 時,指定 device 為 cpu0, cpu1, …, gpu0, gpu1, … ,也可以是 cpux 或 gpux,表示當前任意一個可用的 CPU 或 GPU。

通過 to() 方法可以在另一個 device 上生成當前 Tensor 的拷貝,比如將剛剛創建的 Tensor a 遷移到 CPU 上,再遷移到 GPU 上:

# 下面代碼是否能正確執行取決於你當前所在的環境

b = a.to("cpu0")

print(b.device)

 

c = b.to("gpu0")

print(c.device)

輸出:

cpu0:0

gpu0:0

GPU CPU 切換

MegEngine 在 GPU 和 CPU 同時存在時默認使用 GPU 進行訓練。用戶可以調用 set_default_device() 來根據自身需求設置默認計算設備。

如下代碼設置默認設備為 CPU:

import megengine as mge

 

# 默認使用 CPU

mge.set_default_device('cpux')

如下代碼設置默認設備為GPU:

# 默認使用 GPU

mge.set_default_device('gpux')

如果不想修改代碼,用戶也可通過環境變量 MGE_DEFAULT_DEVICE 來設置默認計算設備:

# 默認使用 CPU

export MGE_DEFAULT_DEVICE='cpux'

 

# 默認使用 GPU

export MGE_DEFAULT_DEVICE='gpux'

算子(Operator

MegEngine 中通過算子 (Operator) 來表示運算。 類似於 NumPy,MegEngine 中的算子支持基於 Tensor 的常見數學運算和操作。 下面介紹幾個簡單示例:

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() 的參數允許存在單個維度的缺省值,用 -1 表示。此時,reshape 會自動推理該維度的值:

# 原始維度是 (2, 3),當給出 -1 的缺省維度值時,可以推理出另一維度為 6

a = a.reshape(1, -1)

print(a.shape)

輸出:

(1, 6)

MegEngine 的 functional 提供了更多的算子,比如深度學習中常用的矩陣乘操作、卷積操作等。

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(c)

輸出:

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)

更多算子可以參見 functional 部分的文檔。

求導器(Grad Manager

神經網絡的優化,通常通過隨機梯度下降來進行。需要根據計算圖的輸出,通過鏈式求導法則,對所有的中間數據節點求梯度,這一過程被稱之為 “反向傳播”。 例如,為了得到圖1中 yy 關於輸入 ww 的梯度,反向傳播的過程如下圖所示:

 

 

 圖2

首先 y=p+by=p+b ,因此 ∂y/∂p=1∂y/∂p=1 ; 接着,反向追溯,p=w∗xp=w∗x ,因此,∂p/∂w=x∂p/∂w=x 。 根據鏈式求導法則,∂y/∂w=(∂y/∂p)∗(∂p/∂w)∂y/∂w=(∂y/∂p)∗(∂p/∂w) , 因此最終 yy 關於輸入 ww 的梯度為 xx 。

MegEngine 為計算圖中的張量提供了自動求導功能,以上圖的例子說明: 假設圖中的 xx 是 shape 為 (1, 3) 的張量, ww 是 shape 為 (3, 1) 的張量, bb 是一個標量。 利用MegEngine 計算 y=x∗w+by=x∗w+b 的過程如下:

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])   # 新建一個求導器,綁定需要求導的變量

with gm:                            # 開始記錄計算圖

    p = F.matmul(x, w)

    y = p + b

    gm.backward(y)                  # 計算 y 的導數

 

print(w.grad)

print(b.grad)

輸出:

Tensor([[1.]

[3.]

[5.]], device=xpux:0)

 

Tensor([1.], device=xpux:0)

可以看到,求出的梯度本身也是 Tensor。

with 代碼段中的前向運算都會被求導器記錄。可以用 record() 和 release() 來替代 with,分別控制求導器的開啟和關閉(不推薦),代碼如下所示。

gm = GradManager().attach([w, b])   # 新建一個求導器,綁定需要求導的變量

gm.record()                         # 開始記錄計算圖

 

p = F.matmul(x, w)

y = p + b

 

gm.backward(y)                      # 計算 y 的導數

gm.release()                        # 停止記錄計算圖並釋放資源

此外,可以使用 detach 方法,把 Tensor 當作一個常量,這樣求導器將不會對其求導。如下所示:

gm = GradManager().attach([w, b])   # 新建一個求導器,綁定需要求導的變量

with gm:                            # 開始記錄計算圖

    p = F.matmul(x, w)

    y = p + b.detach()              # 停止對 b 求導

    gm.backward(y)                  # 計算 y 的導數

 

print(b.grad)

輸出:

None


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM