機器學習工具包(PyTorch/TensorFlow)通常都具備自動微分(Automatic Differentiation)機制,微分求解方法包括手動求解法(Manual Differentiation)、數值微分法(Numerical Differentiation)、符號微法(Symbolic Differentiation)、自動微分法(Automatic Differentiation),具體的詳細介紹能夠參見自動微分(Automatic Differentiation)簡介,這裏主要說一下自動微分法的實現。git
github地址:https://github.com/tiandiweizun/autodiffgithub
git上有很多自動微分的實現,如autograd等,這裏還有一個特別簡單的AutodiffEngine更適合做爲教程,但AutodiffEngine是靜態圖,整個過程對於初學者仍是有點複雜的,主要是不直觀,因而動手autodiff寫了一個簡單的動態圖的求導,裏面的大部分算子的實現仍是參照AutodiffEngine的。算法
設計:其實主要是2個類,一個類Tensor用於保存數據,另外一個類OP支持forward和backward,而後各類具體的運算類,如加減乘除等繼承OP,而後實現具體的forward和backward過程微信
過程:分爲forward和backward兩個過程,forward從前日後計算獲得最終的輸出,並返回新的tensor(以下圖中的v1),新的tensor保存經過哪些子tensor(v-1),哪一個具體的算子(ln)計算獲得的(計算圖),backward按照計算圖計算梯度,並賦值給對應的子tensor(v-1)app
實現:機器學習
先貼一點代碼ide
class Tensor: def __init__(self, data, from_tensors=None, op=None, grad=None): self.data = data # 數據 self.from_tensors = from_tensors # 是從什麼Tensor獲得的,保存計算圖的歷史 self.op = op # 操做符運算 # 梯度 if grad: self.grad = grad else: self.grad = numpy.zeros(self.data.shape) if isinstance(self.data, numpy.ndarray) else 0 def __add__(self, other): # 先判斷other是不是常數,而後再調用 return add.forward([self, other]) if isinstance(other, Tensor) else add_with_const.forward([self, other]) def backward(self, grad=None): # 判斷y的梯度是否存在,若是不存在初始化和y.data同樣類型的1的數據 if grad is None: self.grad = grad = numpy.ones(self.data.shape) if isinstance(self.data, numpy.ndarray) else 1 # 若是op不存在,則說明該Tensor爲根節點,其from_tensors也必然不存在,不然計算梯度 if self.op: grad = self.op.backward(self.from_tensors, grad) if self.from_tensors: for i in range(len(grad)): tensor = self.from_tensors[i] # 把梯度加給對應的子Tensor,由於該Tensor可能參與多個運算 tensor.grad += grad[i] # 子Tensor進行後向過程 tensor.backward(grad[i]) # 清空梯度,訓練的時候,每一個batch應該清空梯度 def zero_gard(self): self.grad = numpy.zeros(self.data.shape) if isinstance(self.data, numpy.ndarray) else 0
class OP: def forward(self, from_tensors): pass def backward(self, from_tensors, grad): pass class Add(OP): def forward(self, from_tensors): return Tensor(from_tensors[0].data + from_tensors[1].data, from_tensors, self) def backward(self, from_tensors, grad): return [grad, grad] add = Add()
這裏以加法爲例,講一下具體的實現。工具
Tensor類有四個屬性,分別用於保存數據、子Tensor、操做符、梯度,OP類有兩個方法,分別是forward和backword,其中Add類繼承OP,實現了具體的forward和backword過程,而後Tensor重載了加法運算,若是是兩個Tensor相加,則調用Add內部的forward。學習
x1_val = 2 * np.ones(3) x2_val = 3 * np.ones(3) x1 = Tensor(x1_val) x2 = Tensor(x2_val) # x1+x2 調用了Add的forward方法,並用[5,5,5]、x1與x二、加法操做構造新的Tensor,而後賦值給y y = x1 + x2 assert np.array_equal(y.data, x1_val + x2_val)
backward過程先是計算梯度,而後把梯度賦值給各個子Tensorspa
# 判斷梯度是否存在,此時不存在則初始化爲[1,1,1] # 調用Add的backward計算獲得梯度[[1,1,1],[1,1,1]] # 把梯度累加給對應的子Tensor,並調用x1和x2的backward # 因爲此時梯度存在,則不須要初始化 # 因爲x1和x2無op和from_tensors,中止並退出 y.backward() assert np.array_equal(x1.grad, np.ones_like(x1_val)) assert np.array_equal(x2.grad, np.ones_like(x2_val))
add_with_const和其餘運算符參見代碼
利用現有的自動求導來訓練一個線性迴歸模型,絕大部分代碼來自於AutodiffEngine裏面的lr_autodiff.py,其中gen_2d_data方法用於生成數據,每一個樣例有3維,其中第一維是bias,test_accuracy判斷sigmoid(w*x)是否大於0.5來決定分類的類別,並與 y進行對比計算準確率。
我這裏僅修改了auto_diff_lr方法,去掉了靜態圖裏面的邏輯,並換成Tensor來封裝。
下圖爲訓練日誌和訓練結果
關於AINLP
AINLP 是一個有趣有AI的天然語言處理社區,專一於 AI、NLP、機器學習、深度學習、推薦算法等相關技術的分享,主題包括文本摘要、智能問答、聊天機器人、機器翻譯、自動生成、知識圖譜、預訓練模型、推薦系統、計算廣告、招聘信息、求職經驗分享等,歡迎關注!加技術交流羣請添加AINLP君微信(id:AINLP2),備註工做/研究方向+加羣目的。