AllenNLP 1.1 : Part1 クイックスタート : 訓練と予測

AllenNLP 1.1 : Part1 クイックスタート : 訓練と予測 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/09/2020 (1.1.0)

* 本ページは、AllenNLP ドキュメントの以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー開催中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

Part1 クイックスタート : 訓練と予測

この章は貴方のモデルをどのように訓練して新しいデータ上でどのように予測を実行するかを概説します。

前の章では、貴方自身のデータセット・リーダーとモデルをどのように書くかを学習しました。この章では、テキスト分類モデルを訓練して新しい入力に対する予測を行なっていきます。この時点で、進めるために 2 つの方法があります : データセット・リーダーとモデルを構築して訓練ループを実行するために貴方自身のスクリプトを書くことができます、あるいは configuration ファイルを書いて allennlp train コマンドを利用することができます。ここでは両者の方法をどのように使うかを示します。

 

1 貴方自身のスクリプトでモデルを訓練する

このセクションでは、allennlp train の代わりに貴方自身の python スクリプトを使用して、データを読み、それをモデルに供給し、そしてモデルを訓練する単純なサンプルをまとめます。殆どの場合のために allennlp train を使用することを勧める一方で、このセクションで訓練ループの紹介を理解することはより容易です。これについてひとたび把握すれば、貴方が望めば、組込みコマンドの使用に切り替えることは容易なはずです。

進める前に、この章を通して利用するデータセットについて少しの話しがここにあります。データセットは極性を伴う IMDB の映画レビューのコレクション、Movie Review データ に由来します。ラベルは二値 (ポジティブとネガティブ) で私達のタスクはレビューテキストからラベルを予測することです。

このセクションは実行可能なサンプルのシリーズを与えていきます、それらは貴方自身でブラウザで実行できてそれらが何を出力するか見ることができます。それらは互いの上に構築されて、前のサンプルからのコードは続くサンプルの Setup ブロックで終了します。

 

データセット・リーダーをテストする

最初のサンプルでは、データセット・リーダーを単純にインスタンス化して、それを使用して映画レビュー・データセットを読み、そしてデータセット・リーダーにより生成される AllenNLP インスタンスを調べます。下では貴方が実行できる (そして望むなら変更可能な) コードを持ちます。

from typing import Dict, Iterable, List

from allennlp.data import DatasetReader, Instance
from allennlp.data.fields import LabelField, TextField
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer
from allennlp.data.tokenizers import Token, Tokenizer, WhitespaceTokenizer
class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None,
                 max_tokens: int = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}
        self.max_tokens = max_tokens

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                tokens = self.tokenizer.tokenize(text)
                if self.max_tokens:
                    tokens = tokens[:self.max_tokens]
                text_field = TextField(tokens, self.token_indexers)
                label_field = LabelField(sentiment)
                fields = {'text': text_field, 'label': label_field}
                yield Instance(fields)

dataset_reader = ClassificationTsvReader(max_tokens=64)
instances = dataset_reader.read("quick_start/data/movie_review/train.tsv")

for instance in instances[:10]:
    print(instance)

上のコードスニペットを実行するとき、最初の 10 インスタンスと (テキストとラベル・フィールドを含む) それらの内容のダンプを見るはずです。(max_tokens=64 を指定することによりインスタンス毎に最初の 64 トークンだけを表示していることに注意してください。)

これはデータセット・リーダーが想定どおりに動作しているかを確認する一つの方法です。貴方のデータ処理コードのために 幾つかの単純なテストを書く ことを強く勧めます、それに望むことを実際に行なっているか確かなものにするためにです。

 

インスタンスをモデルに供給する

次のサンプルでは、モデルをインスタンス化してインスタンスのバッチをそれに供給していきます。この時点で、私達の訓練ループをオブジェクトを (それらの依存性が与えられたとき) インスタンス化する単純な関数へ細分化していきます。Model はそれを構築できる前にデータから計算された Vocabulary を持つ必要がありますが、モデル構築の詳細を訓練ループ関数の内部に配置することは実際には望みません。そのため物事を健全に保つため、モデル構築をメイン訓練関数内部で呼び出す別の関数に取り出します。

貴方がこれを実行するとき、モデルから返された出力を見るはずです。各返された辞書は loss キーに加えて probs キーを含みます、これは各ラベルのための確率を含みます。

from typing import Dict, Iterable, List

import torch
from allennlp.data import DatasetReader, Instance
from allennlp.data import Vocabulary
from allennlp.data.fields import LabelField, TextField
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer
from allennlp.data.tokenizers import Token, Tokenizer, WhitespaceTokenizer
from allennlp.models import Model
from allennlp.modules import TextFieldEmbedder, Seq2VecEncoder
from allennlp.modules.text_field_embedders import BasicTextFieldEmbedder
from allennlp.modules.token_embedders import Embedding
from allennlp.modules.seq2vec_encoders import BagOfEmbeddingsEncoder
from allennlp.nn import util
from allennlp.training.metrics import CategoricalAccuracy


# There's a warning when you call `forward_on_instances` that you don't need
# to worry about right now, so we silence it.
import warnings
warnings.filterwarnings("ignore")


class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None,
                 max_tokens: int = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}
        self.max_tokens = max_tokens

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                tokens = self.tokenizer.tokenize(text)
                if self.max_tokens:
                    tokens = tokens[:self.max_tokens]
                text_field = TextField(tokens, self.token_indexers)
                label_field = LabelField(sentiment)
                fields = {'text': text_field, 'label': label_field}
                yield Instance(fields)
class SimpleClassifier(Model):
    def __init__(self,
                 vocab: Vocabulary,
                 embedder: TextFieldEmbedder,
                 encoder: Seq2VecEncoder):
        super().__init__(vocab)
        self.embedder = embedder
        self.encoder = encoder
        num_labels = vocab.get_vocab_size("labels")
        self.classifier = torch.nn.Linear(encoder.get_output_dim(), num_labels)

    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor) -> Dict[str, torch.Tensor]:
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits, dim=-1)
        # Shape: (1,)
        loss = torch.nn.functional.cross_entropy(logits, label)
        return {'loss': loss, 'probs': probs}

