引言
本著“凡我不能創造的,我就不能理解”的思想,本系列文章會基于純Python以及NumPy從零創建自己的深度學習框架,該框架類似PyTorch能實作自動求導,
要深入理解深度學習,從零開始創建的經驗非常重要,從自己可以理解的角度出發,盡量不適用外部完備的框架前提下,實作我們想要的模型,本系列文章的宗旨就是通過這樣的程序,讓大家切實掌握深度學習底層實作,而不是僅做一個調包俠,
本系列文章首發于微信公眾號:JavaNLP

本文基于前面介紹的計算圖知識,開始實作我們自己的深度學習框架,
就像PyTorch用Tensor來表示張量一樣,我們也創建一個自己的Tensor,
資料型別
由于我們自己的Tensor也需要進行矩陣運算,因此我們直接封裝最常用的矩陣運算工具——NumPy,
首先,我們增加幫助函式來確保用到的資料型別為np.ndarray,
# 默認資料型別
_type = np.float32
# 可以轉換為Numpy陣列的型別
Arrayable = Union[float, list, np.ndarray]
def ensure_array(arrayable: Arrayable) -> np.ndarray:
"""
:param arrayable:
:return:
"""
if isinstance(arrayable, np.ndarray):
# 如果本身是ndarray
return arrayable
# 轉換為Numpy陣列
return np.array(arrayable, dtype=_type)
Tensor初探
所有的代碼都盡量添加型別提示(Typing),已增加代碼的可讀性,接下來,創建我們自己的Tensor實作:
class Tensor:
def __init__(self, data: Arrayable, requires_grad: bool = False) -> None:
'''
初始化Tensor物件
Args:
data: 資料
requires_grad: 是否需要計算梯度
'''
# data 是 np.ndarray
self._data = ensure_array(data)
self.requires_grad = requires_grad
# 保存該Tensor的梯度
self._grad = None
if self.requires_grad:
# 初始化梯度
self.zero_grad()
# 用于計算圖的內部變數
self._ctx = None
呼叫ensure_array確保傳過來的是一個Numpy陣列,requires_grad表示是否需要計算梯度,
下面增加一些屬性方法(屬于上面Tensor類):
@property
def grad(self):
return self._grad
@property
def data(self) -> np.ndarray:
return self._data
@data.setter
def data(self, new_data: np.ndarray) -> None:
self._data = ensure_array(new_data)
# 重新賦值后就沒有梯度了
self._grad = None
通過@property來確保梯度是只讀的,同時讓保存的資料data是可讀可寫的,當修改data時,需要清空梯度,因為系結的資料已經發生了變化,
我們知道Tensor作為張量,它是有形狀(shape)、維度(dimension)等相關屬性的,下面我們就來實作:
# ****一些常用屬性****
@property
def shape(self) -> Tuple:
'''回傳Tensor各維度大小的元素'''
return self.data.shape
@property
def ndim(self) -> int:
'''回傳Tensor的維度個數'''
return self.data.ndim
@property
def dtype(self) -> np.dtype:
'''回傳Tensor中資料的型別'''
return self.data.dtype
@property
def size(self) -> int:
'''
回傳Tensor中元素的個數 等同于np.prod(a.shape)
Returns:
'''
return self.data.size
在Tensor的初始化方法中,有進行梯度初始化的方法,看一下是如何實作的:
def zero_grad(self) -> None:
'''
將梯度初始化為0
Returns:
'''
self._grad = Tensor(np.zeros_like(self.data, dtype=_type))
為了方便除錯,我們實作了了__repr__方法,同時實作__len_魔法方法,回傳資料的長度,
def __repr__(self) -> str:
return f"Tensor({self.data}, requires_grad={self.requires_grad})"
def __len__(self) -> int:
return len(self.data)
最后,實作兩個比較有用的方法,
def assign(self, x) -> "Tensor":
'''將x的值賦予當前Tensor'''
x = ensure_tensor(x)
# 維度必須一致
assert x.shape == self.shape
self.data = x.data
return self
def numpy(self) -> np.ndarray:
"""轉換為Numpy陣列"""
return self.data
assign用于給當前Tensor賦值,因為我們上面讓data是只讀了,所以需要額外提供這個方法,
numpy則是將當前Tensor物件轉換為Numpy陣列,
類似ensure_array,我們也提供了一個確保為Tensor的幫助方法,
Tensorable = Union["Tensor", float, np.ndarray]
def ensure_tensor(tensoralbe: Tensorable) -> "Tensor":
'''
確保是Tensor物件
'''
if isinstance(tensoralbe, Tensor):
return tensoralbe
return Tensor(tensoralbe)
測驗
寫完代碼進行測驗是一個好習慣,我們今天暫且在__main__里面測驗:
if __name__ == '__main__':
t = Tensor(range(10))
print(t)
print(t.shape)
print(t.size)
print(t.dtype)
輸出:
Tensor([0. 1. 2. 3. 4. 5. 6. 7. 8. 9.], requires_grad=False)
(10,)
10
float32
完整代碼
完整代碼筆者上傳到了程式員最大交友網站上去了,地址: 👉 https://github.com/nlp-greyfoss/metagrad
總結
本文我們實作了Tensor物件的基本框架,下篇文章就會學習如何實作基本的反向傳播,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/389050.html
標籤:AI
下一篇:腦腫瘤識別,畢設找上門來了
