Эта статья подготовлена командой OPPO Internet Basic Technology Team, пожалуйста, укажите автора для перепечатки. В то же время приглашаем обратить внимание на нашу общедоступную учетную запись: OPPO_tech, чтобы поделиться с вами передовыми интернет-технологиями и деятельностью OPPO.
BERT — это веха в области НЛП за последние годы. Благодаря хорошему эффекту и широкому спектру применения он широко используется в научных исследованиях и инженерных проектах. С точки зрения исходного кода в этой статье анализируется исходный код части классификатора модели BERT от целого к части.
1. Общее модульное деление
Для инженеров по машинному обучению возможность настраивать пакеты и запускать программы должна стать первым шагом на долгом пути. Этот шаг в основном поможет нам быстро применить модель к реальному бизнесу и повысить нашу уверенность в себе, но этого недостаточно. Чтобы лучше использовать модель в соответствии с различными бизнес-сценариями, нам нужно глубоко понять модель и прочитать исходный код, чтобы идти дальше.
В этой статье интерпретируется исходный код части классификатора проекта с открытым исходным кодом BERT.От начального ввода данных до запуска модели весь процесс можно разделить на модуль обработки данных, модуль обработки признаков, модуль построения модели и модуль запуска модели. В частности, как показано на рисунке ниже:
Рис. 1. Общее модульное деление классификатора BERT.
Поскольку исходная экологическая модель предварительного обучения BERT часто имеет размер в сотни мегабайт или даже гигабайт, скорость обучения модели очень низкая и очень недружественна онлайн-модели BERT.Поэтому последний производный продукт BERT ALBERT, который в настоящее время относительно популярен, используется для завершения онлайн-сервиса BERT. ALBERT использует технологию уменьшения параметров, чтобы уменьшить потребление памяти и в конечном итоге улучшить скорость обучения BERT, и он входит в число лучших в основных тестах, Можно сказать, что он работает быстро и работает хорошо. Исходный код BERT, описанный в этой статье, также основан на проекте с открытым исходным кодом ALBERT.
Проект github с открытым исходным кодом проекта:GitHub.com/Wilson LS 00…
В основном интерпретируйте исходный код части классификатора, код и комментарии находятся в файле run_classifier.py, добро пожаловать, друзья, для разветвления.
2. Модуль обработки данных
Модуль обработки данных в основном отвечает за функции чтения и предварительной обработки данных.
Обработка данных в основном выполняется процессором данных DataProcessor. Существуют разные подклассы процессоров данных в соответствии с разными задачами, и различия здесь заключаются в способах чтения и предварительной обработки данных.
2.1 Как читать данные
Существуют различные способы чтения данных в реальных проектах, такие как csv, tsv, txt и т. д. Например, некоторым проектам нужно читать файлы csv, а другим нужен формат tsv или txt. Мы можем создавать специальные процессоры данных для выполнения различных требований проекта.
2.2 Предварительная обработка данных
Предварительная обработка данных основана на различных задачах NLP для выполнения различных операций, таких как задачи классификации одного предложения, нам нужны форматы text_a и label. Для задачи оценки сходства предложений требуются форматы text_a, text_b и label. Другие задачи аналогичны, и операции предварительной обработки данных выполняются в соответствии с разными задачами НЛП.
Процессор данных в исходном коде объясняется с помощью диаграммы классов:
Рис. 2 Диаграмма классов процессора данных
В соответствии с исходным кодом проекта у нас есть родительский класс DataProcessor. В родительском классе есть пять методов, а именно чтение tsv-файла, получение обучающего набора, получение проверочного набора, получение тестового набора и получение метки. Здесь вы можете добавлять, удалять и изменять функции для получения типов файлов в соответствии с потребностями бизнеса.Например, при чтении csv вы можете добавить get_csv(input_file) и так далее.
class DataProcessor(object):
"""Base class for data converters for sequence classification data sets."""
def get_train_examples(self, data_dir):
"""Gets a collection of `InputExample`s for the train set."""
raise NotImplementedError()
def get_dev_examples(self, data_dir):
"""Gets a collection of `InputExample`s for the dev set."""
raise NotImplementedError()
def get_test_examples(self, data_dir):
"""Gets a collection of `InputExample`s for prediction."""
raise NotImplementedError()
def get_labels(self):
"""Gets the list of labels for this data set."""
raise NotImplementedError()
@classmethod
def _read_tsv(cls, input_file, quotechar=None):
"""Reads a tab separated value file."""
with tf.gfile.Open(input_file, "r") as f:
reader = csv.reader(f, delimiter="\t", quotechar=quotechar)
lines = []
for line in reader:
lines.append(line)
return lines
Следующие два подкласса представляют собой процессор данных SentencePairClassificationProcessor для обработки задач оценки отношения предложений и процессор данных LCQMCPairClassificationProcessor для классификации. Как упоминалось ранее, если нам нужно выполнить задачу классификации одного предложения, мы можем добавить сюда SentenceClassifierProcess для индивидуальной разработки.
В соответствии с исходным кодом проекта, поскольку мы являемся задачей оценки отношений между предложениями, на самом деле это состоит в том, чтобы определить, связаны ли два предложения. Окончательный формат данных, который мы получаем здесь, — это тип списка. Конкретный формат данных выглядит следующим образом:
[(guid,text_a,text_b,label),(guid,text_a,text_b,label),....]
Среди них guid используется в качестве символа для уникальной идентификации пары предложений text_a и text_b, что можно понимать как уникальный идентификатор образца;
text_a и text_b — два предложения, которые нужно оценить;
Поле метки — это метка, установленная на 1, если два предложения похожи, и на 0 в противном случае.
Вышеупомянутые четыре поля guid и text_a являются обязательными. text_b является необязательным. Если он пуст, он становится задачей классификации одного предложения. Если он не пуст, это задача оценки отношений предложений. Метка требуется в наборе для обучения и наборе для проверки и может отсутствовать в наборе для тестирования.
Конкретный код находится в функции _create_examples подкласса SentencePairClassificationProcessor:
def _create_examples(self, lines, set_type):
"""Creates examples for the training and dev sets."""
examples = []
print("length of lines:", len(lines))
for (i, line) in enumerate(lines):
# print('#i:',i,line)
if i == 0:
continue
guid = "%s-%s" % (set_type, i)
try:
label = tokenization.convert_to_unicode(line[2])
text_a = tokenization.convert_to_unicode(line[0])
text_b = tokenization.convert_to_unicode(line[1])
examples.append(
InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label))
except Exception:
print('###error.i:', i, line)
return examples
3. Модуль обработки признаков
Основная функция модуля обработки признаков заключается в преобразовании данных, полученных модулем обработки данных, в признаки и сохранении их в файле TFRecord, который завершается функцией file_based_convert_examples_to_features.
"""
将数据处理模块得到的数据转化成TFRecord文件
input:
examples:数据格式为[(guid,text_a,text_b,label),(guid,text_a,text_b,label),....]
label_list:标签列表
max_seq_length:允许的句子最大长度
tokenizer:分词器
output_file:TFRecord文件存储路径
output:持久化到TFRecord格式文件
"""
def file_based_convert_examples_to_features(
examples,
label_list,
max_seq_length,
tokenizer, output_file):
3.1 Предварительная обработка данных в функции
Операция преобразования данных в признаки в основном выполняется функцией convert_single_example. Традиционному машинному обучению необходимо извлекать признаки из данных, а задача НЛП — получать признаки из сегментации текста и других операций. В модели BERT каждое слово по умолчанию является словом.
"""
将预处理数据加工成模型需要的特征
input:
ex_index:数据条数索引
example:数据格式为[(guid,text_a,text_b,label),(guid,text_a,text_b,label),....]
label_list:标签列表
max_seq_length:允许的句子最大长度,这里如果输入句子长度不足则补0
tokenizer:分词器
output: feature = InputFeatures(
input_ids=input_ids:token embedding:表示词向量,第一个词是CLS,分隔词有SEP,是单词本身
input_mask=input_mask:position embedding:为了令transformer感知词与词之间的位置关系
segment_ids=segment_ids:segment embedding:text_a与text_b的句子关系
label_id=label_id:标签
is_real_example=True)
"""
def convert_single_example(ex_index, example,
label_list, max_seq_length,tokenizer):
....
feature = InputFeatures(
input_ids=input_ids,
input_mask=input_mask,
segment_ids=segment_ids,
label_id=label_id,
is_real_example=True)
return feature
Ввод модели BERT в документе преобразуется в функции, как показано на следующем рисунке:
Рис. 3. Ввод предложения, преобразованный в трехуровневое встраивание
Здесь нужно обратить внимание на операции препроцессинга над text_a и text_b. Сначала будет выполнена токенизация для преобразования text_a и text_b в tokens_a и tokens_b.
Если tokens_b существует, длина tokens_a и tokens_b не может превышать max_seq_length-3, потому что необходимо добавить три символа cls, sep и seq; если tokens_b не существует, длина tokens_a не может превышать max_seq_length-2, потому что cls и sep необходимо добавить символ.
Здесь вышеописанный процесс описывается путем преобразования определенного фрагмента данных в функцию. Теперь у нас есть часть данных в нашем примере с тремя полями:
-
text_a: Какая программа использовалась для создания этой картинки?
-
text_b: Какая программа используется для создания такого изображения?
-
label: 1
После сегментации слов получаем:
жетоны: [CLS] Какое программное обеспечение использовалось для создания этих изображений? [SEP] Какое программное обеспечение вы используете для создания таких изображений? [СЕН]
Где [CLS] — дополнительный флаг начала, добавленный в модель, указывающий, что это начало предложения. [SEP] означает разделитель, мы объединим два предложения в одно предложение и идентифицируем его по разделителю. Разделитель также будет добавлен после объединения второго предложения. Здесь следует отметить, что BERT разделяет сегментацию китайских слов по каждому слову, а не в соответствии с реальными китайскими словами, которые мы обычно понимаем.
После извлечения признака он становится:
Input_ids: 101 6821 4905 1745 4275 3221 4500 784 720 6763 816 1169 868 4905 1743 102 6821 4905 1745 4275 1169 868 3221 4500 784 720 6763 816 1450 8043 1020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
input_mask: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0
segment_ids: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 0 0 0 0 0 0 0 0 0 0 0
label_id: 1
Вот подробное описание того, какие функции мы действительно вводим в модель.
input_ids представляют кодировку вектора слова. В задачах НЛП мы преобразуем текст в представление вектора слов и предоставляем его модели. Предложение разбивается на слова токенизатором в исходном коде BERT, и слова сопоставляются с идентификаторами. Например, в приведенном выше примере в первом предложении 14 символов, во втором предложении также 14 символов, плюс начальный знак и два разделителя, в одном 31 символ. В приведенном выше примере первые 31 позиция в списке input_ids имеют идентификатор каждого сопоставления слов, и идентификатор сопоставления того же слова одинаков. Другие заполняются добавлением 0;
input_mask означает кодировку позиции. Для того, чтобы преобразователь воспринял позиционные отношения между словами, исходный код установит слово в текущей позиции в 1, а остальные будут заполнены 0;
идентификаторы_сегментов представляют собой коды отношения предложений. Если это задача оценки отношения предложения, код отношения предложения, соответствующий позиции text_b, будет установлен на 1. Здесь следует отметить, что до тех пор, пока это задача оценки отношения предложения, независимо от того, связаны ли два предложения или нет, то есть, является ли метка 1 или нет, код отношения предложения, соответствующий позиции text_b, будет установить на 1;
label_id означает, связаны ли два предложения. Метке присваивается значение 1, если есть связь, и 0 в противном случае.
3.2 Характеристики хранятся в файлах формата TFRecord
Когда мы будем обучать модель, все обучающие данные будут загружены в память. С небольшими наборами данных проблем нет, но при столкновении с большими наборами данных наша память не может загрузить все данные, поэтому приходится загружать данные пакетами. Tensorflow предоставляет разработчикам файлы формата TFRecord. TFRecord использует внутреннее двоичное кодирование, которое быстро загружается и удобно для крупномасштабного преобразования данных.
Таким образом, модуль обработки признаков в основном преобразует предварительно обработанные данные в признаки и сохраняет их в файлах формата TFRecord. BERT преобразует ввод предложения в трехуровневое кодирование встраивания.Первый уровень — это кодирование слов, которое в основном представляет само слово, второй уровень — это кодирование положения, в основном для того, чтобы преобразователь воспринимал позиционные отношения между словами и словами, третий Слой кодирования выражает отношения между предложениями. Благодаря этим трем уровням кодирования мы получаем входные данные модели. Чтобы облегчить обучение модели и загрузку данных в больших наборах данных, мы сохраняем функции в файлах формата TFRecord.
4. Строительные блоки модели
Модуль построения модели в основном разделен на построение модели и стандартный ввод модели.
4.1 Построение модели
Используйте функцию model_fn_builder для создания пользовательского средства оценки модели.
"""
自定义模型估计器(model_fn_builder)
input:bert_config:bert相关的配置
num_labels:标签的数量
init_checkpoint:预训练模型
learning_rate:学习率
num_train_steps:模型训练轮数 = (训练集总数/batch_size)*epochs
num_warmup_steps:线性地增加学习率,num_warmup_steps = num_train_steps * warmup_proportion
use_tpu:是否使用TPU
output:构建好的模型
"""
def model_fn_builder(bert_config, num_labels, init_checkpoint, learning_rate,
num_train_steps, num_warmup_steps, use_tpu,
use_one_hot_embeddings):
"""Returns `model_fn` closure for TPUEstimator."""
......
return model_fn
Построение модели здесь в основном завершается функцией create_model, которая в основном выполняет две задачи: во-первых, вызывается класс BertModel в modeling.py для создания модели, во-вторых, вычисляется потеря перекрестной энтропии. Чем меньше значение кросс-энтропии, тем ближе два распределения вероятностей.
"""
创建模型,主要完成两件事:第一件事是调用modeling.py中国的BertModel类创建模型;
第二件事事计算交叉熵损失loss。交叉熵的值越小,两个概率分布就越接近。
"""
def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,
labels, num_labels, use_one_hot_embeddings):
"""Creates a classification model."""
# 建立一个BERT分类模型(create_model)
model = modeling.BertModel(
config=bert_config,
is_training=is_training,
input_ids=input_ids,
input_mask=input_mask,
token_type_ids=segment_ids,
use_one_hot_embeddings=use_one_hot_embeddings)
......
return (loss, per_example_loss, logits, probabilities)
4.2 Стандартный ввод модели
Поскольку исходный проект разработан на основе фреймворка Tensorflow, необходимо преобразовать ранее полученные признаки в стандартный формат ввода модели Tensorflow. Этот блок в основном завершается функцией file_based_input_fn_builder. Ввод обучающего набора, проверочного набора и тестового набора может быть завершен различными входными файлами.
"""
模型标准输入
从TFRecord格式文件中读取特征并转化成TensorFlow标准的数据输入格式
input:input_file:
input_file=train_file:输入文件,可以是训练集、验证集和预测集
seq_length=FLAGS.max_seq_length:句子最大长度
is_training=True:是否训练标志
drop_remainder=True:表示在少于batch_size元素的情况下是否应删除最后一批 ; 默认是不删除。
output:TensorFlow标准的格式输入
"""
def file_based_input_fn_builder(input_file, seq_length, is_training,
drop_remainder):
name_to_features = {
"input_ids": tf.FixedLenFeature([seq_length], tf.int64),
"input_mask": tf.FixedLenFeature([seq_length], tf.int64),
"segment_ids": tf.FixedLenFeature([seq_length], tf.int64),
"label_ids": tf.FixedLenFeature([], tf.int64),
"is_real_example": tf.FixedLenFeature([], tf.int64),
}
......
return input_fn
Здесь следует отметить поле is_training.Для обучающих данных требуется большое количество параллельных операций чтения и записи и перетасовки, для проверочных данных мы не хотим перетасовывать данные, и нам все равно, будут ли они параллельна или нет.
Таким образом, модуль построения модели в основном состоит из двух частей: построения модели и стандартного ввода модели. Построение модели отвечает за создание и настройку модели BERT. Стандартный ввод модели считывает файл формата TFRecord и преобразует его в стандартный ввод модели, а также завершает стандартный ввод обучающего набора, проверочного набора и тестового набора в соответствии с различными входными файлами.
5. Модуль запуска модели
После того, как приведенная выше модель построена, вы можете запустить модель. Модель, работающая в Tensorflow, требует создания объекта Estimator. В основном он создается с помощью tf.contrib.tpu.TPUEstimator() в исходном коде.
"""
Estimator对象包装由model_fn指定的模型
input:给定输入和其他一些参数
use_tpu:是否使用TPU
model_fn:前面构建好的模型
config:模型运行相关的配置
train_batch_size:训练batch大小
eval_batch_size:验证batch大小
predict_batch_size:预测batch大小
output:需要进行训练、计算,或预测的操作
"""
estimator = tf.contrib.tpu.TPUEstimator(
use_tpu=FLAGS.use_tpu,
model_fn=model_fn,
config=run_config,
train_batch_size=FLAGS.train_batch_size,
eval_batch_size=FLAGS.eval_batch_size,
predict_batch_size=FLAGS.predict_batch_size)
5.1 Обучение модели
Обучение модели можно выполнить с помощью estimator.train:
if FLAGS.do_train:
train_input_fn = file_based_input_fn_builder(
input_file=train_file,
seq_length=FLAGS.max_seq_length,
is_training=True,
drop_remainder=True)
....
estimator.train(input_fn=train_input_fn, max_steps=num_train_steps)
5.2 Проверка модели
Проверка модели может быть выполнена с помощью estimator.evaluate:
if FLAGS.do_eval:
eval_input_fn = file_based_input_fn_builder(
input_file=eval_file,
seq_length=FLAGS.max_seq_length,
is_training=False,
drop_remainder=eval_drop_remainder)
....
result = estimator.evaluate(input_fn=eval_input_fn, steps=eval_steps, checkpoint_path=filename)
5.3 Предсказание модели
Предсказание модели можно сделать с помощью estimator.predict:
if FLAGS.do_predict:
predict_input_fn = file_based_input_fn_builder(
input_file=predict_file,
seq_length=FLAGS.max_seq_length,
is_training=False,
drop_remainder=predict_drop_remainder)
....
result = estimator.predict(input_fn=predict_input_fn)
6. Другие модули
Модуль журнала 6.1 тс
import tensorflow as tf
# 日志的显示等级
tf.logging.set_verbosity(tf.logging.INFO)
# 打印提示日志
tf.logging.info("***** Runningtraining *****")
# 打印传参日志
tf.logging.info(" Num examples = %d", len(train_examples))
6.2 Внешний модуль передачи параметров
import tensorflow as tf
flags = tf.flags
FLAGS = flags.FLAGS
flags.DEFINE_string(
"data_dir", None,
"The input data dir. Should contain the .tsv files (or other datafiles) "
"for thetask.")
# 设置哪些参数是必须要传入的
flags.mark_flag_as_required("data_dir")
Суммировать
В этой статье в основном объясняется исходный код части классификатора в BERT. В целом, он в основном делится на модуль обработки данных, модуль обработки функций, модуль построения модели и модуль запуска модели.
Модуль обработки данных в основном отвечает за чтение и предварительную обработку данных; модуль обработки признаков отвечает за преобразование предварительно обработанных данных в признаки и их сохранение в файле формата TFRecord; модуль построения модели в основном отвечает за построение модели BERT и подготовку стандартные входные данные модели; модуль запуска модели в основном отвечает за обучение, проверку и прогнозирование модели.
Мы можем получить глубокое понимание исходного кода классификатора в BERT в глобальном подходе. В дальнейшем классификатор может быть переработан в соответствии с реальными бизнес-требованиями.