ParlAI基本使用【文档翻译】

Intro to ParlAI

What is ParlAI?

ParlAI 是一个基于 python 的平台,用于启用对话 AI 研究。

其目标是为研究人员提供:

  • 用于共享、训练和测试对话模型的统一框架

  • 许多流行的数据集都在一个地方可用,并且能够对它们进行多任务处理

  • 无缝集成 Amazon Mechanical Turk,用于数据收集和人工评估

  • 与 Facebook Messenger 等聊天服务集成,在聊天界面中将代理与人类联系起来

Core Concepts

我们将介绍我们在 ParlAI 框架中使用的一些术语。在 ParlAI 中,我们将环境称为world。在每个world中,都有agents。agents的示例包括模型和数据集。agents之间通过彼此轮流的 acting and observing acts 进行交流。【acting ,observing 属于agent的两个acts】

为了实现这一点,我们将在我们的 Quick Start 中考虑用于在 Twitter 数据集上训练 Transformer ranker 的训练循环。我们称这个train environment 为一个world,它包含两个agents——Transformer model和 dataset。model 和dataset agents 以这种方式相互交互: dataset首先行动并输出一批具有正确标签的训练示例。model 观察此行为以获取训练示例,然后通过对该批次执行单个训练步骤(预测label 并根据loss 更新其参数)来进行操作。dataset 观察这一行为并输出下一批,依此类推。

这只是一个简单概述。下面让我们更详细地了解这些概念以及如何使用它们。

Agents

ParlAI 中最基本的概念是agent。agents可以是人类,可以是一个简单的机器人,它会重复它所听到的任何内容,比如你完美调整的神经网络,正在读取的数据集,或者任何其他可能发送消息或与其环境交互的东西。

agent有两个需要定义的主要方法:

def observe(self, observation): # update internal state with observation
def act(self): # produce action based on internal state

observe() 将观察字典作为输入,该字典通常是另一个代理采取的行动的结果,并相应地更新该代理的内部状态。

act() 从agent产生一个动作。对于dataset agent,这可能会增加所见example的计数器并返回下一批示例。对于neural net agent,这可能是训练步骤或评估步骤。

Messages

消息是我们所说的对象,既由agent 观察(observations),又由agent 的act 函数(actions)返回。这些observations 和actions 是 ParlAI 中的agents在其环境中相互通信的主要方式。 Message 对象是 python dict 的子类,其关键功能是防止用户无意中编辑 action or observation 中的字段。

messages 文档更详细地介绍了每个字段,但下表显示了基础知识。

image-20220801150019723

所有这些字段在技术上都是可选的(optional),每个任务都应该根据该任务中可用的信息类型来使用它们(例如,并非所有task 都包含明确的奖励,或者一组可供选择的候选标签)。

在某些情况下可以使用特定于数据集的字段(Dataset-specific fields),以支持复制论文结果。例如,SQuAD 有一个 answer_starts 字段,在“squad:index”任务中可用。要显示消息的所有字段,只需将 --verbose True 添加到您的命令中。

在验证和测试期间,label字段被重命名为 eval_labels——这样,模型不会意外地在标签上进行训练,但它们仍然可用于计算模型端损失。

模型可以通过以下方式检查他们是否正在对监督任务进行训练:

is_training = 'labels' in observation

Teachers

Teachers 是特殊类型的agent 。他们像所有agents一样执行 actobserve功能,但他们还跟踪通过report功能返回的 metrics ,例如他们提出的问题数量或这些问题被正确回答的次数。

Datasets 和tasks 通常实现Teacher 的一个子类,提供函数,如果需要,从其源下载数据集,将文件读取为正确的格式,并在每次调用教师的act函数时返回一个示例。

student(model)agent 和 bAbI task teacher之间交换的观察结果可能如下所示:

Teacher: {
    'text': 'Sam went to the kitchen\nPat gave Sam the milk\nWhere is the milk?',
    'labels': ['kitchen'],
    'label_candidates': ['hallway', 'kitchen', 'bathroom'],
    'episode_done': False  # indicates next example will be related to this one
}
Student: {
    'text': 'hallway'
}
Teacher: {
    'text': 'Sam went to the hallway\nPat went to the bathroom\nWhere is the milk?',
    'labels': ['hallway'],
    'label_candidates': ['hallway', 'kitchen', 'bathroom'],
    'episode_done': True
}
Student: {
    'text': 'hallway'
}
Teacher: {
    ... # starts next episode
}
...

Worlds

Worlds定义了agent 相互交互的环境。Worlds 必须实现一个parley (谈判)方法。每次对 parley 的调用都会进行一轮交互,通常每个代理包含一个action。

ParlAI 中包含的一个简单world ,我们当前包含的所有任务都使用它,是 DialogPartnerWorld。 DialogPartnerWorld 由one task teacher agent 和one student agent 初始化。每次调用 parley 时,代理之间都会进行一次交换,方式如下:

query = teacher.act()
student.observe(query)
reply = student.act()
teacher.observe(reply)

我们包含的另一个简单世界是 MultiAgentDialogWorld,它与此类似,但将其概括为以循环方式在任意数量的代理之间循环。

Using ParlAI

**Concepts in Action: Simple Display Data Script: **

现在我们了解了基础知识,让我们设置一个简单的脚本来显示任何指定的任务。此实用程序的完整版本包含在 parlai/scripts/display_data.py 中,但我们将从头开始演示我们刚刚介绍的概念。

我们将创建一个新的agent类并实现observe()act() 函数,以便在有task teacher 的世界中,它会观察task teacher 输出的数据,将数据保存为最后一次observation ,然后通过在observation 中打印label 来进行act 。

首先,进行一些引入包:

from parlai.core.agents import Agent
from parlai.core.params import ParlaiParser
from parlai.core.worlds import create_task

Agent 类将是我们自己的agent 的父类。 ParlaiParser 提供了一组默认的命令行参数和解析,create_task 将为我们选择的 ParlAI 中的 任何可行的任务, 自动设置适当的word 和teacher 。

我们定义我们的Agent (我们将其命名为 RepeatLabelAgent):

class RepeatLabelAgent(Agent):
    # initialize by setting id
    def __init__(self, opt):
        self.id = 'RepeatLabel'
    # store observation for later, return it unmodified
    def observe(self, observation):
        self.observation = observation
        return observation
    # return label from before if available
    def act(self):
        reply = {'id': self.id}
        if 'labels' in self.observation:
            reply['text'] = ', '.join(self.observation['labels'])
        else:
            reply['text'] = "I don't know."
        return reply

现在我们有了我们的agent,我们将设置显示循环( display loop )。

parser = ParlaiParser()
opt = parser.parse_args()

agent = RepeatLabelAgent(opt)
world = create_task(opt, agent)

for _ in range(10):
    world.parley()
    print(world.display())
    if world.epoch_done():
        print('EPOCH DONE')
        break

就是这样! world.display() 循环遍历world 的每个agent 并显示它们的最后一个action 。但是,如果你想直接访问数据而不调用 world.display(),你可以直接访问 world.acts:

parser = ParlaiParser()
opt = parser.parse_args()

agent = RepeatLabelAgent(opt)
world = create_task(opt, agent)

