成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

只知道TF和PyTorch還不夠,快來看看怎么從PyTorch轉向自動微分神器JAX

開發 開發工具 深度學習
說到當前的深度學習框架,我們往往繞不開 TensorFlow 和 PyTorch。本文是一個教程貼,教你理解 Jax 的底層邏輯,讓你更輕松地從 PyTorch 等進行遷移。

說到當前的深度學習框架,我們往往繞不開 TensorFlow 和 PyTorch。但除了這兩個框架,一些新生力量也不容小覷,其中之一便是 JAX。它具有正向和反向自動微分功能,非常擅長計算高階導數。這一嶄露頭角的框架究竟有多好用?怎樣用它來展示神經網絡內部復雜的梯度更新和反向傳播?本文是一個教程貼,教你理解 Jax 的底層邏輯,讓你更輕松地從 PyTorch 等進行遷移。

[[326161]]

 

Jax 是谷歌開發的一個 Python 庫,用于機器學習和數學計算。一經推出,Jax 便將其定義為一個 Python+NumPy 的程序包。它有著可以進行微分、向量化,在 TPU 和 GPU 上采用 JIT 語言等特性。簡而言之,這就是 GPU 版本的 numpy,還可以進行自動微分。甚至一些研究者,如 Skye Wanderman-Milne,在去年的 NeurlPS 2019 大會上就介紹了 Jax。

但是,要讓開發者從已經很熟悉的 PyTorch 或 TensorFlow 2.X 轉移到 Jax 上,無疑是一個很大的改變:這兩者在構建計算和反向傳播的方式上有著本質的不同。PyTorch 構建一個計算圖,并計算前向和反向傳播過程。結果節點上的梯度是由中間節點的梯度累計而成的。

Jax 則不同,它讓你用 Python 函數來表達計算過程,并用 grad( ) 將其轉換為一個梯度函數,從而讓你能夠進行評價。但是它并不給出結果,而是給出結果的梯度。兩者的對比如下所示:

這樣一來,你進行編程和構建模型的方式就不一樣了。所以你可以使用 tape-based 的自動微分方法,并使用有狀態的對象。但是 Jax 可能讓你感到很吃驚,因為運行 grad() 函數的時候,它讓微分過程如同函數一樣。

也許你已經決定看看如 flax、trax 或 haiku 這些基于 Jax 的工具。在看 ResNet 等例子時,你會發現它和其他框架中的代碼不一樣。除了定義層、運行訓練外,底層的邏輯是什么樣的?這些小小的 numpy 程序是如何訓練了一個巨大的架構?

本文便是介紹 Jax 構建模型的教程,機器之心節選了其中的兩個部分:

  • 快速回顧 PyTorch 上的 LSTM-LM 應用;
  • 看看 PyTorch 風格的代碼(基于 mutate 狀態),并了解純函數是如何構建模型的(Jax);

PyTorch 上的 LSTM 語言模型

我們首先用 PyTorch 實現 LSTM 語言模型,如下為代碼:

  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) 

然后,我們基于這個 LSTM 神經元構建一個單層的網絡。這里會有一個嵌入層,它和可學習的 (h,c)0 會展示單個參數如何改變。

  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 

構建后,進行訓練:

  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 的代碼已經比較清楚了,但是還是有些問題。盡管我非常注意,但是還是要關注計算圖中的節點數量。那些中間節點需要在正確的時間被清除。

純函數

為了理解 JAX 如何處理這一問題,我們首先需要理解純函數的概念。如果你之前做過函數式編程,那你可能對以下概念比較熟悉:純函數就像數學中的函數或公式。它定義了如何從某些輸入值獲得輸出值。重要的是,它沒有「副作用」,即函數的任何部分都不會訪問或改變任何全局狀態。

我們在 Pytorch 中寫代碼時充滿了中間變量或狀態,而且這些狀態經常會改變,這使得推理和優化工作變得非常棘手。因此,JAX 選擇將程序員限制在純函數的范圍內,不讓上述情況發生。

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

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

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

  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 