def run_training_loop():
    dataset_reader = ClassificationTsvReader(max_tokens=64)
    print("Reading data")
    instances = dataset_reader.read("quick_start/data/movie_review/train.tsv")

    vocab = build_vocab(instances)
    model = build_model(vocab)

    outputs = model.forward_on_instances(instances[:4])
    print(outputs)

def build_vocab(instances: Iterable[Instance]) -> Vocabulary:
    print("Building the vocabulary")
    return Vocabulary.from_instances(instances)

def build_model(vocab: Vocabulary) -> Model:
    print("Building the model")
    vocab_size = vocab.get_vocab_size("tokens")
    embedder = BasicTextFieldEmbedder(
        {"tokens": Embedding(embedding_dim=10, num_embeddings=vocab_size)})
    encoder = BagOfEmbeddingsEncoder(embedding_dim=10)
    return SimpleClassifier(vocab, embedder, encoder)

run_training_loop()

 

モデルを訓練する

最後に、逆伝播を実行してモデルを訓練します。AllenNLP はこのために Trainer を利用します、これは (モデル、optimizer、インスタンス、データローダー等を含む) 必要なコンポーネントに接続して訓練ループを実行する責任を負います。

これを実行するとき、Trainer は訓練データに渡り 5 回 (num_epochs=5) 繰り返します。各エポック後、AllenNLP はそれがどのように上手く (or 悪く) 行なっているかを監視するために検証セットに対して貴方のモデルを実行します。これは e.g., early stopping を行なうことを望む場合、そして一般に監視するために有用です。訓練損失が徐々に減少することを観察します — これは貴方のモデルと訓練パイプラインがそれらが行なうと仮定されているもの (つまり、損失を最初化する) を行なっている兆候です。

import tempfile
from typing import Dict, Iterable, List, Tuple

import allennlp
import torch
from allennlp.data import DataLoader, DatasetReader, Instance, Vocabulary
from allennlp.data.fields import LabelField, TextField
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer
from allennlp.data.tokenizers import Token, Tokenizer, WhitespaceTokenizer
from allennlp.models import Model
from allennlp.modules import TextFieldEmbedder, Seq2VecEncoder
from allennlp.modules.seq2vec_encoders import BagOfEmbeddingsEncoder
from allennlp.modules.token_embedders import Embedding
from allennlp.modules.text_field_embedders import BasicTextFieldEmbedder
from allennlp.nn import util
from allennlp.training.trainer import GradientDescentTrainer, Trainer
from allennlp.training.optimizers import AdamOptimizer
from allennlp.training.metrics import CategoricalAccuracy


class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None,
                 max_tokens: int = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}
        self.max_tokens = max_tokens

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                tokens = self.tokenizer.tokenize(text)
                if self.max_tokens:
                    tokens = tokens[:self.max_tokens]
                text_field = TextField(tokens, self.token_indexers)
                label_field = LabelField(sentiment)
                fields = {'text': text_field, 'label': label_field}
                yield Instance(fields)


class SimpleClassifier(Model):
    def __init__(self,
                 vocab: Vocabulary,
                 embedder: TextFieldEmbedder,
                 encoder: Seq2VecEncoder):
        super().__init__(vocab)
        self.embedder = embedder
        self.encoder = encoder
        num_labels = vocab.get_vocab_size("labels")
        self.classifier = torch.nn.Linear(encoder.get_output_dim(), num_labels)

    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor) -> Dict[str, torch.Tensor]:
        print("In model.forward(); printing here just because binder is so slow")
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits, dim=-1)
        # Shape: (1,)
        loss = torch.nn.functional.cross_entropy(logits, label)
        return {'loss': loss, 'probs': probs}


def build_dataset_reader() -> DatasetReader:
    return ClassificationTsvReader()

def read_data(
    reader: DatasetReader
) -> Tuple[Iterable[Instance], Iterable[Instance]]:
    print("Reading data")
    training_data = reader.read("quick_start/data/movie_review/train.tsv")
    validation_data = reader.read("quick_start/data/movie_review/dev.tsv")
    return training_data, validation_data

def build_vocab(instances: Iterable[Instance]) -> Vocabulary:
    print("Building the vocabulary")
    return Vocabulary.from_instances(instances)

def build_model(vocab: Vocabulary) -> Model:
    print("Building the model")
    vocab_size = vocab.get_vocab_size("tokens")
    embedder = BasicTextFieldEmbedder(
        {"tokens": Embedding(embedding_dim=10, num_embeddings=vocab_size)})
    encoder = BagOfEmbeddingsEncoder(embedding_dim=10)
    return SimpleClassifier(vocab, embedder, encoder)
def run_training_loop():
    dataset_reader = build_dataset_reader()

    # These are a subclass of pytorch Datasets, with some allennlp-specific
    # functionality added.
    train_data, dev_data = read_data(dataset_reader)

    vocab = build_vocab(train_data + dev_data)
    model = build_model(vocab)

    # This is the allennlp-specific functionality in the Dataset object;
    # we need to be able convert strings in the data to integers, and this
    # is how we do it.
    train_data.index_with(vocab)
    dev_data.index_with(vocab)

    # These are again a subclass of pytorch DataLoaders, with an
    # allennlp-specific collate function, that runs our indexing and
    # batching code.
    train_loader, dev_loader = build_data_loaders(train_data, dev_data)

    # You obviously won't want to create a temporary file for your training
    # results, but for execution in binder for this guide, we need to do this.
    with tempfile.TemporaryDirectory() as serialization_dir:
        trainer = build_trainer(
            model,
            serialization_dir,
            train_loader,
            dev_loader
        )
        print("Starting training")
        trainer.train()
        print("Finished training")

