From 673ea3f8443c5d92c0366457ed44e59cb32e948c Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Sun, 17 Jan 2021 17:10:29 +0800 Subject: [PATCH] 2021-01-17 17:10:29 --- new/handson-nlp-pt-1x/5.md | 665 +++++++++++++++++-------------------- 1 file changed, 313 insertions(+), 352 deletions(-) diff --git a/new/handson-nlp-pt-1x/5.md b/new/handson-nlp-pt-1x/5.md index cd5c295d..24ff4101 100644 --- a/new/handson-nlp-pt-1x/5.md +++ b/new/handson-nlp-pt-1x/5.md @@ -157,13 +157,14 @@ LSTM 与 RNN 的结构非常相似。 虽然 LSTM 的各个步骤之间存在一 使用 open(“被标记为句子/sentiment.txt 的情感”)为 f: -评论= f.read() - -数据= pd.DataFrame([review.split('\ t')用于 reviews.split('\ n')]中的评论) - -data.columns = ['Review','Sentiment'] - -数据= data.sample(分数= 1) +```py +with open("sentiment labelled sentences/sentiment.txt") as f: +    reviews = f.read() +     +data = pd.DataFrame([review.split('\t') for review in                      reviews.split('\n')]) +data.columns = ['Review','Sentiment'] +data = data.sample(frac=1) +``` 这将返回以下输出: @@ -177,31 +178,21 @@ data.columns = ['Review','Sentiment'] 首先,我们创建一个函数标记数据,将每个评论分为单个预处理词的列表。 我们遍历数据集,并为每条评论删除所有标点符号,将字母转换为小写字母,并删除任何尾随空格。 然后,我们使用 NLTK 令牌生成器根据此预处理文本创建单个令牌: -def split_words_reviews(data): - -文字=清单(数据['Review']。values) - -clean_text = [] - -对于文本中的 t: - -clean_text.append(t.translate(str.maketrans('',``,标点符号))。lower()。rstrip()) - -tokenized = [clean_text 中 x 的 word_tokenize(x)] - -all_text = [] - -对于分词的令牌: - -对于令牌中的 t: - -all_text.append(t) - -返回标记化的 set(all_text) - -评论,vocab = split_words_reviews(数据) - -评论[0] +```py +def split_words_reviews(data): +    text = list(data['Review'].values) +    clean_text = [] +    for t in text: +        clean_text.append(t.translate(str.maketrans('', '',                   punctuation)).lower().rstrip()) +    tokenized = [word_tokenize(x) for x in clean_text] +    all_text = [] +    for tokens in tokenized: +        for t in tokens: +            all_text.append(t) +    return tokenized, set(all_text) +reviews, vocab = split_words_reviews(data) +reviews[0] +``` 这将产生以下输出: @@ -213,17 +204,14 @@ all_text.append(t) 为了充分准备将句子输入神经网络,我们必须将单词转换为数字。 为了做到这一点,我们创建了两个字典,这将使我们能够将数据从单词转换为索引以及从索引转换为单词。 为此,我们只需遍历语料库并为每个唯一单词分配一个索引: -def create_dictionaries(words): - -word_to_int_dict = {w:i + 1 for i,w in enumerate(words)} - -int_to_word_dict = {i:w 为 w,i 为 word_to_int_dict。 items()} - -返回 word_to_int_dict,int_to_word_dict - -word_to_int_dict,int_to_word_dict = create_dictionaries(vocab) - +```py +def create_dictionaries(words): +    word_to_int_dict = {w:i+1 for i, w in enumerate(words)} +    int_to_word_dict = {i:w for w, i in word_to_int_dict.                            items()} +    return word_to_int_dict, int_to_word_dict +word_to_int_dict, int_to_word_dict = create_dictionaries(vocab) int_to_word_dict +``` 这给出以下输出: @@ -233,9 +221,10 @@ int_to_word_dict 我们的神经网络将接受固定长度的输入; 但是,如果我们浏览我们的评论,我们会发现我们的评论都是不同长度的。 为了确保所有输入的长度相同,我们将*填充*我们的输入句子。 这实质上意味着我们将空标记添加到较短的句子中,以便所有句子的长度相同。 我们必须首先决定要实现的填充长度。 我们首先计算输入评论中句子的最大长度以及平均长度: -打印(np.max([len(x)for reviews in x]))) - -打印(np.mean([len(x)for x in reviews])) +```py +print(np.max([len(x) for x in reviews])) +print(np.mean([len(x) for x in reviews])) +``` 这给出了以下内容: @@ -247,25 +236,21 @@ int_to_word_dict 我们将创建一个函数,使我们能够填充句子,使它们的大小相同。 对于短于序列长度的评论,我们用空标记填充它们。 对于长度超过序列长度的评论,我们只需丢弃超过最大序列长度的所有标记: -def pad_text(tokenized_reviews,seq_length): - -评论= [] - -在 tokenized_reviews 中进行审查: - -如果 len(review)> = seq_length: - -reviews.append(评论[:seq_length]) - -其他: - -reviews.append([''] *(seq_length-len(review))+评论) - -返回 np.array(评论) - -padded_sentences = pad_text(评论,seq_length = 50) - -padded_sentences [0] +```py +def pad_text(tokenized_reviews, seq_length): +     +    reviews = [] +     +    for review in tokenized_reviews: +        if len(review) >= seq_length: +            reviews.append(review[:seq_length]) +        else: +            reviews.append(['']*(seq_length-len(review)) +                    review) +         +    return np.array(reviews) +padded_sentences = pad_text(reviews, seq_length = 50) +padded_sentences[0] +``` 我们的填充语句如下所示: @@ -275,15 +260,17 @@ padded_sentences [0] 我们必须进行进一步的调整,以允许在模型中使用空令牌。 当前,我们的词汇词典不知道如何将空令牌转换为整数以在我们的网络中使用。 因此,我们将它们手动添加到索引为`0`的字典中,这意味着当将空令牌输入模型时,它们的值将为`0`: -int_to_word_dict [0] ='' - -word_to_int_dict [''] = 0 +```py +int_to_word_dict[0] = '' +word_to_int_dict[''] = 0 +``` 现在,我们几乎可以开始训练模型了。 我们执行预处理的最后一步,并将所有填充语句编码为数字序列,以馈入神经网络。 这意味着前面的填充语句现在看起来像这样: -encode_sentences = np.array([[word_to_int_dict [word]待审核单词]用于在 pended_sentences 中审核)) - -encode_sentences [0] +```py +encoded_sentences = np.array([[word_to_int_dict[word] for word in review] for review in padded_sentences]) +encoded_sentences[0] +``` 我们的编码语句表示如下: @@ -305,83 +292,77 @@ encode_sentences [0] 现在,我们将演示如何使用 PyTorch 从头开始对该模型进行编码。 我们创建了一个名为`SentimentLSTM`的类,该类继承自`nn.Module`类。 我们将`__init__`参数定义为 vocab 的大小,模型将具有的 LSTM 层数以及模型隐藏状态的大小: -SentimentLSTM(nn.Module)类: - -定义 __init __(self,n_vocab,n_embed,n_hidden,n_output,n_layers,drop_p = 0.8): - -super().__ init __() - -self.n_vocab = n_vocab - -self.n_layers = n_layers - -self.n_hidden = n_hidden +```py +class SentimentLSTM(nn.Module): +     +    def __init__( + self, n_vocab, n_embed, + n_hidden, n_output, n_layers, + drop_p = 0.8 + ): +        super().__init__() +         +        self.n_vocab = n_vocab   +        self.n_layers = n_layers +        self.n_hidden = n_hidden +``` 然后,我们定义网络的每个层。 首先,我们定义嵌入层,该层的词汇量为字长,嵌入向量的大小为`n_embed`超参数。 我们的 LSTM 层是使用嵌入层的输出向量大小,模型的隐藏状态的长度以及 LSTM 层将具有的层数来定义的。 我们还添加了一个参数来指定可以对 LSTM 进行批量数据训练,并添加一个参数以允许我们通过辍学实现网络正则化。 我们用概率`drop_p`(将在模型创建时指定的超参数)定义另外一个辍学层,以及对最终全连接层和输出/预测节点的定义(具有 S 型激活函数) : -self.embedding = nn.Embedding(n_vocab,n_embed) - -self.lstm = nn.LSTM(n_embed,n_hidden,n_layers,batch_first = True,辍学= drop_p) - -self.dropout = nn.Dropout(drop_p) - -self.fc = nn.Linear(n_hidden,n_output) - -self.sigmoid = nn.Sigmoid() +```py +self.embedding = nn.Embedding(n_vocab, n_embed) +        self.lstm = nn.LSTM( + n_embed, n_hidden, n_layers, + batch_first = True, dropout = drop_p + ) +        self.dropout = nn.Dropout(drop_p) +        self.fc = nn.Linear(n_hidden, n_output) +        self.sigmoid = nn.Sigmoid() +``` 接下来,我们需要在模型类中定义正向传递。 在此前向传递中,我们只是将一层的输出链接在一起,成为下一层的输入。 在这里,我们可以看到我们的嵌入层将`input_words`作为输入并输出了嵌入的单词。 然后,我们的 LSTM 层将嵌入的单词作为输入并输出`lstm_out`。 唯一的区别是,我们使用`view()`将 LSTM 输出中的张量整形为正确的大小,以输入到全连接层中。 重塑隐藏层的输出以匹配输出节点的输出也是如此。 请注意,我们的输出将返回`class = 0`和`class = 1`的预测,因此我们将输出切为仅返回`class = 1`的预测— 也就是说,我们的句子为正的概率为: -def 转发(自己,input_words): - -Embedded_words = self.embedding(input_words) - -lstm_out,h = self.lstm(embedded_words) - -lstm_out = self.dropout(lstm_out) - -lstm_out = lstm_out.contiguous()。view(-1,self.n_hidden) - -fc_out = self.fc(lstm_out) - -sigmoid_out = self.sigmoid(fc_out) - -sigmoid_out = sigmoid_out.view(batch_size,-1) - -sigmoid_last = sigmoid_out [:, -1] - -返回 sigmoid_last,h +```py +def forward (self, input_words): +                           +        embedded_words = self.embedding(input_words) +        lstm_out, h = self.lstm(embedded_words) +        lstm_out = self.dropout(lstm_out) +        lstm_out = lstm_out.contiguous().view(-1, self.n_hidden) +        fc_out = self.fc(lstm_out)                   +        sigmoid_out = self.sigmoid(fc_out)               +        sigmoid_out = sigmoid_out.view(batch_size, -1)   +         +        sigmoid_last = sigmoid_out[:, -1] +         +        return sigmoid_last, h +``` 我们还定义了一个名为`init_hidden()`的函数,该函数使用批量大小的尺寸初始化隐藏层。 如果我们愿意的话,这使我们的模型可以同时训练和预测许多句子,而不仅仅是一次训练一个句子。 请注意,此处将`device`定义为`"cpu"`,以便在本地处理器上运行它。 但是,也可以将其设置为启用了 CUDA 的 GPU,以便在拥有 GPU 的 GPU 上进行训练: -def init_hidden(自己,batch_size): - -设备=“ cpu” - -权重= next(self.parameters())。data - -h =(weights.new(self.n_layers,batch_size,\ - -self.n_hidden).zero _()。to(device),\ - -weights.new(self.n_layers,batch_size,\ - -self.n_hidden).zero _()。to(device)) - -返回 h +```py +def init_hidden (self, batch_size): +         +        device = "cpu" +        weights = next(self.parameters()).data +        h = (weights.new(self.n_layers, batch_size,\ +                 self.n_hidden).zero_().to(device),\ +             weights.new(self.n_layers, batch_size,\ +                 self.n_hidden).zero_().to(device)) +         +        return h +``` 然后,我们通过创建`SentimentLSTM`类的新实例来初始化模型。 我们传递 vocab 的大小,嵌入的大小,隐藏状态的大小,输出大小以及 LSTM 中的层数: -n_vocab = len(word_to_int_dict) - +```py +n_vocab = len(word_to_int_dict) n_embed = 50 - n_hidden = 100 - n_output = 1 - n_layers = 2 - -净=情感 LSTM(n_vocab,n_embed,n_hidden,n_output,n_layers) +net = SentimentLSTM(n_vocab, n_embed, n_hidden, n_output, n_layers) +``` 现在我们已完全定义了模型架构,是时候开始训练我们的模型了。 @@ -391,107 +372,86 @@ n_layers = 2 我们已经将模型输入(`x`)定义为`encode_sentences`,但是我们还必须定义模型输出(`y`)。 我们这样做很简单,如下所示: -标签= np.array([int(x)for data in data ['Sentiment']。values]) +```py +labels = np.array([int(x) for x in data['Sentiment'].values]) +``` 接下来,我们定义训练和验证比率。 在这种情况下,我们将在 80% 的数据上训练模型,在另外 10% 的数据上进行验证,最后在剩余的 10% 的数据上进行测试: +```py train_ratio = 0.8 - -valid_ratio =(1-train_ratio)/ 2 +valid_ratio = (1 - train_ratio)/2 +``` 然后,我们使用这些比率对数据进行切片并将其转换为张量,然后转换为张量数据集: -总计= len(encoded_sentences) - -train_cutoff = int(总* train_ratio) - -valid_cutoff = int(总计*(1-valid_ratio)) - -train_x,train_y = torch.Tensor(encoded_sentences [:train_cutoff])。long(),torch.Tensor(labels [:train_cutoff])。long() - -valid_x,valid_y = Torch.Tensor(encoded_sentences [train_cutoff:valid_cutoff])。long(),torch.Tensor(labels [train_cutoff:valid_cutoff])。long() - -test_x,test_y = torch.Tensor(encoded_sentences [valid_cutoff:])。long(),torch.Tensor(labels [valid_cutoff:]) - -train_data = TensorDataset(train_x,train_y) - -valid_data = TensorDataset(valid_x,valid_y) - -test_data = TensorDataset(test_x,test_y) +```py +total = len(encoded_sentences) +train_cutoff = int(total * train_ratio) +valid_cutoff = int(total * (1 - valid_ratio)) +train_x, train_y = torch.Tensor(encoded_sentences[:train_cutoff]).long(), torch.Tensor(labels[:train_cutoff]).long() +valid_x, valid_y = torch.Tensor(encoded_sentences[train_cutoff : valid_cutoff]).long(), torch.Tensor(labels[train_cutoff : valid_cutoff]).long() +test_x, test_y = torch.Tensor(encoded_sentences[valid_cutoff:]).long(), torch.Tensor(labels[valid_cutoff:]) +train_data = TensorDataset(train_x, train_y) +valid_data = TensorDataset(valid_x, valid_y) +test_data = TensorDataset(test_x, test_y) +``` 然后,我们使用这些数据集创建 PyTorch `DataLoader`对象。 `DataLoader`允许我们使用`batch_size`参数批量处理数据集,从而可以轻松地将不同的批量大小传递给我们的模型。 在这种情况下,我们将使其保持简单,并设置`batch_size = 1`,这意味着我们的模型将针对单个句子进行训练,而不是使用大量数据。 我们还选择随机调整`DataLoader`对象,以便数据以随机顺序(而不是每个时期相同)通过神经网络传递,从而有可能从训练顺序中消除任何有偏差的结果: +```py batch_size = 1 - -train_loader = DataLoader(train_data,batch_size = batch_size,随机播放= True) - -valid_loader = DataLoader(valid_data,batch_size = batch_size,随机播放= True) - -test_loader = DataLoader(test_data,batch_size = batch_size,随机播放= True) +train_loader = DataLoader(train_data, batch_size = batch_size, shuffle = True) +valid_loader = DataLoader(valid_data, batch_size = batch_size, shuffle = True) +test_loader = DataLoader(test_data, batch_size = batch_size, shuffle = True) +``` 现在我们已经为我们的三个数据集定义了`DataLoader`对象,接下来我们定义训练循环。 我们首先定义许多超参数,这些超参数将在我们的训练循环中使用。 最重要的是,我们将损失函数定义为二进制交叉熵(因为我们正在处理单个二进制类别的预测),并且将优化器定义为 **Adam**,学习率为`0.001`。 我们还定义了模型以运行较短的时间(以节省时间),并设置`clip = 5`来定义梯度裁剪: +```py print_every = 2400 - -步= 0 - +step = 0 n_epochs = 3 - -剪辑= 5 - -准则= nn.BCELoss() - -优化程序= optim.Adam(net.parameters(),lr = 0.001) +clip = 5   +criterion = nn.BCELoss() +optimizer = optim.Adam(net.parameters(), lr = 0.001) +``` 我们的训练循环的主体如下所示: -对于范围内的纪元(n_epochs): - -h = net.init_hidden(批量大小) - -对于输入,train_loader 中的标签: - -步骤+ = 1 - -net.zero_grad() - -输出,h = net(输入) - -损失=准则(output.squeeze(),labels.float()) - -loss.backward() - -nn.utils.clip_grad_norm(net.parameters(),剪辑) - -Optimizer.step() +```py +for epoch in range(n_epochs): +    h = net.init_hidden(batch_size) +     +    for inputs, labels in train_loader: +        step += 1   +        net.zero_grad() +        output, h = net(inputs) +        loss = criterion(output.squeeze(), labels.float()) +        loss.backward() +        nn.utils.clip_grad_norm(net.parameters(), clip) +        optimizer.step() +``` 在这里,我们只训练了多个时期的模型,对于每个时期,我们首先使用批量大小参数初始化隐藏层。 在这种情况下,我们设置`batch_size = 1`,因为我们一次只训练我们的模型一个句子。 对于火车装载机中的每批输入语句和标签,我们首先将梯度归零(以防止它们累积),并使用模型的当前状态使用数据的正向传递来计算模型输出。 然后使用此输出,使用模型的预测输出和正确的标签来计算损失。 然后,我们通过网络对该损失进行反向传递,以计算每个阶段的梯度。 接下来,我们使用`grad_clip_norm()`函数裁剪梯度,因为这将阻止梯度爆炸,如本章前面所述。 我们定义了`clip = 5`,这意味着任何给定节点的最大梯度为`5`。 最后,我们通过调用`optimizer.step()`,使用在向后传递中计算出的梯度来更新权重。 如果我们自己运行此循环,我们将训练我们的模型。 但是,我们想在每个时期之后评估模型的性能,以便根据验证数据集确定模型的性能。 我们这样做如下: -if(步骤% print_every)== 0: - -net.eval() - -valid_losses = [] - -对于 v_inputs,valid_loader 中的 v_labels: - -v_output,v_h = net(v_inputs) - -v_loss =条件(v_output.squeeze(),v_labels.float()) - -valid_losses.append(v_loss.item()) - -print(“ Epoch:{} / {}”。format((epoch + 1),n_epochs), - -“步骤:{}”。format(步骤), - -“训练损失:{:. 4f}”。format(loss.item()), - -“验证损失:{:. 4f}”。format(np。mean(valid_losses))) - -net.train() +```py +if (step % print_every) == 0:             +            net.eval() +            valid_losses = [] +            for v_inputs, v_labels in valid_loader: +                       +                v_output, v_h = net(v_inputs) +                v_loss = criterion(v_output.squeeze(),                                    v_labels.float()) +                valid_losses.append(v_loss.item()) +            print("Epoch: {}/{}".format((epoch+1), n_epochs), +                  "Step: {}".format(step), +                  "Training Loss: {:.4f}".format(loss.item()), +                  "Validation Loss: {:.4f}".format(np.                                     mean(valid_losses))) +            net.train() +``` 这意味着在每个时期结束时,我们的模型都会调用`net.eval()`冻结模型的权重,并像以前一样使用我们的数据进行前向传递。 请注意,当我们处于评估模式时,也不会应用辍学。 但是,这次,我们使用验证加载程序,而不是使用训练数据加载程序。 通过这样做,我们可以在我们的验证数据集中计算模型当前状态的总损失。 最后,我们打印结果并调用`net.train()`解冻模型的权重,以便我们可以在下一个纪元再次进行训练。 我们的输出看起来像,如下所示: @@ -501,37 +461,31 @@ net.train() 最后,我们可以保存我们的模型以备将来使用: -torch.save(net.state_dict(),'model.pkl') +```py +torch.save(net.state_dict(), 'model.pkl') +``` 在为三个时期训练了我们的模型之后,我们注意到了两个主要方面。 我们将首先从好消息开始-我们的模型正在学习一些东西! 我们的训练损失不仅下降了,而且在每个时期之后,我们在验证集上的损失也下降了。 这意味着我们的模型仅在三个时期后就可以更好地预测看不见的数据的情绪! 坏消息是,我们的模型过拟合。 我们的训练损失比验证损失要低得多,这表明虽然我们的模型已经学会了如何很好地预测训练数据集,但这并不能推广到看不见的数据集。 预期会发生这种情况,因为我们使用的训练数据非常少(仅 2,400 个训练语句)。 当我们训练整个嵌入层时,很可能许多单词在训练集中只出现一次,而在验证集中却没有出现,反之亦然,这使得该模型几乎不可能归纳出其中所有不同的单词 我们的语料库。 在实践中,我们希望在更大的数据集上训练我们的模型,以使我们的模型学习如何更好地归纳。 我们还在很短的时间内训练了该模型,并且没有执行超参数调整来确定模型的最佳迭代。 随意尝试更改模型中的某些参数(例如训练时间,隐藏状态大小,嵌入大小等),以提高模型的性能。 尽管我们的模型过拟合,但它仍然学到了一些东西。 现在,我们希望在最终的测试数据集上评估我们的模型。 我们使用之前定义的测试加载器对数据执行了最后一次传递。 在此过程中,我们遍历所有测试数据并使用最终模型进行预测: -net.eval() - +```py +net.eval() test_losses = [] - num_correct = 0 - -对于输入,test_loader 中的标签: - -test_output,test_h = net(输入) - -损失=标准(test_output,标签) - -test_losses.append(loss.item()) - -preds = torch.round(test_output.squeeze()) - -Correct_tensor = preds.eq(labels.float()。view_as(preds)) - -正确= np.squeeze(correct_tensor.numpy()) - -num_correct + = np.sum(正确) - -print(“测试损失:{:. 4f}”。format(np.mean(test_losses))) - -print(“测试精度:{:. 2f}”。format(num_correct / len(test_loader.dataset))) +for inputs, labels in test_loader: +    test_output, test_h = net(inputs) +    loss = criterion(test_output, labels) +    test_losses.append(loss.item()) +     +    preds = torch.round(test_output.squeeze()) +    correct_tensor = preds.eq(labels.float().view_as(preds)) +    correct = np.squeeze(correct_tensor.numpy()) +    num_correct += np.sum(correct) +     +print("Test Loss: {:.4f}".format(np.mean(test_losses))) +print("Test Accuracy: {:.2f}".format(num_correct/len(test_loader.dataset))) +``` 我们在测试数据集上的表现如下: @@ -545,61 +499,54 @@ print(“测试精度:{:. 2f}”。format(num_correct / len(test_loader. 既然我们已经有了训练有素的模型,那么应该可以对一个新句子重复我们的预处理步骤,并将其传递给我们的模型,并对其情感进行预测。 我们首先创建一个函数来预处理输入句子以预测: -def preprocess_review(审查): - -review = review.translate(str.maketrans('',``,标点符号))。lower()。rstrip() - -标记化= word_tokenize(评论) - -如果 len(标记)> = 50: - -评论=标记化[:50] - -其他: - -review = ['0'] *(50 镜头(已标记))+已标记 - -最终= [] - -对于审查令牌: - -尝试: - -final.append(word_to_int_dict [令牌]) - -除: - -final.append(word_to_int_dict ['']) - -返回最终 +```py +def preprocess_review(review): +    review = review.translate(str.maketrans('', '', punctuation)).lower().rstrip() +    tokenized = word_tokenize(review) +    if len(tokenized) >= 50: +        review = tokenized[:50] +    else: +        review= ['0']*(50-len(tokenized)) + tokenized +     +    final = [] +     +    for token in review: +        try: +            final.append(word_to_int_dict[token]) +             +        except: +            final.append(word_to_int_dict['']) +         +    return final +``` 我们删除标点符号和尾随空格,将字母转换为小写,并像以前一样对输入句子进行标记化。 我们将句子填充到长度为`50`的序列上,然后使用我们的预先计算的字典将的标记转换为数值。 请注意,我们的输入内容可能包含我们的网络从未见过的新词。 在这种情况下,我们的函数会将它们视为空令牌。 接下来,我们创建实际的`predict()`函数。 我们预处理输入检查,将其转换为张量,然后将其传递给数据加载器。 然后,我们遍历该数据加载器(即使它仅包含一个句子),并通过我们的网络进行审查以获得预测。 最后,我们评估我们的预测并打印出正面还是负面的评价: -def 预测(评论): - -net.eval() - -单词= np.array([preprocess_review(review)]) - -padded_words = torch.from_numpy(words) - -pred_loader = DataLoader(填充字,batch_size = 1,随机播放= True) - -对于 pred_loader 中的 x: - -输出= net(x)[0] .item() - -msg =“这是正面评价。” 如果输出> = 0.5,否则“这是负面评价”。 - -打印(味精) - -print('Prediction ='+ str(输出)) +```py +def predict(review): +    net.eval() +    words = np.array([preprocess_review(review)]) +    padded_words = torch.from_numpy(words) +    pred_loader = DataLoader(padded_words, batch_size = 1, shuffle = True) +    for x in pred_loader: +        output = net(x)[0].item() +     +    msg = ( + "This is a positive review." + if output >= 0.5 + else "This is a negative review." + ) +    print(msg) +    print('Prediction = ' + str(output)) +``` -最后,我们在我们的评论中仅调用`predict()`进行预测: +最后,我们在评论上调用`predict()`来做出预测: -预言(“电影很好”) +```py +predict("The film was good") +``` 这将产生以下输出: @@ -609,7 +556,9 @@ print('Prediction ='+ str(输出)) 我们还尝试对负值使用`predict()`: -预测(“不好”) +```py +predict("It was not good") +``` 结果为以下输出: @@ -629,11 +578,15 @@ print('Prediction ='+ str(输出)) 第一步是在 Heroku 上创建一个免费帐户并安装 Heroku 应用。 然后,在命令行中键入以下命令: -heroku 登录 +```py +heroku login +``` 使用您的帐户详细信息登录。 然后,通过键入以下命令来创建一个新的 **heroku** 项目: -heroku 创建情感分析烧瓶 API +```py +heroku create sentiment-analysis-flask-api +``` 请注意,所有项目名称都必须是唯一的,因此您将需要选择一个非`sentiment-analysis-flask-api`的项目名称。 @@ -645,21 +598,28 @@ heroku 创建情感分析烧瓶 API 首先,在命令行中,为 flask API 创建新文件夹并导航至该文件夹: +```py mkdir flaskAPI - cd flaskAPI +``` 然后,在文件夹中创建一个虚拟环境。 这将是您的 API 将使用的 Python 环境: +```py python3 -m venv vir_env +``` 在您的环境中,使用`pip`安装所需的所有软件包。 这包括您在模型程序中使用的所有软件包,例如 NLTK, **Pandas**,NumPy 和 PyTorch,以及运行 API 所需的软件包,例如 Flask 和 Gunicorn: -pip 安装 nltk 熊猫 numpy 火炬瓶 gunicorn +```py +pip install nltk pandas numpy torch flask gunicorn +``` 然后,我们创建 API 将使用的需求列表。 请注意,当我们将其上传到 Heroku 时,Heroku 将自动下载并安装此列表中的所有软件包。 我们可以通过键入以下命令来做到这一点: -点冻结> requirements.txt +```py +pip freeze > requirements.txt +``` 我们需要进行的一项调整是,用以下内容替换`requirements.txt`文件中的`torch`行: @@ -669,13 +629,12 @@ https://download.pytorch.org/whl/cpu/torch-1.3.1%2Bcpu-cp37-cp37m-linux_x86_64.w 这是仅包含 CPU 实现的 PyTorch 版本的 wheel 文件的链接。 包括完整 GPU 支持的 PyTorch 完整版的大小超过 500 MB,因此它将无法在免费的 Heroku 群集上运行。 使用此更紧凑的 PyTorch 版本意味着您仍然可以在 Heroku 上使用 PyTorch 运行模型。 最后,我们在文件夹中创建另外三个文件,以及模型的最终目录: -触摸 app.py - -触摸 Procfile - -触摸 wsgi.py - -mkdir 模型 +```py +touch app.py +touch Procfile +touch wsgi.py +mkdir models +``` 现在,我们已经创建了 Flash API 所需的所有文件,并且准备开始对文件进行调整。 @@ -685,81 +644,79 @@ mkdir 模型 1. 我们首先进行所有的导入,并创建一个`predict`路由。这样我们就可以用`predict`参数来调用我们的 API,以便在 API 中运行`predict()`方法。 - 进口烧瓶 - - 从烧瓶导入烧瓶,jsonify,请求 - - 导入 json - - 将熊猫作为 pd 导入 - - 从字符串导入标点 - - 将 numpy 导入为 np - - 进口火炬 - - 从 nltk.tokenize 导入 word_tokenize - - 从 torch.utils.data 导入 TensorDataset,DataLoader - - 从火炬进口 nn - - 从火炬进口看好 - - app = Flask(__ name__) - - @ app.route('/ predict',Methods = ['GET']) + ```py + import flask + from flask import Flask, jsonify, request + import json + import pandas as pd + from string import punctuation + import numpy as np + import torch + from nltk.tokenize import word_tokenize + from torch.utils.data import TensorDataset, DataLoader + from torch import nn + from torch import optim + app = Flask(__name__) + @app.route('/predict', methods=['GET']) + ``` 2. 接下来,我们在`app.py`文件中定义·predict()方法。这在很大程度上是我们模型文件的翻版,所以为了避免代码重复,建议你查看本章“技术需求”部分链接的 GitHub 仓库中完成的·app.py 文件。你会发现有几行额外的内容。首先,在`preprocess_review()`函数中,我们会看到以下几行。 - 使用 open('models / word_to_int_dict.json')作为句柄: - - word_to_int_dict = json.load(句柄) + ```py + with open('models/word_to_int_dict.json') as handle: + word_to_int_dict = json.load(handle) + ``` 这将使用我们在主模型笔记本中计算的`word_to_int`字典并将其加载到我们的模型中。 这样我们的词索引与我们训练的模型一致。 然后,我们使用该词典将输入文本转换为编码序列。 确保从原始笔记本输出中提取`word_to_int_dict.json`文件,并将其放置在`model`目录中。 3. 同样,我们也必须从我们训练的模型中加载权重。我们首先定义我们的`SentimentLSTM`类,然后使用`torch.load`加载我们的权重。我们将使用我们原始笔记本中的`.pkl`文件,所以一定要把它也放在`models`目录下。 - 模型=情绪 LSTM(5401,50,100,1,2) - - model.load_state_dict(torch.load(“ models / model_nlp.pkl”)) + ```py + model = SentimentLSTM(5401, 50, 100, 1, 2) + model.load_state_dict(torch.load("models/model_nlp.pkl")) + ``` 4. 我们还必须定义我们 API 的输入和输出。我们希望我们的模型能够从 API 中获取输入,并将其传递给我们的`precess_review()`函数。我们使用`request.get_json()`来完成。 - request_json = request.get_json() - - 我= request_json ['input'] - - 单词= np.array([preprocess_review(review = i)]) + ```py + request_json = request.get_json() + i = request_json['input'] + words = np.array([preprocess_review(review=i)]) + ``` 5. 为了定义我们的输出,我们返回一个 JSON 响应,由我们模型的输出和响应代码`200`组成,这就是我们预测函数返回的内容。 - 输出= model(x)[0] .item() - - 响应= json.dumps({'响应':输出}) - - 返回响应,200 + ```py + output = model(x)[0].item() + response = json.dumps({'response': output}) + return response, 200 + ``` 6. 随着我们的应用程序主体的完成,我们还必须添加两件额外的事情来使我们的 API 运行。首先,我们必须在`wsgi.py`文件中添加以下内容。 - 从应用程序导入应用程序作为应用程序 - - 如果 __name__ ==“ __main__”: - - application.run() + ```py + from app import app as application + if __name__ == "__main__": +     application.run() + ``` 7. 最后,在我们的 Procfile 中添加以下内容。 - 网址:gunicorn app:app --preload + ```py + web: gunicorn app:app --preload + ``` 这就是应用程序运行所需要的全部。 我们可以先使用以下命令在本地启动 API,以测试 API 是否运行: -古尼康--bind 0.0.0.0:8080 wsgi:application -w 1 +```py +gunicorn --bind 0.0.0.0:8080 wsgi:application -w 1 +``` API 在本地运行后,我们可以通过向其传递一个句子来预测结果来向 API 发出请求: -curl -X GET http://0.0.0.0:8080/predict -H“ Content-Type:application / json” -d'{“ input”:“电影很好”}' +```py +curl -X GET http://0.0.0.0:8080/predict -H "Content-Type: application/json" -d '{"input":"the film was good"}' +``` 如果一切正常,您应该从 API 收到预测。 现在我们已经在本地进行 API 预测,现在是将其托管在 Heroku 上的时候了,以便我们可以在云中进行预测。 @@ -767,29 +724,33 @@ curl -X GET http://0.0.0.0:8080/predict -H“ Content-Type:application / json 我们首先需要将文件提交到 Heroku,其方式类似于使用 GitHub 提交文件的方式。 我们只需运行以下命令,即可将工作中的`flaskAPI`目录定义为`git`文件夹: -git 初始化 +```py +git init +``` 在此文件夹中,我们将以下代码添加到`.gitignore`文件中,这将阻止我们将不必要的文件添加到 Heroku 存储库中: +```py vir_env - -__pycache __ / - +__pycache__/ .DS_Store +``` 最后,我们添加第一个`commit`函数,并将其推送到 **heroku** 项目中: -git 添加 -一种 - -git commit -m'在此处提交消息' - -git push heroku 主 +```py +git add . -A +git commit -m 'commit message here' +git push heroku master +``` 编译可能会花费一些时间,因为系统不仅必须将所有文件从本地目录复制到 Heroku,而且 Heroku 将自动构建您定义的环境,安装所有必需的程序包并运行您的 API。 现在,如果一切正常,您的 API 将自动在 Heroku 云上运行。 为了做出预测,您只需使用项目名称而不是`sentiment-analysis-flask-api`向 API 发出请求: -curl -X GET https://sentiment-analysis-flask-api.herokuapp.com/predict -H“内容类型:application / json” -d'{“输入”:“电影很好”}' +```py +curl -X GET https://sentiment-analysis-flask-api.herokuapp.com/predict -H "Content-Type: application/json" -d '{"input":"the film was good"}' +``` 您的应用程序现在将根据模型返回预测。 恭喜,您现在已经学会了如何从头训练 LSTM 模型,将其上传到云中以及使用它进行预测! 展望未来,本教程有望成为您训练自己的 LSTM 模型并将其自己部署到云的基础。 -- GitLab