自然语言推断与数据集:基于 d2l-en 仓库的 SNLI 文本对推理实战指南
文档教程人工智能深度学习NLP计算机视觉强化学习【免费下载链接】d2l-enInteractive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.项目地址https://gitcode.com/gh_mirrors/d2/d2l-en点击查看免费下载导读自然语言推断Natural Language Inference, NLI回答的是一个文本序列hypothesis假设能否从另一个文本序列premise前提中被推断出来这一逻辑关系判定问题。与情感分析只对单条文本做分类不同NLI 需要对成对文本序列进行推理是信息检索、开放域问答等上层应用的基础能力。本文以 d2l-en 仓库中 自然语言推断与数据集章节 为骨架完整讲解 NLI 的三分类任务定义、斯坦福 SNLI 语料的下载与解析、基于 Gluon 与 PyTorch 的自定义数据集类实现并结合仓库源码d2l/torch.py、d2l/mxnet.py剖析download_extract、Vocab、truncate_pad等底层工具的真实调用链。读完本文你将掌握一套可直接复用的 SNLI 数据处理管线从原始 zip 下载、制表符解析、文本清洗到词表构建、定长截断填充再到返回DataLoader迭代器与形状正确的成对输入。自然语言推断判定一对文本之间的逻辑关系在 情感分析章节 中我们讨论了将单条文本序列分类到预定义类别如情感极性的任务。但当我们需要判断一个句子能否从另一个句子推断出来或通过识别语义等价的句子来消除冗余信息时仅对单条序列分类是不够的——我们需要能够对成对的文本序列进行推理。自然语言推断研究的是假设hypothesis能否从前提premise中推断出来其中两者都是文本序列。换句话说自然语言推断判定一对文本序列之间的逻辑关系。这种关系通常分为三类蕴含Entailment假设可以从前提中推断出来。矛盾Contradiction前提中可以推断出假设的否定。中立Neutral其余所有情况。自然语言推断也被称为识别文本蕴含Recognizing Textual Entailment, RTE任务。以下面三组示例来说明三类标签蕴含由于前提中的 hugging one another互相拥抱可以推断出假设中的 showing affection表达感情因此这对句子被标注为entailment。前提Two women are hugging each other.假设Two women are showing affection.矛盾因为 running the coding example运行代码示例表明 not sleeping没有睡觉而不是 sleeping睡觉因此是contradiction。前提A man is running the coding example from Dive into Deep Learning.假设The man is sleeping.中立从 are performing for us为我们表演这一事实既无法推断出 famous出名也无法推断出 not famous不出名因此是neutrality。前提The musicians are performing for us.假设The musicians are famous.自然语言推断一直是理解自然语言的核心课题其应用广泛涵盖从信息检索到开放域问答等多个领域。为了研究该问题我们首先考察一个流行的自然语言推断基准数据集。斯坦福自然语言推断SNLI语料斯坦福自然语言推断SNLI语料库是包含50 万余条标注英文句子对的集合引自Bowman.Angeli.Potts.ea.2015。在 d2l-en 仓库中我们下载并解压 SNLI 数据集到../data/snli_1.0路径相对仓库根目录即data/snli_1.0。首先在d2l.DATA_HUB中注册该数据集。仓库源码中DATA_HUB定义于 d2l/torch.pyDATA_HUB dict()MXNet 与 PyTorch 两个后端均使用同一份注册信息并记录了下载地址与 SHA-1 校验值#tab mxnet from d2l import mxnet as d2l from mxnet import gluon, np, npx import os import re 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)#tab pytorch from d2l import torch as d2l import torch from torch import nn import os import re #save d2l.DATA_HUB[SNLI] ( https://nlp.stanford.edu/projects/snli/snli_1.0.zip, 9fcde07509c7e87ec61c640c1b2753d9041758e4) data_dir d2l.download_extract(SNLI)源码层面的下载与解压链路d2l.download_extract的真实实现在 d2l/torch.py 中它先调用download完成文件下载再按扩展名.zip、.tar、.gz解压到基目录。download函数d2l/torch.py的细节值得注意若传入的url不以http开头则视为DATA_HUB中的键自动取出(url, sha1_hash)元组——这正是download_extract(SNLI)的用法文件保存到../data/目录下download的默认folder../data若文件已存在且 SHA-1 哈希与注册值一致则直接命中缓存返回避免重复下载否则通过requests.get(url, streamTrue, verifyTrue)流式下载并写入文件。也就是说注册的哈希值9fcde0...是完整性校验的关键数据损坏或来源不一致时会触发重新下载。读取数据集read_snli原始 SNLI 数据集包含比我们实验所需丰富得多的信息。因此定义一个read_snli函数只提取其中一部分然后返回前提列表、假设列表及对应的标签列表#tab all #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该函数的关键设计点文本清洗extract_text用正则去掉括号字符()原始 SNLI 文本中常包含括号括起来的标注信息再把连续两个及以上的空白字符压缩为单个空格最后strip()去首尾空白标签映射label_set将entailment、contradiction、neutral分别映射为整数0、1、2供后续分类模型直接使用列结构SNLI 原始tsv文件以\t分隔跳过首行表头f.readlines()[1:]第 1 列为标签、第 2 列为前提、第 3 列为假设过滤无效行只有标签落在label_set中的行才被保留其余如标注为-的行被丢弃。现在打印前 3 对前提和假设及其标签0、1、2 分别对应 entailment、contradiction、neutral#tab all train_data read_snli(data_dir, is_trainTrue) 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)训练集约含55 万对测试集约含1 万对。下面的统计显示三类标签在训练集与测试集中都是均衡的#tab all test_data read_snli(data_dir, is_trainFalse) for data in [train_data, test_data]: print([[row for row in data[2]].count(i) for i in range(3)])均衡的类别分布意味着可以直接用准确率评估模型而不必担心类别先验偏差。定义加载数据集的类SNLIDataset下面通过继承 Gluon 的Dataset类来定义一个加载 SNLI 数据集的类。构造函数中的num_steps参数指定文本序列的长度使每个小批量的序列形状一致。换言之较长的序列中第num_steps个 token 之后的部分会被截断而较短的序列则会追加特殊 tokenpad直到长度达到num_steps。通过实现__getitem__函数可以用索引idx任意访问前提、假设和标签。MXNet 实现继承gluon.data.Dataset#tab mxnet #save class SNLIDataset(gluon.data.Dataset): A customized dataset to load the SNLI dataset. def __init__(self, dataset, num_steps, vocabNone): 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_freq5, 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)PyTorch 实现继承torch.utils.data.Dataset#tab pytorch #save class SNLIDataset(torch.utils.data.Dataset): A customized dataset to load the SNLI dataset. def __init__(self, dataset, num_steps, vocabNone): 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_freq5, reserved_tokens[pad]) 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[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)底层工具函数解析该类的三个核心依赖均可在仓库 d2l/torch.pyMXNet 版对应 d2l/mxnet.py中找到实现d2l.tokenized2l/torch.py按空格把每行切分为词 tokentokenword默认也可切换为字符级切分tokenchar。这里的前提与假设都先被 tokenize 成 token 列表的列表。d2l.Vocabd2l/torch.py从前提与假设的全部 token 构建词表。注意min_freq5表示出现频次低于 5 的 token 会被过滤掉reserved_tokens[pad]保留了填充 token词表还自动包含unk未知 token索引 0__getitem__遇到词表外的 token 时会返回unk索引。这也解释了训练集与测试集共享词表的重要性测试集中的新 token 会落到unk上。d2l.truncate_padd2l/torch.py当len(line) num_steps时截断到前num_steps个 token否则在末尾用padding_token此处为vocab[pad]的索引补齐到num_steps长度。_pad方法正是对每个 token 序列应用vocab[line]token 转索引后再做截断/填充最终得到形状为(样本数, num_steps)的整数张量。此外__getitem__返回的元组结构(premises[idx], hypotheses[idx]), labels[idx]是 NLI 任务区别于情感分析的关键每个样本包含两个输入前提与假设和一个标签。整合全部流程load_data_snli现在调用read_snli函数和SNLIDataset类来下载 SNLI 数据集并返回训练集和测试集的DataLoader实例以及训练集的词表。值得强调的是必须使用从训练集构建的词表作为测试集的词表。这样一来测试集中出现的任何新 token 对在训练集上训练的模型而言都是未知的被映射为unk从而保证评估的真实性避免数据泄露。MXNet 版本#tab mxnet #save def load_data_snli(batch_size, num_steps50): 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, shuffleTrue, num_workersnum_workers) test_iter gluon.data.DataLoader(test_set, batch_size, shuffleFalse, num_workersnum_workers) return train_iter, test_iter, train_set.vocabPyTorch 版本#tab pytorch #save def load_data_snli(batch_size, num_steps50): 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, shuffleTrue, num_workersnum_workers) test_iter torch.utils.data.DataLoader(test_set, batch_size, shuffleFalse, num_workersnum_workers) return train_iter, test_iter, train_set.vocab参数与行为说明num_workers来自d2l.get_dataloader_workers()仓库实现固定返回 4 个进程读取数据d2l/torch.py用于加速数据装载batch_size与num_steps50num_steps是默认的序列长度上限可通过调用时传参覆盖打乱策略训练集shuffleTrue打乱顺序测试集shuffleFalse保持顺序以便稳定评估。这里将批量大小设为 128、序列长度设为 50调用load_data_snli获取数据迭代器和词表然后打印词表大小#tab all train_iter, test_iter, vocab load_data_snli(128, 50) len(vocab)接着打印第一个小批量的形状。与情感分析不同这里有两个输入X[0]和X[1]分别代表前提对与假设对#tab all for X, Y in train_iter: print(X[0].shape) print(X[1].shape) print(Y.shape) break在batch_size128、num_steps50下输出形状应为X[0]: (128, 50)、X[1]: (128, 50)、Y: (128,)——前提与假设各是一个定长 50 的索引序列批量标签是长度为 128 的整数向量。在仓库中的实际消费场景该数据集加载管线并非孤立存在而是后续两个 NLI 模型章节的直接数据来源这印证了其接口设计的通用性自然语言推断使用注意力 在第 335 行直接调用d2l.load_data_snli(batch_size, num_steps)把返回的迭代器与词表喂给基于注意力与 MLP 的可分解注意力模型自然语言推断微调 BERT 在第 262-277 行复用d2l.read_snli(data_dir, True/False)读取原始三元组再按 BERT 的输入格式拼接两个序列重新封装数据集。从源码结构看read_snli之所以被设计成只返回前提、假设、标签三个列表的纯函数正是为了同时服务定长截断填充本节的SNLIDataset与BERT 拼接输入后续章节两种不同的封装方式。这也为读者自己扩展新的 NLI 数据封装例如加入注意力掩码、变长序列打包提供了清晰的切入点。小结自然语言推断研究假设能否从前提中推断出来两者均为文本序列在自然语言推断中前提与假设之间的关系包括蕴含entailment、矛盾contradiction和中立neutral三类斯坦福自然语言推断SNLI语料库是自然语言推断的流行基准数据集仓库提供了完整的 SNLI 处理管线read_snli读取清洗→SNLIDataset分词、建词表、截断填充→load_data_snli返回训练/测试迭代器与词表后续注意力模型与 BERT 微调章节均直接复用该管线。练习机器翻译长期以来基于输出译文与标准译文之间的表层 $n$-gram 匹配来评估。你能设计一种利用自然语言推断来评估机器翻译结果的度量吗提示可把译文作为前提、参考译文作为假设统计蕴含/矛盾/中立比例的思路值得尝试。如何改变超参数来减小词表大小提示考察Vocab的min_freq参数、num_steps序列长度以及分词粒度 word/char 对词表规模的影响。赞分享文档教程人工智能深度学习NLP计算机视觉强化学习【免费下载链接】d2l-enInteractive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.项目地址https://gitcode.com/gh_mirrors/d2/d2l-en点击查看免费下载相关推荐深入理解自然语言推断与SNLI数据集深入理解自然语言推断与SNLI数据集 自然语言处理 NLP 领域中自然语言推断 Natural Language Inference, NLI 是一项基础且重人工智能深度学习机器学习教程深入理解自然语言推理与SNLI数据集深入理解自然语言推理与SNLI数据集 自然语言推理 Natural Language Inference, NLI 是自然语言处理领域中的一个重要任务它研究如文档教程人工智能深度学习NLP计算机视觉强化学习自然语言推断与SNLI数据集实战《动手学深度学习》文本对分类的数据准备全解析自然语言推断与SNLI数据集实战《动手学深度学习》文本对分类的数据准备全解析 自然语言推断Natural Language Inference, NLI是人工智能深度学习机器学习教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考