AllenNLP入门笔记(二)

AllenNLP入门笔记第二篇,来自官方tutorial:Using AllenNLP as a Library (Part 1) - Datasets and Models。之前的样例代码只是一个Python文件,虽然已经包含了一个深度学习自然语言处理项目的全部,但是不够详细,只是提供一个感性认识。重点就是要实现两个类,DatasetReader和Model。

这篇开始,跟随官方tutorial,更详细地学习AllenNLP项目地实现。这回是一个文档分类任务,将学术论文进行分类。

目录

  • 安装AllenNLP
  • 组织模块
  • 写个`DatasetReader`
    • 测试`DatasetReader`
    • 写`DatasetReader`
      • `_read`函数
      • `text_to_instance`
      • `__init__`函数
      • JSON版本和注册器函数
  • 写模型
    • 还是测试先行
    • 真的写模型
      • 模型初始化
      • 前向传播
      • 评测函数
      • 解码函数
  • 训练模型
    • 配置文件
      • `Data`和`Trainer`
      • `Model`
    • AllenNLP命令行
      • `allennlp -h`
      • `allennlp train -h`
      • 训练命令
  • 预测
    • 创建`Predictor`
    • 测试`Predictor`
    • 执行预测
    • 运行Web Demo、定制Web Demo(留坑)
  • 总结

安装AllenNLP

安装AllenNLP就略过了,就用Miniconda创建环境,安装PyTorch(用Conda安装是为了自动配置好CUDA环境),用pip安装AllenNLP。
许多项目中会看到requirements.txt文件,这个文件可以用pip freeze > requirements.txt命令生成,用pip install -r requirements.txt安装项目的依赖。但是好像这个生成依赖文件的命令会包含环境中其他多余包(难道要手写吗?应该有根据Python项目直接生成的吧)。

allennlp==0.8.1
pytest-pythonpath

# Checks style, syntax, and other useful errors.
pylint==1.8.1

# Static type checking
mypy==0.521

# Coverage reports
pytest-cov
codecov

组织模块

构建模型部分被放到了另一篇文章中,整个项目的简易版的项目文件组织如下,数据读取模块、模型模块、预测模块,各自一个文件夹。

my_library/
├── dataset_readers
│   ├── __init__.py
│   └── semantic_scholar_papers.py
├── __init__.py
├── models
│   ├── academic_paper_classifier.py
│   └── __init__.py
└── predictors
    ├── __init__.py
    └── paper_classifier_predictor.py

3 directories, 7 files

__init__模块中,引入所有的模块:

from my_library.dataset_readers import *
from my_library.models import *
from my_library.predictors import *

写个DatasetReader

还没有说数据呢,这个任务的目标是将论文(包含标题和摘要)进行分类,总共三类(人工智能、机器学习和自然语言处理)。数据是用JSON格式组织的,举个栗子:

{
     
  "title": "A review of Web searching studies and a framework for future research",
  "paperAbstract": "Research on Web searching is at an incipient stage. ...",
  "venue": "{AI|ML|ACL}"
}

每个样例至少三个字段,标题、论文摘要、分册(vene)。数据来源是Semantic Scholar提供的开放语料库。

测试DatasetReader

这里为DatasetReader写了一个测试,虽然这不是必须的,但是用来了解AllenNLP自带的测试模块还是很有用的。

from allennlp.common.testing import AllenNlpTestCase
from allennlp.common.util import ensure_list

from my_library.dataset_readers import SemanticScholarDatasetReader


class TestSemanticScholarDatasetReader(AllenNlpTestCase):
    def test_read_from_file(self):

        reader = SemanticScholarDatasetReader()
        instances = ensure_list(reader.read('tests/fixtures/s2_papers.jsonl'))

        instance1 = {
     "title": ["Interferring", "Discourse", "Relations", "in", "Context"],
                     "abstract": ["We", "investigate", "various", "contextual", "effects"],
                     "venue": "ACL"}

        instance2 = {
     "title": ["GRASPER", ":", "A", "Permissive", "Planning", "Robot"],
                     "abstract": ["Execut", "ion", "of", "classical", "plans"],
                     "venue": "AI"}

        instance3 = {
     "title": ["Route", "Planning", "under", "Uncertainty", ":", "The", "Canadian",
                               "Traveller", "Problem"],
                     "abstract": ["The", "Canadian", "Traveller", "problem", "is"],
                     "venue": "AI"}

        assert len(instances) == 10
        fields = instances[0].fields
        assert [t.text for t in fields["title"].tokens] == instance1["title"]
        assert [t.text for t in fields["abstract"].tokens[:5]] == instance1["abstract"]
        assert fields["label"].label == instance1["venue"]
        fields = instances[1].fields
        assert [t.text for t in fields["title"].tokens] == instance2["title"]
        assert [t.text for t in fields["abstract"].tokens[:5]] == instance2["abstract"]
        assert fields["label"].label == instance2["venue"]
        fields = instances[2].fields
        assert [t.text for t in fields["title"].tokens] == instance3["title"]
        assert [t.text for t in fields["abstract"].tokens[:5]] == instance3["abstract"]
        assert fields["label"].label == instance3["venue"]

