
使用闪电般快速的零样本分类器预先标注您的数据
2022 年 7 月 19 日
TL;DR 在这篇博文中,您将学习如何:
- 使用 Neural Magic 的 DeepSparse 构建闪电般快速的零样本分类器;
- 将其与 Rubrix 结合使用,在不到 30 分钟内创建一个高效的训练集;
- 使用该训练集微调 DistilBERT 模型,准确率达到 0.98;
从头开始手动标注您的数据可能是一项繁琐的任务。Rubrix 提供了几种减轻此过程的方法,其中一种是使用模型预测预先标注您的数据。但是,当您处理文本分类任务时,您找到一个已经使用您特定的标签模式训练过的模型的可能性很低。
运行吧,零样本,运行!
零样本分类器可以解决这个问题。这里的想法是在一组标签上训练分类器,然后在它从未见过的不同集合上评估它。构建零样本分类器的一种方法是将分类任务重新定义为自然语言推理。它的工作原理是将每个候选标签作为“假设”(例如“这段文本是 {候选标签}”),并将我们要分类的文本作为“前提”。然后,我们可以将蕴含得分最高的候选标签作为输入文本最可能的标签。Hugging Face 的 transformers 库 对这种方法有一个简洁的实现;您可以在这个讨论中找到更多详细信息。
这种方法的一个缺点是每个标签都需要其前向传递,并且对于许多候选标签,推理在计算上可能会变得非常昂贵。这就是 Neural Magic 的 DeepSparse 引擎 发挥作用的地方。它利用稀疏化和其他技巧,在推理时在 CPU 上实现 GPU 级别的性能。他们的 SparseZoo 包含越来越多的稀疏模型,这些模型比其密集模型更轻量级,但实现了相当的准确率。
定义实验
这篇博文向您展示了如何使用 Neural Magic 的 deepsparse 库编程一个闪电般快速的零样本分类器。为了测试分类器,我们将把它与 Rubrix 一起使用,以预先标注 News Popularity 数据集,并在短时间内构建一个训练数据集,最终微调一个模型以用于我们特定的分类任务。
对于我们的实验,除了 Rubrix,我们还需要以下库:
如果您尚未设置 Rubrix,请查看我们的简短设置指南。
pip install "rubrix[server]" deepsparse "transformers[torch]"
让我们从上面讨论的零样本方法的简单实现开始:
from typing import List, Tuple, Optional, Dictfrom deepsparse.transformers import pipelineimport numpy as npDEFAULT_LABELS = ["microsoft", "economy", "obama", "palestine"]STR2INT = {label: i for i, label in enumerate(DEFAULT_LABELS)}# We use a heavily sparsified DistilBERT model fine-tuned on the MNLI dataset, available on Neural Magic's model zoo.model_path = "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni"sparsified_classifier = pipeline( "text-classification", model_path=model_path, return_all_scores=True, batch_size=16, max_length=64)def zero_shot_prediction( premise: List[str], labels: Optional[List[str]] = None, hypothesis_template = "This title is about {}.",) -> Tuple[np.ndarray, np.ndarray]: """Make a zero-shot prediction given some input texts and a list of labels. Args: premise: Input texts to classify. labels: Labels for the classification task. hypothesis_template: Template of the hypothesis. Will be completed with the labels. Returns: Predictions and probabilities. """ labels = labels or DEFAULT_LABELS # Formulate hypothesis hypos = [hypothesis_template.format(label) for label in labels] # Store entailment logits in an array logits = np.empty((len(premise), len(labels))) # Extract entailment logits for each hypothesis given the premises for i, hypo in enumerate(hypos): predictions = sparsified_classifier(list(zip(premise, [hypo]*len(premise)))) logits[:, i] = extract_entailment_logits(predictions) # Apply softmax probs = np.exp(logits) / np.sum(np.exp(logits), axis=-1, keepdims=True) # Get predictions preds = probs.argmax(axis=1) return preds, probsdef extract_entailment_logits( predictions: List[List[Dict[str, float]]], entailment_id: int = 0) -> np.ndarray: """Helper function to extract the entailment logits of an NLI model output. This is a slight hack, since we arbitrarily assume `sum(logits) == 0` """ probs = np.array([[p["score"] for p in pred] for pred in predictions]) logits = np.log(probs) - np.sum(np.log(probs), axis=-1, keepdims=True) / 3 return logits[:, entailment_id]
一旦我们编写了零样本分类器的代码,就该为我们的小型实验建立基线了。
建立基线
我们的小型实验的基线是将零样本分类器本身应用于 News Popularity 数据集的测试分割。我们将模型输入限制为新闻文章的标题,以使任务更具挑战性并保持模型输入较小。
from datasets import load_datasetfrom sklearn.metrics import classification_report# Load the News Popularity dataset from the Hugging Face Hubds = load_dataset("newspop", split="train")train, test = ds.train_test_split(test_size=10000, seed=43).values()# Make predictions with our lightning-fast zero-shot classifierdef predict(rows): predictions, _ = zero_shot_prediction(rows["title"]) return { "prediction": [INT2STR[pred] for pred in predictions], "label": rows["topic"], }test_prediction = test.map(predict, batched=True, batch_size=16)# Print out the test accuracyprint(classification_report(test_prediction["label"], test_prediction["prediction"]))
推理在我们机器上花费了大约 5 分钟(Intel i7-0750H,6 核),我们已经实现了 0.95 的准确率!更详细地查看标题,我们注意到许多标题明确包含主题关键字,这很适合我们的零样本方法。让我们将性能和速度与 Hugging Face 的 vanilla 零样本管道进行比较。
from transformers import pipeline# Load the zero-shot classifier of the transformers libraryclassifier = pipeline( "zero-shot-classification", model="typeform/distilbert-base-uncased-mnli")# Make predictionsdef predict(rows): outputs = classifier( rows["title"], candidate_labels=DEFAULT_LABELS, hypothesis_template="This title is about {}." ) return { "prediction": [output["labels"][0] for output in outputs], "label": rows["topic"], }test_prediction_tr = test.map(predict, batched=True, batch_size=16)# Print out the test accuracyprint(classification_report(test_prediction_tr["label"], test_prediction_tr["prediction"]))
虽然它实现了相同的准确率,但推理花费的时间大约是 Neural Magic 稀疏模型的四倍!
构建训练数据
让我们尝试通过微调 DistilBERT 基础模型以用于我们特定的分类任务来击败 0.95。为此,我们需要用我们的四个主题标记的训练示例(当然,NewsPop 数据集的训练分割已经包含这些标签,但让我们假设它没有)。我们将使用零样本分类器的模型预测来指导我们的标注过程,而不是从头开始手动标注数据。
因此,首先,让我们将闪电般快速的零样本分类器应用于训练分割的较小随机子集,并将数据以及相应的预测记录到 Rubrix UI。
import rubrix as rb# Create predictions for our traning data that we will use as pre-annotationsdef predict(rows): _, batch_probs = zero_shot_prediction(rows["title"]) prediction = [] for probs in batch_probs: prediction.append( [ {"label": label, "score": score} for label, score in zip(DEFAULT_LABELS, probs) ] ) return {"prediction": prediction}train_predicted = train.select(range(10000)).map(predict, batched=True, batch_size=16)# Create Rubrix records from the datasetrecords = rb.read_datasets(train_predicted, task="TextClassification", inputs=["title", "headline"])# Log the records to the Rubrix UIrb.log(records=records, name="newspop")
我们首先使用 Rubrix UI 的分数过滤器选择预测得分最高的记录。我们很快看到这些是突出的例子,并且使用批量标注工具,我们可以在几分钟内验证大约 500 个例子。在验证预测的同时,我们还跟踪我们标注的标签分布,并使用“预测为”过滤器来平衡标签。
现在是时候获得一些训练示例了,这些示例更有可能使微调模型相对于零样本分类器更具优势。我们选择预测得分最低的记录,并验证或更正零样本预测。最具挑战性的记录在新闻标题中没有主题关键字,但它们在文章的内容中包含主题关键字,我们也将其记录到 Rubrix。例如,我们可以通过搜索“Microsoft”并批量标注结果来利用这一点。
通过这种方式,我们可以在不到 30 分钟内使用 Rubrix 构建一个包含约 1000 个示例的训练集!
微调模型
现在我们有足够的训练示例,让我们使用 Hugging Face transformers 库微调 DistilBERT 模型,看看我们是否可以击败零样本分类器的准确率。
首先,让我们从 Rubrix 加载我们的训练数据,并为训练 transformers 模型做准备。
# Load the annotated data from the Rubrix UIrubrix_dataset = rb.load("newspop")# Prepare the Rubrix dataset for trainingtrain_ds = rubrix_dataset.prepare_for_training()# We will only use the news titles as the model 'text' inputtrain_ds = train_ds.rename_column("title", "text")
然后,我们对输入数据进行标记化,设置用于微调的基础 DistilBERT 模型,并在我们的训练集上对其进行训练。我们已经在下面的代码块中稍微优化了学习率和 epoch 数量。如果您想进一步优化它们,请不要忘记首先将数据拆分为训练集和验证集。
from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer# Tokenize our datatokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")def tokenize(examples): return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=64)tokenized_train_ds = train_ds.map(tokenize, batched=True)# Load our pre-trained modellabels = train_ds.features["label"].namesmodel = AutoModelForSequenceClassification.from_pretrained( "distilbert-base-uncased", num_labels=4, id2label={i: label for i, label in enumerate(labels)}, label2id={label: i for i, label in enumerate(labels)})# Fine-tune the pre-trained model with our training datatraining_args = TrainingArguments( "newspop_titles", learning_rate=5e-5, num_train_epochs=3, per_device_train_batch_size=8,)trainer = Trainer( args=training_args, model=model, train_dataset=tokenized_train_ds,)trainer.train()
作为最后一步,我们将训练好的模型加载到文本分类管道中,并在我们的测试集上对其进行评估。
# Load out fine-tuned model into a pipelinefinetuned_classifier = pipeline( "text-classification", model=model, tokenizer=tokenizer,)# Make predictions with our fine-tuned modeldef predict(rows): outputs = finetuned_classifier(rows["title"]) return { "prediction": [output["label"] for output in outputs], "label": rows["topic"], }test_prediction_ft = test.map(predict, batched=True, batch_size=32)# Print out the test accuracyprint(classification_report(test_prediction_ft["label"], test_prediction_ft["prediction"]))
就是这样。使用我们训练好的模型,我们实现了 0.98 的准确率,将零样本分类器的错误率降低了一半以上。
总结
在这篇博文中,您学习了如何:
- 使用 Neural Magic 的 DeepSparse 引擎构建闪电般快速的零样本分类器;
- 将其与 Rubrix 结合使用,在不到 30 分钟内创建一个高效的训练集;
- 使用该训练集微调 DistilBERT 模型,准确率达到 0.98;
然而,这仅仅是个开始。Neural Magic 正在努力将零样本分类管道直接实施到他们的 DeepSparse 引擎 中,利用他们 SparseZoo 中的模型。因此,闪电般快速的零样本分类器的使用和应用应该会变得更加轻松。
与此同时,Rubrix 正在不断实施新的标注技术。除了使用模型预测预先标注您的数据外,Rubrix 还内置了对弱监督的支持。如果您熟悉机器学习的这个分支,您可能会注意到这篇博文的数据集和任务是其应用的主要候选者。我们邀请您从我们关于弱监督的教程中获得启发,并尝试击败这个小型实验的 0.98 准确率。