在线对话在现代生活中无处不在,涵盖从视频游戏到电信的各个行业。 这导致在线对话数据量呈指数增长,这有助于开发最先进的自然语言处理 (NLP) 系统,如聊天机器人和自然语言生成 (NLG) 模型。 随着时间的推移,用于文本分析的各种 NLP 技术也在不断发展。 这就需要完全托管的服务,该服务可以使用 API 调用集成到应用程序中,而无需广泛的机器学习 (ML) 专业知识。 AWS 提供预训练的 AWS AI 服务,例如 亚马逊领悟,它可以有效地处理涉及分类、文本摘要、实体识别等的 NLP 用例,以从文本中收集见解。
此外,在线对话导致了非传统语言使用的广泛现象。 由于不同平台中存在的不断发展和特定领域的词汇表,以及单词与正确英语的显着词汇偏差,传统的 NLP 技术通常在此文本数据上表现不佳,无论是偶然还是故意作为对抗性攻击的一种形式.
在这篇文章中,我们描述了使用 AWS 上可用的工具和服务对在线对话进行文本分类的多种 ML 方法。
先决条件
在深入研究此用例之前,请完成以下先决条件:
- 设置一个 AWS账户 和 创建 IAM 用户.
- 设置 命令行界面 和 AWS开发工具包.
- (可选)设置您的 Cloud9 IDE环境.
数据集
对于这篇文章,我们使用 毒性分类数据集中的拼图意外偏差,一个针对在线对话中毒性分类的具体问题的基准。 该数据集提供毒性标签以及几个子组属性,例如淫秽、身份攻击、侮辱、威胁和色情。 标签以分数值的形式提供,表示相信该属性应用于给定文本的人工注释者的比例,这很少是一致的。 为了生成二进制标签(例如,有毒或无毒),0.5 的阈值应用于小数值,并且值大于阈值的评论被视为该标签的正类。
子词嵌入和 RNN
对于我们的第一个建模方法,我们结合使用子词嵌入和递归神经网络 (RNN) 来训练文本分类模型。 子词嵌入由 Bojanowski 等人。 2017 年 作为对以前的词级嵌入方法的改进。 传统的 Word2Vec skip-gram 模型经过训练,可以学习目标词的静态向量表示,以最佳方式预测该词的上下文。 另一方面,子词模型将每个目标词表示为组成该词的字符 n-gram 的包,其中 n-gram 由一组 n 个连续字符组成。 这种方法允许嵌入模型更好地表示语料库中相关单词的基本形态,以及计算新的词汇表外 (OOV) 单词的嵌入。 这在在线对话的上下文中尤为重要,在这个问题空间中,用户经常拼错单词(有时是为了逃避检测),并且还会使用一个独特的、不断发展的词汇,这可能无法被一般的训练语料库捕获。
亚马逊SageMaker 使用内置的特定领域文本数据语料库,可以轻松地训练和优化无监督子词嵌入模型 BlazingText算法. 我们还可以下载在大型在线文本数据集上训练的现有通用模型,例如以下 直接从 fastText 获得的英语语言模型. 从您的 SageMaker 笔记本实例中,只需运行以下命令即可下载预训练的 fastText 模型:
!wget -O vectors.zip https://dl.fbaipublicfiles.com/fasttext/vectors-english/crawl-300d-2M-subword.zip
无论您是使用 BlazingText 训练自己的嵌入还是下载了预训练模型,结果都是一个压缩模型二进制文件,您可以使用 gensim 库将给定的目标词嵌入为基于其组成子词的向量:
# Imports
import os
from zipfile import ZipFile
from gensim.models.fasttext import load_facebook_vectors # Unzip the model binary into 'dir_path'
with ZipFile('vectors.zip', 'r') as zipObj: zipObj.extractall(path=<dir_path_name>) # Load embedding model into memory
embed_model = load_facebook_vectors(os.path.join(<dir_path_name>, 'vectors.bin')) # Compute embedding vector for 'word'
word_embedding = embed_model[word]
在我们预处理给定的文本段之后,我们可以使用这种方法为每个组成词(用空格分隔)生成一个向量表示。 然后,我们使用 SageMaker 和深度学习框架(例如 PyTorch)来训练具有二元或多标签分类目标的自定义 RNN,以根据标记的训练示例预测文本是否有毒以及特定的毒性子类型。
将您的预处理文本上传到 亚马逊简单存储服务 (Amazon S3),使用以下代码:
import boto3
s3 = boto3.client('s3') bucket = <bucket_name>
prefix = <prefix_name> s3.upload_file('train.pkl', bucket, os.path.join(prefix, 'train/train.pkl'))
s3.upload_file('valid.pkl', bucket, os.path.join(prefix, 'valid/valid.pkl'))
s3.upload_file('test.pkl', bucket, os.path.join(prefix, 'test/test.pkl'))
要使用 SageMaker 启动可扩展的多 GPU 模型训练,请输入以下代码:
import sagemaker
sess = sagemaker.Session()
role = iam.get_role(RoleName= ‘AmazonSageMakerFullAccess’)['Role']['Arn'] from sagemaker.pytorch import PyTorch # hyperparameters, which are passed into the training job
hyperparameters = { 'epochs': 20, # Maximum number of epochs to train model 'train-batch-size': 128, # Training batch size (No. sentences) 'eval-batch-size': 1024, # Evaluation batch size (No. sentences) 'embed-size': 300, # Vector dimension of word embeddings (Must match embedding model) 'lstm-hidden-size': 200, # Number of neurons in LSTM hidden layer 'lstm-num-layers': 2, # Number of stacked LSTM layers 'proj-size': 100, # Number of neurons in intermediate projection layer 'num-targets': len(<list_of_label_names>), # Number of targets for classification 'class-weight': ' '.join([str(c) for c in <list_of_weights_per_class>]), # Weight to apply to each target during training 'total-length':<max_number_of_words_per_sentence>, 'metric-for-best-model': 'ap_score_weighted', # Metric on which to select the best model
} # create the Estimator
pytorch_estimator = PyTorch( entry_point='train.py', source_dir=<source_dir_path>, instance_type=<train_instance_type>, volume_size=200, instance_count=1, role=role, framework_version='1.6.0’, py_version='py36', hyperparameters=hyperparameters, metric_definitions=[ {'Name': 'validation:accuracy', 'Regex': 'eval_accuracy = (.*?);'}, {'Name': 'validation:f1-micro', 'Regex': 'eval_f1_score_micro = (.*?);'}, {'Name': 'validation:f1-macro', 'Regex': 'eval_f1_score_macro = (.*?);'}, {'Name': 'validation:f1-weighted', 'Regex': 'eval_f1_score_weighted = (.*?);'}, {'Name': 'validation:ap-micro', 'Regex': 'eval_ap_score_micro = (.*?);'}, {'Name': 'validation:ap-macro', 'Regex': 'eval_ap_score_macro = (.*?);'}, {'Name': 'validation:ap-weighted', 'Regex': 'eval_ap_score_weighted = (.*?);'}, {'Name': 'validation:auc-micro', 'Regex': 'eval_auc_score_micro = (.*?);'}, {'Name': 'validation:auc-macro', 'Regex': 'eval_auc_score_macro = (.*?);'}, {'Name': 'validation:auc-weighted', 'Regex': 'eval_auc_score_weighted = (.*?);'} ]
) pytorch_estimator.fit( { 'train': 's3://<bucket_name>/<prefix_name>/train', 'valid': 's3://<bucket_name>/<prefix_name>/valid', 'test': 's3://<bucket_name>/<prefix_name>/test' }
)
内 ,我们定义了一个 PyTorch 数据集,由 train.py
为模型的训练和评估准备文本数据:
def pad_matrix(m: torch.Tensor, max_len: int =100)-> tuple[int, torch.Tensor] : """Pads an embedding matrix to a specified maximum length.""" if m.ndim == 1: m = m.reshape(1, -1) mask = np.ones_like(m) if m.shape[0] > max_len: m = m[:max_len, :] mask = mask[:max_len, :] else: m = np.pad(m, ((0, max_len - m.shape[0]), (0,0))) mask = np.pad(mask, ((0, max_len - mask.shape[0]), (0,0))) return m, mask class EmbeddingDataset(Dataset: torch.utils.data.Dataset): """PyTorch dataset representing pretrained sentence embeddings, masks, and labels.""" def __init__(self, text: str, labels: int, max_len: int=100): self.text = text self.labels = labels self.max_len = max_len def __len__(self) -> int: return len(self.labels) def __getitem__(self, idx: int) -> dict: e = embed_line(self.text[idx]) length = e.shape[0] m, mask = pad_matrix(e, max_len=self.max_len) item = {} item['embeddings'] = torch.from_numpy(m) item['mask'] = torch.from_numpy(mask) item['labels'] = torch.tensor(self.labels[idx]) if length > self.max_len: item['lengths'] = torch.tensor(self.max_len) else: item['lengths'] = torch.tensor(length) return item
请注意,此代码预计 vectors.zip
包含您的 fastText 或 BlazingText 嵌入的文件将存储在 .
此外,您可以轻松地将预训练的 fastText 模型自行部署到实时 SageMaker 端点,以动态计算嵌入向量,以用于相关的单词级任务。 请参阅以下内容 GitHub 示例 以获得更多细节。
拥抱脸的变形金刚
对于我们的第二种建模方法,我们过渡到使用论文中介绍的 Transformer 注意就是您所需要的. Transformer 是深度学习模型,旨在通过依靠自我注意机制来绘制输入和输出之间的全局依赖关系,从而故意避免 RNN 的缺陷。 Transformer 模型架构允许显着更好的并行化,并且可以在相对较短的训练时间内实现高性能。
建立在变形金刚的成功之上,BERT 在论文中介绍 BERT:用于语言理解的深度双向变压器的预训练,为语言表示添加了双向预训练。 受完形填空任务的启发,BERT 使用掩码语言建模 (MLM) 进行了预训练,其中模型学习恢复随机掩码标记的原始单词。 BERT 模型还在下一句预测 (NSP) 任务上进行了预训练,以预测两个句子的阅读顺序是否正确。 自 2018 年问世以来,BERT 及其变体已广泛用于文本分类任务。
我们的解决方案使用了一种称为 RoBERTa 的 BERT 变体,该变体在论文中进行了介绍 RoBERTa:鲁棒优化的BERT预训练方法. RoBERTa 通过优化模型训练进一步提高了 BERT 在各种自然语言任务上的性能,包括在 10 倍大的语料库上训练模型的时间更长、使用优化的超参数、动态随机掩码、去除 NSP 任务等等。
我们基于 RoBERTa 的模型使用 拥抱脸变形金刚 库,这是一个流行的开源 Python 框架,为各种 NLP 任务提供各种最先进的 Transformer 模型的高质量实现。 Hugging Face 已与 AWS 合作 使您能够在 SageMaker 上轻松训练和部署 Transformer 模型。 此功能可通过 Hugging Face AWS 深度学习容器图像,其中包括 Transformers、Tokenizers 和 Datasets 库,以及与 SageMaker 的优化集成,用于模型训练和推理。
在我们的实现中,我们从 Hugging Face Transformers 框架继承了 RoBERTa 架构主干,并使用 SageMaker 来训练和部署我们自己的文本分类模型,我们称之为 RoBERTox。 RoBERTox 使用字节对编码 (BPE),在 带有子词单元的稀有词的神经机器翻译,将输入文本标记为子词表示。 然后,我们可以在 Jigsaw 数据或任何大型特定领域的语料库(例如来自特定游戏的聊天日志)上训练我们的模型和标记器,并将它们用于自定义文本分类。 我们在以下代码中定义了我们的自定义分类模型类:
class RoBERToxForSequenceClassification(CustomLossMixIn, RobertaPreTrainedModel): _keys_to_ignore_on_load_missing = [r"position_ids"] def __init__(self, config: PretrainedConfig, *inputs, **kwargs): """Initialize the RoBERToxForSequenceClassification instance Parameters ---------- config : PretrainedConfig num_labels : Optional[int] if not None, overwrite the default classification head in pretrained model. mode : Optional[str] 'MULTI_CLASS', 'MULTI_LABEL' or "REGRESSION". Used to determine loss class_weight : Optional[List[float]] If not None, add class weight to BCEWithLogitsLoss or CrossEntropyLoss """ super().__init__(config, *inputs, **kwargs) # Define model architecture self.roberta = RobertaModel(self.config, add_pooling_layer=False) self.classifier = RobertaClassificationHead(self.config) self.init_weights() @modeling_roberta.add_start_docstrings_to_model_forward( modeling_roberta.ROBERTA_INPUTS_DOCSTRING.format("batch_size, sequence_length") ) @modeling_roberta.add_code_sample_docstrings( tokenizer_class=modeling_roberta._TOKENIZER_FOR_DOC, checkpoint=modeling_roberta._CHECKPOINT_FOR_DOC, output_type=SequenceClassifierOutput, config_class=modeling_roberta._CONFIG_FOR_DOC, ) def forward( self, input_ids: torch.Tensor = None, attention_mask: torch.Tensor = None, token_type_ids: torch.Tensor = None, position_ids: torch.Tensor =None, head_mask: torch.Tensor =None, inputs_embeds: torch.Tensor =None, labels: torch.Tensor =None, output_attentions: torch.Tensor =None, output_hidden_states: torch.Tensor =None, return_dict: bool =None, sample_weights: torch.Tensor =None, ) -> : dict: """Forward pass to return loss, logits, ... Returns -------- output : SequenceClassifierOutput has those keys: loss, logits, hidden states, attentions """ return_dict = return_dict or self.config.use_return_dict outputs = self.roberta( input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids, position_ids=position_ids, head_mask=head_mask, inputs_embeds=inputs_embeds, output_attentions=output_attentions, output_hidden_states=output_hidden_states, return_dict=return_dict, ) sequence_output = outputs[0] # [CLS] embedding logits = self.classifier(sequence_output) loss = self.compute_loss(logits, labels, sample_weights=sample_weights) if not return_dict: output = (logits,) + outputs[2:] return ((loss,) + output) if loss is not None else output return SequenceClassifierOutput( loss=loss, logits=logits, hidden_states=outputs.hidden_states, attentions=outputs.attentions, ) def compute_loss(self, logits: torch.Tensor, labels: torch.Tensor, sample_weights: Optional[torch.Tensor] = None) -> torch.FloatTensor: return super().compute_loss(logits, labels, sample_weights)
在训练之前,我们使用 Hugging Face 的数据集库准备文本数据和标签,并将结果上传到 Amazon S3:
from datasets import Dataset
import multiprocessing data_train = Dataset.from_pandas(df_train)
… tokenizer = <instantiated_huggingface_tokenizer> def preprocess_function(examples: examples) -> torch.Tensor: result = tokenizer(examples["text"], padding="max_length", max_length=128, truncation=True) return result num_proc = multiprocessing.cpu_count()
print("Number of CPUs =", num_proc) data_train = data_train.map( preprocess_function, batched=True, load_from_cache_file=False, num_proc=num_proc
)
… import botocore
from datasets.filesystems import S3FileSystem s3_session = botocore.session.Session() # create S3FileSystem instance with s3_session
s3 = S3FileSystem(session=s3_session) # saves encoded_dataset to your s3 bucket
data_train.save_to_disk(f's3://<bucket_name>/<prefix_name>/train', fs=s3)
…
我们以与 RNN 类似的方式开始模型的训练:
import sagemaker
sess = sagemaker.Session()
role = sagemaker.get_execution_role()
from sagemaker.huggingface import HuggingFace # hyperparameters, which are passed into the training job
hyperparameters = { 'model-name': <huggingface_base_model_name>, 'epochs': 10, 'train-batch-size': 32, 'eval-batch-size': 64, 'num-labels': len(<list_of_label_names>), 'class-weight': ' '.join([str(c) for c in <list_of_class_weights>]), 'metric-for-best-model': 'ap_score_weighted', 'save-total-limit': 1,
} # create the Estimator
huggingface_estimator = HuggingFace( entry_point='train.py', source_dir=<source_dir_path>, instance_type=<train_instance_type>, instance_count=1, role=role, transformers_version='4.6.1', pytorch_version='1.7.1', py_version='py36', hyperparameters=hyperparameters, metric_definitions=[ {'Name': 'validation:accuracy', 'Regex': 'eval_accuracy = (.*?);'}, {'Name': 'validation:f1-micro', 'Regex': 'eval_f1_score_micro = (.*?);'}, {'Name': 'validation:f1-macro', 'Regex': 'eval_f1_score_macro = (.*?);'}, {'Name': 'validation:f1-weighted', 'Regex': 'eval_f1_score_weighted = (.*?);'}, {'Name': 'validation:ap-micro', 'Regex': 'eval_ap_score_micro = (.*?);'}, {'Name': 'validation:ap-macro', 'Regex': 'eval_ap_score_macro = (.*?);'}, {'Name': 'validation:ap-weighted', 'Regex': 'eval_ap_score_weighted = (.*?);'}, {'Name': 'validation:auc-micro', 'Regex': 'eval_auc_score_micro = (.*?);'}, {'Name': 'validation:auc-macro', 'Regex': 'eval_auc_score_macro = (.*?);'}, {'Name': 'validation:auc-weighted', 'Regex': 'eval_auc_score_weighted = (.*?);'} ]
) huggingface_estimator.fit( { 'train': 's3://<bucket_name>/<prefix_name>/train', 'valid': 's3://<bucket_name>/<prefix_name>/valid', 'test': 's3://<bucket_name>/<prefix_name>/test'
)
最后,以下 Python 代码片段说明了通过实时 SageMaker 端点为 JSON 请求提供实时文本分类服务 RoBERTox 的过程:
from sagemaker.huggingface import HuggingFaceModel
from sagemaker import get_execution_role
from sagemaker.predictor import Predictor
from sagemaker.serializers import JSONSerializer
from sagemaker.deserializers import JSONDeserializer class Classifier(Predictor): def __init__(self, endpoint_name, sagemaker_session): super().__init__(endpoint_name, sagemaker_session, serializer=JSONSerializer(), deserializer=JSONDeserializer()) hf_model = HuggingFaceModel( role=get_execution_role(), model_data=<s3_model_and_tokenizer.tar.gz>, entry_point="inference.py", transformers_version="4.6.1", pytorch_version="1.7.1", py_version="py36", predictor_cls=Classifier
) predictor = hf_model.deploy(instance_type=<deploy_instance_type>, initial_instance_count=1)
模型性能评估:Jigsaw 意外偏差数据集
下表包含对来自 Jigsaw Unintended Bias in Toxicity Detection Kaggle 比赛的数据进行训练和评估的模型的性能指标。 我们为三个不同但相互关联的任务训练了模型:
- 二进制大小写 – 该模型在完整的训练数据集上进行训练,以预测
toxicity
仅标签
- 细粒度案例 – 训练数据的子集
toxicity>=0.5
用于预测其他毒性亚型标签(obscene
, threat
, insult
, identity_attack
, sexual_explicit
)
- 多任务案例 – 完整的训练数据集用于同时预测所有六个标签
我们使用 Jigsaw 提供的分数标签为这三个任务中的每一个训练 RNN 和 RoBERTa 模型,分数标签对应于认为标签适合文本的注释者的比例,以及与网络中的类权重相结合的二进制标签损失函数。 在二元标记方案中,每个可用标签的比例阈值为 0.5(如果标签>=1,则为 0.5,否则为 0),并且模型损失函数的权重基于训练数据集中每个二元标签的相对比例。 在所有情况下,我们发现使用分数标签直接导致最佳性能,表明注释者之间的一致性程度所固有的信息的附加值。
我们展示了两个模型指标:平均精度 (AP),它通过计算在每个分类阈值处实现的精度值的加权平均值来提供精度-召回曲线的摘要,以及接收器操作特征曲线下的面积 (AUC) ,它在真阳性率和假阳性率方面汇总了跨分类阈值的模型性能。 请注意,测试集中给定文本实例的真实类对应于真实比例是否大于或等于 0.5(如果标签>=1,则为 0.5,否则为 0)。
. |
子词嵌入 + RNN |
罗伯塔 |
. |
分数标签 |
二进制标签 + 类加权 |
分数标签 |
二进制标签 + 类加权 |
二进制 |
AP=0.746, 曲线下面积=0.966 |
AP=0.730,AUC=0.963 |
AP=0.758,AUC=0.966 |
AP=0.747,AUC=0.963 |
细粒度 |
AP=0.906,AUC=0.909 |
AP=0.850,AUC=0.851 |
AP=0.913,AUC=0.913 |
AP=0.911,AUC=0.912 |
多任务 |
AP=0.721, 曲线下面积=0.972 |
AP=0.535,AUC=0.907 |
AP=0.740,AUC=0.972 |
AP=0.711,AUC=0.961 |
结论
在这篇文章中,我们介绍了两种使用 AWS ML 服务进行在线对话的文本分类方法。 您可以在在线通信平台上推广这些解决方案,游戏等行业特别有可能从提高检测有害内容的能力中受益。 在未来的帖子中,我们计划进一步讨论端到端架构,以便将模型无缝部署到您的 AWS 账户中。
如果您需要帮助以加快在产品和流程中使用ML,请与 亚马逊机器学习解决方案实验室.
作者简介
瑞安·布兰德(Ryan Brand) 是Amazon Machine Learning Solutions Lab的数据科学家。 他在将机器学习应用于医疗保健和生命科学领域的问题方面具有特定的经验,在业余时间,他喜欢阅读历史和科幻小说。
苏拉夫·巴贝什 是 Amazon ML Solutions Lab 的数据科学家。 他为各个行业的 AWS 客户开发 AI/ML 解决方案。 他的专长是自然语言处理 (NLP),对深度学习充满热情。 工作之余,他喜欢看书和旅行。
周六桐 是 Amazon ML Solutions Lab 的应用科学家。 他为各个行业的 AWS 客户构建定制的 AI/ML 解决方案。 他专攻自然语言处理 (NLP),对多模态深度学习充满热情。 他是一位抒情男高音,工作之余喜欢唱歌。
西亚古拉米 是 Amazon ML 解决方案实验室的高级数据科学家,在那里他为各个行业的客户构建 AI/ML 解决方案。 他对自然语言处理 (NLP) 和深度学习充满热情。 工作之余,Sia 喜欢在大自然中度过时光和打网球。
丹尼尔·霍洛维兹(Daniel Horowitz) 是应用 AI 科学经理。 他领导着 Amazon ML Solutions Lab 的一个科学家团队,致力于通过 ML 解决客户问题并推动云采用。