这个测试类继承自AllenNlpTestCase这个基类,主要就是把DatasetReader读取的数据转换为样本的列表(因为read函数有可能返回的是一个迭代器),然后用assert检验。从测试代码就能看出来,我们要求DatasetReader处理出的Instancefields中有title、abstract和label字段,这三个字段又都有tokens这个属性(字符串列表或者字符串)。根据测试文件可以帮助明确DataReader的功能。

DatasetReader

from typing import Dict
import json
import logging

from overrides import overrides

from allennlp.common.file_utils import cached_path
from allennlp.data.dataset_readers.dataset_reader import DatasetReader
from allennlp.data.fields import LabelField, TextField
from allennlp.data.instance import Instance
from allennlp.data.tokenizers import Tokenizer, WordTokenizer
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer

logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


@DatasetReader.register("s2_papers")
class SemanticScholarDatasetReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None) -> None:
        super().__init__(lazy)
        self._tokenizer = tokenizer or WordTokenizer()
        self._token_indexers = token_indexers or {
     "tokens": SingleIdTokenIndexer()}

    @overrides
    def _read(self, file_path):
        with open(cached_path(file_path), "r") as data_file:
            logger.info("Reading instances from lines in file at: %s", file_path)
            for line in data_file:
                line = line.strip("\n")
                if not line:
                    continue
                paper_json = json.loads(line)
                title = paper_json['title']
                abstract = paper_json['paperAbstract']
                venue = paper_json['venue']
                yield self.text_to_instance(title, abstract, venue)

    @overrides
    def text_to_instance(self, title: str, abstract: str, venue: str = None) -> Instance:  # type: ignore
        # pylint: disable=arguments-differ
        tokenized_title = self._tokenizer.tokenize(title)
        tokenized_abstract = self._tokenizer.tokenize(abstract)
        title_field = TextField(tokenized_title, self._token_indexers)
        abstract_field = TextField(tokenized_abstract, self._token_indexers)
        fields = {
     'title': title_field, 'abstract': abstract_field}
        if venue is not None:
            fields['label'] = LabelField(venue)
        return Instance(fields)

和之前的样例代码一样,DatasetReader类也是三个函数: __init___readtext_to_instance。这里用了三个Python中的特性装饰器、类方法重载和注册器。

_read函数

这里用了python自带的日志模块logging,以前一直都没有用过,需要了解了解。
其实这个函数很简单,就是一行行读取整个数据的json文件,调用同一个类中的text_to_instance生成Instance。当然,这里为了防止内存占用过多,用的是生成器,而不是直接返回一个包含所有实例的列表。

text_to_instance

这个函数才是重点。实际上,如果规模不大,这个函数完全可以整合到_read函数中。这里我们需要的是一个分词器self._tokenizer和一个分词索引self._token_indexers,类中的成员用单下划线开头是为了声明其为内部成员,一般不用于外部调用(虽然实际上Python是不管的,这只是一个命名的规范而已)。上一篇中提到过AllenNLP中的数据组织方法中,每个样本是Instance,每个Instance中有不同的Fields,有TextField,有LabelFields(就是目标)等。
所以这个函数实际上也很简单,就是对title和abstract进行分词和索引,和作为label的vene一样都组织成field。这些fields以字典的方式组织成Instance后返回。

__init__函数

根据上面两个函数,我们可以看出来初始化函数需要的参数和成员,即一个分词器和一个分词索引。分词器使用外部传入的参数,若缺省则使用SpacyTokenizer;分词索引同样来自外部参数(一个字符串到TokenIndexer的字典映射),若缺省则使用只包含“token”到SingleIdTokenIndexer的字典(这些分词器和分词索引之后再详细分辨)。