for _ in range(10):
    world.parley()
    for a in world.acts:
        # print the actions from each agent
        print(a)
          if world.epoch_done():
              print('EPOCH DONE')
              break

Validation and Testing

在验证和测试期间,“label” 字段从observation 字典中删除。这告诉agent 不要使用这些标签进行训练——但是,label 仍然可以通过“eval_labels”字段获得,以防您需要model-side metrics ,例如perplexity (困惑度)。

在这些情况下,我们的 RepeatLabel agent不再有什么可说的。对于提供一组可供选择的候选者的数据集(观察字典中的“label_candidates”),我们可以通过回复其中一个来让我们的agent 有机会获得正确的答案。

让我们修改agent 的 act 函数以在label 不可用时选择随机 label candidate :

import random

def act(self):
    reply = {'id': self.id}
    if 'labels' in self.observation:
        reply['text'] = ', '.join(self.observation['labels'])
    elif 'label_candidates' in self.observation:
        cands = self.observation['label_candidates']
        reply['text'] = random.choice(list(cands))
    else:
        reply['text'] = "I don't know."
    return reply

Tasks

如果您在命令行上运行它,您可以通过以下格式设置“-t {task}”来指定要显示的任务:

  • ‘-t babi’ 在 ‘parlai/core/tasks/babi/agents.py’ 中设置 DefaultTeacher

  • ‘-t babi:task1k’ 在 babi/agents.py 文件中设置 Task1kTeacher,它允许您为某些任务指定特定设置。对于 bAbI,这是指每个任务只有 1000 个唯一训练示例的设置。

  • ‘-t babi:task1k:1’ 将 1 作为参数提供给 Task1kTeacher,Task1kTeacher 将其解释为“我想要任务 1”(与其他 19 个 bAbI 任务相反)。

  • ‘-t babi,squad’ 为 babi 和squad 设置 DefaultTeacher。任何数量的任务都可以用逗号链接在一起以加载每个任务。

  • “-t #qa”指定“qa”类别,在“parlai/core/task_list.py”文件中加载该类别的所有任务

这些flags 在 ParlAI 中使用。以下是使用它们通过现有脚本 display_data 显示数据的一些示例

#Display 10 random examples from task 1 of the "1k training examples" bAbI task:
$ parlai display_data --task babi:task1k:1

#Displays 100 random examples from multi-tasking on the bAbI task and the SQuAD dataset at the same time:
$ parlai display_data --task babi:task1k:1,squad -n 100

在上一节中,我们提到label 在验证和测试时是隐藏的。 –datatype (-dt) 标志指定train、valid 或test 。这些模式可以从命令行使用“-dt valid”/“-dt test”进行设置。如果您想以与测试数据相同的方式查看训练数据(隐藏标签),您还可以设置“-dt train:evalmode”。

ParlAI 会自动下载请求任务所需的数据(使用任务中的 build.py 代码)并将其放入您的 –datapath。默认情况下这是 ParlAI/data,但您可以将其配置为指向其他地方,例如到另一个具有更多内存的磁盘。仅下载您请求的任务。此外,您可以指定 -dt train:stream 或 –datatype valid:stream 来表示您希望数据尽可能在线流式传输,而不是加载到内存中。

您还可以指定 –datatype train:ordered 覆盖训练集中的数据以随机顺序出现的默认行为(而有效和测试数据是默认排序的)。

我们在此处的代码code here或此处的文档documentation here中维护了完整的任务列表。 ParlAI 中的任务集从贡献者那里不断增长。请参阅本教程 this tutorial以制作您自己的任务。

Training and Evaluating Existing Agents

现在,我们将看看我们为训练和评估提供的脚本: train_model and eval_model。这里有些例子:

# Train a seq2seq model on the "10k training examples" bAbI task 1 with batch size of 32 examples until accuracy reaches 95% on validation (requires pytorch):
$ parlai train_model --task babi:task10k:1 --model seq2seq --model-file /tmp/model_s2s --batchsize 32 --validation-every-n-secs 30

# Trains an attentive LSTM model on the SQuAD dataset with a batch size of 32 examples (pytorch and regex):
$ parlai train_model --model drqa --task squad --batchsize 32 --model-file /tmp/model_drqa

# Tests an existing generative transformer from our model zoo
$ parlai eval_model --task convai2 --model-file "zoo:tutorial_transformer_generator/model"

# Evaluate on the bAbI test set with a human agent (using the local keyboard as input):
$ parlai eval_model --model local_human --task babi:Task1k:1 --datatype valid

# Evaluate an IR baseline model on the validation set of ConvAI2:
$ parlai eval_model --model ir_baseline --task convai" --datatype valid

# Display the predictions of that same IR baseline model:
$ parlai display_model --model ir_baseline --task convai2 --datatype valid

主要flags是:

  1. –model (-model) 设置将被训练的agent 类型。 parlAI 中可用的agents在这里here。请参阅本教程this tutorial 以制作您自己的代理。
  • –model-file (-mf) 指向保存模型的文件名。

  • –task (-t) 如前所述。

Interacting with Models

也可以与您的模型交谈!以下是与model zoo中已有model 交谈的示例:

Model Zoo

ParlAI 现在维护了一个模型库,其中包含已接受过任务训练的代理的现有模型文件。有关详细信息,请参阅专用文档部分或此处here for details。

ParlAI 模型Zoo 中的agents 和model 伴随贡献者不断增长。

Task and Datasets in ParlAI

ParlAI 可以支持用于监督学习的固定对话数据(我们称为dataset ),甚至可以支持涉及环境、agent和可能的award的动态任务(我们将一般情况称为task)。

在本教程中,我们将介绍创建新task(或dataset)的不同方式。

所有设置都以几乎相同的方式处理,使用相同的 API,制作基本数据集的步骤当然更少。

要快速添加new dataset,请转到下面的快速入门(Quickstart)

如需更完整的说明,或更复杂的设置(如流式传输大数据),请转到创建新任务:更完整的方式部分[Creating a new task: the more complete way.]。

Quickstart: Adding a new dataset

让我们先看看将新数据集导入 ParlAI 的最简单方法。

如果您有dialog、QA 或其他text-only 数据集,您可以将其放入我们现在将描述的格式(称为 ParlAI Dialog Format))的文本文件中,您可以直接从那里加载它,无需额外代码!

这是一个带有 2 个示例的单集示例数据集:

text:hello how are you today?labels:i'm great thanks! what are you doing?
text:i've just been biking.labels:oh nice, i haven't got on a bike in years!episode_done:True

假设数据在文件 /tmp/data.txt。我们可以使用通常的显示数据脚本查看该数据:

$ parlai dd --task fromfile:parlaiformat --fromfile_datapath /tmp/data.txt

08:19:45 | creating task(s): fromfile:parlaiformat
08:19:45 | Loading ParlAI text data: /tmp/data.txt
- - - NEW EPISODE: tmp/data.txt - - -
hello how are you today?
   i'm great thanks! what are you doing?
i've just been biking.
   oh nice, i haven't got on a bike in years!   episode_done:True
08:19:45 | epoch done
08:19:45 | loaded 1 episodes with a total of 2 examples