# The other `build_*` methods are things we've seen before, so they are
# in the setup section above.
def build_data_loaders(
    train_data: torch.utils.data.Dataset,
    dev_data: torch.utils.data.Dataset,
) -> Tuple[allennlp.data.DataLoader, allennlp.data.DataLoader]:
    # Note that DataLoader is imported from allennlp above, *not* torch.
    # We need to get the allennlp-specific collate function, which is
    # what actually does indexing and batching.
    train_loader = DataLoader(train_data, batch_size=8, shuffle=True)
    dev_loader = DataLoader(dev_data, batch_size=8, shuffle=False)
    return train_loader, dev_loader

def build_trainer(
    model: Model,
    serialization_dir: str,
    train_loader: DataLoader,
    dev_loader: DataLoader
) -> Trainer:
    parameters = [
        [n, p]
        for n, p in model.named_parameters() if p.requires_grad
    ]
    optimizer = AdamOptimizer(parameters)
    trainer = GradientDescentTrainer(
        model=model,
        serialization_dir=serialization_dir,
        data_loader=train_loader,
        validation_data_loader=dev_loader,
        num_epochs=5,
        optimizer=optimizer,
    )
    return trainer


run_training_loop()

Congratulations, AllenNLP を使用して貴方はちょうど最初のモデルを訓練しました!多分貴方は実際にはそれが binder で実行終了するのを待つことを望まないでしょうけれども ; それは極端に遅いです。貴方が望めば、私達の guide レポジトリ に向かって進みそこから貴方のローカルマシン上でコードを実行できます。

 

2 allennlp train でモデルを訓練する

Ok, 私達は単純な訓練ループをどのようにセットアップするか見ました。私達が書いた上の訓練ループコードの殆ど総ては build_* 関数内にあり、単にオブジェクトを構築します。run_training_loop 関数自身は単にコードのおよそ 10 行ほどです。しかしその関数、そして build_* 関数内には正しく理解することが非常に重要な幾つかの詳細があり、そして殆どは単にボイラープレートで大抵の場合はそれについて考えることは実際には重要ではありません。更に、検証のための個別のデータロードを持ったり、訓練モデルをセーブするような、素晴らしい機能を追加していません、それらはメソッドにより多くのボイラープレートさえ追加します。

これらのこと総てを貴方のために処理する組込み訓練スクリプトを持ちそして貴方が書かなければならないコードが DatasetReader と Model クラスだけであるようにそれを作成します。上で持った build_* メソッドの総てを書く代わりに、総ての必要なパラメータを指定する JSON configuration ファイルを書きます。私達の訓練スクリプトはそれらのパラメータを取り、正しい順序でオブジェクトの総てを作成し、そして訓練ループを実行します。

configuration ファイルがどのように動作するかを説明することに専念した このガイドの章 全体があります ; ここでそれらをどのように利用するかの素早い紹介を単に与えます。これらの configuration ファイルを使用する代わりに貴方自身のスクリプトを書くことを選択することを決めるのだれば、それもまた構いません、そしてこれを容易にする様々な方法の示唆を与える ガイドのもう一つの章 を持ちます。

 

Configuration ファイル

一言で言えば、allennlp の configuration ファイルは様々なオブジェクトのためのコンストラクタ・パラメータを単に取りそしてそれらを JSON 辞書に配置します。上で、このように見えた build_model メソッドを持ちました :

def build_model(vocab: Vocabulary) -> Model:
    print("Building the model")
    vocab_size = vocab.get_vocab_size("tokens")
    embedder = BasicTextFieldEmbedder(
        {"tokens": Embedding(embedding_dim=10, num_embeddings=vocab_size)})
    encoder = BagOfEmbeddingsEncoder(embedding_dim=10)
    return SimpleClassifier(vocab, embedder, encoder)

これはこのように見える JSON 辞書に変換されます :

"model": {
    "type": "simple_classifier",
    "embedder": {
        "token_embedders": {
            "tokens": {
                "type": "embedding",
                "embedding_dim": 10
            }
        }
    },
    "encoder": {
        "type": "bag_of_embeddings",
        "embedding_dim": 10
    }
}

build_model で作成されたオブジェクトの総てへのコンストラクタ・パラメータはこの辞書のキーに直接翻訳されます。AllenNLP はこれらのオブジェクトを正しく構築するためにモデルのコンストラクタ・コードの型アノテーションを頼ります。

注意すべき 2 つの特別なことがあります : 第一に、基底型の特定のサブクラス (e.g., Model のサブクラスとしての SimpleClassifier、or Seq2VecEncoder のサブクラスとしての BagOfEmbeddingsEncoder) を選択するため、追加の “type”: “simple_classifier” キーが必要です。文字列 “simple_classifier” は 前の章 で見た Model.register への呼び出しに由来します。

二番目に、ここでは vocab 引数が欠落しています。vocab は build_model メソッドへの引数であり、その内部で構築されなかったのと同じ理由です — 語彙はデータに基づいて別に構築されてから、モデルに渡されます。一般に、build_* メソッドへの引数として現れるオブジェクト間のシーケンシャルな依存性は configuration ファイルから外されます、何故ならばそれらは異なる方法で扱われるためです。再度、これがどのように動作するかの多くの詳細は configuration files の章 にあります。

単にモデルのためだけでなく、データセット・リーダー、データローダ、トレーナー、そして訓練ループに入る他の総てのもののためにこれを行ないます。これは実行された実験のための configuration の総てを保持する単一の JSON ファイルを与えます (私達は実際には Jsonnet と呼ばれる JSON のスーパーセットを利用しています、これは変数とインポートのような fancy な特徴をサポートしますが、plain JSON ファイルもまた動作します)。

単純な分類器にうちては、その configuration ファイルはこのように見えます :