JSON版本和注册器函数

实际上,用户不需要自己写整个DatasetReader,可以写一个JSON配置文件说明结构(模型、训练器也是如此,甚至整个用AllenNLP的项目都可以用JSON文件),让AllenNLP自行生成相应的DatasetReader。因此,需要在DatasetReader的子类声明前注册函数名字,以便AllenNLP能够明确类的名字,否则Python文件被编译成虚拟机的字节码之后就不知道变成什么名字了。JSON文件的写法往后再详述。

文档中还提到可以用pytest库很方便地对DatasetReader进行测试,Python也有自带的单元测试库unittest。(不了解,后面再学吧。)

写模型

还是测试先行

from allennlp.common.testing import ModelTestCase

class AcademicPaperClassifierTest(ModelTestCase):
    def setUp(self):
        super().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,继承自AllenNlpTestCaseAllenNlpTestCase继承自Python标准库unittest.TestCase。这里在setUp函数中添加上用于测试的两个文件,一个是用于训练的参数,一个是用于测试的小数据集(每行一个JSON格式的样本,JSONL格式)。
另一个函数是test_model_can_train_save_and_load,调用ModelTestCase.ensure_model_can_train_save_and_load
这个测试类很重要,保证你用小部分数据集就先测试一下模型没有写错。

真的写模型

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):
    def __init__(self, vocab: Vocabulary,
                 text_field_embedder: TextFieldEmbedder,
                 title_encoder: Seq2VecEncoder,
                 abstract_encoder: Seq2VecEncoder,
                 classifier_feedforward: FeedForward,
                 initializer: InitializerApplicator = InitializerApplicator(),
                 regularizer: Optional[RegularizerApplicator] = None) -> None:
        super(AcademicPaperClassifier, self).__init__(vocab, regularizer)

        self.text_field_embedder = text_field_embedder
        self.num_classes = self.vocab.get_vocab_size("labels")
        self.title_encoder = title_encoder
        self.abstract_encoder = abstract_encoder
        self.classifier_feedforward = classifier_feedforward

        if text_field_embedder.get_output_dim() != title_encoder.get_input_dim():
            raise ConfigurationError("The output dimension of the text_field_embedder must match the "
                                     "input dimension of the title_encoder. Found {} and {}, "
                                     "respectively.".format(text_field_embedder.get_output_dim(),
                                                            title_encoder.get_input_dim()))
        if text_field_embedder.get_output_dim() != abstract_encoder.get_input_dim():
            raise ConfigurationError("The output dimension of the text_field_embedder must match the "
                                     "input dimension of the abstract_encoder. Found {} and {}, "
                                     "respectively.".format(text_field_embedder.get_output_dim(),
                                                            abstract_encoder.get_input_dim()))
        self.metrics = {
     
                "accuracy": CategoricalAccuracy(),
                "accuracy3": CategoricalAccuracy(top_k=3)
        }
        self.loss = torch.nn.CrossEntropyLoss()

        initializer(self)

    @overrides
    def forward(self,  # type: ignore
                title: Dict[str, torch.LongTensor],
                abstract: Dict[str, torch.LongTensor],
                label: torch.LongTensor = None) -> Dict[str, torch.Tensor]:
        embedded_title = self.text_field_embedder(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))
        output_dict = {
     'logits': logits}
        if label is not None:
            loss = self.loss(logits, label)
            for metric in self.metrics.values():
                metric(logits, label)
            output_dict["loss"] = loss

        return output_dict

    @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()}

模型类中有四个函数:__init__forwarddecodeget_metrics

模型初始化

DatasetReader一样,开头注册模型以保证我们可以用配置文件进行调用。这里需要一个Vocabulary单词表来将字符串映射到整数。这里用到的Vocabulary.get_vocab_size函数在文档里没有,它是一个获取单词表大小的函数。本例中,不同的token到索引的映射有自己的namespace,title、abstract和label都有相应的映射。
其他参数包括一个嵌入层TextFieldEmbedderDatasetReader得来的整数索引转换为张量(实际上就是一个大映射,只不过这个层经常是预训练好的);两个分别针对title和abstract的序列编码层Seq2VecEncoder;一个将编码结果计算为结果的前向传播分类器FeedForward;一个初始化器和一个正则化器。
除此之外,还有一个评测metrics字典(这里包含了两种评测,这个评测用的准确率accuracy不是最终测试模型中用的唯一指标,更不是用于训练的指标,只是用于训练阶段的参考),一个用于后向传播的损失函数。

