AIFramework基本概念整理
AIFramework基本概念整理
本文介紹:
? 對(duì)天元 MegEngine 框架中的 Tensor, Operator, GradManager 等基本概念有一定的了解;
? 對(duì)深度學(xué)習(xí)中的前向傳播、反向傳播和參數(shù)更新的具體過程有更加清晰的認(rèn)識(shí);
? 通過寫代碼訓(xùn)練一個(gè)線性回歸模型,對(duì)上面提到的這些概念進(jìn)行具體的實(shí)踐,加深理解。
請(qǐng)先運(yùn)行下面的代碼,檢驗(yàn)環(huán)境中是否已經(jīng)安裝好 MegEngine(訪問官網(wǎng)安裝教程):
[1]:
import megengine
print(megengine.version)
1.3.1
或者可以前往 MegStudio fork 公開項(xiàng)目,無需本地安裝,直接線上體驗(yàn)(開始學(xué)習(xí))
接下來,將學(xué)習(xí)框架中一些基本模塊的使用,先從最基礎(chǔ)的張量(Tensor)和算子(Operator)開始吧~
張量(Tensor)
真實(shí)世界中的很多非結(jié)構(gòu)化的數(shù)據(jù),如文字、圖片、音頻、視頻等,都可以表達(dá)成更容易被計(jì)算機(jī)理解的形式。
MegEngine 使用張量(Tensor)來表示數(shù)據(jù)。類似于 NumPy 中的多維數(shù)組(ndarray),張量可以是標(biāo)量、向量、矩陣或者多維數(shù)組。 在 MegEngine 中得到一個(gè) Tensor 的方式有很多:
? 可以通過 megengine.functional.tensor 的 arange(), ones() 等方法來生成 Tensor,functional 模塊會(huì)在后面介紹;
? 也可以通過 Tensor() 或 tensor() 方法,傳入 Python list 或者 ndarray 來創(chuàng)建一個(gè) Tensor
[2]:
import numpy as np
import megengine as mge # 習(xí)慣將 MegEngine 縮寫為 mge
import megengine.functional as F # 習(xí)慣將 functional 縮寫為 F
1. 生成 Python List,然后轉(zhuǎn)化為 MegEngine Tensor
py_list = range(5)
print(mge.tensor(py_list))
2. 生成 Numpy ndarray,然后轉(zhuǎn)化為 MegEngine Tensor
np_ndarray = np.arange(5).astype(“float32”)
print(mge.tensor(np_ndarray))
3. 使用 functional 模塊直接生成 MegEngine Tensor
mge_tensor = F.arange(5)
print(mge_tensor)
Tensor([0 1 2 3 4], dtype=int32, device=xpux:0)
Tensor([0. 1. 2. 3. 4.], device=xpux:0)
Tensor([0. 1. 2. 3. 4.], device=xpux:0)
通過 dtype 屬性可以獲取 Tensor 的數(shù)據(jù)類型,默認(rèn)為 float32:
? 為了方便,統(tǒng)一使用 NumPy 的 dtype 表示;
? 使用 type() 可以獲取實(shí)際的類型,用來區(qū)分 ndarray 和 Tensor
[3]:
print(mge_tensor.dtype)
print(type(mge_tensor))
<class ‘numpy.float32’>
<class ‘megengine.tensor.Tensor’>
通過 astype() 方法可以拷貝創(chuàng)建一個(gè)指定數(shù)據(jù)類型的新 Tensor ,原 Tensor 不變:
? MegEngine Tensor 目前不支持轉(zhuǎn)化成 float64 類型;
? float64 類型的 ndarray 轉(zhuǎn)化成 Tensor 時(shí)會(huì)變成 float32 類型
[4]:
new_tensor = mge_tensor.astype(“float16”)
print(new_tensor)
Tensor([0. 1. 2. 3. 4.], dtype=float16, device=xpux:0)
通過 device 屬性,可以獲取 Tensor 當(dāng)前所在的設(shè)備:
? 一般地,如果在創(chuàng)建 Tensor 時(shí)不指定 device,其 device 屬性默認(rèn)為 xpux,表示當(dāng)前任意一個(gè)可用的設(shè)備;
? 在 GPU 和 CPU 同時(shí)存在時(shí),MegEngine 將自動(dòng)使用 GPU 作為默認(rèn)設(shè)備進(jìn)行訓(xùn)練 .
? 如果需要查詢 Tensor 所在設(shè)備,可以使用 Tensor.device ;
? 如果需要改變 Tensor 所在設(shè)備,可以使用 Tensor.to 或 functional.copy .
通過 Tensor 自帶的 numpy() 方法,可以拷貝 Tensor 并轉(zhuǎn)化對(duì)應(yīng)的 ndarray,原 Tensor 不變::
[5]:
print(type(mge_tensor.numpy()))
<class ‘numpy.ndarray’>
可以發(fā)現(xiàn),不同類型之間的轉(zhuǎn)化比較靈活,但需要注意:
? MegEngine Tensor 沒有 mge.numpy(mge_tensor) 這種用法;
? tensor 是 Tensor 的一個(gè)別名(Alias),也可以嘗試直接這樣導(dǎo)入:
[6]:
from megengine import tensor
from megengine import Tensor
print(tensor([1, 2, 3])) # 實(shí)際上更希望使用 float32 類型的 Tensor
print(Tensor([1., 2., 3.])) # 因此會(huì)習(xí)慣性地加上一個(gè)點(diǎn)表示這是浮點(diǎn)數(shù)
Tensor([1 2 3], dtype=int32, device=xpux:0)
Tensor([1. 2. 3.], device=xpux:0)
通過 shape 屬性,可以獲取 Tensor 的形狀:
[7]:
matrix_tensor = mge.tensor([[1., 2., 3.],
[4., 5., 6.]])
print(matrix_tensor.shape)
(2, 3)
通過 size 屬性,可以獲取 Tensor 中元素的個(gè)數(shù):
[8]:
print(matrix_tensor.size) # 2 * 3 = 6
6
通過 item() 方法,可以獲得對(duì)應(yīng)的 Python 標(biāo)量對(duì)象:
[9]:
a = mge.tensor([[5.]]) # 可以多維,但必須確保其中只有一個(gè)元素,即 size 為 1
print(a.item(), type(a.item()))
5.0 <class ‘float’>
算子(Operator)
MegEngine 中通過算子 (Operator) 來表示運(yùn)算。MegEngine 中的算子支持基于 Tensor 的常見數(shù)學(xué)運(yùn)算和操作。 比如 Tensor 的元素間(Element-wise)加法、減法和乘法:
[10]:
A = mge.tensor([[2., 4., 2.],
[2., 4., 2.]])
B = mge.tensor([[1., 2., 1.],
[1., 2., 1.]])
print(A + B)
print(A - B)
print(A * B)
print(A / B)
Tensor([[3. 6. 3.]
[3. 6. 3.]], device=xpux:0)
Tensor([[1. 2. 1.]
[1. 2. 1.]], device=xpux:0)
Tensor([[2. 8. 2.]
[2. 8. 2.]], device=xpux:0)
Tensor([[2. 2. 2.]
[2. 2. 2.]], device=xpux:0)
也可以使用 MegEngine 中的 functional 模塊中的各種方法來完成對(duì)應(yīng)計(jì)算:
[11]:
import megengine.functional as F
print(F.add(A, B))
print(F.sub(A, B))
print(F.mul(A, B))
print(F.div(A, B))
Tensor([[3. 6. 3.]
[3. 6. 3.]], device=xpux:0)
Tensor([[1. 2. 1.]
[1. 2. 1.]], device=xpux:0)
Tensor([[2. 8. 2.]
[2. 8. 2.]], device=xpux:0)
Tensor([[2. 2. 2.]
[2. 2. 2.]], device=xpux:0)
Tensor 支持 Python 中常見的切片(Slicing)操作:
[12]:
A = mge.tensor([[1., 2., 3.],
[4., 5., 6.]])
print(A[1, :2])
Tensor([4. 5.], device=xpux:0)
使用 reshape() 方法,可以得到修改形狀后的 Tensor:
[13]:
A = mge.tensor([[1., 2., 3.],
[4., 5., 6.]])
print(A.shape)
A = A.reshape(3, 2)
print(A.shape)
(2, 3)
(3, 2)
另外, reshape() 方法的參數(shù)允許存在單個(gè)維度的缺省值,用 -1 表示。
此時(shí),reshape() 會(huì)自動(dòng)推理該維度的值:
[14]:
A = mge.tensor(np.random.random((2, 3, 4, 5)))
print(A.shape)
A = A.reshape(2, -1, 5)
print(A.shape)
(2, 3, 4, 5)
(2, 12, 5)
在 functional 模塊中提供了更多的算子,比如 Tensor 的矩陣乘可以使用 matmul() 方法:
[15]:
x = mge.tensor([[1., 3., 5.],
[2., 4., 6.]])
w = mge.tensor([[1., 2.],
[3., 4.],
[5., 6.]])
p = F.matmul(x, w)
print§
Tensor([[35. 44.]
[44. 56.]], device=xpux:0)
可以使用 NumPy 的矩陣乘來驗(yàn)證一下這個(gè)結(jié)果:
[16]:
import numpy as np
x = np.array([[1., 3., 5.],
[2., 4., 6.]])
w = np.array([[1., 2.],
[3., 4.],
[5., 6.]])
p = np.matmul(x, w)
print§
[[35. 44.]
[44. 56.]]
更多算子可以參考 functional 模塊的 文檔 部分。
現(xiàn)在可以適當(dāng)休息一下,腦海中回想一下張量(Tensor)和算子(Operator)的概念,然后繼續(xù)閱讀教程后面的部分。
計(jì)算圖(Computing Graph)
MegEngine 是基于計(jì)算圖(Computing Graph)的深度神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)框架,下面通過一個(gè)簡單的數(shù)學(xué)表達(dá)式 y=(w?x)+b 來介紹計(jì)算圖的基本概念,如下圖所示:
計(jì)算之間的各種流程依賴關(guān)系可以構(gòu)成一張計(jì)算圖,從中可以看到,計(jì)算圖中存在:
? 數(shù)據(jù)節(jié)點(diǎn)(圖中的實(shí)心圈):如輸入數(shù)據(jù) x、參數(shù) w 和 b,運(yùn)算得到的中間數(shù)據(jù) p,以及最終的輸出 y;
? 計(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)之間的依賴關(guān)系
在深度學(xué)習(xí)領(lǐng)域,任何復(fù)雜的深度神經(jīng)網(wǎng)絡(luò)模型本質(zhì)上都可以用一個(gè)計(jì)算圖表示出來。
MegEngine 用張量(Tensor)表示計(jì)算圖中的數(shù)據(jù)節(jié)點(diǎn),用算子(Operator)實(shí)現(xiàn)數(shù)據(jù)節(jié)點(diǎn)之間的運(yùn)算。
可以理解成,神經(jīng)網(wǎng)絡(luò)模型的訓(xùn)練其實(shí)就是在重復(fù)以下過程:
? 前向傳播:計(jì)算由計(jì)算圖表示的數(shù)學(xué)表達(dá)式的值的過程。在上圖中則是:
? 輸入 x 和參數(shù) w 首先經(jīng)過乘法運(yùn)算得到中間結(jié)果 p,
? 接著 p 和參數(shù) b 經(jīng)過加法運(yùn)算,得到右側(cè)最終的輸出 y,這就是一個(gè)完整的前向傳播過程。
? 反向傳播:根據(jù)需要優(yōu)化的目標(biāo)(假設(shè)這里就為 y),通過鏈?zhǔn)角髮?dǎo)法則,對(duì)所有的參數(shù)求梯度。在上圖中,即計(jì)算 ?y?w 和 ?y?b.
? 參數(shù)更新:得到梯度后,需要使用梯度下降法(Gradient Descent)對(duì)參數(shù)做更新,從而達(dá)到模型優(yōu)化的效果。在上圖中,即對(duì) w 和 b 做更新。
模型訓(xùn)練完成后便可用于測試(或者說推理),此時(shí)不需要再對(duì)模型本身做任何更改,只需要將數(shù)據(jù)經(jīng)過前向傳播得到對(duì)應(yīng)的輸出即可。
鏈?zhǔn)椒▌t計(jì)算梯度
例如,為了得到上圖中 y 關(guān)于參數(shù) w 的梯度,反向傳播的過程如下圖所示:
? 首先 y=p+b,因此 ?y?p=1
? 接著,反向追溯,p=w?x ,因此,?p?w=x
? 根據(jù)鏈?zhǔn)角髮?dǎo)法則,?y?w=?y?p??p?w=1?x
? 因此最終 y 關(guān)于參數(shù) w 的梯度為 x.
求導(dǎo)器(GradManager)
推導(dǎo)梯度是件枯燥的事情,尤其是當(dāng)模型的前向傳播計(jì)算輸出的過程變得相對(duì)復(fù)雜時(shí),根據(jù)鏈?zhǔn)椒▌t計(jì)算梯度會(huì)變得異常枯燥無味。 自動(dòng)求導(dǎo)是深度學(xué)習(xí)框架對(duì)使用者而言最有用的特性之一,它自動(dòng)地完成了反向傳播過程中根據(jù)鏈?zhǔn)椒▌t去推導(dǎo)參數(shù)梯度的過程。
MegEngine 的 autodiff 模塊為計(jì)算圖中的張量提供了自動(dòng)求導(dǎo)功能,繼續(xù)以上圖的例子進(jìn)行說明:
[17]:
from megengine.autodiff import GradManager
w = mge.tensor([3.])
x = mge.tensor([2.])
b = mge.tensor(-1.)
gm = GradManager().attach([w, b]) # 新建一個(gè)求導(dǎo)器,綁定需要求導(dǎo)的變量,實(shí)例通常習(xí)慣寫成 gm
with gm: # 開始記錄計(jì)算圖
p = F.mul(w, x)
y = p + b
gm.backward(y) # 計(jì)算 y 關(guān)于參數(shù)的導(dǎo)數(shù),過程中不斷地使用鏈?zhǔn)椒▌t
print(w.grad) # 得到結(jié)果為 x
print(b.grad) # 得到結(jié)果為 1
Tensor([2.], device=xpux:0)
Tensor(1.0, device=xpux:0)
? 可以看到,求出的梯度本身也是 Tensor,GradManager 負(fù)責(zé)管理和計(jì)算梯度(在默認(rèn)情況下,Tensor 是不需要計(jì)算梯度的)。
? 可以使用 attach() 來綁定需要計(jì)算梯度的變量(綁定后可使用 detach() 將其取消綁定),使用 backward() 進(jìn)行梯度的計(jì)算。
上面 with 代碼段中的前向運(yùn)算都會(huì)被求導(dǎo)器記錄,有關(guān)求導(dǎo)器的原理,可以查看 GradManager 文檔了解細(xì)節(jié)。
優(yōu)化器(Optimizer)
應(yīng)該注意到了,使用參數(shù)(Parameter)來稱呼張量(Tensor)w 和 b, 因?yàn)榕c輸入 x 不同,計(jì)算圖中的 w 和 b 是需要進(jìn)行更新/優(yōu)化的變量。MegEngine 中使用 Parameter 來表示參數(shù)(注意沒有小寫形式),Parameter 是 Tensor 的子類,其對(duì)象(即網(wǎng)絡(luò)參數(shù))可以被優(yōu)化器更新。
顯然,GradManager 支持對(duì)于 Parameter 的梯度計(jì)算:
[18]:
w = mge.Parameter([3.])
x = mge.Tensor([2.])
b = mge.Parameter(-1.)
print(type(w))
print(type(b))
gm = GradManager().attach([w, b]) # 這次 attach() 傳入的是 Parameter 而不是 Tensor
with gm:
p = F.mul(w, x)
y = p + b
gm.backward(y)
print(type(w.grad)) # 計(jì)算得到的梯度依然是 Tensor
<class ‘megengine.tensor.Parameter’>
<class ‘megengine.tensor.Parameter’>
<class ‘megengine.tensor.Tensor’>
前向傳播和反向傳播的過程完成后,得到了參數(shù)對(duì)應(yīng)需要更新的梯度,如 w 相對(duì)于輸出 y 的梯度 w.grad.
根據(jù)梯度下降的思想,參數(shù) w 的更新規(guī)則為:w = w - lr * w.grad, 其中 lr 是學(xué)習(xí)率(Learning Rate),控制參數(shù)更新速度。
P.S: 類似學(xué)習(xí)率這種,訓(xùn)練前人為進(jìn)行設(shè)定的,而非由模型學(xué)得的參數(shù),通常被稱為超參數(shù)(Hyperparameter)。
MegEngine 的 Optimizer 模塊提供了基于各種常見優(yōu)化策略的優(yōu)化器,如 Adam 和 SGD 等。
它們都繼承自 Optimizer 基類,主要包含參數(shù)梯度的清空 clear_grad() 和參數(shù)更新 step() 這兩個(gè)方法:
[19]:
import megengine.optimizer as optim # 習(xí)慣將 optimizer 縮寫為 optim
optimizer = optim.SGD([w, b], lr=0.01) # 實(shí)例化隨機(jī)梯度下降(SGD)優(yōu)化器,傳入 Parameter w 和 b
optimizer.step() # 更新參數(shù)值 w = w - lr * w.grad
optimizer.clear_grad() # 將參數(shù)的梯度清空,節(jié)省內(nèi)存,以便下一次計(jì)算,w.grad 變?yōu)?None
print(w, w.grad)
Parameter([2.98], device=xpux:0) None
提示:多次實(shí)踐表明,用戶經(jīng)常忘記在更新參數(shù)后做梯度清空操作,因此推薦使用這樣的寫法:optimizer.step().clear_grad()
使用 Numpy 來手動(dòng)模擬一次參數(shù) w 的更新過程:
[20]:
w = np.array([3.])
dw = np.array([2.]) # 前面已經(jīng)計(jì)算出了 w.grad = x, 這里用 dw 表示
lr = 0.01
w = w - lr * dw
print(w) # 和 optimizer.step() 更新后得到的 w 應(yīng)該一致
[2.98]
這樣便成功地進(jìn)行了一次參數(shù)更新,在實(shí)際訓(xùn)練模型時(shí),參數(shù)的更新會(huì)迭代進(jìn)行很多次,迭代次數(shù) epochs 也是一種超參數(shù),需要人為設(shè)定。
損失函數(shù)(Loss Function)
深度神經(jīng)網(wǎng)絡(luò)模型的優(yōu)化過程,實(shí)際上就是使用梯度下降算法來優(yōu)化一個(gè)目標(biāo)函數(shù),從而更新網(wǎng)絡(luò)模型中的參數(shù)。
但請(qǐng)注意,上面用于舉例的表達(dá)式的輸出值其實(shí)并不是需要被優(yōu)化的對(duì)象,目標(biāo)是:模型預(yù)測的輸出結(jié)果和真實(shí)標(biāo)簽盡可能一致。
已經(jīng)知道了,通過前向傳播可以得到模型預(yù)測的輸出,此時(shí)用 損失函數(shù)(Loss Function) 來度量模型輸出與真實(shí)結(jié)果之間的差距。
MegEngine 的 functional 模塊提供了各種常見的損失函數(shù),具體可見 文檔 中的 loss 部分。
對(duì)于 w?x+b 這樣的范圍在實(shí)數(shù)域 R 上的輸出,可以使用均方誤差(Mean Squared Error, MSE)表示模型輸出 ypred 和實(shí)際值 yreal 的差距:
?(ypred,yreal)=1n∑i=1n(y^i?yi)2
注:在上面的公式中 (y^i?yi)2 計(jì)算的是單個(gè)樣本 xi 輸入模型后得到的輸出 y^i 和實(shí)際標(biāo)簽值 yi 的差異,數(shù)據(jù)集中有 n 個(gè)樣本。
[21]:
import megengine.functional as F
pred = np.array([3, 3, 3, 3]).astype(np.float32)
real = np.array([2, 8, 6, 1]).astype(np.float32)
loss = np.mean((pred - real) ** 2) # 根據(jù)公式定義計(jì)算
print(loss)
loss = F.loss.square_loss(pred, real) # MegEngine 中的實(shí)現(xiàn)
print(loss)
9.75
9.75
選定損失函數(shù)作為優(yōu)化目標(biāo)后,便可在訓(xùn)練的過程中通過梯度下降不斷地更新參數(shù) w 和 b, 從而達(dá)到模型優(yōu)化的效果:
w?,b?=argminw,b?(w,b).
練習(xí):線性回歸
接下來,用一個(gè)非常簡單的例子,幫助將前面提到的概念給聯(lián)系起來。
假設(shè)有人提供給一些包含數(shù)據(jù) data 和標(biāo)簽 label 的樣本集合 S 用于訓(xùn)練模型,希望將來給出輸入 x, 模型能對(duì)輸出 y 進(jìn)行較好地預(yù)測:
data=[x1,x2,…,xn]label=[y1,y2,…,yn]S={(x1,y1),(x2,y2),…(xn,yn)}
請(qǐng)運(yùn)行下面的代碼以隨機(jī)生成包含 data 和 label 的樣本:
[22]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
def generate_random_examples(n=100, noise=5):
w = np.random.randint(5, 10)
b = np.random.randint(-10, 10)
print("The real w: {}, b: {}".format(w, b))# 初始化 data 和 label
data = np.zeros((n,))
label = np.zeros((n,))# 生成 n 個(gè)隨機(jī)樣本數(shù)據(jù),并添加一定的噪聲干擾
for i in range(n):data[i] = np.random.uniform(-10, 10)label[i] = w * data[i] + b + np.random.uniform(-noise, noise)plt.scatter(data[i], label[i], marker=".") # 將樣本點(diǎn)繪制在坐標(biāo)圖上# 展示樣本數(shù)據(jù)的分布
plt.plot()
plt.show()
return data, label
original_data, original_label = generate_random_examples()
The real w: 9, b: 0
通過可視化觀察樣本的分布規(guī)律,不難發(fā)現(xiàn),可以:
- 嘗試擬合 y=w?x+b 這樣一個(gè)線性模型(均為標(biāo)量);
- 選擇使用均方誤差損失作為優(yōu)化目標(biāo);
- 通過梯度下降法來更新參數(shù) w 和 b.
Numpy 實(shí)現(xiàn)
對(duì)于這種非常簡單的模型,完全可以使用 Numpy 進(jìn)行算法實(shí)現(xiàn),借此了解一下整個(gè)模型訓(xùn)練的流程:
[23]:
設(shè)置超參數(shù)
epochs = 5
lr = 0.01
獲取數(shù)據(jù)
data = original_data
label = original_label
n = len(data)
參數(shù)初始化
w = 0
b = 0
定義模型
def linear_model(x):
return w * x + b
模型訓(xùn)練
for epoch in range(epochs):
# 初始化單個(gè) epoch 訓(xùn)練得到的損失
loss = 0# 梯度初始化,等同于 Optimizer 中的 clear_grad()
sum_grad_w = 0
sum_grad_b = 0# 為了方便理解,這里沒有使用 “向量化實(shí)現(xiàn)”,而是使用了 for 循環(huán)寫法
for i in range(n):# 前向傳播,主要計(jì)算預(yù)測值 pred 以及損失值 losspred = linear_model(data[i]) # 通常 pred 為將 data 代入 “模型” 得到的輸出,即 model(data)loss += (pred - label[i]) ** 2 # 等同于對(duì)每個(gè)樣本使用 F.nn.loss(pred, label) 計(jì)算后求和# 反向傳播,根據(jù)均方誤差損失計(jì)算參數(shù)的梯度,這里不解釋推導(dǎo)過程,等同于 gm.backward()sum_grad_w += 2 * (pred - label[i]) * data[i]sum_grad_b += 2 * (pred - label[i])# 計(jì)算平均損失,因?yàn)閷?duì)于不同的輸入,求得的損失都會(huì)不同,所以通常求和后取平均
loss = loss / n# 計(jì)算平均梯度,因?yàn)閷?duì)于不同的輸入,求得的梯度都會(huì)不同,所以通常求和后取平均
grad_w = sum_grad_w / n
grad_b = sum_grad_b / n# 更新參數(shù),等同于 Optimizer 中的 step()
w = w - lr * grad_w
b = b - lr * grad_b# 查看參數(shù)和損失
print("epoch = {}, w = {:.3f}, b = {:.3f}, loss = {:.3f}".format(epoch, w, b, loss))# 繪圖查看擬合情況
x = np.array([-10, 10])
y = w * x + b
plt.scatter(data, label, marker=".")
plt.plot(x, y, "-b")
plt.show()
epoch = 0, w = 5.965, b = -0.004, loss = 2702.561
epoch = 1, w = 7.992, b = -0.007, loss = 318.817
epoch = 2, w = 8.680, b = -0.009, loss = 43.688
epoch = 3, w = 8.914, b = -0.011, loss = 11.933
epoch = 4, w = 8.994, b = -0.012, loss = 8.268
可以看到,在 5 個(gè) epoch 的迭代訓(xùn)練中,已經(jīng)得到了一個(gè)擬合狀況不錯(cuò)的線性模型。
MegEngine 實(shí)現(xiàn)
上面的流程,完全可以使用 MegEngine 來實(shí)現(xiàn)(有興趣的讀者可以參照上面的注釋,先嘗試自己實(shí)現(xiàn)):
[24]:
import megengine as mge
import megengine.functional as F
from megengine.autodiff import GradManager
import megengine.optimizer as optim
設(shè)置超參數(shù)
epochs = 5
lr = 0.01
獲取數(shù)據(jù)
data = mge.tensor(original_data)
label = mge.tensor(original_label)
初始化參數(shù)
w = mge.Parameter([0.0])
b = mge.Parameter([0.0])
定義模型
def linear_model(x):
return F.mul(w, x) + b
定義求導(dǎo)器和優(yōu)化器
gm = GradManager().attach([w, b])
optimizer = optim.SGD([w, b], lr=lr)
模型訓(xùn)練
for epoch in range(epochs):
with gm:
pred = linear_model(data)
loss = F.loss.square_loss(pred, label)
gm.backward(loss)
optimizer.step().clear_grad()
print("epoch = {}, w = {:.3f}, b = {:.3f}, loss = {:.3f}".format(epoch, w.item(), b.item(), loss.item()))
epoch = 0, w = 5.965, b = -0.004, loss = 2702.561
epoch = 1, w = 7.992, b = -0.007, loss = 318.817
epoch = 2, w = 8.680, b = -0.009, loss = 43.688
epoch = 3, w = 8.914, b = -0.011, loss = 11.933
epoch = 4, w = 8.994, b = -0.012, loss = 8.268
應(yīng)該會(huì)得到相同的 w, b 以及 loss 值,下面直線的擬合程度也應(yīng)該和 Numpy 實(shí)現(xiàn)一致:
[25]:
繪圖查看擬合情況
x = np.array([-10, 10])
y = w.numpy() * x + b.numpy()
plt.scatter(data, label, marker=".")
plt.plot(x, y, “-b”)
plt.show()
總結(jié)回顧
祝賀完成了入門教程的學(xué)習(xí),現(xiàn)在是時(shí)候休息一下,做一個(gè)簡單的回顧了:
到目前為止,已經(jīng)掌握了天元 MegEngine 框架中的以下概念:
? 計(jì)算圖(Computing Graph):MegEngine 是基于計(jì)算圖的框架,計(jì)算圖中存在數(shù)據(jù)節(jié)點(diǎn)、計(jì)算節(jié)點(diǎn)和邊
? 前向傳播:輸入的數(shù)據(jù)在計(jì)算圖中經(jīng)過計(jì)算得到預(yù)測值,接著使用損失 loss 表示預(yù)測值和實(shí)際值的差異
? 反向傳播:根據(jù)鏈?zhǔn)椒▌t,得到計(jì)算圖中所有參數(shù) w 關(guān)于 loss 的梯度 dw ,實(shí)現(xiàn)在 autodiff 模塊,由 GradManager 進(jìn)行管理
? 參數(shù)更新:根據(jù)梯度下降算法,更新圖中參數(shù),從而達(dá)到優(yōu)化最終 loss 的效果,實(shí)現(xiàn)在 optimzer 模塊
? 張量(Tensor):MegEngine 中的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),用來表示計(jì)算圖中的數(shù)據(jù)節(jié)點(diǎn),可以靈活地與 Numpy 數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化
? 參數(shù)(Parameter):用于和張量做概念上的區(qū)分,模型優(yōu)化的過程實(shí)際上就是優(yōu)化器對(duì)參數(shù)進(jìn)行了更新
? 超參數(shù)(Hype-parameter):其值無法由模型直接經(jīng)過訓(xùn)練學(xué)得,需要人為(或通過其它方法)設(shè)定
? 算子(Operator):基于 Tensor 的各種計(jì)算的實(shí)現(xiàn)(包括損失函數(shù)),實(shí)現(xiàn)在 functional 模塊
通過擬合 f(x)=w?x+b 完成了一個(gè)最簡單的線性回歸模型的訓(xùn)練,干得漂亮!
問題思考
MegEngine 打怪升級(jí)之旅還沒有結(jié)束,在前往下一關(guān)之前,嘗試思考一些問題吧。
關(guān)于向量化實(shí)現(xiàn):
? 當(dāng)發(fā)現(xiàn) Python 代碼運(yùn)行較慢時(shí),通常可以將數(shù)據(jù)處理移入 NumPy 并采用向量化(Vectorization)寫法,實(shí)現(xiàn)最高速度的處理
? 線性模型訓(xùn)練的 NumPy 寫法中,單個(gè) epoch 訓(xùn)練內(nèi)出現(xiàn)了 for 循環(huán),實(shí)際上可以采取向量化的實(shí)現(xiàn)(參考 MegEngine 實(shí)現(xiàn)的寫法)
? 使用向量化的實(shí)現(xiàn),通常計(jì)算的效率會(huì)更高,因此建議:代碼中能夠用向量化代替 for 循環(huán)的地方,就盡可能地使用向量化實(shí)現(xiàn)
關(guān)于設(shè)備:
? 都說 GPU 訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型速度會(huì)比 CPU 訓(xùn)練快非常多,為什么?
? 可以把 Tensor 指定計(jì)算設(shè)備為 GPU 或 CPU,而原生 NumPy 只支持 CPU 計(jì)算,Tensor 轉(zhuǎn)化為 ndarray 的過程是什么樣的?
? 訓(xùn)練的速度是否會(huì)受到訓(xùn)練設(shè)備數(shù)量的影響呢?可不可以多個(gè)設(shè)備一起進(jìn)行訓(xùn)練?
關(guān)于參數(shù)與超參數(shù):
? 現(xiàn)在接觸到了兩個(gè)超參數(shù) epochs 和 lr, 調(diào)整它們的值是否會(huì)對(duì)模型的訓(xùn)練產(chǎn)生影響?(不妨自己動(dòng)手調(diào)整試試)
? 更新參數(shù)所用的梯度 grad_w,是所有樣本的梯度之和 sum_grad_w 求均值,為什么不在每個(gè)樣本反向傳播后立即更新參數(shù) w?
? 看上去得到了一條擬合得很不錯(cuò)的曲線,但是得到的 b 距離真實(shí)的 b 還比較遙遠(yuǎn),為什么?如何解決這種情況?
? 如何選取合適的超參數(shù),對(duì)于超參數(shù)的選取是否有一定的規(guī)律或者經(jīng)驗(yàn)可尋?
關(guān)于數(shù)據(jù)集:
? 在線性模型中使用的是從 NumPy 代碼生成的隨機(jī)數(shù)據(jù),修改數(shù)據(jù)集的樣本數(shù)量 n 和噪聲擾動(dòng)程度 noise 會(huì)有什么影響?
? 對(duì)于現(xiàn)實(shí)中的數(shù)據(jù)集,如何轉(zhuǎn)換成 MegEngine Tensor 的形式進(jìn)行使用?
? 這中間需要經(jīng)過什么樣的預(yù)處理(Preprocessing)過程,有哪些流程是可以交由框架來完成的?
關(guān)于模型:
? 學(xué)會(huì)了定義了非常簡單的線性模型 linear_model, 更復(fù)雜的模型要如何去寫?
? 既然任何神經(jīng)網(wǎng)絡(luò)模型本質(zhì)上都可以用計(jì)算圖來表示,那么神經(jīng)網(wǎng)絡(luò)模型的搭建流程是什么樣的?
關(guān)于最佳實(shí)踐:
? 在編寫代碼時(shí),經(jīng)常會(huì)有根據(jù)前人經(jīng)驗(yàn)總結(jié)出的最佳實(shí)踐(Best Practice)作為參考,例如:
? 參數(shù)的更新和梯度的清空可以寫在一起 optimizer.step().clear_grad()
? 在導(dǎo)入某些包的時(shí)候,通常有約定俗成的縮寫如 import megengine as mge
? 除此以外,還有什么樣的編程習(xí)慣和最佳實(shí)踐值得參考?如何將一份玩具代碼整理變成工程化的代碼?
深度學(xué)習(xí),簡單開發(fā)。鼓勵(lì)在實(shí)踐中不斷思考,并啟發(fā)自己去探索直覺性或理論性的解釋。
總結(jié)
以上是生活随笔為你收集整理的AIFramework基本概念整理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AICompiler动态shape编译框
- 下一篇: 华为4D成像雷达、智能驾驶平台MDC 8