{
    "dataset_reader" : {
        "type": "classification-tsv",
        "token_indexers": {
            "tokens": {
                "type": "single_id"
            }
        }
    },
    "train_data_path": "quick_start/data/movie_review/train.tsv",
    "validation_data_path": "quick_start/data/movie_review/dev.tsv",
    "model": {
        "type": "simple_classifier",
        "embedder": {
            "token_embedders": {
                "tokens": {
                    "type": "embedding",
                    "embedding_dim": 10
                }
            }
        },
        "encoder": {
            "type": "bag_of_embeddings",
            "embedding_dim": 10
        }
    },
    "data_loader": {
        "batch_size": 8,
        "shuffle": true
    },
    "trainer": {
        "optimizer": "adam",
        "num_epochs": 5
    }
}

 
そこではそのために build_* メソッドを持った総てのもののためのエントリを見ることができます (語彙を除いて、それは省略します、何故ならばそのためにはデフォルト・パラメータを単に使用しているからです)。configuration ファイルはコンストラクタ・パラメータを持つ名前により JSON オブジェクトのキーと一致することで読まれます。訓練ループについては、構築しているオブジェクトは TrainModel と呼ばれ、そしてそのコンストラクタを ここ で見ることができます。ここでキーはそれらのパラメータに正確に一致しなければなりません、さもなければ ConfigurationError を得ます。

この configuration ファイルで、コマンドラインから allennlp train [config.json] -s [serialization_directory] を実行することによりモデルを訓練できます。貴方のデータセット・リーダー、モデル、そして他のカスタムコンポーネントが allennlp コマンドで認識されるためには、.register() への呼び出しが実行されなければなりません、これはクラスがインポートされるときに発生します。そしてこのコマンドを実行するとき典型的にはフラグ –include-package [my_python_module] も追加しなければなりません、あるいは allennlp のプラグイン機能を利用します。configuration files の章 でこれがどのように動作するかのより詳細があります。

典型的にはコードをこの方法で実行しないでしょうが、そこで configuration ファイルを使用するサンプルを含めていますので、貴方が望めばそれで遊ぶことができます。

import tempfile
import json
from typing import Dict, Iterable, List

import torch
from allennlp.data import DatasetReader, Instance, Vocabulary
from allennlp.data.fields import LabelField, TextField
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer
from allennlp.data.tokenizers import Token, Tokenizer, WhitespaceTokenizer
from allennlp.models import Model
from allennlp.modules import TextFieldEmbedder, Seq2VecEncoder
from allennlp.nn import util
from allennlp.training.metrics import CategoricalAccuracy


@DatasetReader.register("classification-tsv")
class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None,
                 max_tokens: int = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}
        self.max_tokens = max_tokens

    def text_to_instance(self,
                         tokens: List[Token],
                         label: str = None) -> Instance:
        if self.max_tokens:
            tokens = tokens[:self.max_tokens]
        text_field = TextField(tokens, self.token_indexers)
        fields = {'text': text_field}
        if label:
            fields['label'] = LabelField(label)
        return Instance(fields)

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                tokens = self.tokenizer.tokenize(text)
                yield self.text_to_instance(tokens, sentiment)


@Model.register("simple_classifier")
class SimpleClassifier(Model):
    def __init__(self,
                 vocab: Vocabulary,
                 embedder: TextFieldEmbedder,
                 encoder: Seq2VecEncoder):
        super().__init__(vocab)
        self.embedder = embedder
        self.encoder = encoder
        num_labels = vocab.get_vocab_size("labels")
        self.classifier = torch.nn.Linear(encoder.get_output_dim(), num_labels)
        self.accuracy = CategoricalAccuracy()

    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor = None) -> Dict[str, torch.Tensor]:
        print("In model.forward(); printing here just because binder is so slow")
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits)
        # Shape: (1,)
        output = {'probs': probs}
        if label is not None:
            self.accuracy(logits, label)
            output['loss'] = torch.nn.functional.cross_entropy(logits, label)
        return output

    def get_metrics(self, reset: bool = False) -> Dict[str, float]:
        return {"accuracy": self.accuracy.get_metric(reset)}
config = {
    "dataset_reader" : {
        "type": "classification-tsv",
        "token_indexers": {
            "tokens": {
                "type": "single_id"
            }
        }
    },
    "train_data_path": "quick_start/data/movie_review/train.tsv",
    "validation_data_path": "quick_start/data/movie_review/dev.tsv",
    "model": {
        "type": "simple_classifier",
        "embedder": {
            "token_embedders": {
                "tokens": {
                    "type": "embedding",
                    "embedding_dim": 10
                }
            }
        },
        "encoder": {
            "type": "bag_of_embeddings",
            "embedding_dim": 10
        }
    },
    "data_loader": {
        "batch_size": 8,
        "shuffle": True
    },
    "trainer": {
        "optimizer": "adam",
        "num_epochs": 5
    }
}


with tempfile.TemporaryDirectory() as serialization_dir:
    config_filename = serialization_dir + "/training_config.json"
    with open(config_filename, 'w') as config_file:
        json.dump(config, config_file)
    from allennlp.commands.train import train_model_from_file
    # Instead of this python code, you would typically just call
    # allennlp train [config_file] -s [serialization_dir]
    train_model_from_file(config_filename,
                          serialization_dir,
                          file_friendly_logging=True,
                          force=True)

再度、貴方は多分それが終了するのを待つことを望まないでしょうが、guide レポジトリ をチェックアウトすることによりこれをローカルマシン上で実行できます。quick_start ディレクトリに cd してから、allennlp train my_text_classifier.jsonnet -s model –include-package my_text_classifier を実行します。

これらの configuration ファイルに慣れてそれらがどのように動作するかを理解するには何某かのオーバーヘッドが確実にあります。それらは学習カーブに値する十分に有用であると私達は考えますが、貴方が同意できないのであれば、前のセクションで示されたように、依然としてそれらなしに allennlp のコンポーネントの総てを利用できます。

 

3 モデルを評価する

このセクションでは、テストセットに対して評価メトリクスを計算することにより上でちょうど訓練したテキスト分類モデルを評価していきます。

 

メトリクスを定義する

