Классификация текста с помощью BERT Tokenizer и TF 2.0 в Python

Это 23-я статья из моей серии статей о Python для НЛП. В предыдущей статье [/ python-for-nlp-neural-machine-translation-with-seq2seq-in-keras /] этой серии я объяснил, как выполнять нейронный машинный перевод с использованием архитектуры seq2seq [https: //google.github .io / seq2seq /] с библиотекой Python Keras для глубокого обучения. В этой статье мы изучим BERT [https://en.wikipedia.org/wiki/BERT_(language_model)], что означает двунаправленные представления кодировщика от Tran.

Это 23-я статья из моей серии статей о Python для НЛП. В предыдущей статье этой серии я объяснил, как выполнять нейронный машинный перевод с использованием архитектуры seq2seq с библиотекой Python Keras для глубокого обучения.

В этой статье мы изучим BERT , что означает двунаправленные представления кодировщика от трансформаторов и его применение для классификации текста. BERT - это метод текстового представления, такой как вложения слов. Если вы понятия не имеете, как работают вложения слов, прочтите мою статью о встраивании слов .

Как и встраивание слов, BERT также представляет собой метод представления текста, который представляет собой сочетание множества современных алгоритмов глубокого обучения, таких как двунаправленный кодировщик LSTM и Transformers. BERT был разработан исследователями Google в 2018 году и доказал свою актуальность для множества задач обработки естественного языка, таких как классификация текста, суммирование текста, генерация текста и т. Д. Совсем недавно Google объявил, что BERT является используются в качестве основной части их алгоритмов поиска для лучшего понимания запросов.

В этой статье мы не будем вдаваться в математические подробности реализации BERT, поскольку в Интернете уже есть множество ресурсов. Скорее мы увидим, как выполнить классификацию текста с помощью токенизатора BERT. В этой статье вы увидите, как BERT Tokenizer можно использовать для создания модели классификации текста. В следующей статье я объясню, как BERT Tokenizer вместе со слоем встраивания BERT можно использовать для создания еще более эффективных моделей NLP.

Примечание . Все сценарии в этой статье были протестированы с использованием среды Google Colab , в которой время выполнения Python было установлено на GPU.

Набор данных

Набор данных, используемый в этой статье, можно скачать по этой ссылке Kaggle .

Если вы загрузите набор данных и извлечете сжатый файл, вы увидите файл CSV. Файл содержит 50 000 записей и два столбца: обзор и мнение. Столбец обзора содержит текст отзыва, а столбец тональности отзыва. Столбец тональности может иметь два значения: «положительное» и «отрицательное», что делает нашу задачу проблемой двоичной классификации.

Ранее мы выполняли сентиментальный анализ этого набора данных в предыдущей статье, где мы достигли максимальной точности 92% на обучающем наборе с помощью метода встраивания слов и сверточной нейронной сети. На тестовом наборе максимальная точность составила 85,40% с использованием встраивания слов и одиночного LSTM со 128 узлами. Посмотрим, сможем ли мы добиться большей точности, используя представление BERT.

Установка и импорт необходимых библиотек

Прежде чем вы сможете использовать текстовое представление BERT, вам необходимо установить BERT для TensorFlow 2.0. Выполните следующие команды pip на своем терминале, чтобы установить BERT для TensorFlow 2.0.

 !pip install bert-for-tf2 
 !pip install sentencepiece 

Затем вам нужно убедиться, что вы используете TensorFlow 2.0. Google Colab по умолчанию не запускает ваш скрипт на TensorFlow 2.0. Поэтому, чтобы убедиться, что вы запускаете свой скрипт через TensorFlow 2.0, выполните следующий скрипт:

 try: 
 %tensorflow_version 2.x 
 except Exception: 
 pass 
 import tensorflow as tf 
 
 import tensorflow_hub as hub 
 
 from tensorflow.keras import layers 
 import bert 

В приведенном выше скрипте, помимо TensorFlow 2.0, мы также импортируем tensorflow_hub, который, по сути, является местом, где вы можете найти все предварительно созданные и предварительно обученные модели, разработанные в TensorFlow. Мы будем импортировать и использовать встроенную модель BERT из концентратора TF. Наконец, если в выводе вы видите следующий вывод, все готово:

 TensorFlow 2.x selected. 

Импорт и предварительная обработка набора данных

Следующий скрипт импортирует набор данных с помощью read_csv() данных Pandas. Скрипт также распечатывает форму набора данных.

 movie_reviews = pd.read_csv("/content/drive/My Drive/Colab Datasets/IMDB Dataset.csv") 
 
 movie_reviews.isnull().values.any() 
 
 movie_reviews.shape 

Выход

 (50000, 2) 

Выходные данные показывают, что в нашем наборе данных 50 000 строк и 2 столбца.

Затем мы предварительно обработаем наши данные, чтобы удалить любые знаки препинания и специальные символы. Для этого мы определим функцию, которая принимает в качестве входных данных необработанный текстовый обзор и возвращает соответствующий очищенный текстовый обзор.

 def preprocess_text(sen): 
 # Removing html tags 
 sentence = remove_tags(sen) 
 
 # Remove punctuations and numbers 
 sentence = re.sub('[^a-zA-Z]', ' ', sentence) 
 
 # Single character removal 
 sentence = re.sub(r"\s+[a-zA-Z]\s+", ' ', sentence) 
 
 # Removing multiple spaces 
 sentence = re.sub(r'\s+', ' ', sentence) 
 
 return sentence 

 TAG_RE = re.compile(r'<[^>]+>') 
 
 def remove_tags(text): 
 return TAG_RE.sub('', text) 

Следующий скрипт очищает все текстовые обзоры:

 reviews = [] 
 sentences = list(movie_reviews['review']) 
 for sen in sentences: 
 reviews.append(preprocess_text(sen)) 

Наш набор данных содержит два столбца, что можно проверить из следующего скрипта:

 print(movie_reviews.columns.values) 

Выход:

 ['review' 'sentiment'] 

review столбец содержит текст , в то время как sentiment столбец содержит чувство. Столбец настроений содержит значения в виде текста. Следующий скрипт отображает уникальные значения в столбце sentiment

 movie_reviews.sentiment.unique() 

Выход:

 array(['positive', 'negative'], dtype=object) 

Вы можете видеть, что столбец тональности содержит два уникальных значения: positive и negative . Алгоритмы глубокого обучения работают с числами. Поскольку у нас есть только два уникальных значения на выходе, мы можем преобразовать их в 1 и 0. Следующий скрипт заменяет positive настроение на 1 а отрицательное на 0 .

 y = movie_reviews['sentiment'] 
 
 y = np.array(list(map(lambda x: 1 if x=="positive" else 0, y))) 

Теперь reviews содержит текстовые обзоры, а y содержит соответствующие метки. Напечатаем случайным образом обзор.

 print(reviews[10]) 

Выход:

 Phil the Alien is one of those quirky films where the humour is based around the oddness of everything rather than actual punchlines At first it was very odd and pretty funny but as the movie progressed didn find the jokes or oddness funny anymore Its low budget film thats never problem in itself there were some pretty interesting characters but eventually just lost interest imagine this film would appeal to stoner who is currently partaking For something similar but better try Brother from another planet 

Это явно похоже на негативный отзыв. Давайте просто подтвердим это, напечатав соответствующее значение метки:

 print(y[10]) 

Выход:

 0 

Результат 0 подтверждает, что это отрицательный отзыв. Теперь мы предварительно обработали наши данные, и теперь мы готовы создавать представления BERT из наших текстовых данных.

Создание токенизатора BERT

Чтобы использовать вложения текста BERT в качестве входных данных для обучения модели классификации текста, нам необходимо токенизировать наши текстовые обзоры. Токенизация означает разделение предложения на отдельные слова. Для токенизации нашего текста мы будем использовать токенизатор BERT. Взгляните на следующий сценарий:

 BertTokenizer = bert.bert_tokenization.FullTokenizer 
 bert_layer = hub.KerasLayer("https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/1", 
 trainable=False) 
 vocabulary_file = bert_layer.resolved_object.vocab_file.asset_path.numpy() 
 to_lower_case = bert_layer.resolved_object.do_lower_case.numpy() 
 tokenizer = BertTokenizer(vocabulary_file, to_lower_case) 

В приведенном выше сценарии мы сначала создаем объект класса FullTokenizer из модуля bert.bert_tokenization Затем мы создаем слой внедрения BERT, импортируя модель BERT из hub.KerasLayer . Для trainable установлено значение False , что означает, что мы не будем обучать встраивание BERT. В следующей строке мы создаем файл словаря BERT в виде массива numpy. Затем мы устанавливаем текст в нижний регистр и , наконец , мы перейдем наш vocabulary_file и to_lower_case переменных в BertTokenizer объекта.

Уместно упомянуть, что в этой статье мы будем использовать только BERT Tokenizer. В следующей статье мы будем использовать BERT Embeddings вместе с токенизатором.

Давайте теперь посмотрим, действительно ли работает наш токенизатор BERT. Для этого мы будем токенизировать случайное предложение, как показано ниже:

 tokenizer.tokenize("don't be so judgmental") 

Выход:

 ['don', "'", 't', 'be', 'so', 'judgment', '##al'] 

Вы можете видеть, что текст был успешно токенизирован. Вы также можете получить идентификаторы токенов, используя convert_tokens_to_ids() объекта токенизатора. Взгляните на следующий сценарий:

 tokenizer.convert_tokens_to_ids(tokenizer.tokenize("dont be so judgmental")) 

Выход:

 [2123, 2102, 2022, 2061, 8689, 2389] 

Теперь определим функцию, которая принимает один текстовый обзор и возвращает идентификаторы токенизированных слов в обзоре. Выполните следующий скрипт:

 def tokenize_reviews(text_reviews): 
 return tokenizer.convert_tokens_to_ids(tokenizer.tokenize(text_reviews)) 

И выполните следующий скрипт, чтобы токенизировать все обзоры во входном наборе данных:

 tokenized_reviews = [tokenize_reviews(review) for review in reviews] 

Подготовка данных для обучения

Обзоры в нашем наборе данных имеют разную длину. Некоторые обзоры очень маленькие, а другие очень длинные. Для обучения модели входные предложения должны быть одинаковой длины. Один из способов создать предложения одинаковой длины - дополнить короткие предложения нулями. Однако это может привести к тому, что разреженная матрица будет содержать большое количество нулей. Другой способ - дополнить предложения внутри каждого пакета. Поскольку мы будем обучать модель пакетами, мы можем дополнить предложения в обучающем пакете локально в зависимости от длины самого длинного предложения. Для этого нам сначала нужно найти длину каждого предложения.

Следующий скрипт создает список списков, в каждом подсписке которого содержится размеченный обзор, метка обзора и длина обзора:

 reviews_with_len = [[review, y[i], len(review)] 
 for i, review in enumerate(tokenized_reviews)] 

В нашем наборе данных первая половина отзывов положительная, а вторая половина - отрицательные. Следовательно, чтобы в обучающих пакетах были как положительные, так и отрицательные отзывы, нам необходимо перемешать отзывы. Следующий скрипт случайным образом перемешивает данные:

 random.shuffle(reviews_with_len) 

После перетасовки данных мы отсортируем их по длине обзоров. Для этого мы воспользуемся sort() и сообщим ей, что мы хотим отсортировать список по третьему элементу в подсписке, то есть по длине обзора.

 reviews_with_len.sort(key=lambda x: x[2]) 

После того, как отзывы отсортированы по длине, мы можем удалить атрибут длины из всех обзоров. Для этого выполните следующий сценарий:

 sorted_reviews_labels = [(review_lab[0], review_lab[1]) for review_lab in reviews_with_len] 

После сортировки обзоров мы преобразуем набор данных, чтобы его можно было использовать для обучения моделей TensorFlow 2.0. Выполните следующий код, чтобы преобразовать отсортированный набор данных в форму входного набора данных, совместимую с TensorFlow 2.0.

 processed_dataset = tf.data.Dataset.from_generator(lambda: sorted_reviews_labels, output_types=(tf.int32, tf.int32)) 

Наконец, теперь мы можем дополнить наш набор данных для каждого пакета. Размер пакета, который мы собираемся использовать, равен 32, что означает, что после обработки 32 отзывов веса нейронной сети будут обновлены. Чтобы дополнить обзоры локально по отношению к пакетам, выполните следующие действия:

 BATCH_SIZE = 32 
 batched_dataset = processed_dataset.padded_batch(BATCH_SIZE, padded_shapes=((None, ), ())) 

Напечатаем первый пакет и посмотрим, как к нему применено заполнение:

 next(iter(batched_dataset)) 

Выход:

 (<tf.Tensor: shape=(32, 21), dtype=int32, numpy= 
 array([[ 2054, 5896, 2054, 2466, 2054, 6752, 0, 0, 0, 
 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 0, 0, 0], 
 [ 3078, 5436, 3078, 3257, 3532, 7613, 0, 0, 0, 
 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 0, 0, 0], 
 [ 3191, 1996, 2338, 5293, 1996, 3185, 0, 0, 0, 
 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 0, 0, 0], 
 [ 2062, 23873, 3993, 2062, 11259, 2172, 2172, 2062, 14888, 
 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 0, 0, 0], 
 [ 1045, 2876, 9278, 2023, 2028, 2130, 2006, 7922, 12635, 
 2305, 0, 0, 0, 0, 0, 0, 0, 0, 
 0, 0, 0], 
 ...... 
 
 [ 7244, 2092, 2856, 10828, 1997, 10904, 2402, 2472, 3135, 
 2293, 2466, 2007, 10958, 8428, 10102, 1999, 1996, 4281, 
 4276, 3773, 0], 
 [ 2005, 5760, 7788, 4393, 8808, 2498, 2064, 12826, 2000, 
 1996, 11056, 3152, 3811, 16755, 2169, 1998, 2296, 2028, 
 1997, 2068, 0], 
 [ 2307, 3185, 2926, 1996, 2189, 3802, 2696, 2508, 2012, 
 2197, 2023, 8847, 6702, 2043, 2017, 2031, 2633, 2179, 
 2008, 2569, 2619], 
 [ 2028, 1997, 1996, 4569, 15580, 2102, 5691, 2081, 1999, 
 3522, 2086, 2204, 23191, 5436, 1998, 11813, 6370, 2191, 
 2023, 2028, 4438], 
 [ 2023, 3185, 2097, 2467, 2022, 5934, 1998, 3185, 4438, 
 2004, 2146, 2004, 2045, 2024, 2145, 2111, 2040, 6170, 
 3153, 1998, 2552]], dtype=int32)>, 
 <tf.Tensor: shape=(32,), dtype=int32, numpy= 
 array([0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 
 1, 1, 0, 1, 0, 1, 1, 0, 1, 1], dtype=int32)>) 

Приведенный выше вывод показывает первые пять и последние пять дополненных обзоров. Из последних пяти обзоров вы можете видеть, что общее количество слов в самом большом предложении было 21. Таким образом, в первых пяти обзорах в конце предложений добавляются нули, так что их общая длина также равна 21. Заполнение. для следующего пакета будет отличаться в зависимости от размера самого большого предложения в пакете.

После того, как мы применили заполнение к нашему набору данных, следующим шагом будет разделение набора данных на тестовый и обучающий наборы. Мы можем сделать это с помощью следующего кода:

 TOTAL_BATCHES = math.ceil(len(sorted_reviews_labels) / BATCH_SIZE) 
 TEST_BATCHES = TOTAL_BATCHES // 10 
 batched_dataset.shuffle(TOTAL_BATCHES) 
 test_data = batched_dataset.take(TEST_BATCHES) 
 train_data = batched_dataset.skip(TEST_BATCHES) 

В приведенном выше коде мы сначала находим общее количество пакетов, разделив общее количество записей на 32. Затем 10% данных оставляем для тестирования. Для этого мы используем take() метод batched_dataset() объекта для хранения 10% данных в test_data переменной. Остальные данные хранятся в train_data для обучения с использованием метода skip()

Набор данных подготовлен, и теперь мы готовы создать нашу модель классификации текста.

Создание модели

Теперь мы готовы к созданию нашей модели. Для этого мы создадим класс с именем TEXT_MODEL который наследуется от класса tf.keras.Model Внутри класса мы определим слои нашей модели. Наша модель будет состоять из трех слоев сверточной нейронной сети. Вместо этого вы можете использовать слои LSTM, а также можете увеличивать или уменьшать количество слоев. Я скопировал количество и типы слоев из записной книжки Google Colab от SuperDataScience, и эта архитектура, похоже, неплохо работает и для набора данных обзоров фильмов IMDB.

Давайте теперь создадим наш класс модели:

 class TEXT_MODEL(tf.keras.Model): 
 
 def __init__(self, 
 vocabulary_size, 
 embedding_dimensions=128, 
 cnn_filters=50, 
 dnn_units=512, 
 model_output_classes=2, 
 dropout_rate=0.1, 
 training=False, 
 name="text_model"): 
 super(TEXT_MODEL, self).__init__(name=name) 
 
 self.embedding = layers.Embedding(vocabulary_size, 
 embedding_dimensions) 
 self.cnn_layer1 = layers.Conv1D(filters=cnn_filters, 
 kernel_size=2, 
 padding="valid", 
 activation="relu") 
 self.cnn_layer2 = layers.Conv1D(filters=cnn_filters, 
 kernel_size=3, 
 padding="valid", 
 activation="relu") 
 self.cnn_layer3 = layers.Conv1D(filters=cnn_filters, 
 kernel_size=4, 
 padding="valid", 
 activation="relu") 
 self.pool = layers.GlobalMaxPool1D() 
 
 self.dense_1 = layers.Dense(units=dnn_units, activation="relu") 
 self.dropout = layers.Dropout(rate=dropout_rate) 
 if model_output_classes == 2: 
 self.last_dense = layers.Dense(units=1, 
 activation="sigmoid") 
 else: 
 self.last_dense = layers.Dense(units=model_output_classes, 
 activation="softmax") 
 
 def call(self, inputs, training): 
 l = self.embedding(inputs) 
 l_1 = self.cnn_layer1(l) 
 l_1 = self.pool(l_1) 
 l_2 = self.cnn_layer2(l) 
 l_2 = self.pool(l_2) 
 l_3 = self.cnn_layer3(l) 
 l_3 = self.pool(l_3) 
 
 concatenated = tf.concat([l_1, l_2, l_3], axis=-1) # (batch_size, 3 * cnn_filters) 
 concatenated = self.dense_1(concatenated) 
 concatenated = self.dropout(concatenated, training) 
 model_output = self.last_dense(concatenated) 
 
 return model_output 

Приведенный выше сценарий довольно прост. В конструкторе класса мы инициализируем некоторые атрибуты значениями по умолчанию. Позже эти значения будут заменены значениями, переданными при создании объекта класса TEXT_MODEL

Затем три слоя сверточной нейронной сети были инициализированы значениями ядра или фильтра 2, 3 и 4 соответственно. Опять же, вы можете изменить размер фильтра, если хотите.

Затем внутри функции call() глобальный максимальный пул применяется к выходным данным каждого уровня сверточной нейронной сети. Наконец, три уровня сверточной нейронной сети объединяются вместе, и их выходные данные передаются в первую плотно связанную нейронную сеть. Вторая плотно связанная нейронная сеть используется для прогнозирования настроения вывода, поскольку она содержит только 2 класса. Если у вас есть больше классов на выходе, вы можете соответствующим образом output_classes переменную output_classes.

Теперь давайте определим значения для гиперпараметров нашей модели.

 VOCAB_LENGTH = len(tokenizer.vocab) 
 EMB_DIM = 200 
 CNN_FILTERS = 100 
 DNN_UNITS = 256 
 OUTPUT_CLASSES = 2 
 
 DROPOUT_RATE = 0.2 
 
 NB_EPOCHS = 5 

Затем нам нужно создать объект TEXT_MODEL и передать значения гиперпараметров, которые мы определили на последнем шаге, в конструктор класса TEXT_MODEL

 text_model = TEXT_MODEL(vocabulary_size=VOCAB_LENGTH, 
 embedding_dimensions=EMB_DIM, 
 cnn_filters=CNN_FILTERS, 
 dnn_units=DNN_UNITS, 
 model_output_classes=OUTPUT_CLASSES, 
 dropout_rate=DROPOUT_RATE) 

Прежде чем мы сможем обучить модель, нам нужно ее скомпилировать. Следующий скрипт компилирует модель:

 if OUTPUT_CLASSES == 2: 
 text_model.compile(loss="binary_crossentropy", 
 optimizer="adam", 
 metrics=["accuracy"]) 
 else: 
 text_model.compile(loss="sparse_categorical_crossentropy", 
 optimizer="adam", 
 metrics=["sparse_categorical_accuracy"]) 

Наконец, чтобы обучить нашу модель, мы можем использовать fit класса модели.

 text_model.fit(train_data, epochs=NB_EPOCHS) 

Вот результат через 5 эпох:

 Epoch 1/5 
 1407/1407 [==============================] - 381s 271ms/step - loss: 0.3037 - accuracy: 0.8661 
 Epoch 2/5 
 1407/1407 [==============================] - 381s 271ms/step - loss: 0.1341 - accuracy: 0.9521 
 Epoch 3/5 
 1407/1407 [==============================] - 383s 272ms/step - loss: 0.0732 - accuracy: 0.9742 
 Epoch 4/5 
 1407/1407 [==============================] - 381s 271ms/step - loss: 0.0376 - accuracy: 0.9865 
 Epoch 5/5 
 1407/1407 [==============================] - 383s 272ms/step - loss: 0.0193 - accuracy: 0.9931 
 <tensorflow.python.keras.callbacks.History at 0x7f5f65690048> 

Как видите, на обучающем наборе мы получили точность 99,31%.

Теперь давайте оценим производительность нашей модели на тестовом наборе:

 results = text_model.evaluate(test_dataset) 
 print(results) 

Выход:

 156/Unknown - 4s 28ms/step - loss: 0.4428 - accuracy: 0.8926[0.442786190037926, 0.8926282] 

Из выходных данных мы видим, что на тестовом наборе мы получили точность 89,26%.

Заключение

В этой статье вы увидели, как мы можем использовать BERT Tokenizer для создания встраиваемых слов, которые можно использовать для классификации текста. Мы провели сентиментальный анализ обзоров фильмов IMDB и достигли точности 89,26% на тестовой выборке. В этой статье мы не использовали встраивания BERT, мы использовали только токенизатор BERT для токенизации слов. В следующей статье вы увидите, как BERT Tokenizer вместе с BERT Embeddings можно использовать для классификации текста.

comments powered by Disqus