前向传播

前向传播函数forward就是模型的计算过程,本例模型的计算流程很简单,就是title和abstract经过编码层的编码后相连接,然后过一个分类器。如果输入的数据中包含label,则计算一下lossmetrics。(这里的mask是什么用途不清楚。)
这里的输出的结果需要用softmax变成概率分布,使用输出结果的每一位表示每一种label的概率。整体返回的output_dict仍然用字典表示。

评测函数

评测函数metrics看起来比较简单,就是调用每个评测指标计算结果存到一个字典中。

解码函数

解码函数decode是将前向传播的结果(可能包含其他结果)进行解码,把整数转换为可读性更好的字符串。

训练模型

配置文件

上一篇用的是AllenNLP的training库,这回采用配置文件。实际上两者是相通的,只不过用Python的自由度更高,用JSON(实际上是一种JSON的超集)的结构化更强。

{
     
  "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": 40,
    "patience": 10,
    "cuda_device": -1,
    "grad_clipping": 5.0,
    "validation_metric": "+accuracy",
    "optimizer": {
     
      "type": "adagrad"
    }
  }
}

DataTrainer

配置文件中关于数据和训练器的有dataset_readertrain_data_pathvalidation_data_pathiteratortrainer。本例中给出的两个训练与验证数据的路径都是网络路径,AllenNLP会将其缓存的(应该也是可以指定本地路径的,也可以指定缓存的路径)。
iterator是用来说明trainer如何对数据进行遍历的,本例中采用bucket类型的迭代器,默认会根据最大输入长度填充batch,如果提供一个排序的索引则会提高计算效率(不明白这里的意思)。
trainerpatience参数为10(early stopping的耐心值),结合validation_metric+accuracy(加号表示期望增长),应该说明如果准确率10轮没有增长则提前结束训练。
值得注意的是dataset_reader中的type属性,这和Python文件中注册的DatasetReader类的名字是对应的。

Model

模型的JSON配置除了和注册名相同的paper包括四部分,对应模型类中的四部分:词嵌入层、title和abstract两个编码层以及一个分类层。这里注意一下type这个属性,lstm在这里指的是PyTorchtorch.nn.LSTM外套一个allennlp.modules.pytorch_seq2vec_wrapper以保证其被转为AllenNLP的Seq2VecWrapper
感觉这种方式虽然调整一些参数,调换一些模块方便,但是写起来好像并不方便。。。

AllenNLP命令行

AllenNLP的命令行帮助提示:

allennlp -h

usage: allennlp

Run AllenNLP

optional arguments:
  -h, --help     show this help message and exit
  --version      show program's version number and exit

Commands:
  
    configure    Run the configuration wizard.
    train        Train a model.
    evaluate     Evaluate the specified model + dataset.
    predict      Use a trained model to make predictions.
    make-vocab   Create a vocabulary.
    elmo         Create word vectors using a pretrained ELMo model.
    fine-tune    Continue training a model on a new dataset.
    dry-run      Create a vocabulary, compute dataset statistics and other
                 training utilities.
    test-install
                 Run the unit tests.
    find-lr      Find a learning rate range.
    print-results
                 Print results from allennlp serialization directories to the
                 console.

allennlp train -h

usage: allennlp train [-h] -s SERIALIZATION_DIR [-r] [-f] [-o OVERRIDES]
                      [--file-friendly-logging]
                      [--cache-directory CACHE_DIRECTORY]
                      [--cache-prefix CACHE_PREFIX]
                      [--include-package INCLUDE_PACKAGE]
                      param_path

Train the specified model on the specified dataset.

positional arguments:
  param_path            path to parameter file describing the model to be
                        trained

optional arguments:
  -h, --help            show this help message and exit
  -s SERIALIZATION_DIR, --serialization-dir SERIALIZATION_DIR
                        directory in which to save the model and its logs
  -r, --recover         recover training from the state in serialization_dir
  -f, --force           overwrite the output directory if it exists
  -o OVERRIDES, --overrides OVERRIDES
                        a JSON structure used to override the experiment
                        configuration
  --file-friendly-logging
                        outputs tqdm status on separate lines and slows tqdm
                        refresh rate
  --cache-directory CACHE_DIRECTORY
                        Location to store cache of data preprocessing
  --cache-prefix CACHE_PREFIX
                        Prefix to use for data caching, giving current
                        parameter settings a name in the cache, instead of
                        computing a hash
  --include-package INCLUDE_PACKAGE
                        additional packages to include