AllenNLP では、Model クラスでメトリクスを計算するロジックを実装します。AllenNLP は訓練の間にメトリクスを追跡するための幾つかの有用な機能を与える Metric と呼ばれる抽象を含みます。ここでは、精度 Metric, CategoricalAccuracy を使用していきます、これはモデルがラベルを正しく予想したインスタンスの割合を計算します。

最初に、モデル・コンストラクタで CategoricalAccuracy のインスタンスを作成する必要があります :

class SimpleClassifier(Model):
    def __init__(self,
                 vocab: Vocabulary,
                 embedder: TextFieldEmbedder,
                 encoder: Seq2VecEncoder):
        super().__init__(vocab)
        self.embedder = embedder
        self.encoder = encoder
        num_labels = vocab.get_vocab_size("labels")
        self.classifier = torch.nn.Linear(encoder.get_output_dim(), num_labels)
        self.accuracy = CategoricalAccuracy()

それから、各 forward パスについて、予測と正解ラベルを供給することによりメトリックを更新する必要があります :

class SimpleClassifier(Model):
    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor) -> Dict[str, torch.Tensor]:
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits)
        # Shape: (1,)
        loss = torch.nn.functional.cross_entropy(logits, label)
        self.accuracy(logits, label)
        return {'loss': loss, 'probs': probs}

AllenNLP でメトリクスが動作する方法は、内部的には、各 Metric インスタンスは “counts” を保持します、これはメトリックを計算するために必要で十分です。精度のためには、これらのカウントは合計予測の数でありそして正しい予測の数です。これらのカウントはインスタンス自身への総ての呼び出し後に更新されます, i.e., self.accuracy(logits, label) 行です。カウントをリセットするか否かを指定するフラグとともに get_metrics() を呼び出すことで計算されたメトリックを引き出すことができます。これは訓練 or 検証データセット全体に渡るメトリックを計算することを可能にします。

最後に、貴方のモデルで get_metrics() メソッドを実装する必要があります、下で示されるように、これはメトリック名からそれらの値への辞書を返します :

def get_metrics(self, reset: bool = False) -> Dict[str, float]:
    return {"accuracy": self.accuracy.get_metric(reset)}

AllenNLP のデフォルト訓練ループはこのメソッドを適切な回数呼び出してそして現在のメトリック値を持つロギング情報を提供します。

 

モデルを評価する

貴方のモデルがメトリックを計算する準備ができた今、モデルを訓練するところのパイプライン全体を実行し、予測を行ないそしてテストセットに対してメトリックを計算しましょう。

下のコード・スニペットで、データセット・リーダーを使用してテストセットを読み、そしてモデルを実行してテストセット上でメトリックを得るために AllenNLP のユティリティ関数 evaluate() を使用します。AllenNLP は今では訓練と検証の間に各バッチの後で損失に加えて定義されたメトリックのための値を示すことに気付いてください。

import tempfile
from typing import Dict, Iterable, List, Tuple

import torch

import allennlp
from allennlp.data import DataLoader, DatasetReader, Instance, Vocabulary
from allennlp.data.fields import LabelField, TextField
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer
from allennlp.data.tokenizers import Token, Tokenizer, WhitespaceTokenizer
from allennlp.models import Model
from allennlp.modules import TextFieldEmbedder, Seq2VecEncoder
from allennlp.modules.text_field_embedders import BasicTextFieldEmbedder
from allennlp.modules.token_embedders import Embedding
from allennlp.modules.seq2vec_encoders import BagOfEmbeddingsEncoder
from allennlp.nn import util
from allennlp.training.metrics import CategoricalAccuracy
from allennlp.training.optimizers import AdamOptimizer
from allennlp.training.trainer import Trainer, GradientDescentTrainer
from allennlp.training.util import evaluate


class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None,
                 max_tokens: int = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}
        self.max_tokens = max_tokens

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                tokens = self.tokenizer.tokenize(text)
                if self.max_tokens:
                    tokens = tokens[:self.max_tokens]
                text_field = TextField(tokens, self.token_indexers)
                label_field = LabelField(sentiment)
                fields = {'text': text_field, 'label': label_field}
                yield Instance(fields)


class SimpleClassifier(Model):
    def __init__(self,
                 vocab: Vocabulary,
                 embedder: TextFieldEmbedder,
                 encoder: Seq2VecEncoder):
        super().__init__(vocab)
        self.embedder = embedder
        self.encoder = encoder
        num_labels = vocab.get_vocab_size("labels")
        self.classifier = torch.nn.Linear(encoder.get_output_dim(), num_labels)
        self.accuracy = CategoricalAccuracy()

    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor) -> Dict[str, torch.Tensor]:
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits)
        # Shape: (1,)
        loss = torch.nn.functional.cross_entropy(logits, label)
        self.accuracy(logits, label)
        output = {'loss': loss, 'probs': probs}
        return output

    def get_metrics(self, reset: bool = False) -> Dict[str, float]:
        return {"accuracy": self.accuracy.get_metric(reset)}


def build_dataset_reader() -> DatasetReader:
    return ClassificationTsvReader()

def read_data(
    reader: DatasetReader
) -> Tuple[Iterable[Instance], Iterable[Instance]]:
    print("Reading data")
    training_data = reader.read("quick_start/data/movie_review/train.tsv")
    validation_data = reader.read("quick_start/data/movie_review/dev.tsv")
    return training_data, validation_data

def build_vocab(instances: Iterable[Instance]) -> Vocabulary:
    print("Building the vocabulary")
    return Vocabulary.from_instances(instances)

def build_model(vocab: Vocabulary) -> Model:
    print("Building the model")
    vocab_size = vocab.get_vocab_size("tokens")
    embedder = BasicTextFieldEmbedder(
        {"tokens": Embedding(embedding_dim=10, num_embeddings=vocab_size)})
    encoder = BagOfEmbeddingsEncoder(embedding_dim=10)
    return SimpleClassifier(vocab, embedder, encoder)

