自然语言处理N天-AllenNLP学习(完整实例,预测论文发表场合-中篇)

新建 Microsoft PowerPoint 演示文稿 (2).jpg

本文基于AllenNLP中文教程<AllenNLP 使用教程>,仅作为个人学习记录,作者也是一位骚话博主,和原文对照发现不少骚话蛤蛤蛤
有一篇帖子总结了一下学习处理NLP问题中间的坑。NLP数据预处理要比CV的麻烦很多。

  • 去除停用词,建立词典,加载各种预训练词向量,Sentence -> Word ID -> Word Embedding的过程(Tobias Lee:文本预处理方法小记),其中不仅需要学习pytorch,可能还要学习spacy,NLTK,numpy,pandas,tensorboardX等常用python包。
  • 用到RNN时,还要经过pad,pack,pad的过程,像这样的很多函数在使用时需要有数学基础加上简单的实践,感觉对一个新人来说,高维数据的流动有点抽象,不容易理解。
  • 数据集的读取,tensorboardX的使用。。。。各种东西要学习。在运行别人的代码后打印出信息,不仅看着上档次,而且可以看到很多实用的信息。。。

AllenNLP是在pytorch基础上的封装,它的目标是处理NLP任务,可以减少很多额外的学习。

  • 分词,帮你用spacy,NLTK,或者简单的按空格分词处理。
  • 数据集的读取,它内置了很多数据集的读取,你可以在通过学习它的读取方式,在它的基础上对自己需要的数据集进行读取。 、
  • 在Sentence -> Word ID -> Word Embedding的过程中,Glove,ELMo,BERT等常用的都可以直接使用,需要word,char粒度的都可以。
  • log打印输出,在内置的输出项之外,你可以很方便地加入想要输出的信息。模型的各个组件中的参数都可以存在一个json/jsonnet文件中,修改参数进行实验很方便。

3. 完整实例,预测论文发表场合

第一部分 数据集和模型

第四步 构建Model

完成模型构建之后,就可以进行测试了,在这里先定义测试文件。
注意源文件中,测试文件被单独放在一个文件夹中,有之前DatasetReader以及模型、预测的测试文件。看了一下确实原始数据和我下载的不一样,在这里以教程的为准。

from allennlp.common.testing import ModelTestCase

class AcademicPaperClassifierTest(ModelTestCase):
    def setUp(self):
        super(AcademicPaperClassifierTest,self).setUp()
        self.set_up_model(
            'tests/fixtures/academic_paper_classifier.json',
            'tests/fixtures/s2_papers.jsonl'
        )

    def test_model_can_train_save_and_load(self):
        self.ensure_model_can_train_save_and_load(self.param_file)

这个测试文件使用了allennlp.common.testing.ModelTestCase类,测试的是模型能够训练、保存、恢复、预测。为了能够很好的使用这些测试,我们还需要定义一个测试配置文件;构造一个更小的输入文件。

  • tests/fixtures/academic_paper_classifier.json
  • tests/fixtures/s2_papers.jsonl

这个方法不错,之前测试都是直接加载数据运行。

接下来就是模型了,注意这两个输入都转换成序号。那么我们下一步自然就是需要把序号转换成对应的embeddings。

  • inputs:title 和 abstract
  • output:label
    模型的结构是由AllenNLP封装好的。
模型构造函数
from typing import Dict, Optional

import numpy
from overrides import overrides
import torch
import torch.nn.functional as F

from allennlp.common.checks import ConfigurationError
from allennlp.data import Vocabulary
from allennlp.modules import FeedForward, Seq2VecEncoder, TextFieldEmbedder
from allennlp.models.model import Model
from allennlp.nn import InitializerApplicator, RegularizerApplicator
from allennlp.nn import util
from allennlp.training.metrics import CategoricalAccuracy