文本文件数据格式称为 ParlAI Dialog 格式,在Teachers 文档中有说明;在 pyparlai.core.teachers.ParlAIDialogTeacher 中。本质上,每一行都有一个训练示例,并且 ParlAI 消息中的每个字段都以制表符分隔,字段名称后跟冒号。例如。可以使用“text”、“labels”、“label_candidates”等常用字段,或者如果您有特殊用途,也可以添加自己的字段。

Handling Separate Train/Valid/Test data

一旦您掌握了上述数据的基础知识,您可能希望将数据分离到特定的训练/有效/测试集,因为上面的example 对所有折叠使用相同的数据。这也很容易做到。只需将数据分成三个单独的文件:mydata_train.txtmydata_valid.txtmydata_test.txt。之后,修改您的 parlai 调用如下:

$ python parlai/scripts/display_data.py --task fromfile:parlaiformat --fromfile-datapath /tmp/mydata --fromfile-datatype-extension true

这将导致系统在训练、评估等期间的适当时间添加 _train.txt_valid.txt_test.txt 后缀。

Json file format (instead of text file format)

更喜欢使用 json 而不是文本文件?没问题,设置几乎一样!改用这样的文件(使用与上面相同的示例数据):

{ "dialog": [ [  {"id": "partner1", "text": "hello how are you today?"},  {"id": "partner2", "text": "i'm great thanks! what are you doing?"},  {"id": "partner1", "text": "i've just been bikinig."},        {"id": "partner2", "text": "oh nice, i haven't got on a bike in years!"} ] ]}

对于训练/有效/测试拆分,您可以使用类似的 –jsonfile-datatype-extension true 标志执行与文本文件相同的操作。

Creating a new task: the more complete way

当然,在您第一次实现后,您可能希望实际使用在代码里面,以便与他人分享!

tasks 代码位于 parlai/tasks 目录中。

您将需要在那里为您的新任务创建一个目录。

如果你的数据是 ParlAI 格式,你实际上只需要一点点样板来加载它,参见例如我们刚刚使用的 fromfile 任务代理的代码。

但是现在,让我们完成所有步骤。您将需要:

  1. 添加 __init__.py 文件以确保导入正常工作。
  2. 实现 build.py 以下载和构建任何需要的数据(请参阅第 1 部分:构建数据 Part 1: Building the Data)。
  3. 实现agents.py,至少有一个DefaultTeacher 扩展Teacher 或它的一个孩子(参见第2 部分:创建Teacher Part 2: Creating the Teacher)。
  4. 将任务添加到任务列表(参见第 3 部分:将任务添加到任务列表 Part 3: Add Task to Task List)。

Part 1: Building the Data

如果您不打算将任务提交给 ParlAI,而是希望出于自己的目的从磁盘本地加载数据,则可以跳过本节并直接进入第 2 部分。

我们首先需要创建用于下载和设置将用于任务的数据集的功能。这是在 build.py文件中完成的。可以在 parlai.core.build_data 中找到用于设置数据的有用功能。

import parlai.core.build_data as build_data
import os

现在我们定义build method,它接受参数 opt,其中包含来自命令行(或其默认值)的解析参数,包括数据目录的路径。我们还可以定义一个版本字符串,以便在我们对此任务进行更改时为其他 ParlAI 用户自动删除和更新数据(这里只是将其保留为 None)。然后,我们使用 build_data 实用程序检查此数据是否以前未构建过,或者版本是否已过时。如果没有,我们继续为数据创建目录,然后下载并解压缩它。最后,我们将构建标记为完成,以便 build_data.built() 从现在开始返回 true。下面是设置 SQuAD 数据集的示例。

RESOURCES = [
    DownloadableFile(
        'https://rajpurkar.github.io/SQuAD-explorer/dataset/train-v1.1.json',
        'train-v1.1.json',
        '',
        zipped=False,
    ),
    DownloadableFile(
        'https://rajpurkar.github.io/SQuAD-explorer/dataset/dev-v1.1.json',
        'dev-v1.1.json',
        '',
        zipped=False,
    ),
]

def build(opt):
    dpath = os.path.join(opt['datapath'], 'SQuAD')
    version = None

    if not build_data.built(dpath, version_string=version):
        print('[building data: ' + dpath + ']')
        if build_data.built(dpath):
            # An older version exists, so remove these outdated files.
            build_data.remove_dir(dpath)
        build_data.make_dir(dpath)

        # Download the data.
        for downloadable_file in RESOURCES[:2]:
            downloadable_file.download_file(dpath)

        # Mark the data as built.
        build_data.mark_done(dpath, version_string=version)

Part 2: Creating the Teacher

ParlAIDialogTeacher

对于这个类,使用者必须实现至少一个__init__()功能,经常仅仅创建它就可以。

在这个章节,将展示使用 ParlAIDialogTeacher 类,通过添加Twitter dataset。此任务具有文本形式的数据,并已格式化为遵循 ParlAI 对话框格式,仅此使用 ParlAIDialogTeacher去实现很容易。关于这个类和对话框格式的更多信息可以在教师文档中找到。

在此任务中,代理会收到有关可从 Wikipedia 回答的电影的问题。下面演示了一个示例对话框。

[twitter]: burton is a fave of mine,even his average films are better than most directors.
[labels: keeping my fingers crossed that he still has another ed wood in him before he retires.]
- - - - - - - - - - - - - - - - - - - - -
~~
[twitter]: i saw someone say that we should use glass straws..
[labels: glass or paper straws - preferably no 'straw' waste. ban !]

每个 task 都要求一个DefaultTeacher,由于我们是 ParlAIDialogTeacher 的子类,我们只需要初始化类并设置一些选项参数,如下所示。

class DefaultTeacher(ParlAIDialogTeacher):
    def __init__(self, opt, shared=None):
        opt = copy.deepcopy(opt)

        # get datafile
        opt['parlaidialogteacher_datafile'] = _path(opt, '')

        super().__init__(opt, shared)

_path() 函数,它返回正确数据文件的路径。然后文件的路径存储在 parlaidialogteacher_datafile 键下的选项字典中。

此项被传递给 setup_data() 以便子类可以只覆盖路径而不是函数。我们仍然需要去实现_path()功能,

此示例的版本如下所示。它首先通过调用第 1 部分中描述的 build() 方法来确保构建数据。然后为构建的数据设置路径。

再次注意,如果您从磁盘本地加载数据,您可以跳过此处的 build 调用,而只需在给定 opt[‘datatype’] 的情况下返回本地数据文件的路径。

from .build import build

def _path(opt, filtered):
    # build the data if it does not exist
    build(opt)

    # set up path to data (specific to each dataset)
    dt = opt['datatype'].split(':')[0]
    return os.path.join(opt['datapath'], 'Twitter', dt + '.txt')

以上就是使用ParlAIDialogTeacher为我们的task创建一个 Teacher 所做的全部。

要访问这些数据,我们现在可以使用example目录中的 display_data.py 文件:

parlai display_data --task twitter
DialogTeacher

对于这个类,用户还必须实现他们自己的setup_data()函数,但支持hogwild或batching、streaming data from disk、processing images等其他工作都为他们解决了。