def build_data_loaders(
    train_data: torch.utils.data.Dataset,
    dev_data: torch.utils.data.Dataset,
) -> Tuple[allennlp.data.DataLoader, allennlp.data.DataLoader]:
    # Note that DataLoader is imported from allennlp above, *not* torch.
    # We need to get the allennlp-specific collate function, which is
    # what actually does indexing and batching.
    train_loader = DataLoader(train_data, batch_size=8, shuffle=True)
    dev_loader = DataLoader(dev_data, batch_size=8, shuffle=False)
    return train_loader, dev_loader

def build_trainer(
    model: Model,
    serialization_dir: str,
    train_loader: DataLoader,
    dev_loader: DataLoader
) -> Trainer:
    parameters = [
        [n, p]
        for n, p in model.named_parameters() if p.requires_grad
    ]
    optimizer = AdamOptimizer(parameters)
    trainer = GradientDescentTrainer(
        model=model,
        serialization_dir=serialization_dir,
        data_loader=train_loader,
        validation_data_loader=dev_loader,
        num_epochs=5,
        optimizer=optimizer,
    )
    return trainer

def run_training_loop():
    dataset_reader = build_dataset_reader()

    # These are a subclass of pytorch Datasets, with some allennlp-specific
    # functionality added.
    train_data, dev_data = read_data(dataset_reader)

    vocab = build_vocab(train_data + dev_data)
    model = build_model(vocab)

    # This is the allennlp-specific functionality in the Dataset object;
    # we need to be able convert strings in the data to integers, and this
    # is how we do it.
    train_data.index_with(vocab)
    dev_data.index_with(vocab)

    # These are again a subclass of pytorch DataLoaders, with an
    # allennlp-specific collate function, that runs our indexing and
    # batching code.
    train_loader, dev_loader = build_data_loaders(train_data, dev_data)

    # You obviously won't want to create a temporary file for your training
    # results, but for execution in binder for this guide, we need to do this.
    with tempfile.TemporaryDirectory() as serialization_dir:
        trainer = build_trainer(
            model,
            serialization_dir,
            train_loader,
            dev_loader
        )
        trainer.train()

    return model, dataset_reader
# We've copied the training loop from an earlier example, with updated model
# code, above in the Setup section. We run the training loop to get a trained
# model.
model, dataset_reader = run_training_loop()

# Now we can evaluate the model on a new dataset.
test_data = dataset_reader.read('quick_start/data/movie_review/test.tsv')
test_data.index_with(model.vocab)
data_loader = DataLoader(test_data, batch_size=8)

results = evaluate(model, data_loader)

このコード・スニペットが実行を終了するとき、次の評価結果を見るはずです :

{'accuracy': 0.855, 'loss': 0.3686505307257175}

もう一度、binder で実行するにはこれは非常に遅いですが。python quick_start/evaluate.pyguide レポジトリ からこれを実行できます。

単純な bag-of-embeddings モデルとして、これは悪いスタートではありません!

 

コマンドラインから

コマンドラインからモデルを評価するために、allennlp evalute コマンドを利用できます。このコマンドは、テストインスタンスを含むファイルへのパスと一緒に allennlp train コマンドで作成されたモデル・アーカイブファイルへのパスを取り、そして計算されたメトリクスを返します。

 

4 ラベル付けされていない入力のために予測を行なう

この最後の課題では、訓練されたテキスト分類モデルを使用して新しい、ラベル付されていない入力のために予測を行なっていきます。

 

データセット・リーダーを変更する

ラベル付けされていない入力のために予測を行なうことに進む前に、前に書いたデータセット・リーダーに一つの変更を行なう必要があります。具体的には、トークンとラベルから Instance を作成するためのロジックを下で示されるように text_to_instance() メソッドとしてリファクタリングします。

コードのこのピースを DatasetReader と Predictor の間で共有可能にしています (これは下で詳細に議論します)。何故これが良いアイデアなのでしょう?ここでは、2 つのパイプライン, i.e., 2 つの異なるデータの「フロー」を実際に構築しています — 一つは訓練のためそしてもう一つは予測のためです。インスタンスを作成してそれを 2 つのパイプラインで共有するための共通のロジックをファクタリングすることで、training-serving skew (訓練-サービスの歪み) として知られる問題、2 つの間でどのようにインスタンスが作成されたかの点で可能な矛盾から起きる任意の問題へシステムを影響されにくくしています。これはこの tiny サンプルでは大きな問題ではないように見えるかもしれませんが、特徴抽出コードを異なるパイプライン間で共有可能にすることは現実世界の ML アプリケーションでは重要で、AllenNLP デモ のような、デモを提示することを非常に容易にします。

text_to_instance() の label パラメータをオプションにしていることに注意してください。訓練と評価の間、総てのインスタンスはラベル付けされました、i.e., それらは正解を含む LabelFields を含みました。けれども、初見の入力のために予測を行なっているとき、インスタンスはラベル付けされていません。label パラメータをオプションにすることによってデータセット・リーダは両者のケースをサポートできます。

@DatasetReader.register('classification-tsv')
class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}

    def text_to_instance(self, text: str, label: str = None) -> Instance:
        tokens = self.tokenizer.tokenize(text)
        text_field = TextField(tokens, self.token_indexers)
        fields = {'text': text_field}
        if label:
            fields['label'] = LabelField(label)
        return Instance(fields)

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                yield self.text_to_instance(text, sentiment)

 

モデルを変更する

私達のモデルの forward() メソッドにも幾つかの変更を行なう必要があります。モデルを訓練したとき、総てのインスタンスはラベルを含みそしてモデルはそれらのラベルを使用して計算された損失に基づいて訓練されました。けれども、予測の間、モデルが得るインスタンスはラベル付けされていません。forward() メソッドは損失を計算する必要はありません (実際にはできません) — しれが単に予測を返す必要があるだけです。

予測をサポートするためには、最初に None のデフォルト・パラメータを指定することで label パラメータをオプションにする必要があります。これは貴方にラベル付けされていないインスタンスをモデルに供給させます。二番目に、ラベルが供給されるときに限り損失と精度を計算する必要があります。下で forward() メソッドの変更されたバージョンを見てください :