@Model.register("paper_classifier")
class AcademicPaperClassifier(Model):
    '''
    这个``Model``为学术论文执行文本分类。我们假设我们有一个标题和一个摘要,我们预测一些输出标签。
    基本模型结构:我们将嵌入标题和摘要,并使用单独的Seq2VecEncoders对它们进行编码,获得表示每个内容的单个向量。然后我们将这两个向量连接起来,并通过前馈网络传递结果,我们将使用它作为每个标签的分数。
    '''

    def __init__(self, vocab: Vocabulary,
                 text_field_embedder: TextFieldEmbedder,
                 title_encoder: Seq2VecEncoder,
                 abstract_encoder: Seq2VecEncoder,
                 calssifier_feedforward: FeedForward,
                 initializer: InitializerApplicator = InitializerApplicator(),
                 regularizer: Optional[RegularizerApplicator] = None

                 ) -> None:
       super(AcademicPaperClassifier,self).__init__(vocab,regularizer)
       self.text_field_embeeder=text_field_embedder
       self.abstract_encoder=abstract_encoder
       self.title_encoder=title_encoder
       self.abstract_encoder=abstract_encoder
       self.calssifier_feedforward=calssifier_feedforward
       self.metrics = {
           "accuracy": CategoricalAccuracy(),
           "accuracy3": CategoricalAccuracy(top_k=3)
       }
       self.loss = torch.nn.CrossEntropyLoss()
       initializer(self)
模型前馈神经网络

类似DatasetReader注册模型,方便配置文件的查找。注意,这里出现了一个奇怪的参数Vocabulary,这个参数顾名思义就是我们的数据字典,但是我们在哪里构造的呢?答案是不用构造!写model的时候顺手写上去就行啦,这个是Allennlp帮助我们写好的。
同时这个数据字典其实是个复合字典,包括所有TextField的字典,以及LabelField自己单独的字典。然后需要介绍的参数就是TextFieldEmbedder为所有的TextField类共同建立了一个embeddings。

利用这个embeddings以及我们输入的序号,我们就能够获得一个向量组成的序列。下一步就是对这个序列进行变化。在这里我们使用的是Seq2VecEncoder。这个Encoder可以有很多的变化,在这里我们使用的是最最简单的一种,就是bag of embeddings,直接求平均。当然啦,我们也可以使用什么CNN啦,RNN,Transformer模型。
前馈神经网络呢也是一个预先定义好的Module,我们可以修改这个网络的深度宽度激活函数。InitializerApplicator包含着所有参数的基本初始化方法。如果你想自定义初始化,就需要时候用RegularizerApplicator

    def forward(self,
                title: Dict[str, torch.LongTensor],
                abstract:Dict[str,torch.LongTensor],
                label:torch.LongTensor=None
                )-> Dict[str, torch.Tensor]:
        embedded_title=self.text_field_embeeder(title)
        title_mask = util.get_text_field_mask(title)
        encoded_title = self.title_encoder(embedded_title, title_mask)

        embedded_abstract = self.text_field_embedder(abstract)
        abstract_mask = util.get_text_field_mask(abstract)
        encoded_abstract = self.abstract_encoder(embedded_abstract, abstract_mask)

        logits = self.classifier_feedforward(torch.cat([encoded_title, encoded_abstract], dim=-1))
        class_probabilities = F.softmax(logits)

        output_dict = {"class_probabilities": class_probabilities}

        if label is not None:
            loss = self.loss(logits, label.squeeze(-1))
            for metric in self.metrics.values():
                metric(logits, label.squeeze(-1))
            output_dict["loss"] = loss

        return output_dict

我们首先注意到的应该是这个函数的参数。在这里,参数的名字一定要和DatasetReader中定义的名字保持一致。AllenNLP在这里将会自动的利用你的DatasetReader并且把数据组织成batches的形式。注意,forward函数接收的参数正是一个batch的数据
注意,把labels也传递给forward函数用于计算损失函数。在训练的时候,我们的模型会主动的去寻找这个loss,然后自动的反向传播回去,然后更改参数。同时我们也应该注意到,这个参数是可以为空的,这主要是为了应对prediction的情况。这个将会在后面章节中进行介绍。
输入的类型。label是一个[batch_size,1]大小的tensor。title和abstract两个是TextField类型的,这些TextField转换为字典类型的。这个新的字典呢可能包括了单词id,字母array或者pos标签ID什么的。embedder直接一股脑的扔进去就能够帮你完成转换过程。这就意味着我们TextFieldEmbedder必须和TextField完全对应。对接的过程又是在配置文件中完成的。

模型的decode和metric