本节,将通过添加SQuAD数据集展示DialogTeacher类。这个数据集从网上下载存在磁盘里,并不符合基本的ParlAIDialogTeacher的形式。所以,使用DialogTeacher将会使得实现这种dialog task更容易。

在这个任务中,代理会看到一个来自维基百科的段落,并被要求回答一个关于它的问题。

[id]: squad
[text]: In October 2014, Beyoncé signed a deal to launch an activewear line of clothing with British fashion retailer Topshop. The 50-50 venture is called Parkwood Topshop Athletic Ltd and is scheduled to launch its first dance, fitness and sports ranges in autumn 2015. The line will launch in April 2016.
When will the full line appear?

[labels]: April 2016

创建我们的teacher SquadTeacher类,首先初始化。

class SquadTeacher(DialogTeacher):
    def __init__(self, opt, shared=None):
        self.datatype = opt['datatype']
        build(opt)  # NOTE: the call to build here
        suffix = 'train' if opt['datatype'].startswith('train') else 'dev'
        # whatever is placed into datafile will be passed as the argument to
        # setup_data in the next section.
        opt['datafile'] = os.path.join(opt['datapath'], 'SQuAD', suffix + '-v1.1.json')
        self.id = 'squad'
        super().__init__(opt, shared)

通过创建SquadTeacher作为DialogTeacher的子类,为这项任务创建Teacher 的工作变得更加简单:需要完成的大部分工作将仅限于定义 setup_data 方法。此方法是一个生成器,它将获取数据的路径并为每次调用生成一对元素(The pair)。The pair 的第一个元素是包含对话行为的字典(带有必填字段textlabel以及您的 task 所需的任何其他附加字段,例如 label_candidates)。在这个例子,仅仅返回textlabel 。第二个是一个布尔标志new_episode? 表示当前查询是否开始一个新的情节。

有关此格式的更多信息,请参见教师文档 DialogData 下的文档(setup_data 作为DialogData的 data_loader 提供)。

setup_data的实现如下:

from parlai.utils.io import PathManager
def setup_data(self, path):
    # note that path is the value provided by opt['datafile']
    print('loading: ' + path)
    with PathManager.open(path) as data_file:
        self.squad = json.load(data_file)['data']
    for article in self.squad:
        # each paragraph is a context for the attached questions
        for paragraph in article['paragraphs']:
            # each question is an example
            for qa in paragraph['qas']:
                question = qa['question']
                answers = tuple(a['text'] for a in qa['answers'])
                context = paragraph['context']
                yield {"text": content + "\n" + question, "labels": answers}, True  # 表示当前查询是否开始一个新的情节

从上面的代码中我们可以看到,对于这个任务,每个episode 只包括一个查询,因此new_episode? 总是为True(即每个查询是其情节的开始)。这也可以根据任务的不同而不同。

最后,有一点值得注意,在元组中,no reward, label candidates, or a path to an image。这些字段与本任务无关。

Chunk Teacher

Chunk Teacher 可用于流式传输大量数据(读取:不适合内存),这些数据自然地分成几个单独的文件(或块)。数据被分成块并一次加载一个块。从主线程加载数据。

Task from Scratch

在这种情况下,将继承自 Teacher 类。对于这个类,至少 act() 方法和可能的 observe() 方法必须被覆盖。将不会内置相当多的功能,例如对 hogwild 和batching的支持,但将设置指标并可用于跟踪统计信息,例如正确回答示例的数量。

Part 3: Add Task to Task List

现在,我们的task任务已经完成,必须将其添加到parlai/taskstask_list.py文件中。

该文件仅包含所有任务的 json 格式列表,每个任务都表示为一个字典。我们的任务的示例条目如下所示。

[
    # other tasks...
    {
        "id": "MTurkWikiMovies",
        "display_name": "MTurk WikiMovies",
        "task": "mturkwikimovies",
        "tags": [ "all",  "QA" ],
        "description": "Closed-domain QA dataset asking MTurk-derived questions about movies, answerable from Wikipedia. From Li et al. '16. Link: https://arxiv.org/abs/1611.09823"
    },
    {
        "id": "MNIST_QA",
        "display_name": "MNIST_QA",
        "task": "mnist_qa",
        "tags": [ "all", "Visual" ],
        "description": "Task which requires agents to identify which number they are seeing. From the MNIST dataset."
    },
    # other tasks...
]

Part 4: Executing the Task

测试任务中基本功能的一种简单方法是运行example目录中的 display_data.py 示例。

现在工作已经完成,我们可以使用-t标志将其传递给 ParlAI。例如,要执行 MTurk WikiMovies 任务,我们应该调用:

python display_data.py --task mturkwikimovies

要运行 MNIST_QA 任务,同时以 ascii 格式显示图像,我们可以调用:

python display_data.py --task mnist_qa -im ascii

Worlds, Sharing & Batching

Introduce

本节将介绍worlds,在ParlAI中很重要的一个core,它旨在对 ParlAI 中如何实现某些概念进行高级概述,以便您了解幕后发生的事情,但您不太可能自己编写任何自定义worlds。

worlds 是agent 生活的地方,worlds 定义了agents的通信流程。如果您熟悉强化学习,那么worlds大致类似于environments。

在最基本的层面上,worlds 只是简单地容纳agents 并在每个agent 之间传递消息。每个消息传递都以 Parley 的形式发生。例如,DialogPartnerWorld 包含这个粗略的伪代码作为它的 parley【谈判】:

class SimpleWorld(World):
    def __init__(opt, agents):
        self.teacher, self.model = agents

    def parley():
        # produce an example dataset item
        teacher_act = self.teacher.act()
        # perform preprocessing, vectorizing, etc.
        self.model.observe(teacher_act)
        # produce a model response
        model_act = self.model.act()
        # compute any metrics of the response
        self.teacher.observe(model_act)

标识为下面的图片:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eWDGPC5d-1659521389266)(C:\Users\Tony\AppData\Roaming\Typora\typora-user-images\image-20220730212746542.png)]

The parley method is usually run in a loop, until we run out of examples:

while not world.epoch_done():
    world.parley()

这种简单的循环结构功能强大,让我们能够呈现所有agents的统一视图,无论它们是datasets、models还是通过聊天服务连接的humans:总有一个世界,它有助于agents来回传递消息。

然而,这个循环可能效率低下。 GPU 等现代硬件和随机梯度下降【Stochastic Gradient Descent 】等学习算法受益于批处理或同时处理多个项目。

在本文档的其余部分,我们将介绍batching的工作原理,并最终讨论动态批处理的实现。

Agent Sharing

Agent 共享是我们实现批处理、模型服务和许多其他 ParlAI 功能的主要机制。Agent 共享通过创建Agent 的克隆来工作。 Agent 的每个克隆都有共享状态和独立状态。独立状态包括当前对话上下文(对话历史,每个批次元素都不同)。共享状态包括模型权重等内容。

agent_original = Agent(opt)
agent_copy1 = agent_original.clone()
agent_copy2 = agent_original.clone()
agent_copy3 = agent_original.clone()

克隆是大多数agent在其初始化过程中具有这种粗略结构的原因。

