自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

只知道TF和PyTorch還不夠,快來(lái)看看怎么從PyTorch轉(zhuǎn)向自動(dòng)微分神器JAX

開(kāi)發(fā) 開(kāi)發(fā)工具 深度學(xué)習(xí)
說(shuō)到當(dāng)前的深度學(xué)習(xí)框架,我們往往繞不開(kāi) TensorFlow 和 PyTorch。本文是一個(gè)教程貼,教你理解 Jax 的底層邏輯,讓你更輕松地從 PyTorch 等進(jìn)行遷移。

說(shuō)到當(dāng)前的深度學(xué)習(xí)框架,我們往往繞不開(kāi) TensorFlow 和 PyTorch。但除了這兩個(gè)框架,一些新生力量也不容小覷,其中之一便是 JAX。它具有正向和反向自動(dòng)微分功能,非常擅長(zhǎng)計(jì)算高階導(dǎo)數(shù)。這一嶄露頭角的框架究竟有多好用?怎樣用它來(lái)展示神經(jīng)網(wǎng)絡(luò)內(nèi)部復(fù)雜的梯度更新和反向傳播?本文是一個(gè)教程貼,教你理解 Jax 的底層邏輯,讓你更輕松地從 PyTorch 等進(jìn)行遷移。

[[326161]]

 

Jax 是谷歌開(kāi)發(fā)的一個(gè) Python 庫(kù),用于機(jī)器學(xué)習(xí)和數(shù)學(xué)計(jì)算。一經(jīng)推出,Jax 便將其定義為一個(gè) Python+NumPy 的程序包。它有著可以進(jìn)行微分、向量化,在 TPU 和 GPU 上采用 JIT 語(yǔ)言等特性。簡(jiǎn)而言之,這就是 GPU 版本的 numpy,還可以進(jìn)行自動(dòng)微分。甚至一些研究者,如 Skye Wanderman-Milne,在去年的 NeurlPS 2019 大會(huì)上就介紹了 Jax。

但是,要讓開(kāi)發(fā)者從已經(jīng)很熟悉的 PyTorch 或 TensorFlow 2.X 轉(zhuǎn)移到 Jax 上,無(wú)疑是一個(gè)很大的改變:這兩者在構(gòu)建計(jì)算和反向傳播的方式上有著本質(zhì)的不同。PyTorch 構(gòu)建一個(gè)計(jì)算圖,并計(jì)算前向和反向傳播過(guò)程。結(jié)果節(jié)點(diǎn)上的梯度是由中間節(jié)點(diǎn)的梯度累計(jì)而成的。

Jax 則不同,它讓你用 Python 函數(shù)來(lái)表達(dá)計(jì)算過(guò)程,并用 grad( ) 將其轉(zhuǎn)換為一個(gè)梯度函數(shù),從而讓你能夠進(jìn)行評(píng)價(jià)。但是它并不給出結(jié)果,而是給出結(jié)果的梯度。兩者的對(duì)比如下所示:

這樣一來(lái),你進(jìn)行編程和構(gòu)建模型的方式就不一樣了。所以你可以使用 tape-based 的自動(dòng)微分方法,并使用有狀態(tài)的對(duì)象。但是 Jax 可能讓你感到很吃驚,因?yàn)檫\(yùn)行 grad() 函數(shù)的時(shí)候,它讓微分過(guò)程如同函數(shù)一樣。

也許你已經(jīng)決定看看如 flax、trax 或 haiku 這些基于 Jax 的工具。在看 ResNet 等例子時(shí),你會(huì)發(fā)現(xiàn)它和其他框架中的代碼不一樣。除了定義層、運(yùn)行訓(xùn)練外,底層的邏輯是什么樣的?這些小小的 numpy 程序是如何訓(xùn)練了一個(gè)巨大的架構(gòu)?

