自然语言推理和数据集
自然语言推理和数据集
Natural Language Inference and the Dataset
情绪分析的问题。此任务旨在将单个文本序列分类为预定义的类别,例如一组情感极性。然而,当需要判断一个句子是否可以从另一个句子中推断出来,或者通过识别语义上等价的句子来消除冗余时,知道如何对一个文本序列进行分类是不够的。相反,需要能够对文本序列进行推理。
1. Natural Language Inference
自然语言推理研究一个假设是否可以从一个前提中推断出来,前提和前提都是文本序列。换句话说,自然语言推理决定了一对文本序列之间的逻辑关系。这种关系通常分为三类: 蕴涵:假设可以从前提中推断出来。
矛盾:假设的否定可以从前提推断出来。
中立:所有其情况。
自然语言推理也被称为识别文本蕴涵任务。例如,下面的一对会被标记为蕴涵,因为假设中的“示爱”可以从前提中的“拥抱”中推断出来。
前提:两个女人互相拥抱。
假设:两个女人在表达爱意。
下面是一个矛盾的例子,因为“运行编码示例”表示“不睡觉”而不是“睡眠”。
前提:一个男人正在运行一个代码示例,该示例来自于深度学习。
假设:这个人正在睡觉。
第三个例子显示了一种中立关系,因为“为演出”的事实不能推断出“著名”和“不出名”。
前提:音乐家在为表演。
假设:音乐家是有名的。
自然语言推理一直是理解自然语言的中心话题。在信息检索、开放领域问答等领域有着广泛的应用。为了研究这个问题,将从研究一个流行的自然语言推理基准数据集开始。
2. The Stanford Natural Language Inference (SNLI) Dataset
斯坦福自然语言推理(SNLI)语料库是一个50万标记英语句子对【Bowman等人,2015年】。将提取的SNLI数据集下载并存储在路径../data/SNLI_1.0中。
import collections
from d2l import mxnet as d2l
from mxnet import gluon, np, npx
import os
import re
import zipfile
npx.set_np()
#@save
d2l.DATA_HUB['SNLI'] = (
'https://nlp.stanford.edu/projects/snli/snli_1.0.zip',
'9fcde07509c7e87ec61c640c1b2753d9041758e4')
data_dir = d2l.download_extract('SNLI')
Downloading ../data/snli_1.0.zip from https://nlp.stanford.edu/projects/snli/snli_1.0.zip...
2.1. Reading the Dataset
原始的SNLI数据集包含了比在实验中真正需要的更丰富的信息。因此,定义了一个函数read_snli来只提取部分数据集,然后返回前提、假设及其标签的列表。
#@save
def read_snli(data_dir, is_train):
"""Read the SNLI dataset into premises, hypotheses, and labels."""
def extract_text(s):
# Remove information that will not be used by us
s = re.sub('\\(', '', s)
s = re.sub('\\)', '', s)
# Substitute two or more consecutive whitespace with space
s = re.sub('\\s{2,}', ' ', s)
return s.strip()
label_set = {'entailment': 0, 'contradiction': 1, 'neutral': 2}
file_name = os.path.join(data_dir, 'snli_1.0_train.txt'
if is_train else 'snli_1.0_test.txt')
with open(file_name, 'r') as f:
rows = [row.split('\t') for row in f.readlines()[1:]]
premises = [extract_text(row[1]) for row in rows if row[0] in label_set]
hypotheses = [extract_text(row[2]) for row in rows if row[0] in label_set]
labels = [label_set[row[0]] for row in rows if row[0] in label_set]
return premises, hypotheses, labels
现在让打印第一个 3对前提和假设,以及标签(“0”、“1”和“2”分别对应于“蕴涵”、“矛盾”和“中性”)。
train_data = read_snli(data_dir, is_train=True)
for x0, x1, y in zip(train_data[0][:3], train_data[1][:3], train_data[2][:3]):
print('premise:', x0)
print('hypothesis:', x1)
print('label:', y)
premise: A person on a horse jumps over a broken down airplane .
hypothesis: A person is training his horse for a competition .
label: 2
premise: A person on a horse jumps over a broken down airplane .
hypothesis: A person is at a diner , ordering an omelette .
label: 1
premise: A person on a horse jumps over a broken down airplane .
hypothesis: A person is outdoors , on a horse .
label: 0
test_data = read_snli(data_dir, is_train=False)
for data in [train_data, test_data]:
print([[row for row in data[2]].count(i) for i in range(3)])
训练集有大约55万组,测试集有大约10000组。结果表明,在训练集和测试集中,“蕴涵”、“矛盾”和“中性”三个标签是平衡的。
test_data = read_snli(data_dir, is_train=False)
for data in [train_data, test_data]:
print([[row for row in data[2]].count(i) for i in range(3)])
[183416, 183187, 182764]
[3368, 3237, 3219]
2.2. Defining a Class for Loading the Dataset
下面定义了一个类,通过继承Gluon中的dataset类来加载SNLI数据集。类构造函数中的num_steps参数指定文本序列的长度,以便每个序列的小批量都具有相同的形状。换言之,第一个num_steps后面的标记将被修剪,而特殊标记“<pad>”将被附加到较短的序列中,直到长度变为num_steps。通过实现the __getitem__ function函数,可以任意访问前提、假设和索引idx的标签。
#@save
class SNLIDataset(gluon.data.Dataset):
"""A customized dataset to load the SNLI dataset."""
def __init__(self, dataset, num_steps, vocab=None):
self.num_steps = num_steps
all_premise_tokens = d2l.tokenize(dataset[0])
all_hypothesis_tokens = d2l.tokenize(dataset[1])
if vocab is None:
self.vocab = d2l.Vocab(all_premise_tokens + all_hypothesis_tokens,
min_freq=5, reserved_tokens=['<pad>'])
else:
self.vocab = vocab
self.premises = self._pad(all_premise_tokens)
self.hypotheses = self._pad(all_hypothesis_tokens)
self.labels = np.array(dataset[2])
print('read ' + str(len(self.premises)) + ' examples')
def _pad(self, lines):
return np.array([d2l.truncate_pad(
self.vocab[line], self.num_steps, self.vocab['<pad>'])
for line in lines])
def __getitem__(self, idx):
return (self.premises[idx], self.hypotheses[idx]), self.labels[idx]
def __len__(self):
return len(self.premises)
2.3. Putting All Things Together
现在可以调用read_snli函数和SNLIDataset类来下载snli数据集,并返回训练集和测试集的DataLoader实例以及训练集的词汇表。值得注意的是,必须使用从测试中构建的词汇集。因此,来自测试集的任何新令牌对于训练集上训练的模型都是未知的。
#@save
def load_data_snli(batch_size, num_steps=50):
"""Download the SNLI dataset and return data iterators and vocabulary."""
num_workers = d2l.get_dataloader_workers()
data_dir = d2l.download_extract('SNLI')
train_data = read_snli(data_dir, True)
test_data = read_snli(data_dir, False)
train_set = SNLIDataset(train_data, num_steps)
test_set = SNLIDataset(test_data, num_steps, train_set.vocab)
train_iter = gluon.data.DataLoader(train_set, batch_size, shuffle=True,
num_workers=num_workers)
test_iter = gluon.data.DataLoader(test_set, batch_size, shuffle=False,
num_workers=num_workers)
return train_iter, test_iter, train_set.vocab
这里将批量大小设置为128,序列长度为50,并调用load_data_snli函数来获取数据迭代器和词汇表。然后打印出词汇量。
train_iter, test_iter, vocab = load_data_snli(128, 50)
len(vocab)
read 549367 examples
read 9824 examples
18678
现在打印第一个小批量的形状。与情绪分析相反,输入X[0]和X[1]表示一对前提和假设。
for X, Y in train_iter:
print(X[0].shape)
print(X[1].shape)
print(Y.shape)
break
(128, 50)
(128, 50)
(128,)
3. Summary
- Natural language inference studies whether a hypothesis can be inferred from a premise, where both are a text sequence.
- In natural language inference, relationships between premises and hypotheses include entailment, contradiction, and neutral.
- Stanford Natural Language Inference (SNLI) Corpus is a popular benchmark dataset of natural language inference.