class PseudoAgent(Agent):
    def __init__(self, opt: Opt, shared: dict = None):
        super().__init__(opt, shared)
        if shared is None:
            # When shared is None, we are the original, and we need to
            # initialize any state
            self.model = self.build_model()
        else:
            # When shared is NOT None, we are a clone.
            # Use the shared dict to set any variables. this is where we
            # incorporate any shared state
            self.model = shared['model']

    def share(self):
        # This is used to create the "shared" dictionary that clones will
        # receive in initialization. Make sure you enumerate everything
        # that needs to be seen in all clones!

        # You will probably never call this method yourself. Rather, it is
        # called automatically by `clone()`
        shared = super().share()
        shared['model'] = self.model
        return shared

创建模型的每个克隆都相对便宜:唯一使用的新内存是对话上下文。共享对象使我们能够为昂贵的对象重用内存,例如神经网络权重。

乍一看,创建这些克隆可能看起来很奇怪。为什么我们要创建所有这些副本?区分共享状态和独立状态让我们在编写代理时考虑到独立性。代理只需要专注于单个对话,维护和操纵该对话的状态。每个克隆都将维护单独的对话,因此当我们实施新的数据集或模型时,我们一次只关注一个对话。

构成了Batching或聊天服务背后的基本后端:代理的每个克隆一次只需要专注于一个对话,只有特定的同步点。在下一节中,我们将看看它是如何在批处理中使用的。

Batching

If you’re implementing your own custom Neural Network model, you don’t need to worry about this, except at a high level. This is all handled for you by TorchAgent.

配备了创建克隆的能力,我们可以使用它来实现Batching。对于 3 的batch_size,我们将创建 3 个教师克隆和 3 个代理克隆。这些克隆中的每一个都将保持各自独立的对话。**天真地,**这可以用一个简单的 for 循环来实现:

class NaiveBatchWorld(World):
    def __init__(self, opt, agents):
        # store the originals
        self.teacher, self.model = agents
        # make batchsize copies of all the models
        self.teacher_copies = []
        self.model_copies = []
        self.batchsize = opt['batchsize']
        for i in range(self.batchsize):
            self.teacher_copies.append(self.teacher.clone())
            self.model_copies.append(self.model.clone())

初始化代码可以用下图表示:

image-20220730215213529

继续实现 parley 代码:

def parley(self):
     for i in range(self.batchsize):
        # produce an example dataset item
        teacher_act = self.teacher_copies[i].act()
        # perform preprocessing, vectorizing, etc.
        self.model_copies[i].observe(teacher_act)
        # produce a model response
        model_act = self.model_copies[i].act()
        # compute any metrics of the response
        self.teacher_copies[i].observe(model_act)
image-20220730215413938

然而,这是低效的,并且使我们无法利用现代 GPU 惊人的矢量化功能。相反,我们将实现一个特殊的 batch_act method。此方法将改为一次处理所有行为。

class BatchWorld(World):
    def __init__(self, opt, agents):
        # store the originals
        self.teacher, self.model = agents
        # make batchsize copies of all the models
        self.teacher_copies = []
        self.model_copies = []
        self.batchsize = opt['batchsize']
        for i in range(self.batchsize):
            self.teacher_copies.append(self.teacher.clone())
            self.model_copies.append(self.model.clone())

    def parley(self):
        observations = []
        for i in range(self.batchsize):
            # produce an example dataset item
            teacher_act = self.teacher_copies[i].act()
            # perform preprocessing, vectorizing, etc.
            observation[i] = self.model_copies[i].observe(teacher_act)

        # now batch_act can efficiently do everything on the GPU
        model_acts = self.model.batch_act(observations)

        # return the results of the batch_act back to individual conversations
        for i in range(self.batchsize):
            # self_observe is how we tell each copy what their individual
            # actions are
            self.model_copies[i].self_observe(model_acts[i])
            # compute any metrics of the response
            self.teacher_copies[i].observe(model_acts[i])

这个逻辑比较复杂,但是可以让我们高效的实现批量操作。新的逻辑可以封装在这个图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mMj82PL-1659521389267)(C:\Users\Tony\AppData\Roaming\Typora\typora-user-images\image-20220730215536674.png)]

Dynamic Batching

作为最后的部分,我们将在高层次上讨论如何实现 Dynamic Batching(也称为自适应批处理)。默认情况下,所有 TorchAgent 都支持动态批处理,并且可以使您的训练速度提高 2-3 倍。

动态批处理用于通过对长度相似的示例进行分组以使它们同时发生,从而最大限度地利用 GPU。直观地说,为了最大限度地利用 GPU 内存,我们可以处理一些非常长的对话,也可以处理许多短对话。

如果我们能巧妙地做到这一点,我们将能够最大限度地提高吞吐量并最大限度地减少填充token【Padding tokens】的浪费。每当一个对话比另一个对话长得多时,就会发生填充标记【Padding tokens】,因此我们必须用空标记填充批次以使我们的张量成为完整的矩形。

作为最小化填充的简单算法,我们可以简单地**“分组”长度相似的示例**,以便同时处理它们。为此,我们将维护一种包含许多可能对话的缓冲区。

天真地按顺序处理这些对话中的每一个将导致非常不均匀的批次:很长的 LOTR 引用与我们最短的话语相结合。取而代之的是,我们可以组织我们的对话,使相同长度的对话彼此相邻。这将节省处理时间并最大限度地减少包装。

详细请看Worlds, Sharing & Batching — ParlAI Documentation

Using Torch Generator Agent

parlai.core.torch_generator_agent.TorchGeneratorAgent 是一个抽象父类,它提供了用于构建自回归生成模型的功能。扩展 TorchGeneratorAgent 要求您的模型符合严格的接口,但随后会为您提供丰富的功能,如光束beam searchsampling

Example Models

ParlAI 中的两个主要模型继承自 TorchGeneratorAgent:seq2seq 和transformer。您可以通过以下示例尝试Transformer:

parlai train_model --model transformer/generator \
  --task convai2 --model-file /tmp/testtransformer \
  --beam-size 5 --batchsize 16

Creating a Model

为了编写生成模型,您的代理应该扩展 TorchGeneratorAgent。这个父类实现了train_stepeval_step,所以你只需要实现你的模型并通过 build_model实例化它。 TorchGeneratorAgent 将处理许多常见的生成器功能,例如forced decoding、 beam search、n-gram beam blocking、top-k 和 top-p/nucleus 采样等。
此外,您的模型应实现 TorchGeneratorModel 接口:有关此示例,请参阅下面的教程。

Tutorial

本教程将引导您创建一个简单的生成模型,该模型位于 parlai.agents.examples.seq2seq,由 1 层 LSTM 编码器和解码器组成。

TorchGeneratorAgent

在 ParlAI 中创建生成模型包括子类化 TorchGeneratorAgent 和子类化 TorchGeneratorModel TorchGeneratorAgent 的最小子类只需要实现 build_model(),但如果你想指定任何命令行参数,你还需要添加 add_cmdline_args()。我们下面的实现首先为 TorchGeneratorAgent 添加标志,然后为编码器和解码器的 LSTM 的隐藏维度添加 --hidden-size 标志。

