本篇对AllenNLP中关于数据中的 Datasets,vocabulary,DataLoader,sampler做简单的介绍。
Datasets
数据集被表示为AllennlpDataset
对象,是instance
集合的一个简单封装,并且与PyTorch
的Dataset
基本上相同,不同之处在于它还支持某些额外的功能,例如词汇索引。 AllenNLP
的DatasetReader
在完成读取数据集后返回AllennlpDataset()
或者AllennlpLazyDataset()
。
Vocabulary
Vocabulary
,词汇表,是AllenNLP
中的重要组件,它常常被其他抽象类与组件使用。 简而言之,Vocabulary
管理从字符串到整数ID的映射。 Vocabulary
的输入是Instances
,用于将文本数据(例如token
和标签)转换为整数ID(并最终转换为张量)。
词汇表使用namespace
的概念管理不同的映射。 每个namespace
都是从字符串到整数的独特映射,因此不同namespace
中的字符串将被分别处理。 这样,使用者就可以为单词“a”和字符“ a”或英语中的“ chat”和法语中的“ chat”(英语中的“ cat”)分别设置索引。 如下图所示:
namespace
之间有一个重要的区别:padded
和non-padded
。namespace
默认是padded
的,为填充和词汇表外(OOV)的token
保留映射索引。这对于为文本创建索引时十分有用,因为出现词表外的token
十分常见,而且填充也是必须的。
另一方面,non-padded namespace
不保留特殊token
的映射索引,这更适用于类别标签之类的情况,以“tags”或“labels”结尾的namespace
默认为non-padded
,但是可以通过在创建vocabulary
时提供non_padded_namespaces
参数来修改此行为。
Vocabulary
实例化的方法有以下三种:
-
默认方法,
from_instances
,从Instance
集合实例化Vocabulary
; -
from_files
的类方法可以从序列化的词汇表进行加载。序列化的词汇表通常是由allennlp
之前训练产生的,对应配置文件的type为“from_files”; -
from_files_and_instances
,使用instance
集合扩展已经生成的词汇表,对应配置文件的type为“extend”。
其中默认的from_instances
方法是指将Instance
的集合传递给Vocabulary.from_instances()
方法。这将对Instance
中需要映射为整数的所有字符串进行频数统计,然后根据统计结果来决定词汇表中应包含哪些字符串。get_token_index()
方法通过token
查找索引,get_token_from_index()
是逆运算(通过索引查找token
)。示例代码如下:
from allennlp.data.instance import Instance
from allennlp.data.fields import TextField, LabelField
from allennlp.data.token_indexers import SingleIdTokenIndexer
from allennlp.data.tokenizers import Token
from allennlp.data.vocabulary import Vocabulary
# 创建fields,并标明namespace
token_indexers = {'tokens': SingleIdTokenIndexer(namespace='tokens')}
text_field_pos = TextField(
[Token('The'), Token('best'), Token('movie'), Token('ever'), Token('!')],
token_indexers=token_indexers)
text_field_neg = TextField(
[Token('Such'), Token('an'), Token('awful'), Token('movie'), Token('.')],
token_indexers=token_indexers)
label_field_pos = LabelField('pos', label_namespace='labels')
label_field_neg = LabelField('neg', label_namespace='labels')
# 创建正例与负例两个instance
instance_pos = Instance({'tokens': text_field_pos, 'label': label_field_pos})
instance_neg = Instance({'tokens': text_field_neg, 'label': label_field_neg})
# 创建Vocabulary
vocab = Vocabulary.from_instances([instance_pos, instance_neg])
print('Created a Vocabulary:', vocab)
# 根据单词查找索引,默认情况下namespace为'tokens'。vocab按频率从大到小添加词汇项,因此出现两次的“movie”将获得除了@@PADDING@@和@@UNKNOWN@@之外最小的索引
print('index for token "movie":', vocab.get_token_index('movie'))
print('index for token "!":', vocab.get_token_index('!'))
# 'tokens'的namespace为padded,因此oov与padding也有对应的索引
print('index for token "unknown":', vocab.get_token_index('unknown'))
# index for token "unknown": 1
print('index for token "padding":', vocab.get_token_index('@@PADDING@@'))
# index for token "padding": 0
print('index for label "pos":', vocab.get_token_index('pos', namespace='labels'))
print('index for label "neg":', vocab.get_token_index('neg', namespace='labels'))
# 'labels' 是 non-padded namespace; 因此在查找词表外的单词时会抛出异常
try:
vocab.get_token_index('unknown', namespace='labels')
except KeyError:
print('index for label "unknown": caught KeyError')
# 根据索引查看token
print('token for index=0:', vocab.get_token_from_index(0))
print('token for index=1:', vocab.get_token_from_index(1))
print('token for index=2:', vocab.get_token_from_index(2))
print('label for index=0:', vocab.get_token_from_index(0, namespace='labels'))
print('label for index=1:', vocab.get_token_from_index(1, namespace='labels'))
try:
vocab.get_token_from_index(2, namespace='labels')
except KeyError:
print('label for index=2: caught KeyError')
当词汇表量级非常大时,可以通过使用min_count
参数设置一个阈值并只保留出现频率高于该阈值的单词来对其进行删减,样例如下:
vocab = Vocabulary.from_instances([instance_pos, instance_neg], min_count={'tokens': 2})
DataLoader
DataLoader
的输入是数据集,一个Dataset
对象,并在该数据集上生成一个iterable
。默认情况下,它按原始顺序生成单个instance
,但可以为DatasetLoader
提供各种选项,从而自定义它如何迭代、采样(和/或)批处理instance
。例如,如果设置batch_size
参数,它将生成指定大小的批次。还可以通过提供shuffle=True
来打乱数据集。
AllenNLP
的DataLoader
是PyTorch
的DataLoader
的一个非常简单的子类,主要区别在于一个定制的collate
函数,这个函数在PyTorch
是定义如何获取instance
并将它们进行批处理。在allennlp
中,collate
函数中创建一批instance
并将其转换为张量字典,并应用适当的填充:
def allennlp_collate(instances: List[Instance]) -> TensorDict:
batch = Batch(instances)
return batch.as_tensor_dict(batch.get_padding_lengths())
sampler
即采样器,指定如何迭代给定数据集中的实例,用户可以为DatasetLoader
的批处理参数提供一个sampler
,以便进一步自定义其行为。常用的sampler
如下:
SequentialSampler
按原始顺序顺序对实例进行采样RandomSampler
随机采样实例,重要参数为replacement
,默认为False
,代表是否重复采样。BatchSampler
包装另一个采样器,并生成由底层采样器生成的小批量实例。
示例代码如下:
from allennlp.data import DataLoader, PyTorchDataLoader
from allennlp.data import Instance, Token
from allennlp.data.token_indexers import SingleIdTokenIndexer
from allennlp.data.dataset_readers.dataset_reader import AllennlpDataset
from allennlp.data.fields import LabelField, TextField
from allennlp.data.samplers import SequentialSampler, RandomSampler
from allennlp.data.samplers import BasicBatchSampler
from allennlp.data.vocabulary import Vocabulary
# 创建迷你dataset
token_indexers = {'tokens': SingleIdTokenIndexer()}
instances = [Instance({'label': LabelField(str(label)),'text': TextField([Token(label)], token_indexers = token_indexers)}) for label in 'abcdefghij']
dataset = AllennlpDataset(instances)
vocab = Vocabulary.from_instances(dataset)
dataset.index_with(vocab)
# 批处理默认情况
print("Default:")
data_loader = PyTorchDataLoader(dataset, batch_size=3)
for batch in data_loader:
print(batch)
# drop_last=True,如果最后一个批次小于批次大小,则丢弃
sampler = SequentialSampler(data_source=dataset)
batch_sampler = BasicBatchSampler(sampler, batch_size=3, drop_last=True)
print("\nDropping last:")
data_loader = PyTorchDataLoader(dataset, batch_sampler=batch_sampler)
for batch in data_loader:
print(batch)
# 使用随机采样
sampler = RandomSampler(data_source=dataset)
batch_sampler = BasicBatchSampler(sampler, batch_size=3, drop_last=False)
print("\nWith RandomSampler:")
data_loader = PyTorchDataLoader(dataset, batch_sampler=batch_sampler)
for batch in data_loader:
print(batch)
参考资料