如何用 TensorFlow 在 10 分钟内训练一个分类模型
从数据收集和准备到模型训练和评估—包括源代码
深度学习无处不在。从销售预测到在图像数据上分割皮肤病,只要有高质量的数据,深度学习算法没有做不到的事情。
如果深度学习和 TensorFlow 对你来说很陌生,那你来对地方了。本文将向您展示在表格数据上构建分类模型的整个过程。您将在一次会议中完成从数据收集和准备到训练和评估神经网络模型的过程。我们开始吧。
您将需要安装 TensorFlow 2+、Numpy、Pandas、Matplotlib 和 Scikit-Learn 来跟进。
不想看书?请观看我的视频:
跳到一个部分:
**·** [**DATASET USED**](#efa5)
**·** [**DATA PREPARATION AND EXPLORATION**](#ccbc)
∘ [Basic preparation](#3c5e)
∘ [Converting to a binary classification problem](#6d40)
∘ [Train/test split](#541e)
∘ [Data scaling](#0d9d)
**·** [**TRAINING A CLASSIFICATION MODEL WITH TENSORFLOW**](#3a77)
∘ [Defining a neural network architecture](#1597)
∘ [Visualizing model performance](#c871)
∘ [Making predictions](#11c8)
∘ [Model evaluation on test data](#1656)
你可以在 GitHub 上下载源代码。
使用的数据集
让我们避免不必要的麻烦,坚持简单的数据集。来自 Kaggle 的葡萄酒质量数据集对今天来说已经足够好了:
图片 1——来自 Kaggle 的葡萄酒质量数据集(图片由作者提供)
数据集基本上是干净的,但默认情况下不是为二元分类(好酒/劣酒)而设计的。取而代之的是,葡萄酒是按等级来评定的。我们稍后会解决这个问题。
下载并解压 CSV 文件到你的机器上,然后打开 JupyterLab。您可以自由使用任何其他 IDE,但下面所有的截图都将来自 Jupyter。
数据准备和探索
第一步是导入 Numpy 和 Pandas,然后导入数据集。下面的代码片段实现了这一点,并打印了一个 5 行的随机样本:
import numpy as np
import pandas as pd
df = pd.read_csv('data/winequalityN.csv')
df.sample(5)
以下是数据集的外观:
图片 2 —葡萄酒质量数据集(图片由作者提供)
它基本上是干净的,但仍有一些工作要做。
基本准备
数据集有一些缺失值,但数量并不多,因为总共有 6497 行:
图 3-缺失值计数(作者图片)
运行以下代码来消除它们:
df = df.dropna()
唯一的非数字特征是type
。可以是白色 (4870 行)或红色 (1593 行)。下面的代码片段将这个特性转换成一个名为is_white_wine
的二进制特性,其中如果type
是白色的则值为 1,否则为 0:
df['is_white_wine'] = [
1 if typ == 'white' else 0 for typ in df['type']
]
df.drop('type', axis=1, inplace=True)
现在所有的特性都是数值型的,只剩下一件事要做——将目标变量(quality
)二进制化。
转化为二进制分类问题
葡萄酒的等级从 3 到 9,假设越高越好。以下是价值计数:
图 4 —目标变量值计数(作者图片)
为了简单起见,我们将把它转换成一个二进制变量。我们会将任何 6 级及以上的葡萄酒归类为好 (1),其他所有葡萄酒归类为差 (0)。代码如下:
df['is_good_wine'] = [
1 if quality >= 6 else 0 for quality in df['quality']
]
df.drop('quality', axis=1, inplace=True)
df.head()
这是数据集现在的样子:
图 5-准备后的数据集(作者提供的图片)
你现在有 4091 种好酒和 2372 种劣酒。班级不平衡,但我们可以解决这个问题。接下来,让我们将数据集分成训练集和测试集。
训练/测试分割
我们会坚持标准的 80:20 分成。代码如下:
from sklearn.model_selection import train_test_split
X = df.drop('is_good_wine', axis=1)
y = df['is_good_wine']
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, random_state=42
)
现在,定型集中有 5170 行,测试集中有 1293 行。训练一个稍微像样的神经网络模型应该就够了。在开始培训之前,让我们先对数据进行缩放。
数据缩放
像sulphates
和citric acid
这样的特征的值接近于零,而total sulfur dioxide
是以百为单位。如果你让它们保持原样,你会混淆神经网络,因为它会认为更高规模的特征更重要。
这就是伸缩性发挥作用的地方。我们将使用来自 Scikit-Learn 的StandardScaler
来拟合和转换训练数据,并将转换应用于测试数据:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
下面是前三个缩放行的样子:
图片 6 —缩放的训练集(图片由作者提供)
现在取值范围更窄了,所以神经网络应该能做得更好。让我们训练模型,看看我们是否能得到一些像样的东西。
用 TensorFlow 训练分类模型
在训练二元分类模型时,您需要记住几件事情:
- 输出层结构 —您可能想要用 sigmoid 函数激活一个神经元。这将输出一个概率,然后你可以分配给好酒(P > 0.5)或坏酒(P < = 0.5)。
- 损失函数 —二进制交叉熵是最合适的。不要误认为是分类交叉熵。
- 类平衡 —目标变量中的类平衡吗?换句话说,你有大致相同数量的好酒和坏酒吗?否则,准确性可能不是最佳评估指标。我们还将使用精度和召回。
考虑到以上三点,接下来让我们定义一个神经网络架构。
定义神经网络架构
我完全是随机选择这个架构的,所以可以随意调整。该模型从 12 个输入特征到 128 个神经元的第一隐藏层,接着是 256 个神经元的两个附加隐藏层。最后有一个单神经元输出层。隐藏层使用 ReLU 作为激活函数,输出层使用 Sigmoid。
代码如下:
import tensorflow as tf
tf.random.set_seed(42)
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(
loss=tf.keras.losses.binary_crossentropy,
optimizer=tf.keras.optimizers.Adam(lr=0.03),
metrics=[
tf.keras.metrics.BinaryAccuracy(name='accuracy'),
tf.keras.metrics.Precision(name='precision'),
tf.keras.metrics.Recall(name='recall')
]
)
history = model.fit(X_train_scaled, y_train, epochs=100)
这将启动培训过程。在我的机器上,一个纪元大约需要 1 秒钟(M1·MBP):
图 7-模型训练(图片由作者提供)
我们在训练过程中跟踪丢失、准确度、精确度和召回,并将其保存到history
。我们现在可以可视化这些指标,以了解模型的运行情况。
可视化模型性能
让我们从导入 Matplotlib 并稍微调整一下默认样式开始。以下代码片段将使绘图变大,并删除顶部和右侧的脊线:
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['figure.figsize'] = (18, 8)
rcParams['axes.spines.top'] = False
rcParams['axes.spines.right'] = False
该图将有多条线——损失线、准确度线、精确度线和回忆线。它们都共用 X 轴,代表纪元编号(np.arange(1, 101)
)。我们应该看到损失在减少,其他指标在增加:
plt.plot(
np.arange(1, 101),
history.history['loss'], label='Loss'
)
plt.plot(
np.arange(1, 101),
history.history['accuracy'], label='Accuracy'
)
plt.plot(
np.arange(1, 101),
history.history['precision'], label='Precision'
)
plt.plot(
np.arange(1, 101),
history.history['recall'], label='Recall'
)
plt.title('Evaluation metrics', size=20)
plt.xlabel('Epoch', size=14)
plt.legend();
让我们来看看:
图 8 —培训期间的模特表现(图片由作者提供)
当我们训练模型时,准确度、精确度和召回率稍微增加,而损失减少。所有都有偶尔的峰值,如果你训练模型更长时间,这些峰值有望消失。
根据图表,你可以为更多的时期训练模型,因为没有稳定期的迹象。
但是我们是否过度适应了呢?接下来我们来回答这个问题。
做预测
您现在可以使用predict()
函数来获得缩放测试数据的预测概率:
predictions = model.predict(X_test_scaled)
以下是它们的样子:
图片 9 —预测概率(图片由作者提供)
你必须在评估之前将它们转换成类。逻辑很简单——如果概率大于 0.5,我们指定 1(好酒),否则指定 0(坏酒):
prediction_classes = [
1 if prob > 0.5 else 0 for prob in np.ravel(predictions)
]
下面是前 20 个的样子:
图片 10-预测类(作者图片)
这就是我们所需要的—接下来让我们评估这个模型。
测试数据的模型评估
让我们从混淆矩阵开始:
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test, prediction_classes))
图 11 —混淆矩阵(图片由作者提供)
假阴性(214)比假阳性(99)多,所以测试集上的召回值将低于精度。
下面的代码片段显示了测试集的准确度、精确度和召回率:
from sklearn.metrics import accuracy_score, precision_score, recall_score
print(f'Accuracy: {accuracy_score(y_test, prediction_classes):.2f}')
print(f'Precision: {precision_score(y_test, prediction_classes):.2f}')
print(f'Recall: {recall_score(y_test, prediction_classes):.2f}')
图 12 —测试集的准确度、精确度和召回率(图片由作者提供)
与列车组评估相比,所有值都略低:
- 精度 : 0.82
- 精度 : 0.88
- 回忆 : 0.83
这个模型有点过度拟合了,但在几分钟内仍然是不错的工作。我们将在下一篇文章中讨论优化。
离别赠言
这就做到了——你现在知道如何训练一个简单的神经网络进行二进制分类。我们今天使用的数据集相对干净,几乎不需要任何准备工作。不要习惯那种感觉。
我们还有很多可以改进的地方。例如,您可以向网络添加额外的层,增加神经元的数量,选择不同的激活函数,选择不同的优化器,添加删除层,等等。可能性几乎是无穷无尽的,所以一切都归结于实验。
下一篇文章将涉及优化—您将学习如何自动找到最佳学习速率和神经网络架构,如果您想了解更多,请继续关注。
感谢阅读。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
保持联系
深度学习的情感分析
如何训练自己的高性能情绪分析模型
照片由 Pietro Jeng 在 Unsplash 上拍摄
目标
情感分析是自然语言处理中的一种技术,用于识别与文本相关联的情感。情感分析的常见用例包括监控客户在社交媒体上的反馈、品牌和活动监控。
在本文中,我们将探讨如何利用预先训练好的 HuggingFace 模型,在自定义数据集上训练自己的情感分析模型。我们还将研究如何在 CPU 和 GPU 环境中高效地对微调后的模型执行单次和批量预测。如果您正在寻找一个开箱即用的情感分析模型,请查看我以前的文章如何用 python 执行情感分析,其中只有 3 行代码。
装置
pip install transformers
pip install fast_ml==3.68
pip install datasets
导入包
import numpy as np
import pandas as pd
from fast_ml.model_development import train_valid_test_split
from transformers import Trainer, TrainingArguments, AutoConfig, AutoTokenizer, AutoModelForSequenceClassification
import torch
from torch import nn
from torch.nn.functional import softmax
from sklearn.metrics import classification_report
from sklearn.preprocessing import LabelEncoder
import datasets
启用 GPU 加速器(如果可用)。
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print (f'Device Availble: {DEVICE}')
数据准备
我们将使用一个电子商务数据集,其中包含文本评论和女装评级。
df = pd.read_csv('/kaggle/input/womens-ecommerce-clothing-reviews/Womens Clothing E-Commerce Reviews.csv')
df.drop(columns = ['Unnamed: 0'], inplace = True)
df.head()
我们只对Review Text
和Rating
列感兴趣。Review Text
列用作模型的输入变量,而Rating
列是我们的目标变量,其值从 1(最不利)到 5(最有利)。
为了清楚起见,让我们在每个整数评级后面附加“星”或“星”。
df_reviews = df.loc[:, ['Review Text', 'Rating']].dropna()
df_reviews['Rating'] = df_reviews['Rating'].apply(lambda x: f'{x} Stars' if x != 1 else f'{x} Star')
这是现在数据的样子,1,2,3,4,5 颗星是我们的类标签。
让我们使用 Sklearn 的LabelEncoder
对评级进行编码。
le = LabelEncoder()
df_reviews['Rating'] = le.fit_transform(df_reviews['Rating'])
df_reviews.head()
请注意,Rating
列已经从文本转换为整数列。
Rating
栏中的数字范围从 0 到 4。这些是将用于训练模型的类标签的类 id。每个类 id 对应一个等级。
print (le.classes_)>> ['1 Star' '2 Stars' '3 Stars' **'4 Stars'** '5 Stars']
列表的位置索引是类 id (0 到 4 ),该位置的值是原始评级。例如,在位置号 3,类别 id 是“3 ”,它对应于类别标签“4 星”。
让我们将数据分别按照 80%、10%和 10%的比例拆分为训练、验证和测试。
(train_texts, train_labels,
val_texts, val_labels,
test_texts, test_labels) = train_valid_test_split(df_reviews, target = 'Rating', train_size=0.8, valid_size=0.1, test_size=0.1)
将熊猫系列的评论文本转换成句子列表。
train_texts = train_texts['Review Text'].to_list()
train_labels = train_labels.to_list()
val_texts = val_texts['Review Text'].to_list()
val_labels = val_labels.to_list()
test_texts = test_texts['Review Text'].to_list()
test_labels = test_labels.to_list()
创建一个DataLoader
类,用于在训练和推理阶段处理和加载数据。
class DataLoader(torch.utils.data.Dataset):
def __init__(self, sentences=None, labels=None):
self.sentences = sentences
self.labels = labels
self.tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
if bool(sentences):
self.encodings = self.tokenizer(self.sentences,
truncation = True,
padding = True)
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
if self.labels == None:
item['labels'] = None
else:
item['labels'] = torch.tensor(self.labels[idx])
return item def __len__(self):
return len(self.sentences)
def encode(self, x):
return self.tokenizer(x, return_tensors = 'pt').to(DEVICE)
让我们来看看DataLoader
的运行情况。
train_dataset = DataLoader(train_texts, train_labels)
val_dataset = DataLoader(val_texts, val_labels)
test_dataset = DataLoader(test_texts, test_labels)
DataLoader
初始化一个预训练的标记器,并对输入句子进行编码。我们可以通过使用__getitem__
函数从DataLoader
中获得一条记录。下面是输入句子进行标记化后的结果。
print (train_dataset.__getitem__(0))
输出数据是一个由 3 个键值对组成的字典
input_ids
:这包含一个整数张量,其中每个整数代表原始句子中的单词。tokenizer
步骤将单个单词转换成由整数表示的符号。第一个记号101
是句子的开始记号,而102
记号是句子的结束记号。请注意,有许多尾随零,这是由于在tokenizer
步骤中应用于句子的填充。attention_mask
:这是一个二进制值数组。attention_mask
的每个位置对应input_ids
中相同位置的一个令牌。1
表示应该关注给定位置的令牌,0
表示给定位置的令牌是填充值。labels
:这是目标标签
定义评估指标
我们希望在培训阶段定期对模型性能进行评估。为此,我们需要一个度量计算函数,它接受一个元组(prediction, label)
作为参数,并返回一个度量字典:{'metric1':value1,
metric2 :value2}
。
f1 = datasets.load_metric('f1')
accuracy = datasets.load_metric('accuracy')
precision = datasets.load_metric('precision')
recall = datasets.load_metric('recall')def compute_metrics(eval_pred):
metrics_dict = {}
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
metrics_dict.update(f1.compute(predictions = predictions, references = labels, average = 'macro'))
metrics_dict.update(accuracy.compute(predictions = predictions, references = labels))
metrics_dict.update(precision.compute(predictions = predictions, references = labels, average = 'macro'))
metrics_dict.update(recall.compute(predictions = predictions, references = labels, average = 'macro')) return metrics_dict
培养
接下来,我们从预训练的检查点配置实例化一个distilbert-base-uncased
模型。
id2label = {idx:label for idx, label in enumerate(le.classes_)}
label2id = {label:idx for idx, label in enumerate(le.classes_)}config = AutoConfig.from_pretrained('distilbert-base-uncased',
num_labels = 5,
id2label = id2label,
label2id = label2id)model = AutoModelForSequenceClassification.from_config(config)
num_labels
:类别数id2label
:将类别 id 映射到类别标签的字典{0: '1 Star', 1: '2 Stars', 2: '3 Stars', 3: '4 Stars', 4: '5 Stars'}
label2id
:将类别标签映射到类别 id 的映射字典{'1 Star': 0, '2 Stars': 1, '3 Stars': 2, '4 Stars': 3, '5 Stars': 4}
让我们检查一下模型配置。id2label
和label2id
字典已经合并到配置中。我们可以在推理过程中从模型的配置中检索这些字典,以便为预测的类 id 找出相应的类标签。
print (config)>> DistilBertConfig {
"activation": "gelu",
"architectures": [
"DistilBertForMaskedLM"
],
"attention_dropout": 0.1,
"dim": 768,
"dropout": 0.1,
"hidden_dim": 3072,
"id2label": {
"0": "1 Star",
"1": "2 Stars",
"2": "3 Stars",
"3": "4 Stars",
"4": "5 Stars"
},
"initializer_range": 0.02,
"label2id": {
"1 Star": 0,
"2 Stars": 1,
"3 Stars": 2,
"4 Stars": 3,
"5 Stars": 4
},
"max_position_embeddings": 512,
"model_type": "distilbert",
"n_heads": 12,
"n_layers": 6,
"pad_token_id": 0,
"qa_dropout": 0.1,
"seq_classif_dropout": 0.2,
"sinusoidal_pos_embds": false,
"tie_weights_": true,
"transformers_version": "4.6.1",
"vocab_size": 30522
}
我们还可以使用以下方法来检查模型架构
print (model)
设置训练参数。
training_args = TrainingArguments(
output_dir='/kaggle/working/results',
num_train_epochs=10,
per_device_train_batch_size=64,
per_device_eval_batch_size=64,
warmup_steps=500,
weight_decay=0.05,
report_to='none',
evaluation_strategy='steps',
logging_dir='/kagge/working/logs',
logging_steps=50)
report_to
支持将训练工件和结果记录到 mlflow、tensorboard、azure_ml 等平台per_device_train_batch_size
是训练期间每个 TPU/GPU/CPU 的批量大小。如果您的设备面临内存不足的问题,请降低此值per_device_eval_batch_size
是评估期间每个 TPU/GPU/CPU 的批量大小。如果您的设备面临内存不足的问题,请降低此值logging_step
确定培训期间进行指标评估的频率
实例化Trainer
。在引擎盖下,Trainer
基于给定的训练参数、模型、数据集和指标运行训练和评估循环。
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
compute_metrics=compute_metrics)
开始训练吧!
trainer.train()
每 50 步进行一次评估。我们可以通过改变TrainingArguments
中的logging_steps
参数来改变求值的间隔。除了默认的训练和验证损失指标之外,我们还获得了之前在compute_metric
函数中定义的额外指标。
估价
让我们在测试集上评估我们的训练。
eval_results = trainer.predict(test_dataset)
Trainer
的predict
函数返回 3 项:
- 原始预测分数的数组
print (test_results.predictions)
2.地面真实标签 id
print (test_results.label_ids)
>> [1 1 4 ... 4 3 1]
3.韵律学
print (test_results.metrics)
>> {'test_loss': 0.9638910293579102,
'test_f1': 0.28503729426950286,
'test_accuracy': 0.5982339955849889,
'test_precision': 0.2740061405117546,
'test_recall': 0.30397183356136337,
'test_runtime': 5.7367,
'test_samples_per_second': 394.826,
'test_mem_cpu_alloc_delta': 0,
'test_mem_gpu_alloc_delta': 0,
'test_mem_cpu_peaked_delta': 0,
'test_mem_gpu_peaked_delta': 348141568}
模型预测函数输出未标准化的概率得分。为了找到类别概率,我们对未标准化的分数取一个软最大值。具有最高类别概率的类别被认为是预测类别。我们可以通过取类概率的 argmax 来找到它。我们之前存储在模型配置中的id2label
属性可用于将类别 id (0-4)映射到类别标签(1 星,2 星…).
label2id_mapper = model.config.id2label
proba = softmax(torch.from_numpy(test_results.predictions))
pred = [label2id_mapper[i] for i in torch.argmax(proba, dim = -1).numpy()]
actual = [label2id_mapper[i] for i in test_results.label_ids]
我们使用 Sklearn 的classification_report
来获得精确度、召回率、f1 和准确度分数。
class_report = classification_report(actual, pred, output_dict = True)
pd.DataFrame(class_report)
保存模型
trainer.save_model('/kaggle/working/sentiment_model')
推理
在本节中,我们将了解如何加载已定型模型并对其执行预测。让我们在另一个笔记本上测试一下这个推论。
设置
import pandas as pd
import numpy as np
from transformers import Trainer, TrainingArguments, AutoConfig, AutoTokenizer, AutoModelForSequenceClassification
import torch
from torch import nn
from torch.nn.functional import softmax
这种推断可以在 GPU 或 CPU 环境中工作。如果可用,在您的环境中启用 GPU。
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print (f'Device Availble: {DEVICE}')
这与我们在培训阶段使用的DataLoader
相同
class DataLoader(torch.utils.data.Dataset):
def __init__(self, sentences=None, labels=None):
self.sentences = sentences
self.labels = labels
self.tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
if bool(sentences):
self.encodings = self.tokenizer(self.sentences,
truncation = True,
padding = True)
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
if self.labels == None:
item['labels'] = None
else:
item['labels'] = torch.tensor(self.labels[idx])
return item def __len__(self):
return len(self.sentences)
def encode(self, x):
return self.tokenizer(x, return_tensors = 'pt').to(DEVICE)
创建模型类
SentimentModel
类帮助初始化模型,包含分别用于单个和批量预测的predict_proba
和batch_predict_proba
方法。batch_predict_proba
使用 HuggingFace 的Trainer
进行批量评分。
class SentimentModel():
def __init__(self, model_path):
self.model = AutoModelForSequenceClassification.from_pretrained(model_path).to(DEVICE)
args = TrainingArguments(output_dir='/kaggle/working/results', per_device_eval_batch_size=64)
self.batch_model = Trainer(model = self.model, args= args)
self.single_dataloader = DataLoader()
def batch_predict_proba(self, x):
predictions = self.batch_model.predict(DataLoader(x))
logits = torch.from_numpy(predictions.predictions)
if DEVICE == 'cpu':
proba = torch.nn.functional.softmax(logits, dim = 1).detach().numpy()
else:
proba = torch.nn.functional.softmax(logits, dim = 1).to('cpu').detach().numpy() return proba
def predict_proba(self, x):
x = self.single_dataloader.encode(x).to(DEVICE)
predictions = self.model(**x)
logits = predictions.logits
if DEVICE == 'cpu':
proba = torch.nn.functional.softmax(logits, dim = 1).detach().numpy()
else:
proba = torch.nn.functional.softmax(logits, dim = 1).to('cpu').detach().numpy() return proba
数据准备
让我们加载一些样本数据
df = pd.read_csv('/kaggle/input/womens-ecommerce-clothing-reviews/Womens Clothing E-Commerce Reviews.csv')
df.drop(columns = ['Unnamed: 0'], inplace = True)
df_reviews = df.loc[:, ['Review Text', 'Rating']].dropna()
df_reviews['Rating'] = df_reviews['Rating'].apply(lambda x: f'{x} Stars' if x != 1 else f'{x} Star')
df_reviews.head()
我们将创建两组数据。一个用于批量评分,另一个用于单次评分。
batch_sentences = df_reviews.sample(n = 10000, random_state = 1)['Review Text'].to_list()
single_sentence = df_reviews.sample(n = 1, random_state = 1)['Review Text'].to_list()[0]
预测
实例化模型
sentiment_model = SentimentModel('../input/fine-tune-huggingface-sentiment-analysis/sentiment_model')
使用predict_proba
方法预测单个句子。
single_sentence_probas = sentiment_model.predict_proba(single_sentence)
id2label = sentiment_model.model.config.id2label
predicted_class_label = id2label[np.argmax(single_sentence_probas)]print (predicted_class_label)
>> 5 Stars
使用batch_predict_proba
方法对一批句子进行预测。
batch_sentence_probas = sentiment_model.batch_predict_proba(batch_sentences)
predicted_class_labels = [id2label[i] for i in np.argmax(batch_sentence_probas, axis = -1)]
推理速度
让我们比较一下predict_proba
和batch_predict_proba
方法之间的推理速度
对于 CPU 和 GPU 环境中的 10k 样本数据。我们将对 10k 个样本进行迭代,predict_proba
每次进行一次预测,同时使用batch_predict_proa
方法对所有 10k 个样本进行评分,无需迭代。
%%time
for sentence in batch_sentences:
single_sentence_probas = sentiment_model.predict_proba(sentence)%%time
batch_sentence_probas = sentiment_model.batch_predict_proba(batch_sentences)
GPU 环境
遍历predict_proba
大约需要 2 分钟,而对于 10k 样本数据batch_predict_proba
大约需要 30 秒。批量预测比在 GPU 环境下使用单次预测快了差不多 4 倍。
CPU 环境
在 CPU 环境中,predict_proba
花费了大约 14 分钟,而batch_predict_proba
花费了大约 40 分钟,几乎是原来的 3 倍。
因此,对于大型数据集,如果您有 GPU,请使用batch_predict_proba
。如果您无法访问 GPU,您最好使用predict_proba
遍历数据集。
结论
在本文中,我们研究了:
- 如何利用预训练的 HuggingFace 模型来训练自己的深度学习情感分析模型
- 如何创建单项和批量评分预测方法
- CPU 和 GPU 环境中单次和批量评分的推断速度
这篇文章的笔记本可以在这里找到:
加入 Medium 阅读更多这样的故事。
如何在噪声数据上训练细粒度多标签分类模型
时尚 AI
基于 Fastai 和 DeepFashion 数据集的服装属性识别
图片由 TanaCh 根据 Shutterstock.com的许可使用
噪声标签的问题对于每个处理过手动注释数据的人来说都很熟悉。每当多个贡献者参与数据标注任务时,由于不同的参与者将具有不同的标注标准,这将不可避免地导致数据误标注。面临这个问题的组织经常会推迟深度学习产品的开发,并优先考虑数据清理。然而,有一些方法可以帮助建立适当的模型,同时不断提高数据质量。在本文中,我将解决细粒度多标签分类中的噪声数据问题。
在我的上一篇文章“用 DeepFashion 数据集和 Fastai 进行服装分类”中,我遇到了嘈杂标签的问题。毫无疑问,数据中的噪音影响了模型的质量。即使最终我能够在单标签分类任务中获得良好的准确性,如果我找不到这个问题的解决方案,我也会质疑继续这个项目的可能性。概括地说,我的目标是将服装项目建模为一个图表,该图表包含服装类型、属性及其关系(无论两个项目是否可以组合成一个外观)。作为第一步,我正在为服装类型和属性识别构建模型,这些模型将用于在图形建模之前标记我自己的数据。尽管噪声标签对于简单的单标签分类模型来说不是一个大问题,但是多标签属性分类意味着学习语义相近的类别之间的细微差异。这在有噪声的数据上尤其难以解决。
在本文中,我将介绍为多标签属性分类训练模型的过程。文章包含以下几个部分:
- 数据
- 评估指标的选择
- 损失函数的选择
- 模特培训
- 估价
- 摘要
在我的解决方案中,我使用 PyTorch 和 Fastai 库。本文中使用的完整代码可从这里获得。
数据
我使用了 DeepFasion 数据集,这是一个大规模的服装数据库,用于服装类别和属性预测,由香港中文大学多媒体实验室收集。
分类基准于 2016 年发布。它评估了 FashionNet 模型在预测 46 个类别和 1000 个服装属性方面的性能。原论文请参见 DeepFashion:用丰富的注释支持健壮的服装识别和检索,CVPR 2016 。
DeepFashion 数据库包含几个数据集。对于这个项目,使用了类别和属性预测基准。该数据集包含 289,222 张不同的服装图片,标有 1000 种不同的属性,其中我使用了 98 种属性,分别对应于款式、面料、季节和图案类型。
训练标签以以下格式存储在 multilabel-train.csv 中:
训练数据文件
数据集包含图像的位置、字符串对象形式的标签以及图像是否属于验证集的指示符。
为了加载图像,我们将使用一个数据块,它将图像的完整路径和作为标签的字符串列表作为输入。为此,我们将创建两个助手函数来解析输入格式。我们还将创建一个 splitter 函数,它将基于 is_valid 列创建训练集和验证集。
当调用数据块 API 时,我们需要指定变量的类型。在我们的例子中,自变量是 ImageBlock,因变量是 MultiCategoryBlock。MultiCategoryBlock 希望标签存储为字符串列表。 get_y 方法会搞定的。
让我们看看数据集中的一些图像:
带标签的训练图像
显然,原始数据集在标签中包含了大量噪声。例如,第一张图片上没有掌纹,第二张图片上的“日常”服装看起来比上一张图片上的“时尚”牛仔短裤更花哨。这就是我在本文中要解决的问题,但是首先,我们需要为我们的解决方案选择一个合适的评估指标。
评估指标的选择
现在,当数据被加载时,让我们看一下目标变量:
我们传递给 dblock 变量的标签列表被转换成稀疏的独热编码向量。稀疏性将影响我们对评估指标的选择。fastai 中的默认选择是 accuracy_multi 。但是,它不适用于稀疏标注。我们来看看为什么。方法 accuracy_multi 在 fastai 中定义如下:
如果我们创建一个零张量作为“预测”,并将其与目标值一起发送到 accuracy_multi ,我们将已经达到 98%的准确度。这意味着即使我们的模型不预测任何标签,它仍然有 98%的准确性。
TensorMultiCategory(0.9796)
因此,我们需要找到另一个评估指标,反映我们模型的实际学习进度和性能。一个选择可能是 FBeta 分数。 FBeta 是 F-score 的概括。虽然 F-score 被定义为精度和召回率的调和平均值,并赋予两者相同的权重,但是 FBeta 增加了一个称为 Beta 的权重配置参数。beta 参数决定了回忆在综合得分中的权重。beta < 1 更重视精度,而 beta > 1 更倾向于召回(beta - > 0 只考虑精度,beta - > +inf 只考虑召回)。
让我们看看它是如何处理我们的“0-预测”问题的:
0.0
在没有预测的情况下,它会给我们 FBeta=0。我们还可以测试其他一些极端情况。例如随机预测:
0.001
随机生成的预测的 FBeta 分数接近 0。而在“完美”预测上,其中预测=目标 Fbeta 分数等于 1:
1.0
这看起来像是评估指标的有效选项。现在让我们选择一个损失函数。
损失函数的选择
回到训练标签的例子,它们被编码为独热编码向量。这种编码也被称为硬编码。硬编码标签的问题在于,正确类别的预测概率非常大,而不正确类别的预测概率非常低。该模型将以接近 1 的置信度对每个训练示例进行正确分类,这可能会导致多个问题。第一个问题是过度拟合。另外两个问题与我们数据集中标签的性质有关。
如果我们仔细观察数据集中的标签:
['abstract-print', 'animal', 'baroque', 'basic', 'beach', 'bird-print', 'boho', 'botanical-print', 'camouflage', 'cargo', 'chic', 'chiffon', 'circle', 'civil', 'colorblock', 'cotton', 'cozy', 'crochet', 'cute', 'denim', 'dotted', 'elegant', 'everyday', 'fancy', 'faux-fur', 'faux-leather', 'faux-suede', 'feather', 'floral', 'fur', 'glitter', 'graphic', 'grid-print', 'grunge', 'knotted', 'leaf-print', 'leather', 'leopard-print', 'linen', 'linen-blend', 'logo', 'luxe', 'marble-print', 'medallion-print', 'mesh', 'metallic', 'mixed-print', 'multi-stripe', 'neon', 'neoprene', 'nets', 'netted', 'nylon', 'oil', 'ombre', 'organza', 'ornate-print', 'paisley-print', 'palm-print', 'party', 'patched', 'pattern', 'pleated', 'print', 'relaxed', 'retro', 'safari', 'sateen', 'satin', 'sheer', 'smart', 'soft', 'solid', 'sophisticated', 'sparkling', 'sporty', 'springs', 'square', 'star', 'strap', 'stretch', 'striped', 'suede', 'summer', 'sweet', 'textured', 'thermal', 'tie-dye', 'training', 'triangle', 'tropical', 'tweed', 'utility', 'velvet', 'weekend', 'workout', 'woven', 'zigzag']
我们可以看到衣服属性标签在语义上非常接近。例如:“植物印花”和“花卉”,或者“人造革”和“皮革”,即使对人来说也很难区分。当我们在编码为 One-Hot 的语义相似的标签上训练模型时,正确和不正确的类之间的差异非常大,即使它们在语义上可能非常接近。在这种情况下,模型预测标签的置信度是不受限制的,并且会随着类别而变化,这使得正确类别的分离更具挑战性。
最后,正如我们在数据部分看到的,DeepFashion 数据集受到嘈杂标签的影响。数据由多个参与者手动标注,这些参与者可能具有不同的标注标准,这导致了数据的错误标注。结果,用硬编码标签训练的模型将以高置信度学习不正确的特征。因此,我们需要挑战数据集中的标签,并问自己这样一个问题:真正的标签是否真的是真的。一种方法是通过将损失目标值从 1 降低到 0.9 来给标签分配概率。这种方法被称为标签平滑,并在论文中详细讨论了标签平滑何时有帮助?。
标签平滑防止网络变得过于自信,并使其不太可能过度拟合。此外,它通过引入平滑参数 α 来统一正确和错误类别之间的差异。结果,每个不正确的类标签将与正确的类标签等距,这使得更容易分离语义相似的类。我们将在损失函数中实现标签平滑,并使用它来训练我们的模型。
在 fastai 中,多标签分类问题的损失函数的默认选择是一个 BCEWithLogitsLossFlat ,它是一个带有 Logits 的二元交叉熵。*
*实际上是一个 sigmoid 函数,它是 logit 函数的逆函数,将交叉熵的任意实值映射回[0,1]范围,这是我们通常希望在 0 和 1 之间编码的目标。
我们将把 BCEWithLogitsLossFlat 包装在LabelSmoothingBCEWithLogitsLossFlat类中,并在调用 BCEWithLogitsLossFlat 之前应用标签平滑:
让我们比较一下训练网络时使用和不使用标签平滑的损失值。
无标签平滑:
我们可以看到损失值非常小。由于有 0|1 个目标,该模型对其分配的标签过于自信。
现在让我们用平滑标签来训练网络:
当我们通过平滑标签来训练模型时,我们观察到较高的损失值,这起初可能是违反直觉的。原因是标签平滑降低了目标的确定性。在这种情况下,更高的损失实际上是一个理想的结果。
我们还可以观察到,仅在一个时期的训练之后,该模型就具有 98%的准确性。这是我们之前在选择评估指标时讨论的问题。
模特培训
我们继续使用标签平滑来训练模型。现在,我们将检查我们的数据加载器中的数据,并逐渐增加每个小批量的学习率,以观察损失值如何随着学习率的变化而变化。我们的目标是找到最有效的学习速率,使网络更快地收敛。
三个时期后的损失曲线
由于我们的模型已经训练了三个时期,网络的权重不再是随机的,我们没有观察到任何急剧下降的损失。为了进一步的训练,我们将采用一系列权重,从下降点到损失再次开始增长的点,并检查我们是否可以改进模型。
在这个解决方案中,我使用预先训练好的 ResNet34 模型。从预训练模型转移的层已经擅长识别基本视觉概念,并且不需要太多训练。然而,负责识别特定于我们问题的复杂形状的深层仍然会受益于更高的学习率。因此,我们需要对第一层使用较小的学习速率,对最后一层使用较大的学习速率,以允许它们更快地进行微调。
正如我们所看到的,我们的网络在学习方面取得了进步。然而,很难判断我们是需要继续训练还是停下来不要过度适应模型。绘制培训和验证损失图可以帮助我们评估是否需要继续。
培训与验证损失
该图显示验证损失不再改善那么多。如果我们继续训练,我们将增加训练和验证损失之间的差距,这将意味着我们过度拟合我们的模型。所以,我们最好现在就停止训练,进入下一步,测评。
模型评估
如果我们绘制验证 FBeta 分数,我们可以看到它是如何随着每个时期而改进的。
每个时期的验证 FBeta
对训练数据集的评估
首先,我们将查看训练数据集上的预测,以估计我们的模型是否学习了合理的模式。
对训练数据的评估
第一行包含原始标签,第二行包含预测。我们的预测与标签并不完全匹配,但预测的标签是有意义的。总的来说,我们的模型抓住了主要概念。
对测试数据集的评估
现在让我们加载测试数据,并检查模型在其上的表现。
测试批次
[0.22433756291866302,0.44253026656767896,0.9837779402732849]
第一个值是 loss,第二个是非常接近验证值的 FBeta,最后一个是 fastai 库中的 Accuracy Multi。
测试数据的评估
当查看测试数据上的属性预测时,我会说预测的标签实际上比原始标签更好。
对用户指定数据集的评估
最后,我们将检查模型如何处理我的图像。我用智能手机相机拍了 98 张自己衣服的照片。让我们加载图像,并检查模型是否可以正确地对它们进行分类。
用户数据评估
分配的标签看起来非常好。尽管训练数据有噪声,但模型能够学习时尚概念,并对看不见的用户图像进行很好的概括。值得一提的是,我没有标记自己的数据集,因为这将是一项重大的工作。因此,我将使用训练好的模型来获取我自己的数据的标签。
摘要
在本文中,我演示了如何使用 Fastai 库和 DeepFashion 数据集训练一个多标签属性识别模型。
毫无疑问,通过提高训练标签的质量,可以显著提高模型的性能。然而,该模型已经能够提供足够的结果,即使在噪音数据。正如我们所看到的,有一些方法允许在持续改进训练数据质量的同时提供操作就绪的模型。这是一项重要的学习,因为许多组织由于数据质量而推迟了深度学习产品的开发。
感谢您阅读这篇文章!如有任何问题,请在下方留言或通过 LinkedIn 联系我。
如果你喜欢这篇文章,这里有一些你可能喜欢的其他文章:
如何使用带 spaCy 3 的 BERT 变换器训练联合实体和关系提取分类器
如何使用 Transformer 和 spaCy3 训练关系提取分类器的分步指南
简介
NLP 技术最有用的应用之一是从非结构化文本中提取信息,如合同、财务文档、医疗记录等。—使自动数据查询能够获得新的见解。传统上,命名实体识别被广泛用于识别文本中的实体,并存储数据以进行高级查询和过滤。然而,如果我们想从语义上理解非结构化的文本,仅仅 NER 是不够的,因为我们不知道实体是如何相互关联的。执行联合 NER 和关系提取将通过知识图开辟一种全新的信息检索方式,在知识图中,您可以浏览不同的节点以发现隐藏的关系。因此,联合执行这些任务将是有益的。
在我的上一篇文章中,我们使用 spaCy3 对 NER 的 BERT 模型进行了微调,现在我们将使用 spaCy 的新 Thinc 库向管道添加关系提取。我们按照 spaCy 的文档中概述的步骤训练关系提取模型。我们将比较使用 transformers 和 tok2vec 算法的关系分类器的性能。最后,我们将在网上找到的职位描述上测试该模型。
关系分类:
在其核心,关系提取模型是一个分类器,它为给定的实体对 {e1,e2} 预测关系 r 。在变压器的情况下,该分类器被添加到输出隐藏状态的顶部。有关关系提取的更多信息,请阅读这篇出色的文章概述了关系分类的微调变压器模型的理论。
我们要微调的预训练模型是 roberta-base 模型,但是您可以使用 huggingface 库中可用的任何预训练模型,只需在配置文件中输入名称即可(见下文)。
在本教程中,我们将提取两个实体{经验,技能}之间的关系作为中的经验,以及{文凭,文凭 _ 专业}之间的关系作为中的学位。目标是提取特定技能和与所需文凭相关的文凭专业所需的经验年限。当然,您可以为自己的用例训练自己的关系分类器,例如在健康记录中查找症状的原因/结果,或者在财务文档中查找公司收购。可能性是无限的…
在本教程中,我们将只涵盖实体关系提取部分。对于使用 spaCy 3 微调伯特 NER,请参考我的以前的文章。
数据标注:
正如在我的上一篇文章中,我们使用 UBIAI 文本注释工具来执行联合实体和关系注释,因为它的通用接口允许我们在实体和关系注释之间轻松切换(见下文):
UBIAI 的联合实体和关系注释接口
对于本教程,我只注释了大约 100 个包含实体和关系的文档。对于生产,我们当然需要更多的带注释的数据。
数据准备:
在训练模型之前,我们需要将带注释的数据转换成二进制空间文件。我们首先将 UBIAI 生成的注释拆分到 training/dev/test 中,并分别保存它们。我们修改 spaCy 的教程 repo 中提供的[代码](https://github.com/explosion/projects/blob/v3/tutorials/rel_component/scripts/parse_data.py),为我们自己的注释创建二进制文件(转换代码)。
我们对训练、开发和测试数据集重复这一步骤,以生成三个二进制空间文件(github 中提供的文件)。
关系抽取模型训练:
为了训练,我们将从我们的黄金语料库中提供实体,并在这些实体上训练分类器。
- 打开一个新的 Google Colab 项目,确保在笔记本设置中选择 GPU 作为硬件加速器。确保通过运行以下命令启用 GPU:!英伟达-smi
- 每晚安装空间:
!pip install -U spacy-nightly --pre
- 安装轮包并克隆空间的关系提取报告:
!pip install -U pip setuptools wheel
!python -m spacy project clone tutorials/rel_component
- 安装变压器管道和空间变压器库:
!python -m spacy download en_core_web_trf
!pip install -U spacy transformers
- 将目录更改为 rel_component 文件夹:cd rel_component
- 在 rel_component 中创建一个名为“data”的文件夹,并将培训、开发和测试二进制文件上传到其中:
培训文件夹
- 打开 project.yml 文件并更新培训、开发和测试路径:
train_file: "data/relations_training.spacy"dev_file: "data/relations_dev.spacy"test_file: "data/relations_test.spacy"
- 您可以通过转到 configs/rel_trf.cfg 并输入模型的名称来更改预训练的转换器模型(例如,如果您想要使用不同的语言):
[components.transformer.model]@architectures = "spacy-transformers.TransformerModel.v1"name = "roberta-base" # Transformer model from huggingfacetokenizer_config = {"use_fast": true}
- 在开始训练之前,我们将把 configs/rel_trf.cfg 中的 max_length 从默认的 100 令牌减少到 20,以提高模型的效率。max_length 对应于两个实体之间的最大距离,超过该距离的实体将不被考虑进行关系分类。结果,来自同一文档的两个实体将被分类,只要它们在彼此的最大距离(在标记数量上)内。
[components.relation_extractor.model.create_instance_tensor.get_instances]@misc = "rel_instance_generator.v1"max_length = 20
- 我们最终准备好训练和评估关系提取模型;只需运行以下命令:
!spacy project run train_gpu # command to train train transformers
!spacy project run evaluate # command to evaluate on test dataset
您应该开始看到 P、R 和 F 分数开始更新:
模型培训正在进行中
模型完成训练后,对测试数据集的评估将立即开始,并显示预测与黄金标签。该模型将与我们模型的分数一起保存在名为“training”的文件夹中。
要训练非变压器模型 tok2vec,请改为运行以下命令:
!spacy project run train_cpu # command to train train tok2vec
!spacy project run evaluate
我们可以比较这两种模型的性能:
# Transformer model
"performance":{"rel_micro_p":0.8476190476,"rel_micro_r":0.9468085106,"rel_micro_f":0.8944723618,}
# Tok2vec model
"performance":{"rel_micro_p":0.8604651163,"rel_micro_r":0.7872340426,"rel_micro_f":0.8222222222,}
基于 transformer 的模型的精度和召回分数明显优于 tok2vec,并证明了 transformer 在处理少量注释数据时的有效性。
联合实体和关系提取管道:
假设我们已经在我的上一篇文章中训练了一个 transformer NER 模型,我们将从网上找到的工作描述中提取实体(这不是训练的一部分,也不是开发集的一部分),并将它们提供给关系提取模型以对关系进行分类。
- 安装空间变压器和变压器管道
- 加载 NER 模型并提取实体:
import spacynlp = spacy.load("NER Model Repo/model-best")Text=['''2+ years of non-internship professional software development experience
Programming experience with at least one modern language such as Java, C++, or C# including object-oriented design.1+ years of experience contributing to the architecture and design (architecture, design patterns, reliability and scaling) of new and current systems.Bachelor / MS Degree in Computer Science. Preferably a PhD in data science.8+ years of professional experience in software development. 2+ years of experience in project management.Experience in mentoring junior software engineers to improve their skills, and make them more effective, product software engineers.Experience in data structures, algorithm design, complexity analysis, object-oriented design.3+ years experience in at least one modern programming language such as Java, Scala, Python, C++, C#Experience in professional software engineering practices & best practices for the full software development life cycle, including coding standards, code reviews, source control management, build processes, testing, and operationsExperience in communicating with users, other technical teams, and management to collect requirements, describe software product features, and technical designs.Experience with building complex software systems that have been successfully delivered to customersProven ability to take a project from scoping requirements through actual launch of the project, with experience in the subsequent operation of the system in production''']for doc in nlp.pipe(text, disable=["tagger"]): print(f"spans: {[(e.start, e.text, e.label_) for e in doc.ents]}")
- 我们打印提取的实体:
spans: [(0, '2+ years', 'EXPERIENCE'), (7, 'professional software development', 'SKILLS'), (12, 'Programming', 'SKILLS'), (22, 'Java', 'SKILLS'), (24, 'C++', 'SKILLS'), (27, 'C#', 'SKILLS'), (30, 'object-oriented design', 'SKILLS'), (36, '1+ years', 'EXPERIENCE'), (41, 'contributing to the', 'SKILLS'), (46, 'design', 'SKILLS'), (48, 'architecture', 'SKILLS'), (50, 'design patterns', 'SKILLS'), (55, 'scaling', 'SKILLS'), (60, 'current systems', 'SKILLS'), (64, 'Bachelor', 'DIPLOMA'), (68, 'Computer Science', 'DIPLOMA_MAJOR'), (75, '8+ years', 'EXPERIENCE'), (82, 'software development', 'SKILLS'), (88, 'mentoring junior software engineers', 'SKILLS'), (103, 'product software engineers', 'SKILLS'), (110, 'data structures', 'SKILLS'), (113, 'algorithm design', 'SKILLS'), (116, 'complexity analysis', 'SKILLS'), (119, 'object-oriented design', 'SKILLS'), (135, 'Java', 'SKILLS'), (137, 'Scala', 'SKILLS'), (139, 'Python', 'SKILLS'), (141, 'C++', 'SKILLS'), (143, 'C#', 'SKILLS'), (148, 'professional software engineering', 'SKILLS'), (151, 'practices', 'SKILLS'), (153, 'best practices', 'SKILLS'), (158, 'software development', 'SKILLS'), (164, 'coding', 'SKILLS'), (167, 'code reviews', 'SKILLS'), (170, 'source control management', 'SKILLS'), (174, 'build processes', 'SKILLS'), (177, 'testing', 'SKILLS'), (180, 'operations', 'SKILLS'), (184, 'communicating', 'SKILLS'), (193, 'management', 'SKILLS'), (199, 'software product', 'SKILLS'), (204, 'technical designs', 'SKILLS'), (210, 'building complex software systems', 'SKILLS'), (229, 'scoping requirements', 'SKILLS')]
我们已经成功地从文本中提取了所有的技能、工作年限、文凭和文凭专业!接下来,我们加载关系提取模型,并对实体之间的关系进行分类。
注意:确保将 rel_pipe 和 rel_model 从脚本文件夹复制到主文件夹中:
脚本文件夹
import randomimport typerfrom pathlib import Pathimport spacyfrom spacy.tokens import DocBin, Docfrom spacy.training.example import Examplefrom rel_pipe import make_relation_extractor, score_relationsfrom rel_model import create_relation_model, create_classification_layer, create_instances, create_tensors# We load the relation extraction (REL) modelnlp2 = spacy.load("training/model-best")# We take the entities generated from the NER pipeline and input them to the REL pipelinefor name, proc in nlp2.pipeline:
doc = proc(doc)# Here, we split the paragraph into sentences and apply the relation extraction for each pair of entities found in each sentence.for value, rel_dict in doc._.rel.items():
for sent in doc.sents:
for e in sent.ents:
for b in sent.ents:
if e.start == value[0] and b.start == value[1]:
if rel_dict['EXPERIENCE_IN'] >=0.9 :
print(f" entities: {e.text, b.text} --> predicted relation: {rel_dict}")
在这里,我们显示了所有具有关系 Experience_in 且置信度得分高于 90%的实体:
"entities":("2+ years", "professional software development"") --> **predicted relation**":
{"DEGREE_IN":1.2778723e-07,"EXPERIENCE_IN":0.9694631}"entities":"(""1+ years", "contributing to the"") -->
**predicted relation**":
{"DEGREE_IN":1.4581254e-07,"EXPERIENCE_IN":0.9205434}"entities":"(""1+ years","design"") -->
**predicted relation**":
{"DEGREE_IN":1.8895419e-07,"EXPERIENCE_IN":0.94121873}"entities":"(""1+ years","architecture"") -->
**predicted relation**":
{"DEGREE_IN":1.9635708e-07,"EXPERIENCE_IN":0.9399484}"entities":"(""1+ years","design patterns"") -->
**predicted relation**":
{"DEGREE_IN":1.9823732e-07,"EXPERIENCE_IN":0.9423302}"entities":"(""1+ years", "scaling"") -->
**predicted relation**":
{"DEGREE_IN":1.892173e-07,"EXPERIENCE_IN":0.96628445}entities: ('2+ years', 'project management') -->
**predicted relation**:
{'DEGREE_IN': 5.175297e-07, 'EXPERIENCE_IN': 0.9911635}"entities":"(""8+ years","software development"") -->
**predicted relation**":
{"DEGREE_IN":4.914319e-08,"EXPERIENCE_IN":0.994812}"entities":"(""3+ years","Java"") -->
**predicted relation**":
{"DEGREE_IN":9.288566e-08,"EXPERIENCE_IN":0.99975795}"entities":"(""3+ years","Scala"") -->
**predicted relation**":
{"DEGREE_IN":2.8477e-07,"EXPERIENCE_IN":0.99982494}"entities":"(""3+ years","Python"") -->
**predicted relation**":
{"DEGREE_IN":3.3149718e-07,"EXPERIENCE_IN":0.9998517}"entities":"(""3+ years","C++"") -->
**predicted relation**":
{"DEGREE_IN":2.2569053e-07,"EXPERIENCE_IN":0.99986637}
值得注意的是,我们能够正确地提取几乎所有年的经验以及他们各自的技能,而没有假阳性或假阴性!
让我们看看具有关系 Degree_in: 的实体
entities: ('Bachelor / MS', 'Computer Science') -->
predicted relation:
{'DEGREE_IN': 0.9943974, 'EXPERIENCE_IN':1.8361954e-09} entities: ('PhD', 'data science') --> predicted relation: {'DEGREE_IN': 0.98883855, 'EXPERIENCE_IN': 5.2092592e-09}
再次,我们成功提取了文凭和文凭专业的所有关系!
这再次证明了,无论是对于 NER 还是关系提取,使用少量的注释数据就可以很容易地将 transformer 模型调整到您自己的领域特定情况。
只有一百个带注释的文档,我们能够训练一个性能良好的关系分类器。此外,我们可以使用这个初始模型来自动注释数百个未标记的数据,并进行最小的校正。这可以显著加快标注过程并提高模型性能。
结论:
变形金刚真正改变了自然语言处理的领域,我对它们在信息提取方面的应用感到特别兴奋。我想对 explosion AI(spaCy developers)和 huggingface 提供开源解决方案以促进变压器的采用表示感谢。
如果您的项目需要数据注释,请不要犹豫尝试 UBIAI 注释工具。我们提供了许多可编程的标注解决方案(如 ML 自动标注、正则表达式、字典等),以最大限度地减少手工标注。
最后,查看这篇文章,了解如何利用 NER 和关系提取模型来构建知识图表并提取新的见解。
如果您有任何意见,请在下方留言或发送电子邮件至 admin@ubiai.tools!
在 Twitter 上关注我们
如何使用 Google BigQuery ML 训练一个模型来预测第二天的降雨
用大家都非常熟悉的 SQL 语法预测未来,速度快得惊人,而且简单。
预计会下雨。只有两个简单的词,但有时赌注可能比明天出门前拿一把伞要高得多。雨水可能会破坏野餐计划,也可能会给急于拯救遭受旱灾的农作物的农民带来巨大的快乐。
学习如何预测第二天的降雨是用 Google BigQuery 探索机器学习的一种简单实用的方法。所以,让我们来看看如何让它发生。
在这篇文章中,你会发现:
- 为什么机器学习要用 BigQuery ML?
- 如何将数据集摄取并拆分成训练集和测试集?
- 如何训练和评估一个分类模型?
- 如何预测第二天的降雨?
为什么机器学习要用 BigQuery ML?
想象一下,作为一名生活在 SQL 中的数据分析师,您了解 BigQuery 数据仓库中存储了哪些数据。你可能最近学习了一些关于机器学习的基础知识,但绝不是一个可以闭着眼睛写 Python 代码的狂热的 ML 专家。
然而,你想要用 BigQuery 数据集建立一个机器学习模型,来预测某些可能对你的业务有价值的行为。你需要尽快得到它,因为在商界,时间不等人。
如果你不太了解复杂的 ML 框架,比如 TensorFlow 或 Keras,你应该如何在短时间内实现它呢?在不疯狂使用 Python 或 Java 编程的情况下,如何编写完整的 ML 解决方案呢?
这也是 BigQuery ML 真正能够大放异彩的地方。简而言之,它允许你使用标准的 SQL 查询在 BigQuery 中创建和训练机器学习模型。这种方法有 3 个主要好处。
- 你不需要花费太多的时间去尝试用各种各样的新工具从你的 BigQuery 数据仓库中导出数据
- 如果有法律限制或合规性要求严格控制如何导出数据并将其转移到进行分析和机器学习,您就不必寻求大量批准(当然,遵守访问控制政策和常识在这里仍然适用。)
- 你不需要把自己逼疯去用 Python/ Java 创建一个 ML 解决方案,也不需要加入长长的等待名单去麻烦别人(甚至整个团队)来帮你。
那么,这对企业到底意味着什么呢?这意味着一种更简单、更快速的方式来利用机器学习来预测未来可能发生的事情,以及企业应该如何应对,以有效抓住机遇或减轻风险。
提醒一句,目前 BigQuery ML on 支持一系列特定的模型。它们包括线性回归、逻辑回归、K 均值聚类、张量流、矩阵分解、XGBoost、深度神经网络和基于 ARIMA 的时间序列模型。
我个人认为可用的选项足以为常见的 ML 用例快速构建基线模型。但是请务必查看 BigQuery 文档以获得最新的产品列表。
开始之前
我们的目标和游戏计划
在进入 BigQuery 之前,这里是我们想要实现的:给定今天关于风向、降雨量、最低温度、最高温度、云量等等的观测数据,我们能预测明天是否会下雨吗?
我们拥有的是一个 Kaggle 数据集,包含了 2007 年至 2017 年间澳大利亚选定地点的 10 年天气观测数据。下面是我们关于如何将数据集转化为分类模型的游戏计划,我们可以用它来预测 2021 年的第二天降雨。
作者图片
诚然,我们正试图创建一个非常简化的降雨预报,它无处不在,以至于人们很容易忽视它的巨大价值:从大量数据中预测未来的能力。但是,不要被它的简单性所迷惑,因为预测能力是唯一重要的东西,而不是花哨的 ML 算法或脑筋急转弯技术!
初始设置
在我们为那些令人兴奋的机器学习的东西疯狂之前,这里有 3 件你需要设置的事情。
- weatherAUS.csv:可以从 Kaggle 下载。我们用它来训练和测试 ML 模型
- apr10_predict.csv:可从我的 GitHub &下载,包含 2021 年 4 月 10 日的天气观测。我们用它来预测 4 月 11 日是否会下雨
新项目“澳大利亚铁路”&数据集“降雨预测”已创建(图片由作者提供)
上传到云存储的 2 个 CSV 文件(图片由作者提供)
摄取和分割数据集
摄取数据集
第一步。将云存储中的 2 个 CSV 文件加载到 BigQuery
由于 weatherAUS 数据集将空值写成“NA”字符串,所以我将使用 bq load 命令通过 null_marker 选项直接将“NA”字符串作为空值导入。下面是怎么做的。
在 GCP 控制台上,单击页面顶部的激活云外壳按钮来配置您的 Google 云外壳计算机。给它一分钟左右的加载时间。
作者图片
要加载 weatherAUS 数据集,请输入下面的命令 ,但要记住更新云存储 URI(以 gs://开头,以。因为您的 csv 文件将位于不同的位置 。
*bq load --autodetect --null_marker="NA" --source_format=CSV rainprediction.weatherAUS gs://australia_rain_input/weatherAUS.csv*
作者图片
当看到要求授权云壳的弹出窗口时,点击授权。
作者图片
等到看到当前状态:DONE,然后输入下一个命令来加载 apr10_predict 数据集。 但是也别忘了更新云存储 URI。
*bq load --autodetect --source_format=CSV rainprediction.apr10_predict gs://australia_rain_input/apr10_predict.csv*
看到当前状态:完成此加载作业后,刷新您的浏览器。
作者图片
仔细检查是否可以在 BigQuery 中新创建的数据集下看到两个名为 weatherAUS 和 apr10_predict 的新表。
作者图片
步骤二。查看加载到 BigQuery 中的数据
weatherAUS 表的快速预览显示有 145,460 行数据。标签(即我们试图预测的)是 RainTomorrow,所有其他列是预测器(即我们可能用来预测第二天是否下雨的变量)。
作者图片
不要忘记查看模式来验证数据类型。在 BigQuery ML 中,确保每一列的数据类型正确总是有好处的。
为什么这样因为不同的数据类型会导致非常不同的数据预处理处理,这些处理是由 BigQuery ML 自动执行的。简而言之,混合数据类型可能导致次优的数据处理,最终阻碍 ML 模型的准确性。
作者图片
在我们的例子中,MinTemp、MaxTemp、rainbow、Sunshine 等数值使用字符串类型似乎有点奇怪。请记住这一点,因为我们将在下一节中解决它。
将数据集分成训练集和测试集
接下来,我们将把数据集分成训练集和测试集。
- 训练集:训练一个分类模型,把给定的天气观测映射到规定的标签(即 RainTomorrow)。
- 测试集:严格用于评估最大似然模型是否可以用该模型以前没有见过的新数据进行预测
第一步。创建一个假的唯一 ID 列,并更正数值的数据类型
作者图片
第二步。基于伪唯一 ID 列(UUID)分割数据集
为了获得我的训练和测试集的可重复数据采样,我使用了下面由 Christy Bergman 编写的 BigQuery 代码。
训练集(图片由作者提供)
测试集(图片由作者提供)
训练和评估分类模型
使用创建模型训练 ML 模型
训练一个简单的逻辑回归模型
现在最激动人心的部分来了!让我们创建一个逻辑回归模型,这是分类问题的最简单的模型类型。大伙儿,击鼓吧!
作者图片
在 BigQuery 中运行上述语句后,我们将获得第一个分类模型,该模型将天气观测结果映射为明天下雨是或否。哒哒!
作者图片
定制逻辑回归模型
众所周知,许多精明的 ML 工程师在优化他们的模型时喜欢自由选择。但另一方面,如果你是一名 ML 新手或在关键时刻,必须做出如此多的选择(例如,学习速度,如何以及以什么比例将输入数据分成训练集和评估集等等)可能会令人生畏。
幸运的是, BigQuery ML 已经将某些设置作为默认设置。从上面的例子可以看出,不需要处理无数的选择,只需要几行代码就可以相对容易地建立并运行一个像样的 ML 模型。
但是如果我们想要定制某些设置来满足我们的需求呢?让我们尝试定制我们的逻辑回归模型来反映 3 个变化。
- 随机抽取 20%的训练数据用于评估(目前 BigQuery ML 仅使用 10,000 行进行评估,因为我们的数据集超过 50,000 行。)
- 纠正不平衡的训练数据集(默认选项是不平衡权重。)
- 应用 L2 正则化来惩罚复杂性,从而控制过度拟合(默认选项是没有 L2 正则化。)
作者图片
哦,等等!是不是意味着我们可以定制任何东西,任何东西?没有。至少现在不会,但是可以随时查看 BigQuery 文档来了解您不能定制的内容,并尝试您可以定制的内容。
用 ML 评估分类模型。评价
是时候使用测试数据集来验证我们第一个简单的逻辑回归模型的性能了。让我们看看这样一个简单的模型(没有任何花哨的微调)能够用前所未见的新数据预测明天的降雨。
这个查询再简单不过了。
作者图片
由于测试集的 ROC _ AUC(0.878)与训练集的 ROC _ AUC(0.880)几乎相同,因此我们的模型没有过拟合。0.85 的精度看起来也还过得去。当然,可以用转换条款做更多的功能工程来提高 F1_score,但是现在,让我们坚持现有的。
用分类模型预测第二天的降雨
在 apr10_predict 表中,我收集了 2021 年 4 月 10 日澳大利亚几个地方的每日天气观测。接下来,我们将使用逻辑回归模型来预测 2021 年 4 月 11 日是否会下雨。
作者图片
让我解释一下上面的截图是怎么回事。
- predicted _ rain tomorrow根据我选择的 0.6 阈值显示 4 月 11 日是否可能下雨。只要“下雨”的概率超过 0.6,模型就会预测第二天“明天下雨”。
- 接下来的两列predicted _ rain tomorrow _ probs . label和predicted _ rain tomorrow _ probs . prob显示了 BigQuery 如何得出预测。在第一行中,模型计算出悉尼“不下雨”的概率是 0.75,而“下雨”的概率只有 0.25。因为“下雨”的概率低于阈值 0.6,所以对第二天下雨的预测是“不”。
- 红色的最后一栏是我添加到截图中的,这样你可以很容易地比较预测和 4 月 11 日的实际降雨。这不是模型在执行查询时会给你的。
如果你仍然想知道阈值在分类模型中意味着什么,看看这个简单的解释。但是就预测而言,我们只对达尔文的预测是错误的。我会说这并不坏,因为我们只花很少的时间在 Google BigQuery 中编写简单的查询。
当时间是最重要的,并且您的数据在 BigQuery 中随时可用时,就是这样了——big query ML 是您的最佳选择!
包扎
现在,您可以快速浏览如何训练和评估分类模型,以及如何使用 BigQuery ML 预测第二天的降雨量!BigQuery ML 能否取代其他类似 Tensorflow、Keras 等行业标准的机器学习框架?可能不是因为让 ML 在现实生活中发挥作用是一系列相互竞争的目标之间的权衡,如速度、简单性和准确性。
如果你是一个精明的 ML 工程师,更喜欢大量的自由来试验各种 ML 模型和不同的数据预处理技术,以实现极其精确的预测,你可能最好去别处看看。这是因为 BigQuery ML 目前提供的选择有限,因为它保留了它的简单性和速度。
然而,作为一个欣赏快速测试一个新想法或者创建一个足够好的 ML 基线模型的人,我确实看到了 BigQuery ML 的价值。我不必为尝试从数据仓库导出数据而烦恼,因为我正在 BigQuery(数据仓库本身)中训练和部署 ML 模型。此外,没有必要使用 Python 或 Java 构建一个完整的 ML 管道,因为我可以使用 SQL 用几行代码创建一个模型。有什么不喜欢的?
感谢您的阅读。对我如何能做得更好有反馈,或者只是想聊天?在评论里告诉我或者在 LinkedIn 上找到我。祝大家这周过得愉快!
原载于 2021 年 4 月 12 日 http://thedigitalskye.comhttp://thedigitalskye.com/2021/04/13/how-to-train-a-classification-model-to-predict-next-day-rain-with-google-bigquery-ml/。**
如何从零开始训练一个神经网络
寻找神经网络权重背后的直觉,并附有示例。
在这篇文章中,我将继续我们关于人工神经网络的讨论,并给出一个用 python 编写的非常简单的神经网络的例子。我正在写的这一系列文章的目的是从头开始对 ANN 的作一个完整的解释,而不是躲在特殊的库后面。Tensorflow 非常适合原型制作和生产,但对于教育来说,唯一的学习方法是拿起铅笔和纸,开始学习数学。
在上一篇文章中,我回顾了人工神经网络背后的灵感以及如何对人脑进行数学建模(你可以在这里找到这篇文章)。我们的下一步是为我们的模型找到正确的权重。
将输入映射到输出
假设我们有一个输入和输出的列表。这些可以是任何东西,股票特征和价格,财产特征和销售价格,学生行为和平均考试分数,它们可以是任何东西;但是让我们保持一般的东西,说我们有一些输入和输出。
图片作者
我们的目标是预测最后一次输入的输出。为此,我们必须找到将这些输入映射到其输出的关系(函数)。现在为了学习,我把这个关系做成了线性。这告诉我们可以用身份激活函数代替 sigmoid,而且只有 1 层(输出层)。
如果你看不出为什么一个线性关系只给我们一层具有身份激活功能;请容忍我。在我解释之前,让我给你看一下我们的神经网络,并向你展示它所代表的功能。
*注意我们如何通过应用点积来清理事物。它的代码效率也更高。请记住,这个神经网络也会有偏差,我们可以通过手动输入 1 作为输入之一,然后对这个 1 应用一个权重来给出偏差。现在,忽略偏见。*图片作者
如果我们有更多的层使用相同的功能,那么我们总是可以简化到上面的方程。自己尝试一下,添加一个中间层,每个中间层有 2 个神经元,每个神经元都有身份激活函数,然后写出对应于网络的方程,并将其简化。
你可以用许多不同的方法很容易地解决这里的重量。毕竟只是一个线性方程组。但是,让我们试着在一般情况下解决这个问题,我们没有线性关系。
用误差函数来衡量我们的错误程度
所以,让我们找点乐子吧。如果不把它看作一个线性方程组,我们怎么能算出重量呢?好吧,你必须从某个地方开始,所以让我们先瞎猜一下它们可能是什么。我猜 W1 = .2,W2 = 1.4,W3 = 30。
现在让我们看看我们的猜测有多好,并在其中一个输入上进行测试(使用上表中的相同数据)。
图片作者
回想一下,根据我们的数据,正确的输出(或预期输出)应该是 13.5;我们太离谱了!让我们用一个误差函数来量化我们到底错了多少。当神经网络输出正确的值时,我们希望误差为 0。先说;误差=正确输出-我们的 ANN 的输出(例如;0 = 13.5–13.5,如果我们的权重正确)。
我们快到了。实际地想,我们的错误是我们想要最小化的。那么,我们当前误差函数的最小值是多少?没有最低!实际上,我们可能会有-∞误差。我们通过对函数求平方来解决这个问题,所以它不能是负的,误差=(正确的输出-我们的人工神经网络的输出)。注意这个误差函数的形状是一个以 0 为中心的抛物线。这是单个输出神经元的误差。如果我们有多个输出,那么我们只取所有输出的平均误差。所以,我们有;
*这被称为均方误差函数。这是有 N 个输出的一般情况。总误差是每个输出的平均误差。在我们的例子中,N = 1。*图片作者
最小化我们的错误(最小化我们的错误)
当你把一个砝码改变很小的量时(∂W),你会得到一个误差和变化(∂E).)因此,如果我们想知道这个权重的变化对误差的影响有多大,我们可以取 2,∂E/∂W.的比值,这可以看作是误差函数对这个权重的偏导数,或者这个权重的微小变化如何改变误差。
这对我们有什么帮助?好吧,如果你擅长微积分,你可能知道这是什么方向。但是,如果你不是,看看下面的图表。
*回想一下,导数只是一个斜率。*图片作者
这只是一个任意的函数。取这条线上的任何一点,把你的手指放在上面。现在,大声说出这一点的斜率是正还是负。如果斜率为正,沿+X 方向移动手指一点点。如果是负数,在-X 方向移动它。如果斜率为 0…选择另一个点。为了几个点这样做。注意到什么了吗?
不管怎样,你总是朝着线上更高的值移动手指。
导数的方向总是上升的方向。
如果导数为负,X 的减少将增加 y。如果导数为正,X 的增加将增加 y。
回到我们和∂E/∂W.的神经网络,我们想减少误差,所以我们想把重心移向下降的方向。我们可以通过计算这个导数,然后求负来做到这一点。现在,我们将重心向这个负导数的方向移动一小步。或者;
α确保我们朝着这个方向迈出很小的一步。它被称为学习率,是另一个超参数。图片作者
这只是我们网络中的一个重量。我们可以把这个表达式写成矩阵形式,记住梯度只是一个包含所有∂E/∂Wi.导数的向量矩阵形式如下所示;
记住坡度就是最陡的上坡方向。我们想下降,所以我们去负梯度的方向。图片作者
现在,上述等式可以重复应用于权重,直到它们收敛到正确的值。如果我们的网络更大,找到这个梯度有点痛苦,但只要我们使用可微分的激活函数,它总是可能的。这个等式的重复应用被称为梯度下降算法。
快速小结
让我总结一下过去的 2 页。我们有数据和一些输出。我们知道这些数据和输出之间有某种关系。受人脑的启发,我们可以构建一个人工神经网络。我们用随机权重初始化这个神经网络,然后开始在我们拥有的数据和输出上测试它。当我们测试时,我们通过使用误差函数来量化我们的网络有多错误,然后使用梯度下降来最小化这个误差函数。
例子
既然我们对训练神经网络背后的理论有了一个想法,我们需要专注于如何实现这个理论。早些时候,我给出了一些输入和输出的例子,并为找到它们的关系制定了游戏计划。
手动找到正确的权重是困难的,几乎是不可能的(不把它当作一个线性方程组)。但是通过使用梯度下降算法和我们之前推导出的误差函数,我们可以用正确的方法来计算这些权重。
寻找我们函数的梯度
现在,这是我们从输入到错误的神经网络。记住,为了学习,我们忽略了偏见。我们将在下一个项目中添加它。
图片作者
我们希望找到误差相对于每个重量的导数(每个重量的微小变化对误差的影响),然后将该重量向求反导数的方向(或最陡下降的方向)移动一点点。我们可以使用如下所示的链式法则来做到这一点;
*链式法则看起来吓人,其实就是简单的除法。注意,你可以取消∂Z’s,就像处理分数一样(你基本上就是这样)。另一种思考方式是。如果你想知道权重的变化对误差的影响有多大,你首先需要知道它对 Z 的影响有多大,然后你需要知道 Z 对误差的影响有多大。此外,掠夺者 3 点符号意味着“因此”。*图片作者
就这样,我们找到了我们的梯度!现在我们只需要迭代地应用梯度下降更新来使我们的权重收敛。下面是执行此操作的代码:
*坡度下降的提示。*图片作者
更新权重 2000 次通常就足够了,如果你猜测荒谬的权重在数万或更高,可能需要更多。看看你运行这段代码后得到的权重,是不是得到了[.5,. 9,1.2]?这就是我用来生成这些数据的确切重量。
这难道不可思议吗?!通过使用微积分和数据,我们可以近似任何两个相关事物之间的关系!你可能会说,“这是一个简单的例子,我可以在 2 分钟内解决它,把它当作一个线性方程组”。嗯,你完全正确。但是,如果有 5 或 6 层,每层有 1000 多个神经元,每层都使用 sigmoid 激活功能,会怎么样呢?以任何方式手工解决都是不可能的。我们需要的是一个系统的算法,可以找到我们网络中权重的梯度,我在这里过了。
感谢您的阅读!如果这篇文章在某种程度上帮助了你,或者你有什么意见或问题,请在下面留下回复,让我知道!此外,如果你注意到我在某个地方犯了错误,或者我可以解释得更清楚一些,那么如果你能通过回复让我知道,我会很感激。
这是一系列文章的继续,这些文章从头开始对神经网络进行了直观的解释。其他文章请参见下面的链接:
第 2 部分:如何从头开始训练神经网络
如何用简单的变形金刚训练一个 mT5 翻译模型
mT5 模型在一百多种不同的语言上进行了预训练。让我们看看如何利用这一点来训练一种低资源语言——僧伽罗语的双语翻译模型。
亚历山大·巴甫洛夫·波德瓦尼在 Unsplash 拍摄的照片
mT5 是一个多语言 Transformer 模型,在包含来自 101 种不同语言的文本的数据集(mC4)上进行了预训练。mT5 模型的体系结构(基于 T5)被设计成支持任何自然语言处理任务(分类、NER、问题回答等)。)通过将所需的任务重新架构为序列到序列的任务。
换句话说,文本进去,文本出来。例如,在分类任务中,模型的输入可以是要分类的文本序列,模型的输出将是该序列的类标签。对于翻译来说,这就更简单了。进去的文本是一种语言,出来的文本是另一种语言。
考虑到 mT5 的多语言能力和序列到序列格式对语言翻译的适用性,让我们看看如何为机器翻译微调 mT5 模型。在本文中,我们将训练一个翻译模型在僧伽罗语(我的母语!)和英语。为像僧伽罗语这样的低资源语言训练好的翻译模型是相当具有挑战性的,因为资源(训练数据)的低可用性。希望在一个巨大的数据集(包括僧伽罗语,尽管不是很多)上的多语言预训练将有助于 mT5 模型以直接僧伽罗语-英语(反之亦然)序列的形式补偿不充分的训练数据。
我们将使用简单的变形金刚库(基于 Huggingface 变形金刚库构建)来训练 mT5 模型。训练和测试数据将从Tatoeba 翻译挑战赛中获得。图形和图表由权重&偏差生成,这在用于实验跟踪和超参数优化的简单变压器中得到本地支持。
注:在简单变形金刚 Github repo 的 *examples/t5/mt5_translation*
目录( 链接 )中可以找到本文的所有代码。
概述
- 安装简单的变压器
- 下载数据集进行翻译
- 训练模型
- 评估模型—计算 BLEU 分数
- 包裹
设置
您可以在简易变形金刚 文档 中找到最新的安装说明。
1.从这里安装 Anaconda 或 Miniconda 包管理器。
2.创建新的虚拟环境并安装软件包。
conda create -n st python pandas tqdm sacrebleu
conda activate st
3.如果使用 CUDA:
conda install pytorch>=1.6 cudatoolkit=10.2 -c pytorch
否则:
conda install pytorch cpuonly -c pytorch
4.安装简单的变压器。
pip install simpletransformers
数据准备
培训和测试可从 Tatoeba 翻译挑战数据页面获得。你还可以在那里找到大量其他语言的数据集(包括《指环王》中精灵使用的语言辛达林语😀).
如果您想尝试为另一种语言训练翻译模型,可以下载该语言的数据集,而不是僧伽罗语。本文中的所有其他步骤适用于任何语言数据集。
如果你懒得搜索数据集,这里有 直接链接 *。*😜
- 下载(压缩的)翻译数据集并解压缩存档。
- 提取
train.trg.gz
和train.src.gz
档案(是的,训练数据在档案里面的档案里)。 - 检查
data/eng-sin/
目录下是否有train.trg, train.src, test.trg, test.src
文件。(如果没有,将文件移动到这个位置可能是最容易的,因为代码示例将假设这些文件可以在这里找到)
现在,让我们构建tsv
文件,我们将使用它来训练和测试我们的 mT5 模型。
运行上面的代码会将两个文件train.tsv
和eval.tsv
写入data/
目录。
模特培训
一旦我们有了数据文件,我们就可以开始训练模型了。
首先,我们将导入必要的东西并设置日志记录。
接下来,我们设置我们的培训和评估数据。
这里,我们从数据集中删除了prefix
值,因为我们希望模型根据输入来推断所需的任务。如果输入是英语,那么它应该被翻译成僧伽罗语。如果是僧伽罗语,那就要翻译成英语。模型应该不需要一个前缀就能在训练后搞清楚这一点!
您可以使用前缀值来告诉 mT5(或 T5)执行特定的任务。这对于训练一个可以执行多项任务的模型非常有用,如下文所示。
GPU 内存使用侧记
训练转换器模型所需的 GPU 内存量取决于许多不同的因素(最大序列长度、层数、注意力头数、隐藏维度的大小、词汇表的大小等)。).其中,模型的最大序列长度是最重要的。
对于 mT5 模型中使用的自注意机制,存储器需求随着输入序列长度的平方增长( O(n)空间复杂度)。也就是说,当序列长度加倍时,所需的内存增加了四倍。
此外,mT5 的词汇量比 T5 大得多(大约 250,000 个令牌到大约 32,000 个令牌),这导致 mT5 在所需的 GPU 内存方面相当糟糕。
从所有这些中得出的结论是,我们可以输入到模型中的令牌数(最大序列长度)是以巨大的溢价而来的。基于这一点,如果模型不需要前缀,那么在前缀上使用少量的标记也是一种浪费。
现在,让我们回到训练模型!
这里,我们指定我们希望如何设置模型,并根据model_args
初始化预训练的 mT5 模型。
我使用的最大序列长度(max_seq_length
)为 96,训练/评估批量为 20。通常,批量越大意味着 GPU 利用率越高,因此训练时间越短。如前所述,更长的序列需要更多的 GPU 内存,这意味着更小的批量和更长的训练时间。最大序列长度为 96,这使得该模型可以处理相当长的文本(通常是几个句子),同时保持训练时间的实用性。
请注意,您可能需要调整这些值,以便在您自己的 GPU 上训练模型。如果 GPU 内存不足(CUDA 内存错误),请尝试减少批处理大小和/或最大序列长度。
如果你想尝试微调的模型,你可以在 Huggingface 模型 hub 上找到 这里 。
现在,要运行训练,我们只需要调用train_model()
方法。
就这么简单!经过微调的模型将在培训结束时保存到outputs
目录中(有关模型保存的更多信息,请参见文档)。
通过这些设置,该模型在 RTX 3090 (24 GB VRAM)上完成培训需要 10 个多小时。
培训花费的时间—按作者
我可能已经得到了稍微大一点的批量(正如你在下面看到的),但是我不想冒训练崩溃的风险,因为我整夜都在运行它!
培训期间的 GPU 内存使用情况—按作者
安全起见,批处理大小为 20 意味着 GPU 没有得到充分利用,但是,80%左右也不错!
GPU 利用率(核心数)—按作者
可视化培训进度
在model_args
中设置wandb_project
值告诉简单的变压器自动记录重量&偏差的训练进度。你可以在这里找到这个实验的所有记录数据。
培训损失
培训损失图表—按作者
评估损失
评估损失图表—按作者
这里的实际损失值并没有告诉我们太多,但是它们正在减少的事实确实意味着模型正在学习!
事实上,由于评估损失仍在减少,该模型似乎尚未收敛。再训练一两个纪元可能会很好地提高模型的性能,但是,这将需要另外 10 或 20 个小时!
使用终端命令 *simple-viewer*
,在基于网络的图形用户界面(Streamlit app)中尝试微调后的模型。
评估模型
用于评估和比较机器翻译模型的标准度量是 BLEU 分数,特别是机器翻译年会(WMT)使用的 BLEU 方案。SacreBLEU 库可以用来计算这个分数。
关于 BLEU 评分的更多信息,请参考 Matt Post 的这篇 论文 。
由于 Tatoeba Challenge 还提供了基准翻译模型的 BLEU 分数,因此我们可以轻松地将我们的模型与基准模型进行比较。
现在,让我们加载我们的微调模型,看看它是如何叠加的!
我们导入必要的东西(注意sacrebleu
库)并初始化模型,就像我们为训练所做的一样,除了我们从outputs/
加载微调的模型,而不是预训练的模型。
我们还为使用模型生成文本(解码)设置了一些参数。这里,max_length
是模型输出而不是输入的最大长度。
如果您想进一步了解解码过程,请参考本 文章 中的 解码算法 部分以及本优 笔记本 by Huggingface。
接下来,我们将准备用于评估的数据。
在这里,我们加载评估数据,并准备单独的输入和真实翻译列表(从英语到僧伽罗语,反之亦然)。
加载并准备好模型和评估数据后,我们就可以开始翻译了。对于简单的变压器,我们只需用输入数据调用model.predict()
。然后,我们使用sacrebleu
工具来计算 BLEU 分数。
如果您按照安装说明进行操作,那么 *sacrebleu*
库应该安装在您的虚拟环境中。如果没有,可以用 *pip install sacrebleu*
安装。
运行该程序会得到以下分数(四舍五入):
- 英语到僧伽罗语:10.3
- 僧伽罗语对英语:24.4
这两个分数都比翻译模型在 Tatoeba 挑战赛中发布的分数有所提高!
包扎
尽管训练数据有限,但 mT5 模型在僧伽罗语和英语之间的翻译方面做得很好。
mT5 超过了 Tatoeba 挑战赛公布的分数。但是,应该注意的是,除了僧伽罗语和英语之外,挑战赛中的基准模型还针对其他几种语言进行了训练。此外,mT5 模型需要更多的计算资源来培训和使用。另一方面,mT5 模型有潜力通过更多的训练来提高当前的分数。
最后,你可以在 Huggingface 模型中心这里找到微调过的模型。可以直接用简单的变压器使用,如下图。
from simpletransformers.t5 import T5Model model = T5Model("mt5", "thilina/mt5-sinhalese-english")print(model.predict(["Let's translate!"]))
如何在 Google 云平台上用 Vaex 训练和部署一个机器学习模型
股票图片来自pixabay.com
介绍
T 训练机器学习(ML)模型通常是一项相当漫长且计算密集型的任务,尤其是在使用大量数据时。
无论您使用的是您最喜欢的深度学习框架、可信的梯度增强机器还是定制的系综,模型训练阶段都可以轻松消耗您笔记本电脑或本地服务器上的大部分(如果不是全部)可用资源,从而有效地“冻结”您的机器,并阻止您执行其他任务。或者,你手头可能甚至没有足够的资源来训练一个复杂的模型来处理你辛辛苦苦收集的所有数据,或者使用你精心设计的所有功能。
最重要的是,管理 ML 模型是一个持续的过程:人们可能需要相对频繁地重新训练模型,以考虑新数据、概念漂移、领域中的变化,或者只是通过调整输入特征、架构或超参数来改进模型。
因此,如果没有必要,将 ML 模型创建的繁重阶段外包给由公认的云提供商管理的服务会非常方便。使用这种方法,计算资源将不是问题。如今,人们可以“租用”连接了数百个 vCPUs 和数 TB RAM 的计算实例,或者提供定制的集群配置。此外,用户可以提交多个独立运行的培训作业,这些作业不会相互竞争资源。所有这些意味着您可以将更多的时间花在创建最佳模型上,而几乎没有时间管理、维护和配置您的计算资源。值得注意的是,随着时间的推移,这些服务变得越来越便宜,越来越容易获得。
谷歌云平台和 Vaex
通过谷歌云控制台看到的谷歌云 AI 平台(作者截屏)
那么 Vaex 如何适应这一切呢?即使在云环境中,使用 Vaex 作为构建 ML 解决方案的核心技术也有很多好处。首先,您可以将数据托管在谷歌云存储(GCS)或亚马逊网络服务(AWS) S3 存储桶上,并根据“需要”将其缓慢地传输到您的计算实例。这意味着只下载您的模型需要的特定列,而不是全部文件。甚至可以选择只下载一小部分数据,这对测试和持续集成特别有用。
所有 Vaex 转换都是通过完全并行的高效核外算法完成的。这意味着您总是可以充分利用出租的计算实例,而无需任何额外的设置。无内存复制策略使您可以更轻松地选择所需的机器类型,同时在不牺牲性能的情况下最大限度地降低成本。
整篇文章将重点介绍更多的好处。因此,事不宜迟,让我们看看如何使用 Vaex 来构建 ML 解决方案,然后如何使用 GCP 来实现它。
在开始之前
本文假设了 GCP 的一些基本知识,以及如何通过谷歌云控制台、和通过gcloud
命令行工具与它交互。如果你想跟随这个教程,你需要一个经过认证的谷歌账户,一个 GCP 项目,和一个已经设置好的 GCS 桶。如果你不确定如何做,有许多有用的指南。如果有疑问,GCP 官方文档总是一个好的起点。
本文中的所有材料都可以在这里 以及各种其他 Vaex 示例中找到。
使用 Vaex 创建自定义 ML 管道
这个例子使用了公共的异构活动识别(HAR) 数据集。它包含了从一组志愿者身上获取的几组测量数据,这些志愿者正在进行六种活动中的一种:行走、上下楼梯、坐着、站着和骑自行车。测量结果通过流行的智能手机和智能手表设备获取,包括分别从板载加速度计和陀螺仪采样的三轴加速度和角速度。数据集还包含“*创建时间”*和“*到达时间”*列,它们是分别由操作系统和移动应用程序附加到每个测量样本的时间戳。目标是仅使用单个测量样本来检测佩戴者进行的特定活动。活动本身在“ gt ”栏中指定,该栏代表“地面实况”。
以下示例使用通过智能手机设备获得的加速度计数据。它包含了超过 1300 万个样本。为了简洁起见,我们不会对数据进行任何探索性分析,而是直接构建一个生产就绪的解决方案。
让我们从创建一个 Python 脚本开始,该脚本将获取数据,设计相关的特性,并训练和验证一个模型。因为我们使用的是 Vaex,所以获取数据很简单。如果数据是 HDF5 文件格式,并托管在 GCS(或亚马逊的 S3)上,Vaex 将缓慢地传输分析所需的部分。因为我们已经知道需要哪些数据列,所以可以立即预取它们:
下一步是将数据随机分成 3 组:训练、验证和测试。验证集将在训练阶段用作质量控制,而测试集将是已训练模型的最终独立性能指标。
此时,我们可以开始创建一些有用的功能。让我们从做几个坐标变换开始。我们将把三轴加速度测量值从笛卡尔坐标转换到球坐标,以及通过 PCA 变换转换到它们的“自然”坐标系:
即使上面的一些转换并不太复杂,我们仍然可以选择通过 numba 使用即时编译来加速它们。请注意,我们还使用了在 vaex-ml 版本 0.11 中可用的新 API,而不是更传统的 scikit-learn“fit&transform”方法。
为了捕捉数据中的一些非线性,我们可以在 PCA 组件之间创建一些特征交互:
现在,我们将变得更有创造性。首先,让我们计算每个活动类别的每个主成分的平均值和标准偏差。然后,我们将计算每个主成分的值与每个组的平均值之间的差异,并按该组的标准偏差进行缩放:
请注意我们是如何结合使用 Vaex 和 Pandas 来创建这些功能的。虽然不会存储df_summary
数据帧,但它的值会被“记住”,作为在for
循环中定义的表达式的一部分,该循环遵循groupby
聚合。上面的代码块是一个例子,展示了如何快速清晰地创建新特性,而不需要创建定制的Transformer
类。
特征工程的另一个有趣的方法是对已经定义的特征子集应用聚类算法,并将得到的聚类标签用作附加特征。 vaex-ml 包直接实现了KMeans
聚类算法,所以保证了非常快速和内存高效。使用KMeans
算法,我们创建 3 组聚类标签:一组通过对 PCA 组件进行聚类,另一组通过对 PCA 交互组件进行聚类:
在 Vaex 中,任何模型都被视为一个转换器,因此它的输出很容易用作下游计算图中的任何其他功能。
最后,我们还可以利用时间戳特性,计算"*到达时间"*和"*创建时间"*列之间的差异,我们对其应用标准缩放:
定义完所有特性后,为了方便起见,我们可以将它们收集到一个列表中:
数据准备的最后一部分是将目标列" gt" 编码成数字格式。在编码之后,我们还将定义一个逆映射字典,稍后我们将使用它将预测的类翻译成它们真正的标签。
至此,我们终于准备好开始训练模型了。您可能已经注意到,我们没有费心去显式地创建一个管道来允许所有的数据转换被传播到验证和测试集。这是因为 Vaex 数据帧隐式记录了对数据进行的所有转换和修改。过滤器、分类编码、缩放,甚至 ML 模型的输出都被认为是数据转换,并且是数据帧的状态的一部分。因此,为了加快验证设置,以便我们可以在模型训练期间将其用作参考点,我们只需获取df_train
的状态并将其应用于df_val
:
现在我们准备实例化和训练模型,我们已经选择它作为一个 LightGBM 分类器:
当与 Vaex 配合使用时,ML 型号也是变压器。这意味着可以将预测添加到数据帧中,就像应用另一种变换一样。这在构建集成时非常有用,对于执行模型诊断也是如此。在我们的例子中,LightGBM 模型的输出是概率数组。为了使输出对模型的最终用户更有意义,我们将找到最可能的类,并对其应用逆转换,这样我们就可以获得最可能的活动的名称——这是一系列转换中的又一个!
一旦模型被训练,我们可以通过计算验证集和测试集上的两个度量来了解它的性能,到目前为止,后者在这个过程中完全没有使用过。同样,要获得预测,我们需要做的就是从df_train
获得状态,现在包括模型预测,并将其应用于df_val
和df_test
数据帧:
注意上面和前面代码块中log
函数的用法,它是标准 Python 日志记录系统的一个实例。当这段代码在 AI 平台上运行时,日志将被自动捕获,并在 GCP 的集中云日志部分提供。整洁!
我们结束了。最后一步是将最终的状态文件保存在 Google 云存储(GCS)桶中,以便以后部署。Vaex 可以将状态文件直接保存到 GCS 或 S3 桶中;
在 GCP 训练定制的 Vaex 管道
现在,我们的训练脚本已经准备好了,它需要被制作成一个 Python 包,以便可以在 AI 平台上安装和执行。让我们称我们的训练模块为“har_model”。其组成文件应按照以下树形结构组织:
作者制作的训练目录树。
请注意,我们还包含了一个空的“init”。py ”,因此 Python 将“har_model”目录视为一个包。“setup.py”脚本安装软件包以及所需的依赖项:
人工智能平台的好处在于,在向 GCP 提交作业之前,我们可以在本地运行我们的包。这对于调试和测试非常有用。以下 shell 命令将以与云中相同的方式在本地执行培训脚本:
由于当前解决方案中的核心技术是 Vaex,人们可以很容易地限制它使用一小部分数据来使测试运行得更快。一旦我们确定培训模块按预期运行,我们就可以通过以下命令向 GCP 提交培训作业:
给定大量的参数,将上面的命令作为 shell 脚本的一部分来执行会非常方便。这样,它可以被版本控制,或者作为 CI/CD 管道的一部分。
一旦上述命令被执行,人工智能平台培训工作将开始,你可以在 GCP 的日志记录部分监控其进展。使用我们在上面的例子中选择的机器类型( *n1-highcpu-32,*32 vcpu,28GB RAM),整个培训工作需要大约 20 分钟。工作完成后,我们可以检查日志,看看模型在测试集上的表现如何:
GCP 日志查看器的屏幕截图,显示了上面文本中描述的培训作业的日志输出。
就是这样——通过最少的设置,我们成功地在 GCP 训练了一条完全定制的 Vaex 管道!管道本身包含完整的特征工程和数据处理步骤、分类算法和后处理操作,具有 Vaex 状态文件的形式,并保存到指定的 GCS 存储桶中,以备部署。
在 GCP 铺设 Vaex 管道
AI 平台是部署 ML 模型的一种相当方便的方式。它确保了预测服务的高可用性,并使部署和查询多个模型版本变得容易,这对于进行 A/B 测试是很有用的。
部署 Vaex 管道非常简单——预测服务器需要做的就是将传入的批次或数据样本转换为 Vaex 数据帧,并对其应用状态文件。
为了部署定制的 Vaex 管道,我们必须指导 AI 平台如何处理特定于我们问题的请求。我们可以通过编写一个实现预测器接口的小类来做到这一点:
上面的VaexPredictor
类有两个关键方法:from_path
方法只是从 GCS 桶中读取状态文件,而predict
方法将数据转换为 Vaex 数据帧格式,对其应用状态文件,并返回预测。注意,predict
方法可以方便地截取以 Python list
或dict
类型传递的数据。
下一步是将VaexPredictor
类打包成一个.tar.gz
源代码发行版 Python 包。该包需要包括获得预测所需的所有依赖项。创建这样的包需要一个“setup.py”文件:
通过运行以下 shell 命令来创建软件包:
最后,我们需要将预测包移动到 GCS,这样 AI 平台就可以拾取并部署它:
为了方便起见,将上述两个命令捆绑在一个 bash 脚本中可能会很方便,尤其是在创建预测包时需要迭代几次的情况下。
作为参考,该示例项目的部署部分的目录树应该如下所示:
我们现在准备好部署预测包了。首先在 shell 中定义一些环境变量会非常方便:
模型部署是通过以下两个命令完成的。首先,我们需要在 AI 平台上创建一个“模型”资源,如下所示:
然后,我们创建模型的“版本”资源,它指向模型制品,即状态文件,以及预测器类:
执行上述命令可能需要一两分钟的时间。就是这样!我们的 Vaex 模型现在已经部署完毕,可以响应传入的预测请求了!
我们现在准备好查询我们的模型。发送到 AI 平台的批量数据需要采用 JSON 格式。如果输入是列表的形式,文件的每一行都应该是包含单个样本特征的列表。应注意的是,要素的顺序与预测器类的预期一致。这种文件的示例如下所示:
一个列表格式的输入文件的例子,input_list.json,用于查询“har_model”。
然后使用以下命令发送预测请求:
输入数据也可以格式化为 JSON 对象。这里可以更加灵活—一行可以是单个或多个样本:
一个 dict 格式的输入文件的例子,input_dict.json,用于查询“har_model”。
下面是使用上面的文件查询我们的“har_model”的简短屏幕截图:
就这么简单!
ML 模型的寿命不是无限的。当取消部署模型时,需要首先删除版本资源,然后删除模型资源:
最后,值得注意的是,尽管我们在本例中在 GCP 上训练了模型,但这根本不是部署需求。所有需要做的就是让状态文件驻留在一个 GCS 桶中,以便预测模块可以拾取它。人们可以训练模型并在本地创建状态文件,或者使用任何其他可用的服务。
摘要
我希望这篇文章证明了 Vaex 是构建 ML 解决方案的优秀工具。它的表达系统和自动管道对这项任务特别有用,而它高效的核外算法确保了速度并保持低计算成本。
将 Vaex 与 GCP 结合使用会带来可观的价值。Vaex 能够直接从 GCS 传输数据,并且只传输那些对模型绝对必要的部分。在 Google Clouds 的 AI 平台上训练 ML 模型也相当方便,尤其是对于要求更高、运行时间更长的模型。由于 Vaex 模型的整个转换管道都包含在一个单独的状态文件中,用 AI 平台部署它是很简单的。
数据科学快乐!
附录:AI 平台统一
2020 年 11 月中旬,谷歌推出了人工智能平台的下一个迭代,称为人工智能平台统一。顾名思义,这个版本统一了 GCP 提供的所有 ML 相关服务:autoML,随时可以使用的 API 以及培训和部署定制模型的选项都可以在同一个地方找到。
新的人工智能平台带来的一个重大改进是可以选择使用定制的 Docker 容器来训练和部署模型。与“经典”人工智能平台相比,这带来了额外的灵活性,在“经典”人工智能平台中,只有特定的环境可用,安装或修改其内容的选项有限。
让我们看看如何使用统一的 AI 平台来训练和部署我们在本文前面构建的 Vaex 解决方案,现在使用定制的 Docker 容器。
训练模型相当简单:我们需要做的就是创建一个 Docker 映像,当它启动时将执行我们之前准备的训练脚本。我们首先创建一个docker file:
在上面的 Dockerfile、 " env.yml 中,我们需要的所有依赖项都可以通过 conda 、 mamba 或 pip 安装。“setup.py”和“har_model”组成了我们前面定义的模型训练包。然后,我们安装所需的依赖项和模型训练包,最后设置一个 入口点 ,以便在容器运行时开始训练过程。提示:如果你想建造非常小的码头集装箱,看看 Uwe Korn 的指南。
现在,我们可以在本地构建 Docker 映像,并将其推送到 Google 的容器注册表,但是简单地使用云构建并在 GCP 构建映像要方便得多:
然后,我们可以启动容器,并通过执行以下命令开始培训工作:
可以通过云日志记录来监控培训进度,云日志记录可以捕获来自自定义 Docker 容器的任何日志。大约 20 分钟后,作业应该完成,我们可以通过 Google Cloud 控制台对其进行检查:
现在,让我们在统一的 AI 平台上部署我们刚刚使用自定义 Docker 容器训练的模型。启动时,容器应该运行一个 web 应用程序,该应用程序将使用预测来响应请求。web 应用程序应该至少实现两种方法:一种是人工智能平台将用来进行“健康检查”的方法,即确保 web 应用程序按预期运行,另一种方法将接受传入的预测请求并使用答案进行响应。有关容器要求和所有可用定制选项的更多信息,您可以查看官方文档。
我们不会详细讨论如何构建这样一个 web 应用程序,因为 web 上有大量的相关资源。作为参考,您可以在这里看到我们为这个示例准备的 web 应用程序。
在构建和测试 web 应用程序之后,我们需要创建一个 Docker 容器,在启动时运行它。按照与创建模型训练容器相同的步骤,很容易做到这一点。
一旦 Docker 映像在 Container Registry 中可用,我们需要通过下面的gcloud
命令将它变成一个模型资源:
下一步是创建用于访问模型的模型端点:
我们现在可以像这样将模型资源部署到端点:
这一步可能需要几分钟才能完成。请注意,您可以将几个模型资源部署到一个端点,也可以将一个模型部署到多个端点。
模型现在终于部署好了,可以接受请求了。请求应采用 JSON 格式,并具有以下结构:
在这里你可以看到对于这个特殊的例子,这样一个文件看起来会是什么样子。Google Cloud 控制台还会给你一个如何查询模型端点的例子。它看起来会像这样:
就是这样!当您的模型超过其生存期时,不要忘记取消部署它并删除端点和模型资源,以避免不必要的成本。可以这样做:
最后,我们可以将培训和部署视为两个独立的、完全独立的过程。这意味着可以使用“经典的”人工智能平台进行训练,使用统一的人工智能平台部署模型,反之亦然。当然,人们总是可以“在内部”或者使用任何可用的资源来创建模型,并且只使用 GCP 来服务。
如何训练伯特
训练变形金刚的速射指南
像这样的形式需要预先训练——由作者生成图像。
transformer 模型的成功在很大程度上要归功于它能够采用 Google 和 OpenAI 等公司在巨大数据集上预先训练的模型,并将它们应用到我们自己的用例中。
有时,这就是我们所需要的——我们采用模型并按原样滚动。
但在其他时候,我们发现我们确实需要对模型进行微调。我们需要在我们的特定用例上对它进行更多的培训。
每个变压器模型都是不同的,针对不同用例的微调也是不同的——因此我们将重点关注核心 BERT 模型的微调。并附有一些脚注,说明我们如何为几个最常见的应用程序修改它。
你可以在这里观看文章的视频版本:
它是如何工作的
首先,这些是如何工作的?我假设你对变形金刚和伯特有一定的了解,如果你不知道我在说什么——先看看这篇文章。
《变形金刚》的威力源自谷歌和 OpenAI 等大公司将变形金刚模型预先训练到非常高的标准。
现在,当我说按照非常高的标准进行预培训时,OpenAI 的 GPT-3 的估计培训成本在 460 万美元到 1200 万美元之间[1][2]。
我没有多余的 1200 万美元来训练模特,你呢?
通常,原始的预训练模型对于我们的需求来说已经足够了,我们不需要担心进一步的训练。
但有时,我们可能需要——幸运的是,变形金刚在制造时就考虑到了这一点。对于 BERT,我们可以将进一步培训的可能性分为两类。
首先,我们对核心 BERT 模型本身进行了微调。这种方法包括使用 Google 在训练原始模型时使用的相同训练方法——我们稍后将更深入地讨论这一点。
(左)带分类头的 BERT,(右)带问答头的 BERT。
第二,我们可以添加不同的头到我们的模型中,这给了伯特新的能力。这些是我们模型末尾的额外层,为不同的用例修改输出。例如,我们会使用不同的标题来回答问题或进行分类。
在本文中,我们将重点关注对核心 BERT 模型的微调——这允许我们对 BERT 进行微调,以更好地理解我们的用例中特定的语言风格。
微调内核
使用两种方法训练 BERT 的核心,下一句预测(NSP)和屏蔽语言建模(MLM)。
1。 下一个句子预测包括将句子对作为模型的输入,这些对中的一些对将是真的对,其他的将不是。
两个连续的句子形成一个“真对”,其他的都不是真对。
BERTs 在这里的任务是准确地识别哪些对是真正的对,哪些不是。
还记得我说过我们可以用不同的头来训练伯特吗?NSP(和 MLM)也用特殊的头。这里使用的 head 将来自分类器令牌的输出处理到一个密集的 NN 中,输出两个类。
我们的分类头密集层消耗来自分类任务中使用的[CLS]
(分类器)标记位置的输出。
这个[CLS]
令牌的输出是一个 768 维的向量,它被传递给我们具有两个节点的密集 NN 层——我们的IsNextSentence
和NotNextSentence
类。
BERT 中 NSP 任务的高级视图。
这两个输出是我们对 BERT 是否相信句子 B 在句子 a 之后的真/假预测。索引 0 告诉我们 BERT 相信句子 B 在句子 a 之后。
训练后,NSP 头被丢弃——我们保留的只是许多 BERT 层中经过微调的权重。
2。 屏蔽语言建模包括获取一大块文本,屏蔽给定数量的标记,并要求 BERT 预测被屏蔽的单词是什么。
通过屏蔽操作处理原始文本,用**【屏蔽】**标记替换随机标记。
每个序列中有 15%的单词被[MASK]
标记屏蔽。
一个分类头被附加到该模型上,并且每个标记将被馈送到一个前馈神经网络中,随后是一个 softmax 函数。每个令牌的输出维数等于 vocab 的大小。
MLM 进程的高级视图。
这意味着从每个记号位置,我们将得到最高概率记号的输出预测。我们用我们的词汇把它翻译成一个特定的单词。
在训练期间,当计算损失函数时,对未被屏蔽的记号的预测被忽略。
同样,与 NSP 一样,MLM 头部在训练后被丢弃——留给我们优化的模型权重。
用代码
我们知道 NSP 和 MLM 的微调是如何工作的,但是我们如何在代码中应用它呢?
好了,我们可以从导入变形金刚、 PyTorch ,以及我们的训练数据— 冥想(在这里找到训练数据的副本)开始。
现在我们在text
中有一个段落列表——一些,但不是全部,包含多个句子。这是我们在构建 NSP 培训数据时需要的。
为 NSP 做准备
为了准备 NSP 的数据,我们需要创建一个非随机句子(两个句子最初在一起)和随机句子的混合。
为此,我们将创建一个从text
中提取的句子包,然后我们可以在创建随机NotNextSentence
对时从中随机选择一个句子。
我们的包包含与文本相同的数据,但是按句子分割——通过使用句点字符来标识。
在创建我们的bag
之后,我们可以继续创建我们的 50/50 随机/非随机 NSP 训练数据。为此,我们将创建一个句子 As、句子 Bs 以及它们各自的IsNextSentence
或NotNextSentence
标签的列表。
我们可以在控制台输出中看到,标签 1 表示随机句子(NotNextSentence
),标签 0 表示非随机句子(IsNextSentence
)。
标记化
我们现在可以标记我们的数据。如同典型的 BERT 模型一样,我们将序列截断/填充到长度为 512 的记号。
这里有几件事我们应该注意。因为我们标记了两个句子,所以我们的标记器在 token_type_ids 张量中自动将 0 值应用于句子 A,将 1 值应用于句子 B。尾随零与填充标记对齐。
其次,在 input_ids 张量中,记号赋予器自动在这两个句子之间放置一个 SEP 记号(102)——标记两者之间的边界。
伯特在表演 NSP 的时候需要看到这两者。
NSP 标签
我们的 NSP 标签必须放在一个叫做 next_sentence_label 的张量中。我们通过获取我们的label
变量,并将其转换为torch.LongTensor
——也必须使用.T
进行转置,从而轻松地创建它:
MLM 的屏蔽
对于 MLM,我们需要clone
我们当前的 input_ids 张量来创建一个 MLM 标签张量——然后我们移动到屏蔽 input_ids 张量中大约 15%的记号。
现在我们克隆了我们的标签,我们屏蔽了 input_ids 中的令牌。
请注意,我们在这里添加了一些规则,通过在创建mask_arr
时添加额外的逻辑,我们确保不屏蔽任何特殊的令牌,例如 CLS (101)、 SEP (102)和 PAD (0)令牌。
数据加载器
我们所有的输入和标签张量都准备好了——我们现在需要做的就是将它们格式化为 PyTorch dataset 对象,以便可以将它们加载到 PyTorch Dataloader 中——这将在训练期间向我们的模型提供批量数据。
数据加载器期望使用__len__
方法检查数据集中的样本总数,使用__getitem__
方法提取样本。
培训设置
进入训练循环之前的最后一步是准备模型训练设置。
我们首先检查我们是否有可用的 GPU,如果有,我们将模型移到它上面进行训练。然后,我们激活模型中的训练参数,并用加权衰减初始化 Adam 优化器。
培养
最后,我们开始训练我们的模型。我们训练两个纪元,并使用tqdm
为我们的训练循环创建一个进度条。
在循环中,我们:
- 初始化渐变,这样我们就不会从上一步计算的渐变开始。
- 将所有批量张量移动到选中的
device
(GPU 或 CPU)。 - 将一切输入模型,提取损失。
- 使用
loss.backward()
计算每个参数的损失。 - 基于计算的损失更新参数权重。
- 将相关信息打印到进度条(
loop
)。
就这样,我们用 MLM 和 NSP 对我们的模型进行了微调!
关于使用屏蔽语言建模和下一个句子预测微调 BERT 的文章到此结束。我们已经介绍了什么是 MLM 和 NSP,它们是如何工作的,以及我们如何用它们来微调我们的模型。
有很多地方可以对 BERT 进行微调,但是概念和实现并不太复杂——同时功能非常强大。
使用我们在这里学到的知识,我们可以采用 NLP 中最好的模型,并对它们进行微调以适应我们更特定于领域的语言用例——只需要未标记的文本——通常是很容易找到的数据源。
我希望你喜欢这篇文章!如果你有任何问题,请通过 Twitter 或在下面的评论中告诉我。如果你想要更多这样的内容,我也会在 YouTube 上发布。
感谢阅读!
参考
[1] B. Dickson,《GPT 3 号未披露的故事》是 OpenAI 的转变 (2020),TechTalks
[2] K. Wiggers, OpenAI 巨大的 GPT-3 模型令人印象深刻,但大小并不代表一切 (2020),VentureBeat
如果你有兴趣了解更多关于 MLM 和 NSP 背后的逻辑,以及一般的变形金刚,请查看我的 NLP 变形金刚课程:
*所有图片均由作者提供,除非另有说明
如何训练 Bert 进行任何语言的问答
从零开始的多语言问答简单指南
杰里米·贝赞格在 Unsplash 上的照片
问答(Q&A)变压器应用广泛,是现代自然语言处理的非常酷的应用。
乍一看,我们大多数人会认为建造这样的东西是一个非常困难的壮举。幸运的是,我们大多数人都错了。
尽管变形金刚有着令人难以置信的性能,但训练或微调它们来完成特定任务却异常简单。
再加上网上可用的许多大规模语言数据集,我们就拥有了构建一些令人难以置信的工具所需的一切。
我们将解释从头开始训练 Bert transformer 模型所需的关键步骤,以及我们如何针对 Q & A 微调该模型。这里的一个关键因素是数据集,我们也将在整篇文章中概述它。
从零开始问答
第一,变形金刚训练流程是什么样子的?虽然这些模型非常强大,但训练过程却出奇的简单。
我们首先需要明白的是,有一个核心 Bert 模型。这个核心模型充当了中央转换引擎——正是这个引擎构建了对语言的理解。
这个中央模块的输出本身并不是特别有用。它只是一个语言理解引擎,它不能告诉你一个句子是快乐还是悲伤,识别实体,或者回答我们的问题。
对于所有这些任务以及更多的任务,我们需要一个训练有素的变压器头。
变压器的头部连接到核心引擎。核心引擎消耗一些文本,并将其处理成有意义的数字向量。这些向量是我们变压器头的丰富信息输入。
每个 transformer 头的结构都是为特定任务而构建的,许多只不过是由几个前馈神经网络层组成,其组织方式是,一旦训练了头,我们就可以产生有意义的功能。
因此,要从头构建问答 Bert 模型,我们必须:
- 训练一个核心 Bert 引擎。
- 培养一个问答 Bert 问答头。
有时可能没有使用您的语言的现有 Bert 模型。如果是这种情况,您还必须训练一个 Bert 单词片段标记器。我已经在这里写了这个。
为了简洁起见,这里我们将只关注两个模型训练任务。让我们进入核心的 Bert 训练过程。
训练核心
伯特最初是用两个并行的过程训练的。掩蔽语言建模(MLM)和下一句预测(NSP)。两者都需要大量的训练数据,这些数据必须是非结构化文本的形式。
幸运的是,如果说我们在互联网上有很多东西的话,那就是非结构化文本。
MLM 和 NSP 的数据集
对于 MLM 和 NSP,我们只需要一个大型的非结构化文本数据集。在多语言非结构化文本方面,没有比 OSCAR 语料库更好的来源了。
OSCAR 目前涵盖 166 种语言,从英语到纳瓦特尔语。完整列表可在此处找到。
从 OSCAR 下载数据最简单的方法是通过 HuggingFace 的datasets
库。我个人会 100%推荐采取这种方式。我们可以非常快速地下载——例如——奥斯卡的英文子集,如下所示:
还有大量其他可用的数据集,我们可以在 HuggingFace 的数据集查看器上找到。
当下载较小的数据集时,我们可以毫无问题地使用上面的代码——但是较大的数据集(特别是来自 OSCAR 的数据集)可能会非常大——OSCAR 的意大利子集有 69GB 的数据,而英国只有 1.8TB。
在这种情况下,我们可以通过将streaming=True
参数添加到load_datasets
来流式传输数据。
然后,要么用streaming=True
构建完整的输入管道,要么只遍历数据集的较小部分并将它们存储在内存中(通常streaming=True
会导致代码问题,在这种情况下,后一种选择是最好的)。
如果你需要通过 HF 数据集下载数据的细节方面的帮助,我会在这段视频中一一介绍:
一旦数据下载完毕,我们就开始用 MLM ( 和 NSP,如果愿意的话)训练核心模型。
屏蔽语言建模
MLM 包括向伯特提供一个句子,其中几个单词(或记号)已经使用掩码记号隐藏起来。这个掩码标记无非是隐藏原始单词。
**ORIGINAL**: "flying fish flew by the space station"**MASKED**: "*[MASK]* fish flew by the *[MASK]* station"
然后,原始句子作为核心 Bert 模型的目标输出传递。通过这样做,Bert 被迫阅读句子中的其他单词,并试图理解上下文以正确预测被屏蔽的单词。
下一句预测
除了 MLM 之外,还发现一种叫做 NSP 的额外训练过程提高了 Bert 在下游任务(那些需要特定变压器头的任务)中的性能。
当 Bert 试图猜测掩码标记背后的真实单词时,它还负责识别作为输入提供的两个句子是否属于一起。
这意味着,给定两个句子——A
和B
。句子B
是句子A
的逻辑延续吗?
**Logical continuation (*IsNext*)**
A: "Alia went to the shop to buy pasta"
B: "The shop was closed"**Not logical continuation (NotNext)**
A: "Alia went to the shop to buy pasta"
B: "The armadillo was not very happy with the lion"
MLM 和 NSP 是否都被用于训练核心的伯特模型——或者仅仅是 MLM,取决于你的偏好。只训练 MLM 更简单,通常能达到 MLM 和 NSP 可能表现的 90%左右。
在下面的视频中,我们介绍了 MLM 的训练过程(如果你想用 MLM 和 NSP,在这里找到)。
问答负责人
我们有一个经过全面训练的核心 Bert 模型,我们可以采用该核心并添加几个头部,从而允许我们使用该模型执行不同的任务。但是,这些负责人最初是未经培训的,因此我们必须对他们进行培训!
如果您喜欢视频,我们在这里也涵盖一切:
对于问答,最受欢迎的数据集是斯坦福问答数据集(SQuAD)。除了最初的英文版本,现在还有其他几种语言版本。
**Language options *(on HF datasets at the time of writing)***Spanish: **squad_es** Portuguese: **squad_v1_pt** Italian: **squad_it** Korean: [**squad_kor_v1**, **squad_kor_v2**]Thai: [**iapp_wiki_qa_squad**, **thaiqa_squad**]English: [**squad**, **squad_v2**, **squadshifts**, **squad_adversarial**]
为了下载英国队的数据,我们使用:
**Language options *(at the time of writing)***Spanish: **squad_es** Portuguese: **squad_v1_pt** Italian: **squad_it** Korean: [**squad_kor_v1**, **squad_kor_v2**]Thai: [**iapp_wiki_qa_squad**, **thaiqa_squad**]English: [**squad**, **squad_v2**, **squadshifts**, **squad_adversarial**]
对于每个样本,我们的数据可以分为三个部分:
- 问题 —包含我们将向 Bert 提出的问题的字符串。
- 上下文 —包含我们问题答案的更大的序列(段落)。
- 回答 —回答我们问题的一段上下文。
给定一个问题和上下文,我们的 Q & A 模型必须读取两者并返回上下文中预测答案的标记位置。
我们的问题、上下文和答案输入的示例,以及来自模型的(希望的)正确预测。请注意,这些跨度值并不精确,而是假设一个单词=一个标记。
格式化答案
在我们开始标记化、训练等之前,我们需要将我们的answers
特性重新格式化为正确的训练格式。目前看起来像是:
{'text': ['the answer is here'], 'answer_start': [71]}
71
代表我们的context
字符串中答案开始的字符位置。我们可以通过简单地将text
的长度加到answer_start
来得到答案范围,我们首先这样做:
数据格式现在可以进行标记化了。
标记化
我们需要对阵容数据进行记号化,以便它可以被我们的 Bert 模型读取。对于context
和question
特征,我们可以使用标准的tokenizer()
函数:
它将我们的context
和question
字符串编码成单个令牌数组。这将作为我们 Q & A 培训的输入,但是我们还没有目标。
我们的目标是答案的开始和结束位置,这是我们之前使用context
字符串中的字符开始和结束位置构建的。然而,我们将把令牌输入到 Bert 中,所以我们需要提供令牌的开始和结束位置。
为此,我们需要将字符的开始和结束位置转换成记号的开始和结束位置——使用我们的add_token_positions
函数很容易做到:
这个函数给我们的Encoding
对象增加了两个张量(我们将它们输入到 Bert 中)—start_positions
和end_positions
。
我们的张量现在准备好训练 Bert Q&A 头了。
培养
我们将使用 PyTorch 进行训练,这意味着我们需要将我们构建的张量转换成 PyTorch Dataset
对象。
我们将使用一个Dataloader
对象把我们的Dataset
输入到我们的 Q & A 训练循环中,我们用:
最后,我们设置模型参数并开始训练循环。
训练完我们的模型后,我们需要做的就是保存它!
我们完成了,我们从零开始训练了一个问答模型。
这就是本文的全部内容,我们已经介绍了训练用于问答的 Bert transformer 模型的核心和头部背后的要点,并探索了一些我们可以用于这两者的最佳数据集。
我希望你喜欢它!如果你有任何问题,请通过 Twitter 或在下面的评论中告诉我。如果你想要更多这样的内容,我也会在 YouTube 上发布。
感谢阅读!
*所有图片均由作者提供,除非另有说明