build_model() 中,我们通过传入agent的 dict(由 TorchAgent 设置)和 hidden size来实例化我们的示例模型(定义如下)。我们添加行以选择性地将预先存在的token embeddings复制到模型的embedding表中。

示例Agent定义如下:

import parlai.core.torch_generator_agent as tga

class Seq2seqAgent(tga.TorchGeneratorAgent):

    @classmethod
    def add_cmdline_args(cls, argparser, partial_opt=None):
        super().add_cmdline_args(argparser, partial_opt=partial_opt)
        group = argparser.add_argument_group('Example TGA Agent')
        group.add_argument(
            '-hid', '--hidden-size', type=int, default=1024, help='Hidden size.'
        )

    def build_model(self):
        model = ExampleModel(self.dict, self.opt['hidden_size'])
        if self.opt['embedding_type'] != 'random':
            self._copy_embeddings(
                model.embeddings.weight, self.opt['embedding_type']
            )
        return model

TorchGeneratorModel

我们现在继承 TorchGeneratorModel 来创建 ExampleModel。我们通过首先调用 super().__init__() 并传入用于padding、satrt、end和 UNK 的字典标记来初始化它;然后,我们使用 nn.Embedding 创建一个嵌入查找表,并实例化编码器和解码器,如以下部分所述。

import torch.nn as nn
import torch.nn.functional as F

class ExampleModel(tga.TorchGeneratorModel):

    def __init__(self, dictionary, hidden_size=1024):
        super().__init__(
            padding_idx=dictionary[dictionary.null_token],
            start_idx=dictionary[dictionary.start_token],
            end_idx=dictionary[dictionary.end_token],
            unknown_idx=dictionary[dictionary.unk_token],
        )
        self.embeddings = nn.Embedding(len(dictionary), hidden_size)
        self.encoder = Encoder(self.embeddings, hidden_size)
        self.decoder = Decoder(self.embeddings, hidden_size)

我们接下来定义一个函数将解码器的输出投影回token空间:

    def output(self, decoder_output):
        return F.linear(decoder_output, self.embeddings.weight)

最后,我们定义了两个函数来重新索引编码器和解码器的 latent states。对于编码器,我们传入的indices会索引批处理中的样本,而对于解码器,indices会索引我们想要为下一步解码(例如,在beam search中)保留的候选者。我们在beam search的最开始和评估期间对候选者进行排名时重新索引编码器,并在每一步解码后重新索引解码器。由于我们的编码器和解码器都基于 LSTM,因此这些编码器/解码器状态是隐藏状态和单元状态 [hidden and cell states]:

    def reorder_encoder_states(self, encoder_states, indices):
        h, c = encoder_states
        return h[:, indices, :], c[:, indices, :]

    def reorder_decoder_incremental_state(self, incr_state, indices):
        h, c = incr_state
        return h[:, indices, :], c[:, indices, :]

Creating the encoder

编码器是直截了当的:它包含一个嵌入层和一个 LSTM,并且通过编码器的前向传递包括将输入标记的序列顺序地传递给它们。返回最终的隐藏状态。

class Encoder(nn.Module):

    def __init__(self, embeddings, hidden_size):
        super().__init__()
        _vocab_size, esz = embeddings.weight.shape
        self.embeddings = embeddings
        self.lstm = nn.LSTM(
            input_size=esz, hidden_size=hidden_size, num_layers=1, batch_first=True
        )

    def forward(self, input_tokens):
        embedded = self.embeddings(input_tokens)
        _output, hidden = self.lstm(embedded)
        return hidden

Creating the decoder

解码器的初始化方式与编码器相同,但现在正向传递反映了这样一个事实,即输入tokens需要一次通过embedder 器和 LSTM 一个token,而不是一次全部传递。如果这是第一次通过解码器,我们将元组 encoder_state 传递给 LSTM,它由 initial hidden 和 cell state 组成,取自编码器的输出。如果这是通过解码器的后续传递,LSTM 将为我们提供隐藏和单元状态的当前值,因此我们可能在使用 ExampleModel().reorder_decoder_incremental_state() 重新索引状态之后将其传递回 LSTM 。

class Decoder(nn.Module):

    def __init__(self, embeddings, hidden_size):
        super().__init__()
        _vocab_size, self.esz = embeddings.weight.shape
        self.embeddings = embeddings
        self.lstm = nn.LSTM(
            input_size=self.esz, hidden_size=hidden_size, num_layers=1, batch_first=True
        )

    def forward(self, input, encoder_state, incr_state=None):
        embedded = self.embeddings(input)
        if incr_state is None:
            state = encoder_state
        else:
            state = incr_state
        output, incr_state = self.lstm(embedded, state)
        return output, incr_state

Training

parlai train_model --model examples/seq2seq \
    --model-file /tmp/example_model \
    --task convai2 --batchsize 32 --num-epochs 2 --truncate 128

Understanding and adding metrics

Introduction and Standard Metrics

ParlAI 包含许多内置指标,这些指标在我们训练和评估模型时会自动计算。其中一些指标是文本生成指标,在我们生成文本的任何时候都会发生:这包括 F1、BLEU 和Accuracy。

List of metrics

例如,让我们尝试一个固定响应模型,它总是返回给定的固定响应,并在 DailyDialog 数据集上进行评估:

$ parlai eval_model --model fixed_response --task dailydialog --fixed-response "how may i help you ?" --metrics rouge
... after a while ...
14:41:40 | Evaluating task dailydialog using datatype valid.
14:41:40 | creating task(s): dailydialog
14:41:41 | Finished evaluating tasks ['dailydialog'] using datatype valid
    accuracy  bleu-4  exs    f1  rouge_1  rouge_2  rouge_L
    .0001239 .002617 8069 .1163   .09887  .007285   .09525
  • accuracy:这是响应的完美、精确、匹配,在数据集中的所有示例中平均

  • BLEU-4:这是预测响应和参考响应之间的 BLEU 分数。它是在标记化的文本上测量的,并使用 NLTK 来计算它。

  • F1:这是您的文本和参考响应之间的 Unigram F1 重叠。

  • exs:我们评估的示例数

Metrics 的一个好处是它们会自动记录到 .trainstats 文件中,并且在 Tensorboard 中(当启用 --tensorboard-log true 时)。因此,指标比在代码中添加打印语句更可靠。

Agent-specific metrics

一些 agents 包含为它们计算的自己的metrics。例如,生成模型会自动计算 ppl(perplexity)和 token_acc,这两者都衡量了生成模型预测单个标记的能力。例如,让我们在 DailyDialog 上评估 BlenderBot 90M 模型

$ parlai eval_model --task dailydialog --model-file zoo:blender/blender_90M/model --batchsize 32
...
14:54:14 | Evaluating task dailydialog using datatype valid.
14:54:14 | creating task(s): dailydialog
...
15:26:19 | Finished evaluating tasks ['dailydialog'] using datatype valid
accuracy  bleu-4  ctpb  ctps  exps  exs    f1  gpu_mem  loss      lr  ltpb  ltps   ppl  token_acc  total_train_updates   tpb   tps
       0 .002097 14202 442.5 6.446 8069 .1345    .0384 2.979 7.5e-06  3242   101 19.67      .4133               339012 17445 543.5