现在我们已经理解了模型的基本输入,来看看它的基本逻辑。

  1. 找到title和abstract的embeddings,然后对这些向量进行操作。注意我们需要利用一个叫masks的变量来标识哪些元素仅仅是用来标识边界的,而不需要模型考虑。
  2. 我们对这些向量进行了一通操作之后,生成了一个向量。将这个向量输入一个前馈神经网络中就可以得到logits(预测为各个类的概率),有了这个概率我们就可以得到最终预测的结果。
  3. 如果是训练过程的话,我们还需要计算损失和评价标准。

decode函数包括两个功能

  • 接收forward函数的返回值,并且对这个返回值进行操作,比如说算出具体是那个词啊等等。
  • 将数字变成字符,方便阅读。好啦,至此我们的模型已经构建好啦,现在我们可以测试啦。
    @overrides
    def decode(self, output_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
        class_probabilities = F.softmax(output_dict['logits'], dim=-1)
        output_dict['class_probabilities'] = class_probabilities

        predictions = class_probabilities.cpu().data.numpy()
        argmax_indices = numpy.argmax(predictions, axis=-1)
        labels = [self.vocab.get_token_from_index(x, namespace="labels")
                  for x in argmax_indices]
        output_dict['label'] = labels
        return output_dict

    @overrides
    def get_metrics(self, reset: bool = False) -> Dict[str, float]:
        return {metric_name: metric.get_metric(reset) for metric_name, metric in self.metrics.items()}

训练模型

在这里就是使用JSON完成一个配置文件。
在这里调整了训练迭代数,实际上这个实验我没跑完,自己手动写的老有问题,我怀疑是文件路径出错了,就把源文档的test下载下来,跑通了。
看样子以后要保存一个文件结构。

{
  "dataset_reader": {
    "type": "s2_papers"
  },
  "train_data_path": "https://s3-us-west-2.amazonaws.com/allennlp/datasets/academic-papers-example/train.jsonl",
  "validation_data_path": "https://s3-us-west-2.amazonaws.com/allennlp/datasets/academic-papers-example/dev.jsonl",
  "model": {
    "type": "paper_classifier",
    "text_field_embedder": {
      "token_embedders": {
        "tokens": {
          "type": "embedding",
          "pretrained_file": "https://s3-us-west-2.amazonaws.com/allennlp/datasets/glove/glove.6B.100d.txt.gz",
          "embedding_dim": 100,
          "trainable": false
        }
      }
    },
    "title_encoder": {
      "type": "lstm",
      "bidirectional": true,
      "input_size": 100,
      "hidden_size": 100,
      "num_layers": 1,
      "dropout": 0.2
    },
    "abstract_encoder": {
      "type": "lstm",
      "bidirectional": true,
      "input_size": 100,
      "hidden_size": 100,
      "num_layers": 1,
      "dropout": 0.2
    },
    "classifier_feedforward": {
      "input_dim": 400,
      "num_layers": 2,
      "hidden_dims": [200, 3],
      "activations": ["relu", "linear"],
      "dropout": [0.2, 0.0]
    }
  },
  "iterator": {
    "type": "bucket",
    "sorting_keys": [["abstract", "num_tokens"], ["title", "num_tokens"]],
    "batch_size": 64
  },
  "trainer": {
    "num_epochs": 10,
    "patience": 2,
    "cuda_device": -1,
    "grad_clipping": 5.0,
    "validation_metric": "+accuracy",
    "optimizer": {
      "type": "adagrad"
    }
  }
}

以下为训练结果

2019-03-10 17:43:05,747 - INFO - allennlp.common.util - Metrics: {
"best_epoch": 7,
"peak_cpu_memory_MB": 0,
"training_duration": "00:24:52",
"training_start_epoch": 0,
"training_epochs": 8,
"epoch": 8,
"training_accuracy": 0.9099333333333334,
"training_accuracy3": 1.0,
"training_loss": 0.24596523192334682,
"training_cpu_memory_MB": 0.0,
"validation_accuracy": 0.8095,
"validation_accuracy3": 1.0,
"validation_loss": 0.5315047986805439,
"best_validation_accuracy": 0.814,
"best_validation_accuracy3": 1.0,
"best_validation_loss": 0.5119817899540067
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容