本文便是介紹 Jax 構(gòu)建模型的教程,機(jī)器之心節(jié)選了其中的兩個(gè)部分:

  • 快速回顧 PyTorch 上的 LSTM-LM 應(yīng)用;
  • 看看 PyTorch 風(fēng)格的代碼(基于 mutate 狀態(tài)),并了解純函數(shù)是如何構(gòu)建模型的(Jax);

PyTorch 上的 LSTM 語(yǔ)言模型

我們首先用 PyTorch 實(shí)現(xiàn) LSTM 語(yǔ)言模型,如下為代碼:

  1. import torch 
  2. class LSTMCell(torch.nn.Module):  
  3.     def __init__(self, in_dim, out_dim):  
  4.         super(LSTMCell, self).__init__()  
  5.         self.weight_ih = torch.nn.Parameter(torch.rand(4*out_dim, in_dim))  
  6.         self.weight_hh = torch.nn.Parameter(torch.rand(4*out_dim, out_dim))  
  7.         self.bias = torch.nn.Parameter(torch.zeros(4*out_dim,))   
  8.  
  9.     def forward(self, inputs, h, c):  
  10.         ifgo = self.weight_ih @ inputs + self.weight_hh @ h + self.bias  
  11.         i, f, g, o = torch.chunk(ifgo, 4)  
  12.         i = torch.sigmoid(i)  
  13.         f = torch.sigmoid(f)  
  14.         g = torch.tanh(g)  
  15.         o = torch.sigmoid(o)  
  16.         new_c = f * c + i * g  
  17.         new_h = o * torch.tanh(new_c)  
  18.         return (new_h, new_c) 

然后,我們基于這個(gè) LSTM 神經(jīng)元構(gòu)建一個(gè)單層的網(wǎng)絡(luò)。這里會(huì)有一個(gè)嵌入層,它和可學(xué)習(xí)的 (h,c)0 會(huì)展示單個(gè)參數(shù)如何改變。

  1. class LSTMLM(torch.nn.Module):  
  2.     def __init__(self, vocab_size, dim=17):  
  3.         super().__init__()  
  4.         self.cell = LSTMCell(dim, dim)  
  5.         self.embeddings = torch.nn.Parameter(torch.rand(vocab_size, dim))  
  6.         self.c_0 = torch.nn.Parameter(torch.zeros(dim)) 
  7.  
  8.     @property  
  9.     def hc_0(self):  
  10.         return (torch.tanh(self.c_0), self.c_0) 
  11.  
  12.     def forward(self, seq, hc):  
  13.          loss = torch.tensor(0.)  
  14.           for idx in seq:  
  15.               loss -torch.log_softmax(self.embeddings @ hc[0], dim=-1)[idx]  
  16.               hc = self.cell(self.embeddings[idx,:], *hc)  
  17.           return loss, hc   
  18.  
  19.     def greedy_argmax(self, hc, length=6):  
  20.         with torch.no_grad():  
  21.             idxs = []  
  22.             for i in range(length):  
  23.                 idx = torch.argmax(self.embeddings @ hc[0])  
  24.                 idxs.append(idx.item())  
  25.                 hc = self.cell(self.embeddings[idx,:], *hc)  
  26.         return idxs 

構(gòu)建后,進(jìn)行訓(xùn)練:

  1. torch.manual_seed(0) 
  2. # As training data, we will have indices of words/wordpieces/characters, 
  3. # we just assume they are tokenized and integerized (toy example obviously). 
  4. import jax.numpy as jnp 
  5. vocab_size = 43 # prime trick! :) 
  6. training_data = jnp.array([4, 8, 15, 16, 23, 42]) 
  7.  
  8. lm = LSTMLM(vocab_sizevocab_size=vocab_size) 
  9. print("Sample before:", lm.greedy_argmax(lm.hc_0)) 
  10.  
  11. bptt_length = 3 # to illustrate hc.detach-ing 
  12.  
  13. for epoch in range(101):  
  14.     hc = lm.hc_0  
  15.     totalloss = 0.  
  16.     for start in range(0, len(training_data), bptt_length):  
  17.         batch = training_data[start:start+bptt_length]  
  18.         loss, (h, c) = lm(batch, hc)  
  19.         hc = (h.detach(), c.detach())  
  20.         if epoch % 50 == 0:  
  21.             totalloss += loss.item()  
  22.         loss.backward()  
  23.         for name, param in lm.named_parameters():  
  24.             if param.grad is not None:  
  25.                 param.data -0.1 * param.grad  
  26.                 del param.grad  
  27.      if totalloss:  
  28.          print("Loss:", totalloss) 
  29.           
  30. print("Sample after:", lm.greedy_argmax(lm.hc_0)) 
  31. Sample before: [42, 34, 34, 34, 34, 34] 
  32. Loss: 25.953862190246582 
  33. Loss: 3.7642268538475037 
  34. Loss: 1.9537211656570435 
  35. Sample after: [4, 8, 15, 16, 23, 42] 