在这里,我们看到了许多额外的指标,我们将在下面解释每个指标。它们可以大致分为 diagnostic/performance 指标和 modeling 指标。modeling 指标为:

  • ppl and token_acc: perplexity 和 per-token 的准确性。这些是生成性能指标。

diagnostic指标是:

tpbctpbltpb:代表tokens per batch、context-tokens per batch、label-tokens per batch。这些对于测量批次的密集程度很有用,并且在实验 dynamic batching 时很有帮助。 tpb 始终是 ctpb 和 lptb 的总和。

tpsctpsltps:类似,但代表“ tokens per second ”。他们衡量我们训练的速度。同样,exps 每秒测量示例。

gpu_mem:粗略测量您的模型正在使用多少 GPU 内存,但这只是近似值。这对于确定是否可以增加模型大小或批量大小很有用。

loss:损失度量

total_train_updates:此模型训练的 SGD 更新次数。您会在训练期间看到这种增加,但在评估期间不会。

Adding custom metrics

当然,您可能希望添加自己的自定义指标:这是因为您正在开发特殊模型、特殊数据集,还是希望您可以访问其他信息。metrics可以由Teacher或Model计算。在模型中,它们可以在本地或全局范围内计算。对于选择每个location,有不同的原因:

  • Teacher metrics:这是计算依赖于特定数据集的指标的最佳选择。这些指标仅在对此数据集进行评估时可用。它们的优点是易于计算和理解。modeling metric 的一个示例是 slot_p,它是我们的一些面向任务(Task Oriented Datasets)的数据集的一部分,例如 google_sgd

  • Global metrics(model metric):全局度量由模型计算,并被全局跟踪。这些指标易于理解和跟踪,但在执行多任务时效果不佳。全局度量的一个示例包括 gpu_mem,它取决于系统范围的内存使用情况,并且不能绑定到特定任务。

  • Local metrics(模型指标):loacl metrics是 teacher指标的模型模拟(model-analogue)。它们是在每个示例的基础上计算和记录的,因此它们在多任务处理时工作得很好。但是,对于某些模型,它们可能非常复杂。局部度量的示例包括perplexity(PPL),它应该基于每个示例计算,但必须由模型计算,因此不能作为教师度量。

我们将引导您依次编写这些方法,并演示如何在您的设置中添加这些指标的示例。

Teacher metrics

Teacher metrics 对于依赖特定数据集的项目很有用。例如,在我们的一些面向任务的数据集中,例如 google_sgd,我们希望额外计算槽(slot)周围的指标。

可以通过向Teacher 添加以下方法来添加Teacher metrics :

    def custom_evaluation(
        self,
        teacher_action: Message,
        labels: Optional[Tuple[str]],
        model_response: Message,
    ) -> None:
        pass

此方法的参数如下:

  • teacher_action:这是teacher发给model的最后一条信息。这可能包含“text”和“label”字段,以及您可能拥有的任何自定义字段。.
  • labels: The gold label(s). 这也可以在teacher_action 中作为信息找到,很方便地进行提取。
  • model_response: 完整的模型响应,包括模型可能发送的任何额外字段。

让我们举一个实际的例子。我们将添加一个 custom metric 来计算模型说出“hello”这个词的频率,并将其命名为 hello_avg。

我们将添加一个 custom teacher。对于此示例,我们将使用您可能在我们的快速入门教程中看到的 @register 语法。

from parlai.core.loader import register_teacher
from parlai.core.metrics import AverageMetric
from parlai.tasks.dailydialog.agents import DefaultTeacher as DailyDialogTeacher

@register_teacher("hello_daily")
class CustomDailyDialogTeacher(DailyDialogTeacher):
    def custom_evaluation(
        self, teacher_action, labels, model_response
    ) -> None:
        if 'text' not in model_response:
            # model didn't speak, skip this example
            return
        model_text = model_response['text']
        if 'hello' in model_text:
            # count 1 / 1 messages having "hello"
            self.metrics.add('hello_avg', AverageMetric(1, 1))
        else:
            # count 0 / 1 messages having "hello"
            self.metrics.add('hello_avg', AverageMetric(0, 1))

if __name__ == '__main__':
    from parlai.scripts.eval_model import EvalModel

    EvalModel.main(
       task='hello_daily',
       model_file='zoo:blender/blender_90M/model',
       batchsize=32,
    )

如果我们运行脚本,我们的输出中将有一个新的指标:

18:07:30 | Finished evaluating tasks ['hello_daily'] using datatype valid
    accuracy  bleu-4  ctpb  ctps  exps  exs    f1  gpu_mem  hello_avg  loss  ltpb  ltps   ppl  token_acc  tpb   tps
           0 .002035  2172   230 3.351 8069 .1346   .05211      .1228 2.979 495.9 52.52 19.67      .4133 2668 282.6

有关完整列表(以及高级案例的视图),请参阅指标 API 文档。metrics API documentation

Agent (model) level metrics

在上面的示例中,我们处理了由 teacher 定义的 metrics。然而,有时我们的模型会有只有他们想要计算的特殊指标,我们称之为 Agent-level metric 。Perplexity 就是一个例子。

要计算模型级指标,我们可以定义全局指标或本地指标。全局度量可以在任何地方计算,并且易于使用,但在多任务处理时无法区分不同的教师。我们来看另一个例子,计算老师说“你好”的次数。

要计算model-level metrics,我们可以定义 Global metrics或 local metrics。Global metrics可以在任何地方计算,并且易于使用,但在多任务处理时无法区分不同的teacher。我们来看另一个例子,计算teacher说“hello”的次数。

Global metrics

Global metrics 在模型中的任何位置计算,并且具有类似于 Teacher的接口:

agent.global_metrics.add('my_metric', AverageMetric(1, 2))

Global metrics 之所以这样调用,是因为它们可以在agent代码中的任何位置调用。例如,我们可以添加一个metrics 来计算模型在观察中看到单词“hello”的次数。我们将在扩展 TransformerGeneratorAgent 时这样做,以便我们可以将它与我们之前使用的 BlenderBot 模型结合起来。

from parlai.core.metrics import AverageMetric
from parlai.core.loader import register_agent
from parlai.agents.transformer.transformer import TransformerGeneratorAgent


@register_agent('GlobalHelloCounter')
class GlobalHelloCounterAgent(TransformerGeneratorAgent):
    def observe(self, observation):
        retval = super().observe(observation)
        if 'text' in observation:
            text = observation['text']
            self.global_metrics.add(
                'global_hello', AverageMetric(int('hello' in text), 1)
            )
        return retval


if __name__ == '__main__':
    from parlai.scripts.eval_model import EvalModel

    EvalModel.main(
        task='dailydialog',
        model='GlobalHelloCounter',
        model_file='zoo:blender/blender_90M/model',
        batchsize=32,
    )

请注意,这与我们在教程前半部分实施的 Teacher Metrics非常不同。在Teacher Metrics中,我们计算了model said hello 的次数。在这里,我们计算老师打招呼的次数。

如何确定在何处实施自定义指标:

  • If you want your metric to be model-agnostic [与模型无关], then it should be implemented in the Teacher.

  • If you want your metric to be dataset-agnostic, then it should be implemented in the Model agent.

  • If you need your metric to be both model and dataset agnostic, then you should do it within the Model, using a mixin or abstract class.