class SimpleClassifier(Model):
    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor = None) -> Dict[str, torch.Tensor]:
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits)
        output = {'probs': probs}
        if label is not None:
            self.accuracy(logits, label)
            # Shape: (1,)
            output['loss'] = torch.nn.functional.cross_entropy(logits, label)
        return output

 

貴方の predictor を書く

デモ設定で予測を行なうため、AllenNLP は Predictor を使用します、これは貴方の訓練されたモデルの薄いラッパーです。Predictor の主要ジョブはインスタンスの JSON 表現を取り、それをデータセット・リーダーを使用して Instance に変換し (上で言及された text_to_instance)、それをモデルに渡し、そして JSON serializable 形式で予測を返すことです。

貴方のタスクのための Predictor を構築するために、Predictor から継承して 2, 3 のメソッドを実装する必要があるだけです (下の predict() と _json_to_instances() 参照) — 残りは基底クラスにより処理されます。

@Predictor.register("sentence_classifier")
class SentenceClassifierPredictor(Predictor):
    def predict(self, sentence: str) -> JsonDict:
        # This method is implemented in the base class.
        return self.predict_json({"sentence": sentence})

    def _json_to_instance(self, json_dict: JsonDict) -> Instance:
        sentence = json_dict["sentence"]
        return self._dataset_reader.text_to_instance(sentence)

AllenNLP は一般的なタスクのための Predictor の実装を提供します。実際には、それはテキスト分類タスクのための一般的な Predictor、TextClassifierPredictor を含みますので、貴方自身のものを書く必要さえありません。ここでは、実演のためにスクラッチから一つを単独に書いていますが、貴方のタスクのための predictor が既にそこにあるかを常に確かめるべきです。

 

予測を行なう

今は、総てのピースを一つにまとめて幾つかの初見のデータのための予測を行ないましょう。下のコードスニペットでは、最初に貴方のモデルを上で行なったように訓練してから、新しいインスタンスのために予測を行なうため SentenceClassifierPredictor でモデルをラップしています。返された結果 (output[‘probs’]) は単にクラスラベルのための確率の配列ですから、ラベル ID をそのラベル文字列に変換し戻すために vocab.get_token_from_index() を使用します。

import tempfile
from typing import Dict, Iterable, List, Tuple

import torch

import allennlp
from allennlp.common import JsonDict
from allennlp.data import DataLoader, DatasetReader, Instance
from allennlp.data import Vocabulary
from allennlp.data.fields import LabelField, TextField
from allennlp.data.token_indexers import TokenIndexer, SingleIdTokenIndexer
from allennlp.data.tokenizers import Token, Tokenizer, WhitespaceTokenizer
from allennlp.models import Model
from allennlp.modules import TextFieldEmbedder, Seq2VecEncoder
from allennlp.modules.text_field_embedders import BasicTextFieldEmbedder
from allennlp.modules.token_embedders import Embedding
from allennlp.modules.seq2vec_encoders import BagOfEmbeddingsEncoder
from allennlp.predictors import Predictor
from allennlp.nn import util
from allennlp.training.metrics import CategoricalAccuracy
from allennlp.training.trainer import Trainer, GradientDescentTrainer
from allennlp.training.optimizers import AdamOptimizer


class ClassificationTsvReader(DatasetReader):
    def __init__(self,
                 lazy: bool = False,
                 tokenizer: Tokenizer = None,
                 token_indexers: Dict[str, TokenIndexer] = None,
                 max_tokens: int = None):
        super().__init__(lazy)
        self.tokenizer = tokenizer or WhitespaceTokenizer()
        self.token_indexers = token_indexers or {'tokens': SingleIdTokenIndexer()}
        self.max_tokens = max_tokens

    def text_to_instance(self, text: str, label: str = None) -> Instance:
        tokens = self.tokenizer.tokenize(text)
        if self.max_tokens:
            tokens = tokens[:self.max_tokens]
        text_field = TextField(tokens, self.token_indexers)
        fields = {'text': text_field}
        if label:
            fields['label'] = LabelField(label)
        return Instance(fields)

    def _read(self, file_path: str) -> Iterable[Instance]:
        with open(file_path, 'r') as lines:
            for line in lines:
                text, sentiment = line.strip().split('\t')
                yield self.text_to_instance(text, sentiment)


class SimpleClassifier(Model):
    def __init__(self,
                 vocab: Vocabulary,
                 embedder: TextFieldEmbedder,
                 encoder: Seq2VecEncoder):
        super().__init__(vocab)
        self.embedder = embedder
        self.encoder = encoder
        num_labels = vocab.get_vocab_size("labels")
        self.classifier = torch.nn.Linear(encoder.get_output_dim(), num_labels)
        self.accuracy = CategoricalAccuracy()

    def forward(self,
                text: Dict[str, torch.Tensor],
                label: torch.Tensor = None) -> Dict[str, torch.Tensor]:
        # Shape: (batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # Shape: (batch_size, num_tokens)
        mask = util.get_text_field_mask(text)
        # Shape: (batch_size, encoding_dim)
        encoded_text = self.encoder(embedded_text, mask)
        # Shape: (batch_size, num_labels)
        logits = self.classifier(encoded_text)
        # Shape: (batch_size, num_labels)
        probs = torch.nn.functional.softmax(logits)
        output = {'probs': probs}
        if label is not None:
            self.accuracy(logits, label)
            # Shape: (1,)
            output['loss'] = torch.nn.functional.cross_entropy(logits, label)
        return output

    def get_metrics(self, reset: bool = False) -> Dict[str, float]:
        return {"accuracy": self.accuracy.get_metric(reset)}

    def get_metrics(self, reset: bool = False) -> Dict[str, float]:
        return {"accuracy": self.accuracy.get_metric(reset)}


def build_dataset_reader() -> DatasetReader:
    return ClassificationTsvReader()