训练命令

allennlp train \
    experiments/venue_classifier.json \
    -s /tmp/venue_output_dir \
    --include-package my_library

训练命令中的首要参数是上述的配置文件,-s表示保存模型和日志的目录,–include-package命令包含的my_library中保存我们的DatasetReader、Model和Predictor(这样才能通过JSON配置文件找到这些注册过名字的类)。

感觉这样用大篇幅JSON实在是不好写,实现简单的模型问题不大,但是复杂的、定制化的还是Python好用。看看allennlp.commands库中的模块,比如allennlp.commands.train、allennlp.commands.evaluate等等。

预测

创建Predictor

有了上文的DatasetReader和模型中的forward,预测函数只需要注册相应的模型名字,重载predict_json函数。

from overrides import overrides

from allennlp.common.util import JsonDict
from allennlp.data import Instance
from allennlp.predictors.predictor import Predictor

@Predictor.register('paper-classifier')
class PaperClassifierPredictor(Predictor):
    """"Predictor wrapper for the AcademicPaperClassifier"""
    def predict_json(self, inputs: JsonDict) -> JsonDict:
        instance = self._json_to_instance(inputs)
        output_dict = self.predict_instance(instance)
        # label_dict will be like {0: "ACL", 1: "AI", ...}
        label_dict = self._model.vocab.get_index_to_token_vocabulary('labels')
        # Convert it to list ["ACL", "AI", ...]
        all_labels = [label_dict[i] for i in range(len(label_dict))]
        output_dict["all_labels"] = all_labels
        return output_dict

    @overrides
    def _json_to_instance(self, json_dict: JsonDict) -> Instance:
        title = json_dict['title']
        abstract = json_dict['paperAbstract']
        return self._dataset_reader.text_to_instance(title=title, abstract=abstract)

后续会逐一看看AllenNLP的各个模块的API和源代码,在那里API里能够看到,allennlp.commands.predictallennlp predict命令就是调用的这个模块)是allennlp.predictors.predictor的一层包装。在allennlp.predictors.predictor定义中Predictor类有两个参数,分别是模型和DatasetReader,其中有一个函数是predict_json(就是这里重载的函数)。

测试Predictor

同样也要为Predictor配备一个测试文件。

from unittest import TestCase

from pytest import approx
from allennlp.models.archival import load_archive
from allennlp.predictors import Predictor

# required so that our custom model + predictor + dataset reader
# will be registered by name
import my_library

class TestPaperClassifierPredictor(TestCase):
    def test_uses_named_inputs(self):
        inputs = {
     
            "title": "Interferring Discourse Relations in Context",
            "paperAbstract": (
                    "We investigate various contextual effects on text "
                    "interpretation, and account for them by providing "
                    "contextual constraints in a logical theory of text "
                    "interpretation. On the basis of the way these constraints "
                    "interact with the other knowledge sources, we draw some "
                    "general conclusions about the role of domain-specific "
                    "information, top-down and bottom-up discourse information "
                    "flow, and the usefulness of formalisation in discourse theory."
            )
        }

        archive = load_archive('tests/fixtures/model.tar.gz')
        predictor = Predictor.from_archive(archive, 'paper-classifier')

        result = predictor.predict_json(inputs)

        label = result.get("label")
        assert label in {
     'AI', 'ML', 'ACL'}
        all_labels = result.get("all_labels")
        assert all_labels == ['AI', 'ACL', 'ML']

        class_probabilities = result.get("class_probabilities")
        assert class_probabilities is not None
        assert all(cp > 0 for cp in class_probabilities)
        assert sum(class_probabilities) == approx(1.0)

测试文件主要就是加载模型文件,加载predictor,检验一下预测结果中有没有满足每一项的概率大于0,加起来是否为1。这个测试类是继承自TestCase的,所以只是自行定制的。当然,要注意将my_library模块引入,里面包含着之前实现的模型和预测器。

执行预测

执行预测就使用allennlp predict,注意和训练的命令一样,要包含my_library库(里面有我们的DatasetReaderModelpredict),还要指明predictor的注册名字。

