.. _sec_natural-language-inference-and-dataset: Inferência de Linguagem Natural e o *Dataset* ============================================= Em :numref:`sec_sentiment`, discutimos o problema da análise de sentimento. Esta tarefa visa classificar uma única sequência de texto em categorias predefinidas, como um conjunto de polaridades de sentimento. No entanto, quando há a necessidade de decidir se uma frase pode ser inferida de outra ou eliminar a redundância identificando frases semanticamente equivalentes, saber classificar uma sequência de texto é insuficiente. Em vez disso, precisamos ser capazes de raciocinar sobre pares de sequências de texto. Inferência de Linguagem Natural ------------------------------- *Inferência de linguagem natural* estuda se uma *hipótese* pode ser inferida de uma *premissa*, onde ambas são uma sequência de texto. Em outras palavras, a inferência de linguagem natural determina a relação lógica entre um par de sequências de texto. Esses relacionamentos geralmente se enquadram em três tipos: - *Implicação*: a hipótese pode ser inferida a partir da premissa. - *Contradição*: a negação da hipótese pode ser inferida a partir da premissa. - *Neutro*: todos os outros casos. A inferência de linguagem natural também é conhecida como a tarefa de reconhecimento de vinculação textual. Por exemplo, o par a seguir será rotulado como *implicação* porque “mostrar afeto” na hipótese pode ser inferido de “abraçar um ao outro” na premissa. Premissa: Duas mulheres estão se abraçando. .. Hipótese: Duas mulheres estão demonstrando afeto. A seguir está um exemplo de *contradição*, pois “executando o exemplo de codificação” indica “não dormindo” em vez de “dormindo”. Premissa: Um homem está executando o exemplo de codificação do *Dive into Deep Learning*. .. Hipótese: O homem está dormindo. O terceiro exemplo mostra uma relação de *neutralidade* porque nem “famoso” nem “não famoso” podem ser inferidos do fato de que “estão se apresentando para nós”. Premissa: Os músicos estão se apresentando para nós. .. Hipótese: Os músicos são famosos. A inferência da linguagem natural tem sido um tópico central para a compreensão da linguagem natural. Desfruta de uma ampla gama de aplicações, desde recuperação de informações para resposta a perguntas de domínio aberto. Para estudar esse problema, começaremos investigando um popular conjunto de dados de referência de inferência em linguagem natural. Conjunto de dados Stanford Natural Language Inference (SNLI) ------------------------------------------------------------ Stanford Natural Language Inference (SNLI) Corpus é uma coleção de mais de :math:`500.000` pares de frases em inglês rotulados :cite:`Bowman.Angeli.Potts.ea.2015`. Baixamos e armazenamos o conjunto de dados SNLI extraído no caminho ``../data/snli_1.0``. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python import os import re from mxnet import gluon, np, npx from d2l import mxnet as d2l 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') .. parsed-literal:: :class: output Downloading ../data/snli_1.0.zip from https://nlp.stanford.edu/projects/snli/snli_1.0.zip... .. raw:: html
.. raw:: html
.. code:: python import os import re import torch from torch import nn from d2l import torch as d2l #@save d2l.DATA_HUB['SNLI'] = ( 'https://nlp.stanford.edu/projects/snli/snli_1.0.zip', '9fcde07509c7e87ec61c640c1b2753d9041758e4') data_dir = d2l.download_extract('SNLI') .. raw:: html
.. raw:: html
Lendo o *Dataset* ~~~~~~~~~~~~~~~~~ O conjunto de dados SNLI original contém informações muito mais ricas do que realmente precisamos em nossos experimentos. Assim, definimos uma função ``read_snli`` para extrair apenas parte do conjunto de dados e, em seguida, retornar listas de premissas, hipóteses e seus rótulos. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@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 .. raw:: html
.. raw:: html
.. code:: python #@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 .. raw:: html
.. raw:: html
Agora, vamos imprimir os primeiros :math:`3` pares de premissa e hipótese, bem como seus rótulos (“0”, “1” e “2” correspondem a “implicação”, “contradição” e “neutro”, respectivamente). .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python 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) .. parsed-literal:: :class: output 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 .. raw:: html
.. raw:: html
.. code:: python 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) .. parsed-literal:: :class: output 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 .. raw:: html
.. raw:: html
O conjunto de treinamento tem cerca de :math:`550.000` pares, e o conjunto de teste tem cerca de :math:`10.000` pares. O seguinte mostra que os três rótulos “implicação”, “contradição” e “neutro” são equilibrados em o conjunto de treinamento e o conjunto de teste. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python 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)]) .. parsed-literal:: :class: output [183416, 183187, 182764] [3368, 3237, 3219] .. raw:: html
.. raw:: html
.. code:: python 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)]) .. parsed-literal:: :class: output [183416, 183187, 182764] [3368, 3237, 3219] .. raw:: html
.. raw:: html
Definindo uma Classe para Carregar o *Dataset* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Abaixo, definimos uma classe para carregar o dataset SNLI herdando da classe ``Dataset`` no Gluon. O argumento ``num_steps`` no construtor de classe especifica o comprimento de uma sequência de texto para que cada minibatch de sequências tenha a mesma forma. Em outras palavras, tokens após os primeiros ``num_steps`` em uma sequência mais longa são cortados, enquanto tokens especiais “& lt; pad & gt;” serão anexados a sequências mais curtas até que seu comprimento se torne ``num_steps``. Implementando a função ``__getitem__``, podemos acessar arbitrariamente a premissa, hipótese e rótulo com o índice ``idx``. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@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=['']) 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['']) 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) .. raw:: html
.. raw:: html
.. code:: python #@save class SNLIDataset(torch.utils.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=['']) else: self.vocab = vocab self.premises = self._pad(all_premise_tokens) self.hypotheses = self._pad(all_hypothesis_tokens) self.labels = torch.tensor(dataset[2]) print('read ' + str(len(self.premises)) + ' examples') def _pad(self, lines): return torch.tensor([d2l.truncate_pad( self.vocab[line], self.num_steps, self.vocab['']) 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) .. raw:: html
.. raw:: html
Juntando Tudo ~~~~~~~~~~~~~ Agora podemos invocar a função ``read_snli`` e a classe ``SNLIDataset`` para baixar o conjunto de dados SNLI e retornar instâncias de ``DataLoader`` para ambos os conjuntos de treinamento e teste, junto com o vocabulário do conjunto de treinamento. Vale ressaltar que devemos utilizar o vocabulário construído a partir do conjunto de treinamento como aquele do conjunto de teste. Como resultado, qualquer novo *token* do conjunto de teste será desconhecido para o modelo treinado no conjunto de treinamento. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@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 .. raw:: html
.. raw:: html
.. code:: python #@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 = torch.utils.data.DataLoader(train_set, batch_size, shuffle=True, num_workers=num_workers) test_iter = torch.utils.data.DataLoader(test_set, batch_size, shuffle=False, num_workers=num_workers) return train_iter, test_iter, train_set.vocab .. raw:: html
.. raw:: html
Aqui, definimos o tamanho do lote em :math:`128` e o comprimento da sequência em :math:`50`, e invoque a função ``load_data_snli`` para obter os iteradores de dados e vocabulário. Em seguida, imprimimos o tamanho do vocabulário. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python train_iter, test_iter, vocab = load_data_snli(128, 50) len(vocab) .. parsed-literal:: :class: output read 549367 examples read 9824 examples .. parsed-literal:: :class: output 18678 .. raw:: html
.. raw:: html
.. code:: python train_iter, test_iter, vocab = load_data_snli(128, 50) len(vocab) .. parsed-literal:: :class: output read 549367 examples read 9824 examples .. parsed-literal:: :class: output 18678 .. raw:: html
.. raw:: html
Agora imprimimos a forma do primeiro minibatch. Ao contrário da análise de sentimento, temos :math:`2` entradas ``X[0]`` e ``X[1]`` representando pares de premissas e hipóteses. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python for X, Y in train_iter: print(X[0].shape) print(X[1].shape) print(Y.shape) break .. parsed-literal:: :class: output (128, 50) (128, 50) (128,) .. raw:: html
.. raw:: html
.. code:: python for X, Y in train_iter: print(X[0].shape) print(X[1].shape) print(Y.shape) break .. parsed-literal:: :class: output torch.Size([128, 50]) torch.Size([128, 50]) torch.Size([128]) .. raw:: html
.. raw:: html
Resumo ------ - A inferência em linguagem natural estuda se uma hipótese pode ser inferida de uma premissa, onde ambas são uma sequência de texto. - Na inferência em linguagem natural, as relações entre premissas e hipóteses incluem implicação, contradição e neutro. - *Stanford Natural Language Inference* (SNLI) Corpus é um popular *dataset* de referência de inferência em linguagem natural. Exercícios ---------- 1. A tradução automática há muito é avaliada com base na correspondência superficial de :math:`n`-grama entre uma tradução de saída e uma tradução de verdade. Você pode criar uma medida para avaliar os resultados da tradução automática usando a inferência de linguagem natural? 2. Como podemos alterar os hiperparâmetros para reduzir o tamanho do vocabulário? .. raw:: html
mxnetpytorch
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
.. raw:: html