【深度学习DL-PyTorch】四、深度学习工具PyTorch
神经网络又称为全连接或密集网络。
一、张量(Tensor)
张量(Tensor)是向量和矩阵的泛化形式。所以,张量可以是一维、二维、三维、...六维...等等。
向量是一位的张量;
矩阵是二维的张量;
张量是PyTorch和其他神经网络框架中的基本数据结构。
特征是指神经网络的输入特征/输入数据。
torch.randn()创建由正态变量组成的张量,即来自正态分布的随机正态变量。
torch.randn_like()传入一个张量并查看该张量的形状,然后创建形状相同的另一个张量。
torch.sum()
可以使用矩阵乘法进行相同的运算,大多数情况下,都建议使用矩阵乘法。因为矩阵乘法更高效,这些线性代数运算已经使用了现代库加快了速度。推荐使用矩阵乘法,因为在 GPU 上使用现代库和高效计算资源使矩阵乘法更高效。
torch.mm()
矩阵乘法更简单,并且对传入的张量要求更严格。必须符合矩阵乘法的条件:第一个矩阵的列数,必须等于第二个矩阵的行数。能够按照预期的方式进行运算。
torch.matmul() 支持广播,如果传入大小/形状很奇怪的张量,那么可能获得意料之外的输出结果。
tensor.shape() 在构建神经网络时,最常见的错误是形状错误,所以在设计神经网络架构时很重要的步骤就是,使张量的形状保持匹配 。
tensor.reshape()创建一个张量,形状是要求的形状,但是内存中的实际数据没有改变。有时候,它会返回克隆版本,也就是说,它把数据复制到内存中的另一个部分,然后返回该内存部分存储的张量。也就是说,复制数据比直接更改张量形状(不克隆数据)效率要低。
tensor.resize_() 下划线表示resize_这个方法是原地操作(in-place operation),原地操作是指根本不改变数据,只是改变位于该内存地址中的数据对应的张量
。resize_方法的问题在于如果要求的形状比原始张量的元素多或少时,可能会丢失数据或者使用未初始化的内存创建虚假的数据。
tensor.view()
会返回一个新张量包含的数据和旧张量在内存中的一样。不论什么时候,它都只是返回一个新的张量,不会更改内存中的任何数据。如果想获得新的大小使张量具有新的形状和不同数量的元素,就会报错。使用view方法可以确保在更改张量形状时始终获得相同数量的元素。
-
weights.reshape(a, b)
有时候将返回一个新的张量,数据和weights
的一样,大小为(a, b)
;有时候返回克隆版,将数据复制到内存的另一个部分。 -
weights.resize_(a, b)
返回形状不同的相同张量。但是,如果新形状的元素数量比原始张量的少,则会从张量里删除某些元素(但是不会从内存中删除)。如果新形状的元素比原始张量的多,则新元素在内存里未初始化。注意,方法末尾的下划线表示这个方法是原地运算。要详细了解如何在 PyTorch 中进行原地运算,请参阅此论坛话题。 -
weights.view(a, b)
将返回一个张量,数据和weights
的一样,大小为(a, b)
。
torch.from_numpy()
tensor.numpy()
需要注意的是:内存会在Numpy数组和torch张量之间共享。也就是说,如果对Numpy数组或张量执行任何原地操作就会改变对方的值。
二、在PyTorch中构建神经网络
反向传播_链式法则.png如果希望神经网络能够学习非线性关系和规律,希望输出是非线性的,那么就需要在隐藏层里使用非线性激活函数。
ReLU是线性修正单元的简称,它是最简单的非线性函数,与S型函数和双曲正切函数相比,使用ReLU时网络的训练速度快多了。
2.1构建神经网络
PyTorch 提供了nn模块,大大地简化了网络构建过程。以下演示如何构建上述同一个网络,即包含 784 个输入、256 个隐藏单元、10 个输出单元和一个 softmax 输出。
from torch import nn
class Network(nn.Module):
def __init__(self):
super().__init__()
# Inputs to hidden layer linear transformation
self.hidden = nn.Linear(784, 256)
# Output layer, 10 units - one for each digit
self.output = nn.Linear(256, 10)
# Define sigmoid activation and softmax output
self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)
def forward(self, x):
# Pass the input tensor through each of our operations
x = self.hidden(x)
x = self.sigmoid(x)
x = self.output(x)
x = self.softmax(x)
return x
class Network(nn.Module):
先继承 nn.Module
。与 super().__init__()
相结合,创建一个跟踪架构的类,并提供大量有用的方法和属性。注意,在为网络创建类时,必须继承 nn.Module
。类可以随意命名。
self.hidden = nn.Linear(784, 256)
这行创建一个线性转换模块 ,其中有 784 个输入和 256 个输出,并赋值给 self.hidden
。该模块会自动创建权重和偏差张量,供我们在 forward
方法中使用。创建网络 (net
) 后,你可以使用 net.hidden.weight
和 net.hidden.bias
访问权重和偏差张量。
self.output = nn.Linear(256, 10)
同样,这里会创建另一个有 256 个输入和 10 个输出的线性转换。
self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)
然后,定义了 S 型激活函数和 softmax 输出的运算。在 nn.Softmax(dim=1)
中设置 dim=1
会计算各个列的 softmax 值。
def forward(self, x):
用 nn.Module
创建的 PyTorch 网络必须定义 forward
方法。它会接受一个张量 x
并将其传入你在 __init__
方法中定义的运算。
x = self.hidden(x)
x = self.sigmoid(x)
x = self.output(x)
x = self.softmax(x)
将输入张量 x
传入重新赋值给 x
的每个运算。可以看出输入张量经过隐藏层,然后经过 S 型函数、输出层,最终经过 softmax 函数。.变量可以命名为任何名称,只要运算的输入和输出与你要构建的网络架构匹配即可。在 __init__
方法中的定义顺序不重要,但是需要在 forward
方法中正确地设定运算顺序。
现在我们可以创建一个 Network
对象。
# Create the network and look at it's text representation
model = Network()
model
可以使用 torch.nn.functional
模块来更简练清晰地定义网络。这是最常见的网络定义方式,因为很多运算是简单的元素级函数。我们通常将此模块导入为 F
,即 import torch.nn.functional as F
。
import torch.nn.functional as F
class Network(nn.Module):
def __init__(self):
super().__init__()
# Inputs to hidden layer linear transformation
self.hidden = nn.Linear(784, 256)
# Output layer, 10 units - one for each digit
self.output = nn.Linear(256, 10)
def forward(self, x):
# Hidden layer with sigmoid activation
x = F.sigmoid(self.hidden(x))
# Output layer with softmax activation
x = F.softmax(self.output(x), dim=1)
return x
2.2 激活函数
到目前为止,我们只学习了 softmax 激活函数,但是通常任何函数都可以用作激活函数。但是,要使网络能逼近非线性函数,激活函数必须是非线性函数。下面是一些常见的激活函数示例:Tanh(双曲正切)和 ReLU(修正线性单元)。
activation.png在实践中,ReLU 几乎一直用作隐藏层激活函数。
2.3 使用 nn.Sequential
PyTorch 提供了一种方便的方法来构建这类网络(其中张量按顺序执行各种运算):nn.Sequential
(文档)。使用它来构建等效网络:
# Hyperparameters for our network
input_size = 784
hidden_sizes = [128, 64]
output_size = 10
# Build a feed-forward network
model = nn.Sequential(nn.Linear(input_size, hidden_sizes[0]),
nn.ReLU(),
nn.Linear(hidden_sizes[0], hidden_sizes[1]),
nn.ReLU(),
nn.Linear(hidden_sizes[1], output_size),
nn.Softmax(dim=1))
print(model)
# Forward pass through the network and display output
images, labels = next(iter(trainloader))
images.resize_(images.shape[0], 1, 784)
ps = model.forward(images[0,:])
helper.view_classify(images[0].view(1, 28, 28), ps)
还可以传入 OrderedDict
以命名单个层级和运算,而不是使用递增的整数。注意,因为字典键必须是唯一的,所以每个运算都必须具有不同的名称。
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
('fc1', nn.Linear(input_size, hidden_sizes[0])),
('relu1', nn.ReLU()),
('fc2', nn.Linear(hidden_sizes[0], hidden_sizes[1])),
('relu2', nn.ReLU()),
('output', nn.Linear(hidden_sizes[1], output_size)),
('softmax', nn.Softmax(dim=1))]))
model
三、训练神经网络
3.1Autograd 自动计算梯度
我们已经知道如何计算损失,那么如何使用损失进行反向传播呢?Torch 提供了模块 autograd
,用于自动计算张量的梯度。我们可以使用它计算所有参数相对于损失的梯度。Autograd 的计算方式是跟踪对张量执行的运算,然后反向经过这些运算并一路计算梯度。为了确保 PyTorch 能跟踪对张量执行的运算并计算梯度,你需要在张量上设置 requires_grad = True
。你可以在创建时使用 requires_grad
关键字或随时使用 x.requires_grad_(True)
。
可以使用 torch.no_grad()
关闭某段代码的梯度:
x = torch.zeros(1, requires_grad=True)
>>> with torch.no_grad():
... y = x * 2
>>> y.requires_grad
False
3.2损失和 Autograd
使用 PyTorch 创建网络时,所有参数都通过 requires_grad = True
初始化。这意味着,当我们计算损失和调用 loss.backward()
时,会计算参数的梯度。这些梯度用于在梯度下降步骤中更新权重。下面是使用反向传播计算梯度的示例。
# Build a feed-forward network
model = nn.Sequential(nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
nn.LogSoftmax(dim=1))
criterion = nn.NLLLoss()
images, labels = next(iter(trainloader))
images = images.view(images.shape[0], -1)
logps = model(images)
loss = criterion(logps, labels)
3.3Autograd 自动计算梯度
PyTorch训练网络 的一般流程是:
- 通过网络进行正向传递以获取logits
- 使用 logits 计算损失
- 通过
loss.backward()
对网络进行反向传递以计算梯度 - 使用优化器更新权重
model = nn.Sequential(nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 10),
nn.LogSoftmax(dim=1))
criterion = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.003)
epochs = 5
for e in range(epochs):
running_loss = 0
for images, labels in trainloader:
# Flatten MNIST images into a 784 long vector
images = images.view(images.shape[0], -1)
# TODO: Training pass
optimizer.zero_grad()
output = model.forward(images)
loss = criterion(output, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
else:
print(f"Training loss: {running_loss/len(trainloader)}")
四、训练神经网络的基本步骤
- 1.清空所有已优化变量的梯度
- 2.前向传播:通过向模型传入输入,计算预测输出。
- 3.计算损失
- 4.反向传播:计算损失相对于模型参数的梯度
- 5.执行一个优化步骤(参数更新)
- 6.更新平均训练损失
The steps for training/learning from a batch of data are described in the comments below:
- 1.Clear the gradients of all optimized variables
- 2.Forward pass: compute predicted outputs by passing inputs to the model
- 3.Calculate the loss
- 4.Backward pass: compute gradient of the loss with respect to model parameters
- 5.Perform a single optimization step (parameter update)
- 6.Update average training loss
# number of epochs to train the model
n_epochs = 30 # suggest training between 20-50 epochs
model.train() # prep model for training
for epoch in range(n_epochs):
# monitor training loss
train_loss = 0.0
###################
# train the model #
###################
for data, target in train_loader:
# clear the gradients of all optimized variables
optimizer.zero_grad()
# forward pass: compute predicted outputs by passing inputs to the model
output = model(data)
# calculate the loss
loss = criterion(output, target)
# backward pass: compute gradient of the loss with respect to model parameters
loss.backward()
# perform a single optimization step (parameter update)
optimizer.step()
# update running training loss
train_loss += loss.item()*data.size(0)
# print training statistics
# calculate average loss over an epoch
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(
epoch+1,
train_loss
))