usage: allennlp [command] predict [-h]
                                  [--output-file OUTPUT_FILE]
                                  [--batch-size BATCH_SIZE]
                                  [--silent]
                                  [--cuda-device CUDA_DEVICE]
                                  [-o OVERRIDES]
                                  [--include-package INCLUDE_PACKAGE]
                                  [--predictor PREDICTOR]
                                  archive_file input_file

allennlp predict \
    tests/fixtures/model.tar.gz \
    tests/fixtures/s2_papers.jsonl \
    --include-package my_library \
    --predictor paper-classifier

通过--output-file指明输出结果到文件,结果大致如下:

prediction:  {
     "instance": {
     "logits": [0.008737504482269287, 0.22074833512306213, -0.005263201892375946], "class_probabilities": [0.31034138798713684, 0.38363200426101685, 0.3060266375541687], "label": "ACL"}, "all_labels": ["AI", "ACL", "ML"]}

运行Web Demo、定制Web Demo(留坑)

Web Demo就是用来将model、trainer参数和结果展示在浏览器中,但是这个功能还在开发中,allennlp-server还没有并入allennlp库中。最关键的是,我安装运行失败了(((φ(◎ロ◎;)φ))),所以留个坑吧。

总结

看看完整的目录吧

.
├── build_tools
│   └── travis
│       ├── after_success.sh
│       ├── install.sh
│       └── test_script.sh
├── codecov.yml
├── experiments
│   ├── venue_classifier_boe.json
│   └── venue_classifier.json
├── my_library
│   ├── dataset_readers
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   └── semantic_scholar_papers.cpython-37.pyc
│   │   └── semantic_scholar_papers.py
│   ├── __init__.py
│   ├── models
│   │   ├── academic_paper_classifier.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── academic_paper_classifier.cpython-37.pyc
│   │       └── __init__.cpython-37.pyc
│   ├── predictors
│   │   ├── __init__.py
│   │   ├── paper_classifier_predictor.py
│   │   └── __pycache__
│   │       ├── __init__.cpython-37.pyc
│   │       └── paper_classifier_predictor.cpython-37.pyc
│   └── __pycache__
│       └── __init__.cpython-37.pyc
├── pytest.ini
├── README.md
├── requirements.txt
├── static_html
│   ├── demo.css
│   └── index.html
├── tests
│   ├── dataset_readers
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   └── semantic_scholar_dataset_reader_test.cpython-37-pytest-5.3.5.pyc
│   │   └── semantic_scholar_dataset_reader_test.py
│   ├── fixtures
│   │   ├── academic_paper_classifier.json
│   │   ├── model.tar.gz
│   │   └── s2_papers.jsonl
│   ├── models
│   │   ├── academic_paper_classifier_test.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── academic_paper_classifier_test.cpython-37-pytest-5.3.5.pyc
│   │       └── __init__.cpython-37.pyc
│   └── predictors
│       ├── predictor_test.py
│       └── __pycache__
│           └── predictor_test.cpython-37-pytest-5.3.5.pyc
└── tmp
    ├── prediction.txt
    └── venue_output_dir
        ├── best.th
        ├── config.json
        ├── log
        │   ├── train
        │   │   └── events.out.tfevents.1583980176.xiaoyong-huawei-laptop
        │   └── validation
        │       └── events.out.tfevents.1583980176.xiaoyong-huawei-laptop
        ├── metrics_epoch_0.json
        ├── metrics.json
        ├── model_state_epoch_0.th
        ├── model.tar.gz
        ├── stderr.log
        ├── stdout.log
        ├── training_state_epoch_0.th
        └── vocabulary
            ├── labels.txt
            ├── non_padded_namespaces.txt
            └── tokens.txt

26 directories, 53 files

目录中最主要的就是这个my_library目录,里面有我们的模型、DatasetReader和Predictor;其次是experiments,里面是一份训练的配置文件(一份就够了,多余的是不同的实验设置);然后是test目录,其中是单元测试文件,不过这里用的是bash脚本去调用的,实际上也可以不管;tmp目录中是训练的日志、模型文件以及预测的结果输出;那个build_tools文件是用bash脚本来配置环境、测试用的,其实不太需要,其他的也不太重要了。整个目录是以最终要打包为目的写的,实际上也不用这么规范,保证my_library、experiments和test三个目录就够了,当然数据可以单独建立一个目录。

你可能感兴趣的:(NLP,AllenNLP,python,深度学习,人工智能,nlp,自然语言处理)