本教程译文的第一部分,请见我的上一篇博文:
Stanford CS224N: PyTorch Tutorial (Winter ‘21) —— 斯坦福CS224N PyTorch教程 (第一部分)_放肆荒原的博客-CSDN博客
PyTorch 运算与 NumPy 的运算非常相似。 我们可以使用标量和其他张量。
In [40]:
# Create an example tensor
# 创建一个示例张量
x = torch.ones((3,2,2))
x
Out [40]:
tensor([[[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.]]])
In [41]:
# Perform elementwise addition
# Use - for subtraction
# 执行元素级加法
# 使用 - 进行减法
x + 2
Out [41]:
tensor([[[3., 3.],
[3., 3.]],
[[3., 3.],
[3., 3.]],
[[3., 3.],
[3., 3.]]])
In [42]:
# Perform elementwise multiplication
# Use / for division
# 执行元素乘法
# 用 / 进行除法
x * 2
Out [42]:
tensor([[[2., 2.],
[2., 2.]],
[[2., 2.],
[2., 2.]],
[[2., 2.],
[2., 2.]]])
我们可以在大小兼容的不同张量之间应用相同的运算。
In [43]:
# Create a 4x3 tensor of 6s
# 创建一个全6的4x3张量
a = torch.ones((4,3)) * 6
a
Out [43]:
tensor([[6., 6., 6.],
[6., 6., 6.],
[6., 6., 6.],
[6., 6., 6.]])
In [44]:
# Create a 1D tensor of 2s
# 创建一个全2的1D张量
b = torch.ones(3) * 2
b
Out [44]:
tensor([2., 2., 2.])
In [45]:
# Divide a by b
# 把a除以b
a / b
Out [45]:
tensor([[3., 3., 3.],
[3., 3., 3.],
[3., 3., 3.],
[3., 3., 3.]])
我们可以使用 tensor.matmul(other_tensor) 进行矩阵乘法,使用 tensor.T 进行转置。 矩阵乘法也可以用@ 来执行。
In [46]:
# Alternative to a.matmul(b)
# a @ b.T returns the same result since b is 1D tensor and the 2nd dimension
# is inferred
# 可替代 a.matmul(b)
# a @ b.T 返回相同的结果,因为b是1D张量,第二维是推断的。
a @ b
Out [46]:
tensor([36., 36., 36., 36.])
In [47]:
pp.pprint(a.shape)
pp.pprint(a.T.shape)
Out [47]:
torch.Size([4, 3])
torch.Size([3, 4])
我们可以使用 mean(dim) 和 std(dim) 方法沿某个维度获取均值和标准差。 也就是说,如果我们想得到一个 4x3x2 矩阵中的平均 3x2 矩阵,我们将设置 dim 为 0。我们可以不带参数调用这些方法来获取整个张量的均值和标准差。 要使用 mean 和 std 我们的张量应该是浮点类型。
In [48]:
# Create an example tensor
# 创建一个示例张量
m = torch.tensor(
[
[1., 1.],
[2., 2.],
[3., 3.],
[4., 4.]
]
)
pp.pprint("Mean: {}".format(m.mean()))
pp.pprint("Mean in the 0th dimension: {}".format(m.mean(0)))
pp.pprint("Mean in the 1st dimension: {}".format(m.mean(1)))
Out [48]:
'Mean: 2.5'
'Mean in the 0th dimension: tensor([2.5000, 2.5000])'
'Mean in the 1st dimension: tensor([1., 2., 3., 4.])'
我们可以使用 torch.cat 连接张量。
In [49]:
# Concatenate in dimension 0 and 1
# 在维度 0 和 1 中连接
a_cat0 = torch.cat([a, a, a], dim=0)
a_cat1 = torch.cat([a, a, a], dim=1)
print("Initial shape: {}".format(a.shape))
print("Shape after concatenation in dimension 0: {}".format(a_cat0.shape))
print("Shape after concatenation in dimension 1: {}".format(a_cat1.shape))
Out [49]:
Initial shape: torch.Size([4, 3])
Shape after concatenation in dimension 0: torch.Size([12, 3])
Shape after concatenation in dimension 1: torch.Size([4, 9])
PyTorch 中的大部分操作都没有就地操作。 但是,PyTorch 通过在方法名称的末尾添加下划线 (_) 来提供可用的就地操作版本。
In [50]:
# Print our tensor
a
Out [50]:
tensor([[6., 6., 6.],
[6., 6., 6.],
[6., 6., 6.],
[6., 6., 6.]])
In [51]:
# add() is not in place
# add() 不能就地操作
a.add(a)
a
Out [51]:
tensor([[6., 6., 6.],
[6., 6., 6.],
[6., 6., 6.],
[6., 6., 6.]])
In [52]:
# add_() is in place
# add_() 是就地操作的
a.add_(a)
a
Out [52]:
tensor([[12., 12., 12.],
[12., 12., 12.],
[12., 12., 12.],
[12., 12., 12.]])
PyTorch 和其他机器学习库以其自动微分功能而闻名。 也就是说,鉴于我们已经定义了需要执行的一组操作,框架本身可以弄清楚如何计算梯度。 我们可以调用backward() 方法让PyTorch 计算梯度,然后将其存储在grad 属性中。
In [53]:
# Create an example tensor
# requires_grad parameter tells PyTorch to store gradients
# 创建一个示例张量
# requires_grad参数告诉 PyTorch 存储梯度
x = torch.tensor([2.], requires_grad=True)
# Print the gradient if it is calculated
# Currently None since x is a scalar
# 如果梯度计算出来就打印
# 目前没有,因为 x 是一个标量
pp.pprint(x.grad)
None
In [54]:
# Calculating the gradient of y with respect to x
# 计算 y 相对于 x 的梯度
y = x * x * 3 # 3x^2
y.backward()
pp.pprint(x.grad) # d(y)/d(x) = d(3x^2)/d(x) = 6x = 12
tensor([12.])
让我们再次从不同的张量运行反向传播,看看会发生什么。
In [55]:
z = x * x * 3 # 3x^2
z.backward()
pp.pprint(x.grad)
tensor([24.])
我们可以看到 x.grad 被更新为到目前为止计算的梯度的总和。 当我们在神经网络中运行反向传播时,我们会在进行更新之前汇总特定神经元的所有梯度。 这正是这里发生的事情! 这也是我们需要在每次训练迭代中运行 zero_grad() 的原因(稍后会详细介绍)。 否则我们的梯度会从一个训练迭代到另一个训练迭代不断增加,这将导致我们的更新错误。
到目前为止,我们已经研究了张量,它们的性质和对张量的基本运算。如果我们从头开始构建网络的各个层,那么熟悉这些功能尤其有用。我们将在作业3中使用这些模块,但接下来,我们将在PyTorch的torch.nn模块中使用预定义的模块。然后,我们将把这些块放在一起,创建复杂的网络。让我们先用一个别名导入这个模块,这样我们就不必在每次使用它时都键入torch。
In [56]:
import torch.nn as nn
我们可以使用 nn.Linear(H_in, H_out) 创建一个线性层。 这将采用 (N, *, H_in) 维矩阵并输出 (N, *, H_out) 矩阵。 * 表示两者之间可以有任意数量的维度。 线性层执行操作 Ax+b,其中 A 和 b 随机初始化。 如果我们不想让线性层学习偏置参数,我们可以用bias=False 来初始化我们的层。
In [57]:
# Create the inputs
# 创建输入
input = torch.ones(2,3,4)
# Make a linear layers transforming N,*,H_in dimensinal inputs to N,*,H_out
# 创建一个线性层,将 N,*,H_in 维度输入转换为 N,*,H_out
# dimensional outputs
linear = nn.Linear(4, 2)
linear_output = linear(input)
linear_output
Out [57]:
tensor([[[0.1796, 0.1423],
[0.1796, 0.1423],
[0.1796, 0.1423]],
[[0.1796, 0.1423],
[0.1796, 0.1423],
[0.1796, 0.1423]]], grad_fn=)
nn 模块中还有其他几个预配置层。 一些常用的例子是 nn.Conv2d、nn.ConvTranspose2d、nn.BatchNorm1d、nn.BatchNorm2d、nn.Upsample 和 nn.MaxPool2d 等等。 随着课程的进展,我们将更多地了解这些。 目前,唯一要记住的重要事情是我们可以将这些层中的每一个都视为即插即用的组件:我们将提供所需的维度,PyTorch 将负责设置它们。
我们还可以使用 nn 模块将激活函数应用于我们的张量。 激活函数用于为我们的网络添加非线性。 一些激活函数的例子是 nn.ReLU()、nn.Sigmoid() 和 nn.LeakyReLU()。 激活函数分别对每个元素进行操作,因此我们作为输出得到的张量的形状与我们传入的张量的形状相同。
In [58]:
linear_output
Out [58]:
tensor([[[0.1796, 0.1423],
[0.1796, 0.1423],
[0.1796, 0.1423]],
[[0.1796, 0.1423],
[0.1796, 0.1423],
[0.1796, 0.1423]]], grad_fn=)
In [59]:
sigmoid = nn.Sigmoid()
output = sigmoid(linear_output)
output
Out [59]:
tensor([[[0.5448, 0.5355],
[0.5448, 0.5355],
[0.5448, 0.5355]],
[[0.5448, 0.5355],
[0.5448, 0.5355],
[0.5448, 0.5355]]], grad_fn=)
到目前为止,我们已经看到,我们可以创建层,并将一个层的输出作为下一个层的输入传递。我们可以使用nn.Sequentual,而不是创建中间张量并传递它们,它正是这样做的。
In [60]:
block = nn.Sequential(
nn.Linear(4, 2),
nn.Sigmoid()
)
input = torch.ones(2,3,4)
output = block(input)
output
Out [60]:
tensor([[[0.3630, 0.3951],
[0.3630, 0.3951],
[0.3630, 0.3951]],
[[0.3630, 0.3951],
[0.3630, 0.3951],
[0.3630, 0.3951]]], grad_fn=)
除了使用预定义的模块,我们还可以通过扩展 nn.Module 类来构建我们自己的模块。 例如,我们可以使用前面介绍的张量自行构建一个 nn.Linear(它也扩展了 nn.Module)! 我们还可以构建新的、更复杂的模块,例如自定义神经网络。 您将在以后的作业中练习这些。
要创建自定义模块,我们要做的第一件事就是扩展 nn.Module。 然后我们可以在 __init__ 函数中初始化我们的参数,从调用超类的 __init__ 函数开始。 我们定义的所有 nn 模块对象的类属性都被视为参数,可以在训练期间学习。 张量不是参数,但如果将它们包装在 nn.Parameter 类中,则可以将它们变成参数。
所有扩展 nn.Module 的类也应该实现一个 forward(x) 函数,其中 x 是一个张量。 这是在将参数传递给我们的模块时调用的函数,例如在 model(x) 中。
In [61]:
class MultilayerPerceptron(nn.Module):
def __init__(self, input_size, hidden_size):
# Call to the __init__ function of the super class
# 调用超类的 __init__ 函数
super(MultilayerPerceptron, self).__init__()
# Bookkeeping: Saving the initialization parameters
# 簿记:保存初始化参数
self.input_size = input_size
self.hidden_size = hidden_size
# Defining of our model
# There isn't anything specific about the naming of `self.model`. It could
# be something arbitrary.
# 定义我们的模型
# 关于`self.model`的命名没有什么特别的,它是任意的。
self.model = nn.Sequential(
nn.Linear(self.input_size, self.hidden_size),
nn.ReLU(),
nn.Linear(self.hidden_size, self.input_size),
nn.Sigmoid()
)
def forward(self, x):
output = self.model(x)
return output
这是定义相同类的另一种方法。 可以看到,我们可以通过在 __init__ 方法中定义各个层并在 forward 方法中连接它们来替换 nn.Sequential。
class MultilayerPerceptron(nn.Module):
def __init__(self, input_size, hidden_size):
# Call to the __init__ function of the super class
# 调用超类的 __init__ 函数
super(MultilayerPerceptron, self).__init__()
# Bookkeeping: Saving the initialization parameters
# 簿记:保存初始化参数
self.input_size = input_size
self.hidden_size = hidden_size
# Defining of our layers
# 定义我们的层
self.linear = nn.Linear(self.input_size, self.hidden_size)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(self.hidden_size, self.input_size)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
linear = self.linear(x)
relu = self.relu(linear)
linear2 = self.linear2(relu)
output = self.sigmoid(linear2)
return output
现在我们已经定义了我们的类,我们可以实例化它并看看它做了什么。
In [63]:
# Make a sample input
# 做一个样本输入
input = torch.randn(2, 5)
# Create our model
# 创建我们的模型
model = MultilayerPerceptron(5, 3)
# Pass our input through our model
# 传递我们的输入到我们的模型
model(input)
Out [63]:
tensor([[0.5887, 0.4685, 0.4995, 0.6062, 0.3894],
[0.5292, 0.5144, 0.4310, 0.5821, 0.3681]], grad_fn=)
我们可以使用 named_parameters() 和 parameters() 方法检查模型的参数。
In [64]:
list(model.named_parameters())
Out [64]:
[('linear.weight', Parameter containing:
tensor([[ 0.0517, 0.0466, -0.3616, -0.3459, -0.3524],
[ 0.0680, 0.0945, -0.3261, 0.1835, 0.4344],
[-0.3985, 0.1973, -0.1373, -0.2314, -0.1868]], requires_grad=True)),
('linear.bias', Parameter containing:
tensor([-0.0924, 0.1563, -0.3934], requires_grad=True)),
('linear2.weight', Parameter containing:
tensor([[-0.1111, 0.4216, -0.0263],
[-0.5714, -0.3207, -0.5514],
[ 0.1727, 0.4809, -0.4426],
[ 0.3308, 0.1744, -0.2814],
[ 0.2347, 0.1584, 0.0537]], requires_grad=True)),
('linear2.bias', Parameter containing:
tensor([ 0.1169, 0.0575, -0.2778, 0.3314, -0.5406], requires_grad=True))]
我们已经展示了使用backward()函数是如何计算梯度的。对于我们的模型来说,拥有梯度是不够的。我们还需要知道如何更新模型的参数。这就是优化器的用武之地。torch.optim 模块包含几个我们可以使用的优化器。 一些流行的例子是 optim.SGD 和 optim.Adam。在初始化优化器时,我们传递我们的模型参数,这些参数可以通过 model.parameters() 访问,告诉优化器它将优化哪些值。 优化器还有一个学习率 (lr) 参数,它决定了每一步将进行多大的更新。 不同的优化器也有不同的超参数。
In [65]:
import torch.optim as optim
有了优化函数后,我们可以定义要优化的损失。 我们可以自己定义损失,也可以使用 PyTorch 中预定义的损失函数之一,例如 nn.BCELoss()。 现在让我们把所有东西放在一起! 我们将从创建一些虚拟数据开始。
In [66]:
# Create the y data
# 创建y数据
y = torch.ones(10, 5)
# Add some noise to our goal y to generate our x
# We want out model to predict our original data, albeit the noise
# 给我们的目标 y 添加一些噪音来生成我们的 x
# 我们希望我们的模型预测我们的原始数据,尽管有噪音
x = y + torch.randn_like(y)
x
Out [66]:
tensor([[ 0.6017, -0.1942, 1.5013, 0.8077, 0.4822],
[ 2.4198, 1.6348, 1.7984, 2.7030, 1.8514],
[-0.2632, 1.1230, 0.4369, 1.1091, -0.7259],
[ 1.5842, 1.6495, -0.6160, 1.4506, 0.5238],
[-0.4652, 1.1538, 4.0669, -0.4767, 3.2751],
[ 1.6152, 0.1758, 0.6275, 1.8919, 2.0594],
[ 2.4223, 0.6465, 1.0125, 0.2578, -0.2029],
[ 1.6735, 0.9132, 0.3643, 0.9575, 1.7279],
[ 3.0019, 0.7942, 2.0360, 1.3991, 1.3139],
[ 0.8813, 0.7213, 1.6067, -0.5509, 1.3748]])
现在,我们可以定义我们的模型、优化器和损失函数。
In [67]:
# Instantiate the model
# 实例化模型
model = MultilayerPerceptron(5, 3)
# Define the optimizer
# 定义优化器
adam = optim.Adam(model.parameters(), lr=1e-1)
# Define loss using a predefined loss function
# 使用预定义的损失函数定义损失
loss_function = nn.BCELoss()
# Calculate how our model is doing now
# 计算看看我们的模型现在的表现
y_pred = model(x)
loss_function(y_pred, y).item()
Out [67]:
0.7392909526824951
让我们看看我们是否可以让我们的模型实现更小的损失。 现在我们拥有了所需的一切,我们可以设置训练循环。
In [68]:
# Set the number of epoch, which determines the number of training iterations
# 设置epoch数,决定训练迭代次数
n_epoch = 10
for epoch in range(n_epoch):
# Set the gradients to 0
# 设置梯度为0
adam.zero_grad()
# Get the model predictions
# 获取模型预测
y_pred = model(x)
# Get the loss
# 获取损失
loss = loss_function(y_pred, y)
# Print stats
# 打印统计信息
print(f"Epoch {epoch}: traing loss: {loss}")
# Compute the gradients
# 计算梯度
loss.backward()
# Take a step to optimize the weights
# 走一个step优化权重
adam.step()
Epoch 0: traing loss: 0.7392909526824951
Epoch 1: traing loss: 0.6668772101402283
Epoch 2: traing loss: 0.5994036793708801
Epoch 3: traing loss: 0.5073628425598145
Epoch 4: traing loss: 0.4047197103500366
Epoch 5: traing loss: 0.30285021662712097
Epoch 6: traing loss: 0.21625970304012299
Epoch 7: traing loss: 0.13478505611419678
Epoch 8: traing loss: 0.0776328295469284
Epoch 9: traing loss: 0.042155299335718155
你可以看到我们的损失在减少。 现在让我们检查一下我们模型的预测,看看它们是否接近我们原来的 y,它全是 1。
In [69]:
# See how our model performs on the training data
# 查看我们的模型在训练数据上的表现
y_pred = model(x)
y_pred
Out [69]:
tensor([[0.8869, 0.9687, 0.9948, 0.9771, 0.9804],
[0.9883, 0.9996, 1.0000, 0.9999, 0.9999],
[0.8704, 0.9590, 0.9919, 0.9672, 0.9716],
[0.9508, 0.9937, 0.9996, 0.9973, 0.9979],
[0.9012, 0.9760, 0.9967, 0.9839, 0.9864],
[0.9526, 0.9941, 0.9997, 0.9975, 0.9981],
[0.9466, 0.9926, 0.9995, 0.9967, 0.9973],
[0.9450, 0.9922, 0.9995, 0.9964, 0.9971],
[0.9812, 0.9989, 1.0000, 0.9997, 0.9998],
[0.8866, 0.9685, 0.9948, 0.9769, 0.9803]], grad_fn=)
In [70]:
# Create test data and check how our model performs on it
# 创建测试数据并检查我们的模型在其上的表现
x2 = y + torch.randn_like(y)
y_pred = model(x2)
y_pred
Out [70]:
tensor([[0.9582, 0.9954, 0.9998, 0.9982, 0.9986],
[0.9217, 0.9847, 0.9984, 0.9912, 0.9927],
[0.9209, 0.9844, 0.9984, 0.9909, 0.9925],
[0.9410, 0.9911, 0.9994, 0.9957, 0.9966],
[0.9694, 0.9974, 0.9999, 0.9992, 0.9994],
[0.9360, 0.9896, 0.9992, 0.9947, 0.9957],
[0.9561, 0.9949, 0.9997, 0.9980, 0.9984],
[0.9290, 0.9874, 0.9988, 0.9931, 0.9944],
[0.9754, 0.9983, 1.0000, 0.9995, 0.9996],
[0.8905, 0.9706, 0.9953, 0.9789, 0.9821]], grad_fn=)
太棒了! 看起来我们的模型几乎完美地学会了从我们传入的 x 中过滤掉噪声!
(第三部分 演示:词窗分类 一(Demo: Word Window Classification I))