运行脚本,我们看到我们的新指标出现了。如上所述,由于语义不同,值略有不同。

21:57:50 | Finished evaluating tasks ['dailydialog'] using datatype valid
    accuracy  bleu-4  ctpb  ctps  exps  exs    f1  global_hello  gpu_mem  loss  ltpb  ltps   ppl  token_acc   tpb   tps
           0 .002097 14202 435.1 6.338 8069 .1345      .0009914   .02795 2.979  3242 99.32 19.67      .4133 17445 534.4

global metric 效果很好,但是有一些缺点:如果我们开始在一个多任务数据集上训练,我们将无法区分两个数据集的 global_hello,我们只能计算组合的微平均。以下是上述代理的培训日志摘录:

09:14:52 | time:112s total_exs:90180 epochs:0.41
                clip  ctpb  ctps  exps  exs  global_hello  gnorm  gpu_mem  loss  lr  ltpb  ltps   ppl  token_acc  total_train_updates   tpb   tps   ups
   all             1  9831 66874 841.9 8416        .01081  2.018    .3474 5.078   1  1746 11878 163.9      .2370                  729 11577 78752 6.803
   convai2                             3434        .01081                 5.288                 197.9      .2120
   dailydialog                         4982        .01081                 4.868                   130      .2620

请注意 global_hello 在两者中是如何相同的,因为模型无法区分这两种设置。在下一节中,我们将展示如何使用loacl metrics来解决这个问题。

在上面的示例中,我们在观察函数中记录了全局度量。但是,可以从任何地方记录全局指标。

Local metrics

观察到 global metric 在多任务处理中无法区分设置的局限性,我们希望对此进行改进。让我们添加一个loacl metric,每个example 都会记录该指标。通过每个example 记录此指标,我们可以明确识别哪些指标来自哪个数据集,并正确报告平均值。

Let’s look at an example. 我们将在 batchify 函数中添加一个 metric ,该函数在 batch_act 中调用,用于将 Messages 对象列表转换为 Batch 对象。这是我们做填充等事情的地方。我们将做一些与之前的运行略有不同的事情。在这种情况下,我们将计算单词“hello”的tokens 数。

from parlai.core.metrics import AverageMetric
from parlai.core.loader import register_agent
from parlai.agents.transformer.transformer import TransformerGeneratorAgent

@register_agent('LocalHelloCounter')
class LocalHelloCounterAgent(TransformerGeneratorAgent):
    def batchify(self, observations):
        batch = super().batchify(observations)
        if hasattr(batch, 'text_vec'):
            num_hello = ["hello" in o['text'] for o in observations]
            self.record_local_metric(
                'local_hello',
                AverageMetric.many(num_hello),
            )
        return batch


if __name__ == '__main__':
    from parlai.scripts.train_model import TrainModel

    TrainModel.main(
        task='dailydialog,convai2',
        model='LocalHelloCounter',
        dict_file='zoo:blender/blender_90M/model.dict',
        batchsize=32,
    )

当我们运行这个训练脚本时,我们会得到一个这样的输出:

09:49:00 | time:101s total_exs:56160 epochs:0.26
                clip  ctpb  ctps  exps  exs  gnorm  gpu_mem  local_hello  loss  lr  ltpb  ltps   ppl  token_acc  total_train_updates  tpb   tps  ups
   all             1  3676 63204 550.2 5504  2.146    .1512       .01423 4.623   1 436.2  7500 101.8      .2757                 1755 4112 70704 17.2
   convai2                             3652                       .02793 4.659                 105.5      .2651
   dailydialog                         1852                       .00054 4.587                 98.17      .2863

请注意 local_hello 指标现在如何区分来自 convai2 的hello 和来自dailydialog 的hello ?平均值隐藏了一个数据集有很多 hello 而另一个没有的事实。

当您关心训练时间指标的保真度(fidelity ) 时,local metrics 值得实施。在评估期间,我们单独评估每个数据集,因此我们可以确保全局指标不会混淆。

Under the hood:本地指标通过在返回消息中包含“metrics”字段来工作。这是一个将字段名称映射到度量值的字典。当教师收到模型的响应时,它会利用指标字段来更新其一侧的计数器。

Mutators

数据变体

Mutators是与任务无关的数据转换,适用于任何数据集。Mutators的示例包括:

  • 反转对话中的所有转折

  • 对数据集进行下采样

  • 轮流洗牌

  • 以及更多

当您想要对数据的不同变体进行训练或测试时,Mutators 特别有用。例如,Sundkar 等人(2019 年)表明,不同的模型对 turns or words shuffled 的反应不同。

Usage

--mutators 参数应该可用于每个有 --task 参数的脚本。只需开始添加它以使用变异数据集。

例如,最简单的变异器之一是 flatten,它只是使对话变平。

parlai display_data --task dailydialog --mutators flatten

Composability

可组合性。

mutators 被有意设计为可组合的。也就是说,我们可以通过在命令行上指定多个来将mututators堆叠在一起:

parlai display_data -t dailydialog --mutators word_shuffle+flatten
parlai display_data -t dailydialog --mutators word_shuffle,flatten  # equivalent

这将运行 word_shuffle mutator,并将输出通过pipes 传递给 flatten mutator。

More

Mutators — ParlAI Documentation

            .00054 4.587                 98.17      .2863

请注意 `local_hello` 指标现在如何区分来自 convai2 的hello 和来自dailydialog 的hello ?平均值隐藏了一个数据集有很多 hello 而另一个没有的事实。

当您关心训练时间指标的保真度(fidelity ) 时,local metrics 值得实施。在评估期间,我们单独评估每个数据集,因此我们可以确保全局指标不会混淆。

**Under the hood**:本地指标通过在返回消息中包含“metrics”字段来工作。这是一个将字段名称映射到度量值的字典。当教师收到模型的响应时,它会利用指标字段来更新其一侧的计数器。

## Mutators

数据变体

Mutators是与任务无关的数据转换,适用于任何数据集。Mutators的示例包括:

- 反转对话中的所有转折

- 对数据集进行下采样

- 轮流洗牌

- 以及更多

当您想要对数据的不同变体进行训练或测试时,Mutators 特别有用。例如,Sundkar 等人(2019 年)表明,不同的模型对 turns or words shuffled 的反应不同。

### Usage

`--mutators` 参数应该可用于每个有` --task` 参数的脚本。只需开始添加它以使用变异数据集。

例如,最简单的变异器之一是 flatten,它只是使对话变平。

```bash
parlai display_data --task dailydialog --mutators flatten

Composability

可组合性。

mutators 被有意设计为可组合的。也就是说,我们可以通过在命令行上指定多个来将mututators堆叠在一起:

parlai display_data -t dailydialog --mutators word_shuffle+flatten
parlai display_data -t dailydialog --mutators word_shuffle,flatten  # equivalent

这将运行 word_shuffle mutator,并将输出通过pipes 传递给 flatten mutator。

More

Mutators — ParlAI Documentation

你可能感兴趣的:(笔记,人工智能,深度学习,nlp)