# “第 2 章”:适用于 NLP 的 PyTorch 1.x 入门 **PyTorch** 是基于 Python 的机器学习库。 它包含两个主要功能:通过硬件加速(使用 GPU)有效执行张量操作的能力以及构建深度神经网络的能力。 PyTorch 还使用动态计算图代替静态计算图,这使其与 TensorFlow 等类似库区分开来。 通过演示如何使用张量表示语言以及如何使用神经网络向 NLP 学习,我们将显示这两个功能对于自然语言处理特别有用。 在本章中,我们将向您展示如何在计算机上启动和运行 PyTorch,以及演示其一些关键功能。 然后,在探索 PyTorch 的某些 NLP 功能(例如其执行张量运算的能力)之前,我们将把 PyTorch 与其他深度学习框架进行比较,最后演示如何构建简单的神经网络。 总之,本章将涵盖以下主题: * 安装 PyTorch * 将 PyTorch 与其他深度学习框架进行比较 * PyTorch 的 NLP 功能 # 技术要求 在本章中,需要安装 Python。 建议使用最新版本的 Python(3.6 或更高版本)。 还建议使用 Anaconda 软件包管理器来安装 PyTorch。 需要 CUDA 兼容 GPU 才能在 GPU 上运行张量操作。 本章的所有代码都可以在[这个页面](https://github.com/PacktPublishing/Hands-On-Natural-Language-Processing-with-PyTorch-1.x)中找到。 # 安装和使用 PyTorch 1.x 像大多数 Python 软件包一样,PyTorch 的安装非常简单。 这样做有两种主要方法。 首先,要使用命令行中的`pip`简单地安装。 只需键入以下命令: 点安装火炬 torchvision 尽管此安装方法很快,但建议使用 Anaconda 进行安装,因为它包括运行 PyTorch 所需的所有依赖项和二进制文件。 此外,稍后将需要 Anaconda 使用 CUDA 在 GPU 上启用训练模型。 可以在 Anaconda 中通过在命令行中输入以下内容来安装 PyTorch: 康达安装火炬 torchvision -c pytorch 要检查 PyTorch 是否正常工作,我们可以打开 Jupyter Notebook 并运行一些简单的命令: 1. To define a Tensor in PyTorch, we can do the following: 进口火炬 x = torch.tensor([1.,2。]) 打印(x) 结果为以下输出: **![Figure 2.1 – Tensor output ](img/B12365_02_1.png)** 图 2.1 –张量输出 这表明 PyTorch 中的张量被保存为它们自己的数据类型(与 NumPy 中的数组保存方式相同)。 2. We can perform basic operations such as multiplication using standard Python operators: x = torch.tensor([1。,2.]) y = torch.tensor([3。,4.]) 打印(x * y) 结果为以下输出: ![Figure 2.2 – Tensor multiplication output ](img/B12365_02_2.jpg) 图 2.2 –张量乘法输出 3. We can also select individual elements from a tensor, as follows: x = torch.tensor([[1。,2。],[5。,3。],[0。,4.]]) 打印(x [0] [1]) 结果为以下输出: ![Figure 2.3 – Tensor selection output ](img/B12365_02_3.jpg) 图 2.3 –张量选择输出 但是,请注意,与 NumPy 数组不同,从张量对象中选择单个元素会返回另一个张量。 为了从张量返回单个值,可以使用 **.item()**函数: 打印(x [0] [1] .item()) 结果为以下输出: ![Figure 2.4 – Output of the .item() function ](img/B12365_02_4.jpg) 图 2.4 – .item()函数的输出 ## 张量 在我们继续之前,重要的是您充分了解张量的属性。 张量具有属性,称为**阶数**,该属性实质上确定张量的维数。 一阶张量是一维张量,等效于向量或数字列表。 2 阶张量是具有二维的张量,等效于矩阵,而 3 阶张量则由三个维度组成。 PyTorch 中张量可以具有的最大阶数没有限制: ![Figure 2.5 – Tensor matrix ](img/B12365_02_5.jpg) 图 2.5 –张量矩阵 您可以通过键入以下命令来检查任何张量的大小: 形状 结果为以下输出: ![Figure 2.6 – Tensor shape output ](img/B12365_02_6.jpg) 图 2.6 –张量形状输出 这表明这是一个 3x2 张量(阶数 2)。 # 使用 CUDA 启用 PyTorch 加速 PyTorch 的主要优点之一是它能够通过使用**图形处理单元**(**GPU**)来实现加速。 深度学习是一种易于并行化的计算任务,这意味着可以将计算分解为较小的任务,并可以在许多较小的处理器中进行计算。 意味着无需在单个 CPU 上执行任务,而是在 GPU 上执行计算更为有效。 GPU 最初是为了有效地渲染图形而创建的,但是由于深度学习在的普及中得到了发展,因此 GPU 被广泛用于同时执行多种计算的能力。 传统的 CPU 可能包含大约四个或八个内核,而 GPU 则包含数百个较小的内核。 由于可以同时在所有这些内核上执行计算,因此 GPU 可以快速减少执行深度学习任务所需的时间。 考虑神经网络内的一次通过。 我们可能会采集少量数据,将其通过我们的网络以获得损失,然后反向传播,根据梯度调整参数。 如果在传统的 CPU 上要处理大量数据,则必须等到批次 1 完成后才能计算批次 2: ![Figure 2.7 – One pass in a neural network ](img/B12365_02_7.jpg) 图 2.7 –神经网络中的一遍 但是,在 GPU 上,我们可以同时执行所有这些步骤,这意味着不需要批处理即可在批处理 2 开始之前完成。 我们可以同时计算所有批次的参数更新,然后一次执行所有参数更新(因为结果彼此独立)。 并行方法可以极大地加快机器学习过程: ![Figure 2.8 – Parallel approach to perform passes ](img/B12365_02_8.jpg) 图 2.8 –并行执行通行证的方法 **计算统一设备体系结构**(**CUDA**)是 Nvidia GPU 特有的技术,可在 PyTorch 上实现硬件加速。 为了启用 CUDA,我们首先必须确保我们系统上的图形卡兼容 CUDA。 [可在此处找到 CUDA 兼容 GPU 的列表](https://developer.nvidia.com/cuda-gpus)。 如果您具有兼容 CUDA 的 GPU,[则可以从此链接安装 CUDA](https://developer.nvidia.com/cuda-downloads)。 我们将使用以下步骤激活它: 1. 首先,为了在 PyTorch 上实际启用 CUDA 支持,您将必须从源代码构建 PyTorch。 有关如何完成此操作的详细信息,请参见以下网址:https://github.com/pytorch/pytorch#from-source。 2. Then, to actually CUDA within our PyTorch code, we must type the following into our Python code: cuda = torch.device('cuda') 这会将我们的默认 CUDA 设备的名称设置为**'cuda'**。 3. We can then execute operations on this device by manually specifying the device argument in any tensor operations: x = torch.tensor([5。,3.],device = cuda) 另外,我们可以通过调用 **cuda** 方法来做到这一点: y = torch.tensor([4.,2.])。cuda() 4. We can then run a simple operation to ensure this is working correctly: x * y 结果为以下输出: ![Figure 2.9 – Tensor multiplication output using CUDA ](img/B12365_02_9.jpg) 图 2.9 –使用 CUDA 的张量乘法输出 由于我们只是在创建张量,因此速度的变化在此阶段不会很明显,但是当以后开始大规模训练模型时,我们将看到使用 CUDA 并行化计算的速度优势。 通过并行训练我们的模型,我们将能够节省大量时间。 # 将 PyTorch 与其他深度学习框架进行比较 PyTorch 是当今深度学习中使用的主要框架之一。 还存在其他广泛使用的框架,例如 TensorFlow,Theano 和 Caffe 等。 尽管它们在很多方面都非常相似,但是它们的操作方式还是有一些关键的区别。 其中包括: * 如何计算模型 * 计算图的编译方式 * 创建具有可变层的动态计算图的能力 * 语法差异 可以说,PyTorch 与其他框架之间的主要区别在于模型本身的计算方式。 PyTorch 使用称为 **autograd** 的自动微分方法,该方法允许动态定义和执行计算图。 这与其他框架(如 TensorFlow)相反,后者是静态框架。 在这些静态框架中,必须在最终执行之前定义和编译计算图。 尽管使用预编译的模型可以提高生产效率,但在研究和勘探项目中却无法提供相同水平的灵活性。 在训练模型之前,诸如 PyTorch 之类的框架无需预先编译计算图。 PyTorch 使用的动态计算图意味着在执行图时对其进行编译,从而可以随时定义图。 动态模型构建方法在 NLP 领域特别有用。 让我们考虑两个我们希望对以下内容进行情感分析的句子: ![Figure 2.10 – Model construction in PyTorch ](img/B12365_02_10.jpg) 图 2.10 – PyTorch 中的模型构造 我们可以将这些句子中的每一个表示为单个单词向量的序列,然后形成我们对神经网络的输入。 但是,正如我们所看到的,我们每个输入的大小都是。 在固定计算图中,这些变化的输入大小可能是个问题,但是对于 PyTorch 这样的框架,模型能够动态调整以解决输入结构的变化。 这就是为什么 PyTorch 经常被 NLP 相关的深度学习首选的原因之一。 PyTorch 与其他深度学习框架之间的另一个主要区别是语法。 PyTorch 通常是具有 Python 经验的开发人员首选,因为它本质上被认为是非常 Python 的。 PyTorch 与 Python 生态系统的其他方面很好地集成在一起,如果您具有 Python 的先验知识,则非常容易学习。 现在,我们将从头开始编写我们自己的神经网络,以演示 PyTorch 语法。 # 在 PyTorch 中构建简单的神经网络 现在,我们将逐步在 PyTorch 中逐步构建神经网络。 在这里,我们有一个小的 **.csv** 文件,其中包含来自 MNIST 数据集的图像的几个示例。 MNIST 数据集由我们想要尝试分类的 0 到 9 之间的手绘数字组成。 以下是来自 MNIST 数据集的示例,其中包括手绘数字 1: ![Figure 2.11 – Sample image from the MNIST dataset ](img/B12365_02_11.jpg) 图 2.11 –来自 MNIST 数据集的样本图像 这些图像的尺寸为 28x28:总共 784 像素。 我们在 **train.csv** 中的数据集由 1,000 幅这些图像组成,每幅图像均由 784 个像素值以及正确的数字分类(在这种情况下为 1)组成。 ## 加载数据 我们将从加载数据开始,如下所示: 1. First, we need to load our training dataset, as follows: 火车= pd.read_csv(“ train.csv”) train_labels = train ['label']。values train = train.drop(“ label”,axis = 1).values.reshape(len(train),1,28,28) 请注意,我们将输入重塑为[ **1,** **1,** **28,** **28**),每个张量为 1,000 张图像 由 28x28 像素组成。 2. Next, we convert our training data and training labels into PyTorch tensors so they can be fed into the neural network: X =火炬张量(train.astype(float)) y = Torch.Tensor(train_labels).long() 注意这两个张量的数据类型。 浮点张量由 32 位浮点数组成,而长张量由 64 位整数组成。 为了使 PyTorch 能够计算渐变,我们的 **X** 功能必须为浮点数,而我们的标签必须为该分类模型中的整数(因为我们正在尝试预测 1、2、3 和 等等),因此 1.5 的预测就没有意义。 ## 建立分类器 接下来,我们可以开始构造实际的神经网络分类器: 类 MNISTClassifier(nn.Module): def __init __(): super().__ init __() self.fc1 = nn.Linear(784,392) self.fc2 = nn.Linear(392,196) self.fc3 = nn.Linear(196,98) self.fc4 = nn.Linear(98,10) 我们像从 Python PyTorch 中继承 **nn.Module** 一样,在 Python 中构建普通类,从而构建分类器。 在我们的**初始**方法中,我们定义了神经网络的每一层。 在这里,我们定义了大小可变的完全连接的线性层。 我们的第一层接受 **784** 输入,因为这是我们要分类的每个图像的大小(28x28)。 然后,我们看到一层的输出必须与下一层的输入具有相同的值,这意味着我们的第一个完全连接的层输出 **392** 个单位,而我们的第二层则采用 **392** 单位作为输入。 对每一层都重复一次,每次它们具有一半的单位数量,直到我们到达最终的完全连接层为止,该层输出 **10** 个单位。 这是我们分类层的长度。 我们的网络现在看起来像这样: ![Figure 2.12 – Our neural network ](img/B12365_02_12.jpg) 图 2.12 –我们的神经网络 在这里,我们可以看到我们的最后一层输出了 **10** 个单位。 这是因为我们希望预测每个图像是否为 0 到 9 之间的数字,总共是 10 种不同的可能分类。 我们的输出是长度为 **10** 的向量,并且包含图像的 10 种可能值中的每一个的预测。 在进行最终分类时,我们将数值最高的数字分类作为模型的最终预测。 例如,对于给定的预测,我们的模型可能会预测图像类型为 1 的概率为 10%,类型 2 的概率为 10%,类型 3 的概率为 80%。 因此,我们将类型 3 作为预测,因为它以最高概率被预测。 ## 实施辍学 在我们的 **MNISTClassifier** 类的**初始**方法中,我们还定义了一种 dropout 方法,以帮助规范网络: self.dropout = nn.Dropout(p = 0.2) 辍学是一种规范化我们的神经网络以防止过度拟合的方法。 在每个训练纪元上,对于已应用丢包的层中的每个节点,都有可能(此处定义为 *p* = 20%)该层内的每个节点将不用于训练/反向传播 。 这意味着,在训练时,我们的网络会针对过拟合变得健壮,因为在训练过程的每次迭代中都不会使用每个节点。 这可以防止我们的网络过于依赖网络中特定节点的预测。 ## 定义前向通行证 接下来,我们在分类器中定义正向传递: def forward(self,x): x = x.view(x.shape [0],-1) x = self.dropout(F.relu(self.fc1(x))) x = self.dropout(F.relu(self.fc2(x))) x = self.dropout(F.relu(self.fc3(x))) x = F.log_softmax(self.fc4(x),dim = 1) 分类器中的 **forward()**方法是我们在其中应用激活函数并定义在我们的网络中应用 dropout 的位置的方法。 我们的**正向**方法定义了输入将通过网络的路径。 首先,它获取我们的输入 **x,**,并将其整形以在网络中使用,并将其转换为一维矢量。 然后,我们将其通过我们的第一个完全连接的层,并将其包装在 **ReLU** 激活函数中,以使其为非线性。 我们也将其包装在我们的[drop]中,如 **init** 方法中所定义。 我们对网络中的所有其他层重复此过程。 对于我们的最终预测层,我们将其包装在对数 **softmax** 层中。 我们将使用它来轻松计算我们的损失函数,如下所示。 ## 设置模型参数 接下来,我们定义我们的模型参数: 型号= MNISTClassifier() loss_function = nn.NLLLoss() opt = optim.Adam(model.parameters(),lr = 0.001) 我们将 **MNISTClassifier** 类的实例初始化为模型。 我们还将的损失定义为**负对数似然损失**: 损失(y)= -log(y) 假设我们的图像为 7。如果我们以概率 1 预测类别 7,则损失为 *-log(1)= 0* ,但是如果我们仅以概率 0.7 预测类别 7,则损失为 将是 *-log(0.7)= 0.3* 。 这意味着我们与正确预测的距离越远,损失就越接近无穷大: ![Figure 2.13 – Representation of loss for our network ](img/B12365_02_13.jpg) 图 2.13 –我们网络的损失表示 然后将其汇总到数据集中所有正确的类中,以计算总损失。 请注意,我们在构建分类器时定义了 log softmax,因为它已经应用了 softmax 函数(将预测输出限制在 0 到 1 之间)并获取了日志。 这意味着 *log(y)*已经被计算出来,因此我们要计算网络上的总损耗所需要做的就是计算输出的负和。 我们还将优化器定义为 Adam 优化器。 优化器控制模型中的**学习率**。 模型的学习率定义了每次训练期间参数更新的大小。 学习速率的大小越大,梯度下降期间参数更新的大小越大。 优化器动态控制该学习速率,以便在初始化模型时,参数更新很大。 但是,随着模型的学习和向损耗最小化点的靠近,优化器将控制学习率,因此参数更新变得更小,可以更精确地定位局部最小值。 ## 训练我们的网络 最后,我们实际上可以开始训练我们的网络了: 1. First, create a loop that runs once for each epoch of our training. Here, we will run our training loop for 50 epochs. We first take our input tensor of images and our output tensor of labels and transform them into PyTorch variables. A **variable** is a PyTorch object that contains a **backward()** method that we can use to perform backpropagation through our network: 对于范围(50)中的时代: 图片=变量(X) 标签=变量(y) 2. Next, we call **zero_grad()** on our optimizer to set our calculated gradients to zero. Within PyTorch, gradients are calculated cumulatively on each backpropagation. While this is useful in some models, such as when training RNNs, for our example, we wish to calculate the gradients from scratch after each epoch, so we make sure to reset the gradients to zero after each pass: opt.zero_grad() 3. Next, we use our model's current state to make predictions on our dataset. This is effectively our forward pass as we then use these predictions to calculate our loss: 输出=模型(图像) 4. Using the outputs and the true labels of our dataset, we calculate the total loss of our model using the defined loss function, which in this case is the negative log likelihood. On calculating this loss, we can then make a **backward()** call to backpropagate our loss through the network. We then use **step()** using our optimizer in order to update our model parameters accordingly: 损失= loss_function(输出,标签) loss.backward() opt.step() 5. Finally, after each epoch is complete, we print the total loss. We can observe this to make sure our model is learning: 打印('Epoch [%d /%d]损失:%.4f'%(epoch + 1,50,loss.data.item())) 一般而言,我们预计损失会在每个时期减少。 我们的输出将如下所示: ![Figure 2.14 – Training epochs ](img/B12365_02_14.jpg) 图 2.14 –训练纪元 ## 进行预测 现在我们的模型已经过训练,我们可以使用它对看不见的数据进行预测。 我们首先从读取我们的测试数据集(该数据未用于训练我们的模型): 测试= pd.read_csv(“ test.csv”) test_labels = test ['label']。values 测试= test.drop(“ label”,axis = 1).values.reshape(len(test),1,28,28) X_test =火炬张量(test.astype(float)) y_test = torch.Tensor(test_labels).long() 在这里,我们执行与加载训练数据集时相同的步骤:重新整形测试数据并将其转换为 PyTorch 张量。 接下来,要使用我们训练有素的模型进行预测,我们只需运行以下命令: preds =模型(X_test) 与我们在模型中训练数据的正向传递上计算输出的方式相同,现在我们将测试数据传递通过模型并获得预测。 我们可以像这样查看其中一张图像的预测: 打印(preds [0]) 结果为以下输出: ![Figure 2.15 – Prediction outputs ](img/B12365_02_15.jpg) 图 2.15 –预测输出 在这里,我们可以看到我们的预测是一个长度为 10 的向量,并且对每个可能的类别(0 到 9 之间的数字)进行了预测。 预测值最高的是我们的模型选择的预测值。 在这种情况下,它是向量的第十个单位,等于数字 9。请注意,由于我们较早使用对数 softmax,因此我们的预测是对数而非原始概率。 要将它们转换回概率,我们可以使用 *x* 对其进行转换。 现在,我们可以构造一个摘要 DataFrame,其中包含我们的真实测试数据标签以及模型预测的标签: _,projectionlabel = torch.max(preds.data,1) projectionlabel = projectionlabel.tolist() projectionlabel = pd.Series(predictionlabel) test_labels = pd.Series(test_labels) pred_table = pd.concat([predictionlabel,test_labels],axis = 1) pred_table.columns = ['预测值','真实值'] 显示(pred_table.head()) 结果为以下输出: ![Figure 2.16 – Prediction table ](img/B12365_02_16.jpg) 图 2.16 –预测表 注意 **torch.max()**函数如何自动选择具有最高值的预测。 在这里我们可以看到,基于的少量数据,我们的模型似乎做出了一些不错的预测! ## 评估我们的模型 既然我们已经从模型中获得了一些预测,我们就可以使用这些预测来评估模型的质量。 如上一章所述,评估模型性能的一种基本方法是**准确性**。 在这里,我们只是将正确的预测(预测的图像标签等于实际的图像标签)计算为模型做出的预测总数的百分比: preds = len(预测标签) 正确= len([如果 x == y,则为 zip(预测标签,test_labels)中的 x,y 为 1) 打印((正确/偏好)* 100) 结果为以下输出: ![Figure 2.17 – Accuracy score ](img/B12365_02_17.jpg) 图 2.17 –准确性得分 恭喜你! 您的第一个神经网络能够正确识别近 90%的看不见的数字图像。 随着我们的前进,我们将看到可能会带来更先进的性能。 但是,到目前为止,我们已经证明使用 PyTorch 创建简单的深度神经网络非常简单。 只需几行代码就可以编写代码,从而获得超越基本机器学习模型(例如回归)所能达到的性能。 # 用于 PyTorch 的 NLP 现在我们已经学习了如何构建神经网络,我们将看到如何使用 PyTorch 为 NLP 构建模型。 在此示例中,我们将创建一个基本的词袋分类器,以对给定句子的语言进行分类。 ## 设置分类器 在此示例中,我们将选择西班牙语和英语的句子: 1. First, we split each sentence into a list of words and take the language of each sentence as a label. We take a section of sentences to train our model on and keep a small section to one side as our test set. We do this so that we can evaluate the performance of our model after it has been trained: (“这是我最喜欢的章节”。lower()。split(),\ “英语”), (“我在图书馆”。lower()。Split(),“西班牙语”) 请注意,我们还将每个单词转换为小写,这将阻止单词在单词袋中重复计算。 如果我们有单词**本书**和单词**本书**,我们希望将它们视为相同的单词,因此将它们转换为小写。 2. Next, we build our word index, which is simply a dictionary of all the words in our corpus, and then create a unique index value for each word. This can be easily done with a short **for** loop: word_dict = {} 我= 0 用于单词,training_data + test_data 中的语言: 逐字逐句: 如果单词不在 word_dict 中: word_dict [word] = i 我+ = 1 打印(word_dict) 结果为以下输出: ![Figure 2.18 – Setting up the classifier ](img/B12365_02_18.jpg) 图 2.18 –设置分类器 请注意,在这里,我们遍历了所有训练数据和测试数据。 如果我们只是在训练数据上创建单词索引,则在评估测试集时,我们将拥有在原始训练中看不到的新单词,因此我们将无法创建真正的单词袋 这些单词的表示形式。 3. Now, we build our classifier in a similar fashion to how we built our neural network in the previous section; that is, by building a new class that inherits from **nn.Module**. 在这里,我们定义分类器,以使其由单个线性层组成,该线性层具有对数 softmax 激活函数,近似于逻辑回归。 通过在此处添加额外的线性层,我们可以轻松地将其扩展到作为神经网络运行,但是单层参数将达到我们的目的。 请密切注意线性层的输入和输出大小: corpus_size = len(word_dict) 语言= 2 label_index = {“西班牙文”:0,“英语”:1} BagofWordsClassifier 类(nn.Module): def __init __(自身,语言,语料库大小): 超级(BagofWordsClassifier,self).__ init __() self.linear = nn.Linear(corpus_size,语言) def forward(自己,bow_vec): 返回 F.log_softmax(self.linear(bow_vec),dim = 1) 输入的长度为 **corpus_size** ,这只是我们的语料库中唯一词的总数。 这是因为对模型的每个输入都是一个词袋表示,由每个句子中的单词计数组成,如果给定单词​​未出现在我们的句子中,则计数为 0。 我们的输出大小为 2,这是我们可以预测的语言数量。 我们的最终预测将包括我们的句子是英语的概率与我们的句子是西班牙文的概率,而我们的最终预测是概率最高的那个。 4. Next, we define some utility functions. We first define **make_bow_vector**, which takes the sentence and transforms it into a bag-of-words representation. We first create a vector consisting of all zeros. We then loop through them and for each word in the sentence, we increment the count of that index within the bag-of-words vector by one. We finally reshape this vector using **with .view()** for entry into our classifier: def make_bow_vector(句子,word_index): word_vec = torch.zeros(corpus_size) 对于句子中的单词: word_vec [word_dict [word]] + = 1 返回 word_vec.view(1,-1) 5. Similarly, we define **make_target**, which simply takes the label of the sentence (Spanish or English) and returns its relevant index (**0** or **1**): def make_target(label,label_index): 返回 torch.LongTensor([label_index [label]]) 6. We can now create an instance of our model, ready for training. We also define our loss function as Negative Log Likelihood as we are using a log softmax function, and then define our optimizer in order to use standard **stochastic** **gradient** **descent** (**SGD**): 模型= BagofWordsClassifier(语言,语料库大小) loss_function = nn.NLLLoss() 优化程序= optim.SGD(model.parameters(),lr = 0.1) 现在,我们准备训练模型。 ## 训练分类器 首先,我们建立一个循环,该循环由希望模型运行的时期数组成。 在这种情况下,我们将选择 100 个纪元。 在此循环中,我们首先将梯度归零(否则,PyTorch 会累积计算梯度),然后对于每个句子/标签对,分别将其转换为词袋矢量和目标。 然后,通过使数据向前通过模型的当前状态,我们计算出该特定句子对的预测输出。 然后使用此预测,获取我们的预测标签和实际标签,并在两者上调用定义的 **loss_function** ,以获取此句子的损失度量。 通过向后调用**向后()**,我们通过模型反向传播此损失,并在优化器上调用 **step()**,从而更新模型参数。 最后,我们每 10 个训练步骤打印一次损失: 对于范围(100)中的时代: 对于句子,在 training_data 中标记: model.zero_grad() bow_vec = make_bow_vector(句子,word_dict) 目标= make_target(标签,标签索引) log_probs =模型(bow_vec) 损失= loss_function(log_probs,目标) loss.backward() Optimizer.step() 如果时代%10 == 0: print('Epoch:',str(epoch + 1),',Loss:'+ str(loss.item())) 结果为以下输出: ![Figure 2.19 – Training loss ](img/B12365_02_19.jpg) 图 2.19 –培训损失 在这里,我们可以看到,随着模型的学习,我们的损失随着时间而减少。 尽管本例中的训练集很小,但是我们仍然可以证明我们的模型学到了一些有用的东西,如下所示: 1. 我们根据未接受模型训练的测试数据中的几句话来评估模型。 在这里,我们首先设置 **torch.no_grad()**,这将停用 **autograd** 引擎,因为由于我们不再训练模型,因此不再需要计算梯度。 接下来,我们将测试句子转换为词袋向量,并将其输入模型以获取预测。 2. We then simply print the sentence, the true label of the sentence, and then the predicted probabilities. Note that we transform the predicted values from log probabilities back into probabilities. We obtain two probabilities for each prediction, but if we refer back to the label index, we can see that the first probability (index 0) corresponds to Spanish, whereas the other one corresponds to English: def make_predictions(data): 使用 torch.no_grad(): 句子=数据[0] 标签=数据[1] bow_vec = make_bow_vector(句子,word_dict) log_probs =模型(bow_vec) 打印(句子) 打印(标签+':') 打印(np.exp(log_probs)) make_predictions(test_data [0]) make_predictions(test_data [1]) 结果为以下输出: ![Figure 2.20 – Predicted output ](img/B12365_02_20.jpg) 图 2.20 –预测的输出 在这里,我们可以看到,对于我们的两个预测,我们的模型都可以预测正确的答案,但是为什么会这样呢? 我们的模型究竟学到了什么? 我们可以看到我们的第一个测试句子包含单词 **estoy** ,该单词先前在我们的训练集中的西班牙语句子中出现过。 同样,我们可以看到**这本书**一词在我们的训练集中以英语句子出现。 由于我们的模型由单层组成,因此每个节点上的参数都易于解释。 3. Here, we define a function that takes a word as input and returns the weights on each of the parameters within the layer. For a given word, we get the index of this word from our dictionary and then select these parameters from the same index within the model. Note that our model returns two parameters as we are making two predictions; that is, the model's contribution to the Spanish prediction and the model's contribution to the English prediction: def return_params(word): 索引= word_dict [word] 对于 model.parameters()中的 p: 昏暗= len(p.size()) 如果暗淡== 2: 打印(单词+':') print('西班牙参数='+ str(p [0] [index] .item())) print('英文参数='+ str(p [1] [index] .item())) 打印('\ n') return_params(“我是”) return_params('book') 结果为以下输出: ![Figure 2.21 – Predicted output for the updated function ](img/B12365_02_21.jpg) 图 2.21 –更新功能的预测输出 在这里,我们可以看到,对于单词 **estoy** 而言,此参数对西班牙语的预测为正,对英语的预测为负。 这意味着对于我们句子中的每个单词“ **estoy** ”,该句子更有可能是西班牙语句子。 类似地,对于**书**一词,我们可以看到这对句子为英语的预测有积极作用。 我们可以证明我们的模型仅基于对其进行了训练而学习。 如果我们尝试预测尚未训练过的单词,则可以看到它无法做出准确的决定。 在这种情况下,我们的模型认为英语单词“ **not”** 是西班牙语: new_sentence =([[“ not”],“英语”) make_predictions(new_sentence) 结果为以下输出: ![Figure 2.22 – Final output ](img/B12365_02_22.jpg) 图 2.22 – 最终输出 # 摘要 在本章中,我们介绍了 PyTorch 及其一些关键功能。 希望您现在对 PyTorch 与其他深度学习框架有何不同以及如何用于构建基本神经网络有了更好的了解。 尽管这些简单的示例只是冰山一角,但我们已经说明了 PyTorch 是用于 NLP 分析和学习的强大工具。 在以后的章节中,我们将演示如何利用 PyTorch 的独特属性来构建高度复杂的模型,以解决非常复杂的机器学习任务。