PyTorch全连接ReLU网络
PyTorch全連接ReLU網絡
1.PyTorch的核心是兩個主要特征:
? 一個n維張量,類似于numpy,但可以在GPU上運行
? 搭建和訓練神經網絡時的自動微分/求導機制
本文將使用全連接的ReLU網絡作為運行示例。該網絡將有一個單一的隱藏層,并將使用梯度下降訓練,通過最小化網絡輸出和真正結果的歐幾里得距離,來擬合隨機生成的數據。
2.張量
2.1 熱身: Numpy
在介紹PyTorch之前,將首先使用numpy實現網絡。 Numpy提供了一個n維數組對象,以及許多用于操作這些數組的函數。Numpy是用于科學計算的通用框架; 對計算圖、深度學習和梯度一無所知。然而,可以很容易地使用NumPy,手動實現網絡的前向和反向傳播,來擬合隨機數據:
-- coding: utf-8 --
import numpy as np
N是批量大小; D_in是輸入維度;
49/5000 H是隱藏的維度; D_out是輸出維度。
N, D_in, H, D_out = 64, 1000, 100, 10
創建隨機輸入和輸出數據
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)
隨機初始化權重
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)
learning_rate = 1e-6
for t in range(500):
# 前向傳遞:計算預測值y
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
# 計算和打印損失loss
loss = np.square(y_pred - y).sum()
print(t, loss)# 反向傳播,計算w1和w2對loss的梯度
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.T.dot(grad_y_pred)
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)# 更新權重
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
2.2 PyTorch:張量
Numpy是一個很棒的框架,但不能利用GPU來加速其數值計算。 對于現代深度神經網絡,GPU通常提供50倍或更高的加速,所以,numpy不能滿足當代深度學習的需求。
在這里,先介紹最基本的PyTorch概念:
張量(Tensor):PyTorch的tensor在概念上與numpy的array相同: tensor是一個n維數組,PyTorch提供了許多函數,用于操作這些張量。任何希望使用NumPy執行的計算,也可以使用PyTorch的tensor來完成,可以認為是科學計算的通用工具。
與Numpy不同,PyTorch可以利用GPU加速其數值計算。要在GPU上運行Tensor, 在構造張量使用device參數把tensor建立在GPU上。
本文使用tensors將隨機數據上訓練一個兩層的網絡。和前面NumPy的例子類似,使用PyTorch的tensor,手動在網絡中實現前向傳播和反向傳播:
-- coding: utf-8 --
import torch
dtype = torch.float
device = torch.device(“cpu”)
device = torch.device(“cuda:0”)#取消注釋以在GPU上運行
N是批量大小; D_in是輸入維度;
H是隱藏的維度; D_out是輸出維度。
N, D_in, H, D_out = 64, 1000, 100, 10
#創建隨機輸入和輸出數據
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
隨機初始化權重
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)
learning_rate = 1e-6
for t in range(500):
# 前向傳遞:計算預測y
h = x.mm(w1)
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)
# 計算和打印損失
loss = (y_pred - y).pow(2).sum().item()
print(t, loss)# Backprop計算w1和w2相對于損耗的梯度
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)# 使用梯度下降更新權重
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
3.自動求導
3.1 PyTorch:張量和自動求導
在上面的例子中,需要手動實現神經網絡的前向和后向傳遞。手動實現反向傳遞,對于小型雙層網絡來說并不是什么大問 題,但對于大型復雜網絡來說,很快就會變得非常繁瑣。
但是,可以使用自動微分,來自動計算神經網絡中的后向傳遞。 PyTorch中的 autograd包提供了這個功能。當使用autograd時,網絡前向傳播將定義一個計算圖;圖中的節點是tensor,邊是函數, 這些函數是輸出tensor到輸入tensor的映射。這張計算圖使得在網絡中反向傳播時,梯度的計算十分簡單。
聽起來很復雜,在實踐中使用起來非常簡單。 如果想計算某些的tensor的梯度,只需要在建立這個tensor時,加入這么一句:requires_grad=True。這個tensor上的任何PyTorch的操作都將構造一個計算圖,從而允許稍后在圖中執行反向傳播。如果這個tensor x的requires_grad=True,那么反向傳播之后x.grad將會是另一個張量,其為x關于某個標量值的梯度。
有時,可能希望防止PyTorch在requires_grad=True的張量執行某些操作時,構建計算圖;例如,在訓練神經網絡時,通常不希望通過權重更新步驟進行反向傳播。在這種情況下,可以使用torch.no_grad()上下文管理器來防止構造計算圖。
下面使用PyTorch的Tensors和autograd來實現的兩層的神經網絡;不再需要手動執行網絡的反向傳播:
-- coding: utf-8 --
import torch
dtype = torch.float
device = torch.device(“cpu”)
device = torch.device(“cuda:0”)#取消注釋以在GPU上運行
N是批量大小; D_in是輸入維度;
H是隱藏的維度; D_out是輸出維度。
N, D_in, H, D_out = 64, 1000, 100, 10
創建隨機Tensors以保持輸入和輸出。
設置requires_grad = False表示不需要計算漸變
在向后傳球期間對于這些Tensors。
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
為權重創建隨機Tensors。
設置requires_grad = True表示想要計算漸變
在向后傳球期間尊重這些張貼。
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 前向傳播:使用tensors上的操作計算預測值y;
# 由于w1和w2有requires_grad=True,涉及這些張量的操作將讓PyTorch構建計算圖,
# 從而允許自動計算梯度。由于不再手工實現反向傳播,所以不需要保留中間值的引用。
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# 使用Tensors上的操作計算和打印丟失。
# loss是一個形狀為()的張量
# loss.item() 得到這個張量對應的python數值
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())# 使用autograd計算反向傳播。這個調用將計算loss對所有requires_grad=True的tensor的梯度。
# 這次調用后,w1.grad和w2.grad將分別是loss對w1和w2的梯度張量。
loss.backward()# 使用梯度下降更新權重。對于這一步,只想對w1和w2的值進行原地改變;不想為更新階段構建計算圖,
# 所以使用torch.no_grad()上下文管理器防止PyTorch為更新構建計算圖
with torch.no_grad():w1 -= learning_rate * w1.gradw2 -= learning_rate * w2.grad# 反向傳播后手動將梯度設置為零w1.grad.zero_()w2.grad.zero_()
3.2 PyTorch:定義新的自動求導函數
在底層,每一個原始的自動求導運算,實際上是兩個在Tensor上運行的函數。其中,forward函數計算,從輸入Tensors獲得的輸出Tensors。而backward函數接收輸出Tensors,對于某個標量值的梯度,并且計算輸入Tensors相對于該相同標量值的梯度。
在PyTorch中,可以很容易地通過定義torch.autograd.Function的子類,實現forward和backward函數,來定義自己的自動求導運算。之后,就可以使用這個新的自動梯度運算符了。然后,可以通過構造一個實例并像調用函數一樣,傳入包含輸入數據的tensor調用,這樣來使用新的自動求導運算。
這個例子中,自定義一個自動求導函數,來展示ReLU的非線性。并用實現的兩層網絡:
import torch
class MyReLU(torch.autograd.Function):
“”"
可以通過建立torch.autograd的子類來實現自定義的autograd函數,
并完成張量的正向和反向傳播。
“”"
@staticmethod
def forward(ctx, x):
“”"
在正向傳播中,接收到一個上下文對象和一個包含輸入的張量;
必須返回一個包含輸出的張量,
并且可以使用上下文對象來緩存對象,以便在反向傳播中使用。
“”"
ctx.save_for_backward(x)
return x.clamp(min=0)
@staticmethod
def backward(ctx, grad_output):"""在反向傳播中,接收到上下文對象和一個張量,其包含了相對于正向傳播過程中產生的輸出的損失的梯度。可以從上下文對象中檢索緩存的數據,并且必須計算并返回與正向傳播的輸入相關的損失的梯度。"""x, = ctx.saved_tensorsgrad_x = grad_output.clone()grad_x[x < 0] = 0return grad_x
device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)
N是批大小; D_in 是輸入維度;
H 是隱藏層維度; D_out 是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10
產生輸入和輸出的隨機張量
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
產生隨機權重的張量
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 正向傳播:使用張量上的操作來計算輸出值y;
# 通過調用 MyReLU.apply 函數來使用自定義的ReLU
y_pred = MyReLU.apply(x.mm(w1)).mm(w2)
# 計算并輸出loss
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())# 使用autograd計算反向傳播過程。
loss.backward()with torch.no_grad():# 用梯度下降更新權重w1 -= learning_rate * w1.gradw2 -= learning_rate * w2.grad# 在反向傳播之后手動清零梯度w1.grad.zero_()w2.grad.zero_()
3.3 TensorFlow:靜態圖
PyTorch自動求導看起來非常像TensorFlow:這兩個框架中,都定義計算圖,使用自動微分來計算梯度。兩者最大的不同就是TensorFlow的計算圖是靜態的,而PyTorch使用動態的計算圖。
在TensorFlow中,定義計算圖一次,然后重復執行這個相同的圖,可能會提供不同的輸入數據。而在PyTorch中,每一個前向通道定義一個新的計算圖。
靜態圖的好處在于,可以預先對圖進行優化。例如,一個框架可能要融合一些圖的運算來提升效率,或者產生一個策略來將圖分布到多個GPU或機器上。如果重復使用相同的圖,那么在重復運行同一個圖時,前期潛在的代價高昂的預先優化的消耗就會被分攤開。
靜態圖和動態圖的一個區別是控制流。對于一些模型,希望對每個數據點執行不同的計算。例如,一個遞歸神經網絡,可能對于每個數據點,執行不同的時間步數,這個展開(unrolling)可以作為一個循環來實現。對于一個靜態圖,循環結構要作為圖的一部分。因此,TensorFlow提供了運算符(例如tf.scan)來把循環嵌入到圖當中。對于動態圖來說,情況更加簡單:既然為每個例子即時創建圖,可以使用普通的命令式控制流,來為每個輸入執行不同的計算。
為了與上面的PyTorch自動梯度實例做對比,使用TensorFlow來擬合一個簡單的2層網絡:
import tensorflow as tf
import numpy as np
首先建立計算圖(computational graph)
N是批大小;D是輸入維度;
H是隱藏層維度;D_out是輸出維度。
N, D_in, H, D_out = 64, 1000, 100, 10
為輸入和目標數據創建placeholder;
當執行計算圖時,他將會被真實的數據填充
x = tf.placeholder(tf.float32, shape=(None, D_in))
y = tf.placeholder(tf.float32, shape=(None, D_out))
為權重創建Variable并用隨機數據初始化
TensorFlow的Variable在執行計算圖時不會改變
w1 = tf.Variable(tf.random_normal((D_in, H)))
w2 = tf.Variable(tf.random_normal((H, D_out)))
前向傳播:使用TensorFlow的張量運算計算預測值y。
注意這段代碼實際上不執行任何數值運算;
只是建立了稍后將執行的計算圖。
h = tf.matmul(x, w1)
h_relu = tf.maximum(h, tf.zeros(1))
y_pred = tf.matmul(h_relu, w2)
使用TensorFlow的張量運算損失(loss)
loss = tf.reduce_sum((y - y_pred) ** 2.0)
計算loss對于w1和w2的導數
grad_w1, grad_w2 = tf.gradients(loss, [w1, w2])
使用梯度下降更新權重。為了實際更新權重,需要在執行計算圖時計算new_w1和new_w2。
注意,在TensorFlow中,更新權重值的行為是計算圖的一部分;
但在PyTorch中,這發生在計算圖形之外。
learning_rate = 1e-6
new_w1 = w1.assign(w1 - learning_rate * grad_w1)
new_w2 = w2.assign(w2 - learning_rate * grad_w2)
現在搭建好了計算圖,所以開始一個TensorFlow的會話(session)來實際執行計算圖。
with tf.Session() as sess:
# 運行一次計算圖來初始化Variable w1和w2
sess.run(tf.global_variables_initializer())# 創建numpy數組來存儲輸入x和目標y的實際數據
x_value = np.random.randn(N, D_in)
y_value = np.random.randn(N, D_out)for _ in range(500):# 多次運行計算圖。每次執行時,都用feed_dict參數,# 將x_value綁定到x,將y_value綁定到y,# 每次執行圖形時都要計算損失、new_w1和new_w2;# 這些張量的值以numpy數組的形式返回。loss_value, _, _ = sess.run([loss, new_w1, new_w2], feed_dict={x: x_value, y: y_value})print(loss_value)
4.nn模塊
4.1 PyTorch:nn
計算圖和autograd是十分強大的工具,可以定義復雜的操作并自動求導;然而對于大規模的網絡,autograd太過于底層。 在構建神經網絡時,經常考慮將計算安排成層,其中一些具有可學習的參數,將在學習過程中進行優化。
TensorFlow里,有類似Keras,TensorFlow-Slim和TFLearn這種封裝了底層計算圖的高度抽象的接口,這使得構建網絡十分方便。
在PyTorch中,包nn完成了同樣的功能。nn包中定義一組大致等價于層的模塊。一個模塊接受輸入的tesnor,計算輸出的tensor,而且還保存了一些內部狀態比如需要學習的tensor的參數等。nn包中也定義了一組損失函數(loss functions),用來訓練神經網絡。
這個例子中,用nn包實現兩層的網絡:
-- coding: utf-8 --
import torch
N是批大小;D是輸入維度
H是隱藏層維度;D_out是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10
#創建輸入和輸出隨機張量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
使用nn包將的模型定義為一系列的層。
nn.Sequential是包含其他模塊的模塊,并按順序應用這些模塊來產生其輸出。
每個線性模塊使用線性函數從輸入計算輸出,并保存其內部的權重和偏差張量。
在構造模型之后,使用.to()方法將其移動到所需的設備。
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
nn包還包含常用的損失函數的定義;
在這種情況下,將使用平均平方誤差(MSE)作為的損失函數。
設置reduction=‘sum’,表示計算的是平方誤差的“和”,而不是平均值;
這是為了與前面手工計算損失的例子保持一致,
但是在實踐中,通過設置reduction='elementwise_mean’來使用均方誤差作為損失更為常見。
loss_fn = torch.nn.MSELoss(reduction=‘sum’)
learning_rate = 1e-4
for t in range(500):
# 前向傳播:通過向模型傳入x計算預測的y。
# 模塊對象重載了__call__運算符,所以可以像函數那樣調用。
# 這么做相當于向模塊傳入了一個張量,然后返回了一個輸出張量。
y_pred = model(x)
# 計算并打印損失。# 傳遞包含y的預測值和真實值的張量,損失函數返回包含損失的張量。
loss = loss_fn(y_pred, y)
print(t, loss.item())# 反向傳播之前清零梯度
model.zero_grad()# 反向傳播:計算模型的損失對所有可學習參數的導數(梯度)。
# 在內部,每個模塊的參數存儲在requires_grad=True的張量中,
# 因此這個調用將計算模型中所有可學習參數的梯度。
loss.backward()# 使用梯度下降更新權重。
# 每個參數都是張量,所以可以像以前那樣可以得到的數值和梯度
with torch.no_grad():for param in model.parameters():param -= learning_rate * param.grad
4.2 PyTorch:optim
到目前為止,已經通過手動改變,包含可學習參數的張量,來更新模型的權重。對于隨機梯度下降(SGD/stochastic gradient descent)等簡單的優化算法來說,這不是一個很大的負擔,但在實踐中,經常使用AdaGrad、RMSProp、Adam等更復雜的優化器,來訓練神經網絡。
import torch
N是批大小;D是輸入維度
H是隱藏層維度;D_out是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10
產生隨機輸入和輸出張量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
使用nn包定義模型和損失函數
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction=‘sum’)
使用optim包定義優化器(Optimizer)。Optimizer將會為更新模型的權重。
這里使用Adam優化方法;optim包還包含了許多別的優化算法。
Adam構造函數的第一個參數告訴優化器應該更新哪些張量。
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
# 前向傳播:通過像模型輸入x計算預測的y
y_pred = model(x)# 計算并打印loss
loss = loss_fn(y_pred, y)
print(t, loss.item())# 在反向傳播之前,使用optimizer將要更新的所有張量的梯度清零(這些張量是模型可學習的權重)
optimizer.zero_grad()# 反向傳播:根據模型的參數計算loss的梯度
loss.backward()# 調用Optimizer的step函數使所有參數更新
optimizer.step()
4.3 PyTorch:自定義nn模塊
有時候,需要指定比現有模塊序列更復雜的模型;對于這些情況,可以通過繼承nn.Module并定義forward函數,這個forward函數可以使用其他模塊或者其他的自動求導運算,來接收輸入tensor,產生輸出tensor。
在這個例子中,用自定義Module的子類構建兩層網絡:
import torch
class TwoLayerNet(torch.nn.Module):
def init(self, D_in, H, D_out):
“”"
在構造函數中,實例化了兩個nn.Linear模塊,并將作為成員變量。
“”"
super(TwoLayerNet, self).init()
self.linear1 = torch.nn.Linear(D_in, H)
self.linear2 = torch.nn.Linear(H, D_out)
def forward(self, x):"""在前向傳播的函數中,接收一個輸入的張量,也必須返回一個輸出張量。可以使用構造函數中定義的模塊以及張量上的任意的(可微分的)操作。"""h_relu = self.linear1(x).clamp(min=0)y_pred = self.linear2(h_relu)return y_pred
N是批大小; D_in 是輸入維度;
H 是隱藏層維度; D_out 是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10
產生輸入和輸出的隨機張量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
通過實例化上面定義的類來構建的模型。
model = TwoLayerNet(D_in, H, D_out)
構造損失函數和優化器。
SGD構造函數中對model.parameters()的調用,
將包含模型的一部分,即兩個nn.Linear模塊的可學習參數。
loss_fn = torch.nn.MSELoss(reduction=‘sum’)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
# 前向傳播:通過向模型傳遞x計算預測值y
y_pred = model(x)
#計算并輸出loss
loss = loss_fn(y_pred, y)
print(t, loss.item())# 清零梯度,反向傳播,更新權重
optimizer.zero_grad()
loss.backward()
optimizer.step()
4.4 PyTorch:控制流和權重共享
作為動態圖和權重共享的一個例子,實現了一個非常奇怪的模型:一個全連接的ReLU網絡,在每一次前向傳播時,隱藏層的層數為隨機1到4之間的數,這樣可以多次重用相同的權重來計算。
因為這個模型可以使用普通的Python流控制來實現循環,并且可以通過在定義轉發時,多次重用同一個模塊,來實現最內層之間的權重共享。
利用Mudule的子類很容易實現這個模型:
import random
import torch
class DynamicNet(torch.nn.Module):
def init(self, D_in, H, D_out):
“”"
在構造函數中,構造了三個nn.Linear實例,將在前向傳播時被使用。
“”"
super(DynamicNet, self).init()
self.input_linear = torch.nn.Linear(D_in, H)
self.middle_linear = torch.nn.Linear(H, H)
self.output_linear = torch.nn.Linear(H, D_out)
def forward(self, x):"""對于模型的前向傳播,隨機選擇0、1、2、3,并重用了多次計算隱藏層的middle_linear模塊。由于每個前向傳播構建一個動態計算圖,可以在定義模型的前向傳播時使用常規Python控制流運算符,如循環或條件語句。在這里,還看到,在定義計算圖形時多次重用同一個模塊是完全安全的。這是Lua Torch的一大改進,因為Lua Torch中每個模塊只能使用一次。"""h_relu = self.input_linear(x).clamp(min=0)for _ in range(random.randint(0, 3)):h_relu = self.middle_linear(h_relu).clamp(min=0)y_pred = self.output_linear(h_relu)return y_pred
N是批大小;D是輸入維度
H是隱藏層維度;D_out是輸出維度
N, D_in, H, D_out = 64, 1000, 100, 10
產生輸入和輸出隨機張量
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
實例化上面定義的類來構造的模型
model = DynamicNet(D_in, H, D_out)
構造的損失函數(loss function)和優化器(Optimizer)。
用平凡的隨機梯度下降訓練這個奇怪的模型是困難的,所以使用了momentum方法。
criterion = torch.nn.MSELoss(reduction=‘sum’)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
# 前向傳播:通過向模型傳入x計算預測的y。
y_pred = model(x)# 計算并打印損失
loss = criterion(y_pred, y)
print(t, loss.item())# 清零梯度,反向傳播,更新權重
optimizer.zero_grad()
loss.backward()
optimizer.step()
總結
以上是生活随笔為你收集整理的PyTorch全连接ReLU网络的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TensorFlow文本情感分析实现
- 下一篇: PyTorch迁移学习