可以看到,PyTorch 的代碼已經(jīng)比較清楚了,但是還是有些問(wèn)題。盡管我非常注意,但是還是要關(guān)注計(jì)算圖中的節(jié)點(diǎn)數(shù)量。那些中間節(jié)點(diǎn)需要在正確的時(shí)間被清除。

純函數(shù)

為了理解 JAX 如何處理這一問(wèn)題,我們首先需要理解純函數(shù)的概念。如果你之前做過(guò)函數(shù)式編程,那你可能對(duì)以下概念比較熟悉:純函數(shù)就像數(shù)學(xué)中的函數(shù)或公式。它定義了如何從某些輸入值獲得輸出值。重要的是,它沒(méi)有「副作用」,即函數(shù)的任何部分都不會(huì)訪問(wèn)或改變?nèi)魏稳譅顟B(tài)。

我們?cè)?Pytorch 中寫代碼時(shí)充滿了中間變量或狀態(tài),而且這些狀態(tài)經(jīng)常會(huì)改變,這使得推理和優(yōu)化工作變得非常棘手。因此,JAX 選擇將程序員限制在純函數(shù)的范圍內(nèi),不讓上述情況發(fā)生。

在深入了解 JAX 之前,可以先看幾個(gè)純函數(shù)的例子。純函數(shù)必須滿足以下條件:

  • 你在什么情況下執(zhí)行函數(shù)、何時(shí)執(zhí)行函數(shù)應(yīng)該不影響輸出——只要輸入不變,輸出也應(yīng)該不變;
  • 無(wú)論我們將函數(shù)執(zhí)行了 0 次、1 次還是多次,事后應(yīng)該都是無(wú)法辨別的。

以下非純函數(shù)都至少違背了上述條件中的一條:

  1. import random 
  2. import time 
  3. nr_executions = 0 
  4.  
  5. def pure_fn_1(x):  
  6.     return 2 * x 
  7.      
  8. def pure_fn_2(xs):  
  9.     ys = []  
  10.     for x in xs:  
  11.         # Mutating stateful variables *inside* the function is fine!  
  12.         ys.append(2 * x)  
  13.     return ys 
  14.  
  15. def impure_fn_1(xs):  
  16.     # Mutating arguments has lasting consequences outside the function! :(  
  17.     xs.append(sum(xs))  
  18.     return xs 
  19.  
  20. def impure_fn_2(x):  
  21.     # Very obviously mutating  
  22.     global state is bad... global  
  23.     nr_executions nr_executions += 1  
  24.     return 2 * x 
  25.  
  26. def impure_fn_3(x):  
  27.     # ...but just accessing it is, too, because now the function depends on the  
  28.     # execution context!  
  29.     return nr_executions * x 
  30.  
  31. def impure_fn_4(x):  
  32.     # Things like IO are classic examples of impurity.  
  33.     # All three of the following lines are violations of purity:  
  34.     print("Hello!")  
  35.     user_input = input()  
  36.     execution_time = time.time()  
  37.     return 2 * x 
  38.  
  39. def impure_fn_5(x):  
  40.     # Which constraint does this violate? Both, actually! You access the current  
  41.     # state of randomness *and* advance the number generator!  
  42.     p = random.random()  
  43.     return p * x 
  44. Let's see a pure function that JAX operates on: the example from the intro figure. 
  45.  
  46. # (almost) 1-D linear regression 
  47. def f(w, x):  
  48.     return w * x 
  49.  
  50. print(f(13., 42.)) 
  51. 546.0 