目前為止還沒有出現什么狀況。JAX 現在允許你將下列函數轉換為另一個函數,不是返回結果,而是返回函數結果針對函數第一個參數的梯度。

  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 

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

首先,我們來添加一個偏置項,并嘗試將一維線性回歸變量包裝成一個我們習慣使用的對象——一種線性回歸「層」(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 

接下來要怎么利用梯度進行訓練呢?我們需要一個純函數,它將我們的模型權重作為函數的輸入參數,可能會像這樣:

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

你要說服自己這是對的。現在,這是可行的,但顯然,在 loss_fn 的定義部分枚舉所有參數是不可行的。

幸運的是,JAX 不僅可以對標量、向量、矩陣進行微分,還能對許多類似樹的數據結構進行微分。這種結構被稱為 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: 

現在看起來好多了!我們可以寫一個下面這樣的訓練循環:

  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

注意,現在已經可以使用更多的 JAX helper 來進行自我更新:由于參數和梯度擁有共同的(類似樹的)結構,我們可以想象將它們置于頂端,創造一個新樹,其值在任何地方都是這兩個樹的「組合」,如下所示:

  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專欄機構“機器之心”的原創譯文,微信公眾號“機器之心( id: almosthuman2014)”】 

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

 

責任編輯:趙寧寧 來源: 51CTO專欄
相關推薦

2022-01-21 08:21:02

Web 安全前端程序員

2024-06-03 00:00:06

高性能數據傳輸應用程序

2018-03-06 09:54:48

數據庫備份恢復

2018-05-02 15:41:27

JavaScript人臉檢測圖像識別

2017-11-24 08:00:55

前端JSCSS

2020-11-24 06:00:55

PythonPython之父編程語言

2018-01-30 17:54:37

數據庫MySQLSQL Server

2018-03-12 10:35:01

LinuxBash快捷鍵

2021-04-19 09:23:26

數字化

2022-06-15 14:48:39

谷歌TensorFlowMeta

2020-04-16 09:35:53

數據科學機器學習數據分析

2018-04-18 17:08:45

2020-08-04 07:02:00

TCPIP算法

2020-07-08 15:26:24

PyTorch框架機器學習

2023-11-29 14:48:01

JAXPyTorch

2022-11-28 07:32:46

迭代器remove數據庫

2025-01-13 00:00:05

2017-11-14 21:00:04

Linux內核4.14新特性

2020-06-08 15:06:33

Pandas可視化數據

2025-01-13 07:10:00

前端開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: www天天操 | 亚洲视频在线观看 | 亚洲图片视频一区 | 毛片免费观看 | 夜夜骑综合 | 亚洲精品乱码久久久久久黑人 | 一区二区三区在线免费观看 | 午夜影院在线 | 理论片87福利理论电影 | 国产在线精品一区二区 | 国产精品国产三级国产aⅴ无密码 | 一区二区三区中文字幕 | 国产精品欧美精品日韩精品 | 水蜜桃久久夜色精品一区 | 天堂久久久久久久 | 久久99精品国产 | 中文字幕免费视频 | 国产精品一区二区三区在线 | 国产精品久久久久无码av | 国产精品无码久久久久 | 精品欧美一区二区三区久久久 | 中文字幕在线播放第一页 | 免费黄色av网站 | 日韩在线观看精品 | 99国内精品久久久久久久 | 日韩在线精品强乱中文字幕 | 亚洲精品永久免费 | 国产激情视频在线 | 日韩三级视频 | 国产精品日日做人人爱 | 超碰精品在线 | 欧美一区二区三区在线观看 | 黄色片视频免费 | 国产午夜精品一区二区三区嫩草 | 91精品国模一区二区三区 | 国产偷录视频叫床高潮对白 | 欧美国产精品一区二区三区 | 欧美xxxx性| 一二三四在线视频观看社区 | 久久国品片 | 在线观看亚洲精品视频 |