對比PyTorch和TensorFlow的自動差異和動態(tài)模型
使用自定義模型類從頭開始訓練線性回歸,比較PyTorch 1.x和TensorFlow 2.x之間的自動差異和動態(tài)模型子類化方法,
這篇簡短的文章重點介紹如何在PyTorch 1.x和TensorFlow 2.x中分別使用帶有模塊/模型API的動態(tài)子類化模型,以及這些框架在訓練循環(huán)中如何使用AutoDiff獲得損失的梯度并從頭開始實現 一個非常幼稚的漸變后代實現。

生成噪聲的線性數據
為了專注于自動差異/自動漸變功能的核心,我們將使用最簡單的模型,即線性回歸模型,然后我們將首先使用numpy生成一些線性數據,以添加隨機級別的噪聲。
- def generate_data(m=0.1, b=0.3, n=200):
- x = np.random.uniform(-10, 10, n)
- noise = np.random.normal(0, 0.15, n)
- y = (m * x + b ) + noise return x.astype(np.float32), y.astype(np.float32)
- x, y = generate_data()plt.figure(figsize = (12,5))
- ax = plt.subplot(111)
- ax.scatter(x,y, c = "b", label="samples")

模型
然后,我們將在TF和PyTorch中實現從零開始的線性回歸模型,而無需使用任何層或激活器,而只需定義兩個張量w和b,分別代表線性模型的權重和偏差,并簡單地實現線性函數即可:y = wx + b
正如您在下面看到的,我們的模型的TF和PyTorch類定義基本上完全相同,但在一些api名稱上只有很小的差異。
唯一值得注意的區(qū)別是,PyTorch明確地使用Parameter對象定義權重和要由圖形"捕獲"的偏置張量,而TF似乎在這里更"神奇",而是自動捕獲用于圖形的參數。
確實在PyTorch參數中是Tensor子類,當與Module api一起使用時,它們具有非常特殊的屬性,可以自動將自身添加到Module參數列表中,并會出現在在parameters()迭代器中。
無論如何,兩個框架都能夠從此類定義和執(zhí)行方法(call或 forward ),參數和圖形定義中提取信息,以便向前執(zhí)行圖形執(zhí)行,并且正如我們將看到的那樣,通過自動可微分獲得梯度功能,以便能夠執(zhí)行反向傳播。
TensorFlow動態(tài)模型
- class LinearRegressionKeras(tf.keras.Model):
- def __init__(self):
- super().__init__() self.w = tf.Variable(tf.random.uniform(shape=[1], -0.1, 0.1))
- self.b = tf.Variable(tf.random.uniform(shape=[1], -0.1, 0.1))
- def __call__(self,x):
- return x * self.w + self.b
PyTorch動態(tài)模型
- class LinearRegressionPyTorch(torch.nn.Module):
- def __init__(self):
- super().__init__() self.w = torch.nn.Parameter(torch.Tensor(1, 1).uniform_(-0.1, 0.1))
- self.b = torch.nn.Parameter(torch.Tensor(1).uniform_(-0.1, 0.1))
- def forward(self, x):
- return x @ self.w + self.b
訓練循環(huán),反向傳播和優(yōu)化器
現在我們已經實現了簡單的TensorFlow和PyTorch模型,我們可以定義TF和PyTorch api來實現均方誤差的損失函數,最后實例化我們的模型類并運行訓練循環(huán)。
同樣,本著眼于自動差異/自動漸變功能核心的目的,我們將使用TF和PyTorch特定的自動差異實現方式實現自定義訓練循環(huán),以便為我們的簡單線性函數提供漸變并手動優(yōu)化權重和偏差參數以及臨時和樸素的漸變后代優(yōu)化器。
在TensorFlow訓練循環(huán)中,我們將特別明確地使用GradientTape API來記錄模型的正向執(zhí)行和損失計算,然后從該GradientTape中獲得用于優(yōu)化權重和偏差參數的梯度。
相反,在這種情況下,PyTorch提供了一種更"神奇"的自動漸變方法,隱式捕獲了對參數張量的任何操作,并為我們提供了相同的梯度以用于優(yōu)化權重和偏置參數,而無需使用任何特定的api。
一旦我們有了權重和偏差梯度,就可以在PyTorch和TensorFlow上實現我們的自定義梯度派生方法,就像將權重和偏差參數減去這些梯度乘以恒定的學習率一樣簡單。
此處的最后一個微小區(qū)別是,當PyTorch在向后傳播中更新權重和偏差參數時,以更隱蔽和"魔術"的方式實現自動差異/自動graf時,我們需要確保不要繼續(xù)讓PyTorch從最后一次更新操作中提取grad,這次明確調用no_grad api,最后將權重和bias參數的梯度歸零。
TensorFlow訓練循環(huán)
- def squared_error(y_pred, y_true):
- return tf.reduce_mean(tf.square(y_pred - y_true))
- tf_model = LinearRegressionKeras()[w, b] = tf_model.trainable_variablesfor epoch in range(epochs):
- with tf.GradientTape() as tape:
- predictions = tf_model(x) loss = squared_error(predictions, y) w_grad, b_grad = tape.gradient(loss, tf_model.trainable_variables) w.assign(w - w_grad * learning_rate) b.assign(b - b_grad * learning_rate) if epoch % 20 == 0:
- print(f"Epoch {epoch} : Loss {loss.numpy()}")
PyTorch訓練循環(huán)
- def squared_error(y_pred, y_true):
- return torch.mean(torch.square(y_pred - y_true))
- torch_model = LinearRegressionPyTorch()[w, b] = torch_model.parameters()for epoch in range(epochs):
- y_pred = torch_model(inputs) loss = squared_error(y_pred, labels) loss.backward() with torch.no_grad():
- w -= w.grad * learning_rate b -= b.grad * learning_rate w.grad.zero_() b.grad.zero_() if epoch % 20 == 0:
- print(f"Epoch {epoch} : Loss {loss.data}")
結論
正如我們所看到的,TensorFlow和PyTorch自動區(qū)分和動態(tài)子分類API非常相似,當然,兩種模型的訓練也給我們非常相似的結果。
在下面的代碼片段中,我們將分別使用Tensorflow和PyTorch trainable_variables和parameters方法來訪問模型參數并繪制學習到的線性函數的圖。
繪制結果
- [w_tf, b_tf] = tf_model.trainable_variables
- [w_torch, b_torch] = torch_model.parameters()with torch.no_grad(): plt.figure(figsize = (12,5))
- ax = plt.subplot(111)
- ax.scatter(x, y, c = "b", label="samples")
- ax.plot(x, w_tf * x + b_tf, "r", 5.0, "tensorflow")
- ax.plot(x, w_torch * inputs + b_torch, "c", 5.0, "pytorch")
- ax.legend() plt.xlabel("x1")
- plt.ylabel("y",rotation = 0)