目前為止還沒(méi)有出現(xiàn)什么狀況。JAX 現(xiàn)在允許你將下列函數(shù)轉(zhuǎn)換為另一個(gè)函數(shù),不是返回結(jié)果,而是返回函數(shù)結(jié)果針對(duì)函數(shù)第一個(gè)參數(shù)的梯度。

  1. import jax 
  2. import jax.numpy as jnp 
  3.  
  4. # Gradient: with respect to weights! JAX uses the first argument by default. 
  5. df_dw = jax.grad(f) 
  6.  
  7. def manual_df_dw(w, x):  
  8.     return x 
  9.      
  10. assert df_dw(13., 42.) == manual_df_dw(13., 42.) 
  11.  
  12. print(df_dw(13., 42.)) 
  13. 42.0 

到目前為止,前面的所有內(nèi)容你大概都在 JAX 的 README 文檔見(jiàn)過(guò),內(nèi)容也很合理。但怎么跳轉(zhuǎn)到類似 PyTorch 代碼里的那種大模塊呢?

首先,我們來(lái)添加一個(gè)偏置項(xiàng),并嘗試將一維線性回歸變量包裝成一個(gè)我們習(xí)慣使用的對(duì)象——一種線性回歸「層」(LinearRegressor「layer」):

  1. class LinearRegressor():  
  2.     def __init__(self, w, b):  
  3.     self.w = w  
  4.     self.b = b  
  5.      
  6.     def predict(self, x):  
  7.         return self.w * x + self.b  
  8.          
  9.     def rms(self, xs: jnp.ndarray, ys: jnp.ndarray):  
  10.         return jnp.sqrt(jnp.sum(jnp.square(self.w * xs + self.b - ys))) 
  11.          
  12. my_regressor = LinearRegressor(13., 0.) 
  13.  
  14. # A kind of loss fuction, used for training 
  15. xs = jnp.array([42.0]) 
  16. ys = jnp.array([500.0]) 
  17. print(my_regressor.rms(xs, ys)) 
  18.  
  19. # Prediction for test data 
  20. print(my_regressor.predict(42.)) 
  21. 46.0 
  22. 546.0 

接下來(lái)要怎么利用梯度進(jìn)行訓(xùn)練呢?我們需要一個(gè)純函數(shù),它將我們的模型權(quán)重作為函數(shù)的輸入?yún)?shù),可能會(huì)像這樣:

  1. def loss_fn(w, b, xs, ys):  
  2.     my_regressor = LinearRegressor(w, b)  
  3.     return my_regressor.rms(xsxs=xs, ysys=ys) 
  4.      
  5. # We use argnums=(0, 1) to tell JAX to give us 
  6. # gradients wrt first and second parameter. 
  7. grad_fn = jax.grad(loss_fn, argnums=(0, 1)) 
  8.  
  9. print(loss_fn(13., 0., xs, ys)) 
  10. print(grad_fn(13., 0., xs, ys)) 
  11. 46.0 
  12. (DeviceArray(42., dtype=float32), DeviceArray(1., dtype=float32)) 

你要說(shuō)服自己這是對(duì)的?,F(xiàn)在,這是可行的,但顯然,在 loss_fn 的定義部分枚舉所有參數(shù)是不可行的。