def read_data(
    reader: DatasetReader
) -> Tuple[Iterable[Instance], Iterable[Instance]]:
    print("Reading data")
    training_data = reader.read("quick_start/data/movie_review/train.tsv")
    validation_data = reader.read("quick_start/data/movie_review/dev.tsv")
    return training_data, validation_data

def build_vocab(instances: Iterable[Instance]) -> Vocabulary:
    print("Building the vocabulary")
    return Vocabulary.from_instances(instances)

def build_model(vocab: Vocabulary) -> Model:
    print("Building the model")
    vocab_size = vocab.get_vocab_size("tokens")
    embedder = BasicTextFieldEmbedder(
        {"tokens": Embedding(embedding_dim=10, num_embeddings=vocab_size)})
    encoder = BagOfEmbeddingsEncoder(embedding_dim=10)
    return SimpleClassifier(vocab, embedder, encoder)

def build_data_loaders(
    train_data: torch.utils.data.Dataset,
    dev_data: torch.utils.data.Dataset,
) -> Tuple[allennlp.data.DataLoader, allennlp.data.DataLoader]:
    # Note that DataLoader is imported from allennlp above, *not* torch.
    # We need to get the allennlp-specific collate function, which is
    # what actually does indexing and batching.
    train_loader = DataLoader(train_data, batch_size=8, shuffle=True)
    dev_loader = DataLoader(dev_data, batch_size=8, shuffle=False)
    return train_loader, dev_loader

def build_trainer(
    model: Model,
    serialization_dir: str,
    train_loader: DataLoader,
    dev_loader: DataLoader
) -> Trainer:
    parameters = [
        [n, p]
        for n, p in model.named_parameters() if p.requires_grad
    ]
    optimizer = AdamOptimizer(parameters)
    trainer = GradientDescentTrainer(
        model=model,
        serialization_dir=serialization_dir,
        data_loader=train_loader,
        validation_data_loader=dev_loader,
        num_epochs=5,
        optimizer=optimizer,
    )
    return trainer

def run_training_loop():
    dataset_reader = build_dataset_reader()

    # These are a subclass of pytorch Datasets, with some allennlp-specific
    # functionality added.
    train_data, dev_data = read_data(dataset_reader)

    vocab = build_vocab(train_data + dev_data)
    model = build_model(vocab)

    # This is the allennlp-specific functionality in the Dataset object;
    # we need to be able convert strings in the data to integers, and this
    # is how we do it.
    train_data.index_with(vocab)
    dev_data.index_with(vocab)

    # These are again a subclass of pytorch DataLoaders, with an
    # allennlp-specific collate function, that runs our indexing and
    # batching code.
    train_loader, dev_loader = build_data_loaders(train_data, dev_data)

    # You obviously won't want to create a temporary file for your training
    # results, but for execution in binder for this guide, we need to do this.
    with tempfile.TemporaryDirectory() as serialization_dir:
        trainer = build_trainer(
            model,
            serialization_dir,
            train_loader,
            dev_loader
        )
        trainer.train()

    return model, dataset_reader
class SentenceClassifierPredictor(Predictor):
    def predict(self, sentence: str) -> JsonDict:
        return self.predict_json({"sentence": sentence})

    def _json_to_instance(self, json_dict: JsonDict) -> Instance:
        sentence = json_dict["sentence"]
        return self._dataset_reader.text_to_instance(sentence)


# We've copied the training loop from an earlier example, with updated model
# code, above in the Setup section. We run the training loop to get a trained
# model.
model, dataset_reader = run_training_loop()
vocab = model.vocab
predictor = SentenceClassifierPredictor(model, dataset_reader)

output = predictor.predict('A good movie!')
print([(vocab.get_token_from_index(label_id, 'labels'), prob)
       for label_id, prob in enumerate(output['probs'])])
output = predictor.predict('This was a monstrous waste of time.')
print([(vocab.get_token_from_index(label_id, 'labels'), prob)
       for label_id, prob in enumerate(output['probs'])])

上のコードを実行するとき、以下に類似した結果を得るはずです :

[('neg', 0.48853254318237305), ('pos', 0.511467456817627)]
[('neg', 0.5346643924713135), ('pos', 0.4653356373310089)]

これは少なくともこれらのインスタンスについては貴方のモデルは正しい予測をしていることを意味します!

 

コマンドラインから

コマンドラインからモデルと相互作用することを好むのであれば、AllenNLP は予測を行なうために predict コマンドを提供します。このコマンドは allennlp evaluate (上参照) に非常に類似しています —それはシリアライズ化されたテスト・インスタンスを含む JSON ファイルへのパスとともに allennlp train コマンドにより作成されたモデル・アーカイブファイルへのパスを取り、そしてこれらのインスタンスに対してモデルを実行します。

全体として、ここまでに構築したモデル、データセット・リーダ、そして predictor が my_text_classifier と命名されたあるモジュール内で定義されると仮定すると、モデルを訓練し、それを評価し、そして初見データのために予測を行なうために以下の AllenNLP コマンドを使用するでしょう。AllenNLP が貴方のモジュールを見つけられるように –include-package オプションを供給する必要があることを忘れないでください。総てのサンプルコードは このレポ でセットアップされます。それをクローンして、quick_start ディレクトリに cd して、そしてちょうどこれが貴方自身のプロジェクト・ディレクトリであるかのようにこれらのコマンドを実行する必要があるだけです。

$ allennlp train \
    my_text_classifier.jsonnet \
    --serialization-dir model \
    --include-package my_text_classifier

$ allennlp evaluate \
    model/model.tar.gz \
    data/movie_review/test.tsv \
    --include-package my_text_classifier

$ allennlp predict \
    model/model.tar.gz \
    data/movie_review/test.jsonl \
    --include-package my_text_classifier \
    --predictor sentence_classifier

And that’s it! 単純ですが、この章では AllenNLP を使用して full-fledged NLP モデルを訓練して実行しまいsた。次の章では幾つかのより進んだ AllenNLP 特徴、そして貴方が次に試すことを望むかもしれないもののプレビューを与えます。

 

以上