幸運(yùn)的是,JAX 不僅可以對(duì)標(biāo)量、向量、矩陣進(jìn)行微分,還能對(duì)許多類似樹(shù)的數(shù)據(jù)結(jié)構(gòu)進(jìn)行微分。這種結(jié)構(gòu)被稱為 pytree,包括 python dicts:

  1. def loss_fn(params, xs, ys):  
  2.     my_regressor = LinearRegressor(params['w'], params['b'])  
  3.     return my_regressor.rms(xsxs=xs, ysys=ys) 
  4.  
  5. grad_fn = jax.grad(loss_fn) 
  6.  
  7. print(loss_fn({'w': 13., 'b': 0.}, xs, ys)) 
  8. print(grad_fn({'w': 13., 'b': 0.}, xs, ys)) 
  9. 46.0 
  10. {'b': DeviceArray(1., dtype=float32), 'w': DeviceArray(42., dtype=float32)}So this already looks nicer! We could write a training loop like this: 

現(xiàn)在看起來(lái)好多了!我們可以寫一個(gè)下面這樣的訓(xùn)練循環(huán):

  1. params = {'w': 13., 'b': 0.} 
  2.  
  3. for _ in range(15):  
  4.     print(loss_fn(params, xs, ys))  
  5.     grads = grad_fn(params, xs, ys)  
  6.     for name in params.keys():  
  7.         params[name] -0.002 * grads[name] 
  8.          
  9. # Now, predict: 
  10. LinearRegressor(params['w'], params['b']).predict(42.) 
  11. 46.0 
  12. 42.47003 
  13. 38.940002 
  14. 35.410034 
  15. 31.880066 
  16. 28.350098 
  17. 24.820068 
  18. 21.2901 
  19. 17.760132 
  20. 14.230164 
  21. 10.700165 
  22. 7.170166 
  23. 3.6401978 
  24. 0.110198975 
  25. 3.4197998 
  26. DeviceArray(500.1102, dtype=float32

注意,現(xiàn)在已經(jīng)可以使用更多的 JAX helper 來(lái)進(jìn)行自我更新:由于參數(shù)和梯度擁有共同的(類似樹(shù)的)結(jié)構(gòu),我們可以想象將它們置于頂端,創(chuàng)造一個(gè)新樹(shù),其值在任何地方都是這兩個(gè)樹(shù)的「組合」,如下所示:

  1. def update_combiner(param, grad, lr=0.002):  
  2.     return param - lr * grad 
  3.      
  4. params = jax.tree_multimap(update_combiner, params, grads) 
  5. # instead of: 
  6. # for name in params.keys(): 
  7. # params[name] -0.1 * grads[name] 

參考鏈接:https://sjmielke.com/jax-purify.htm

【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號(hào)“機(jī)器之心( id: almosthuman2014)”】 

戳這里,看該作者更多好文

 

責(zé)任編輯:趙寧寧 來(lái)源: 51CTO專欄
相關(guān)推薦

2022-01-21 08:21:02

Web 安全前端程序員

2024-06-03 00:00:06

高性能數(shù)據(jù)傳輸應(yīng)用程序

2018-03-06 09:54:48

數(shù)據(jù)庫(kù)備份恢復(fù)

2018-05-02 15:41:27

JavaScript人臉檢測(cè)圖像識(shí)別

2017-11-24 08:00:55

前端JSCSS

2020-11-24 06:00:55

PythonPython之父編程語(yǔ)言

2022-06-15 14:48:39

谷歌TensorFlowMeta

2018-04-18 17:08:45

2020-04-16 09:35:53

數(shù)據(jù)科學(xué)機(jī)器學(xué)習(xí)數(shù)據(jù)分析

2018-01-30 17:54:37

數(shù)據(jù)庫(kù)MySQLSQL Server

2021-04-19 09:23:26

數(shù)字化

2018-03-12 10:35:01

LinuxBash快捷鍵

2023-11-29 14:48:01

JAXPyTorch

2020-08-04 07:02:00

TCPIP算法

2020-07-08 15:26:24

PyTorch框架機(jī)器學(xué)習(xí)

2022-11-28 07:32:46

迭代器remove數(shù)據(jù)庫(kù)

2025-01-13 00:00:05

2022-09-21 10:40:57

TensorFlowPyTorchJAX

2021-10-22 09:00:00

Windows 11Windows微軟

2020-08-19 10:34:26

編程語(yǔ)言開(kāi)發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)