Взгляд на мультиклассовую классификацию вопросов о переполнении стека с помощью TensorFlow.
Давайте построим простейшую форму модели мультиклассовой классификации нейронной сети, чтобы обучить мультиклассовый классификатор предсказывать тег вопроса программирования на Переполнение стека.
Stack Overflow - это вопросы и ответы для профессиональных программистов и энтузиастов. В этом посте используется дамп данных вопросов из StackOverflow. В этом руководстве мы будем использовать вопросы программирования, такие как (Как отсортировать словарь по значению?), Каждый из которых помечен ровно одной категорией (Python
, CSharp
, JavaScript
или Java
).
Нейронная сеть будет смоделирована для выполнения многоклассовой классификации вопросов.
Первым делом нужно загрузить данные. Данные доступны в различных местах, как описано здесь. Однако мы будем использовать набор данных, подготовленный для нас Tensorflow. Мы можем использовать служебные функции из TensorFlow, содержащие несколько тысяч вопросов, извлеченных из гораздо более крупного общедоступного набора данных Stack Overflow на BigQuery, который содержит более 17 миллионов сообщений, а именно:
# Downloading and exploring the StackOverflow dataset data_url = 'https://storage.googleapis.com/download.tensorflow.org/data/stack_overflow_16k.tar.gz' dataset = tf.keras.utils.get_file( 'stack_overflow_16k.tar.gz', data_url, untar=True, cache_dir='stack_overflow', cache_subdir='.') dataset_dir = pathlib.Path(dataset).parent
Напечатаем образец того, как выглядит вопрос
train_dir = dataset_dir/'train' sample_file = os.path.join(train_dir, 'python/1755.txt') with open(sample_file) as f: print(f.read()) why does this blank program print true x=true.def stupid():. x=false.stupid().print x
Возникает вопрос: «Почему эта пустая программа выводит true x = true.def stupid () :. x = false.stupid (). print x »
Внимание, спойлер!
Наша модель изучает синтаксис языка как представление в слоях, что помогает нейронной сети классифицировать ^^
Поскольку мы загружаем набор данных (и любой набор данных может быть загружен таким образом), нам придется создать отдельный набор данных для обучения и проверки из наших данных, что можно сделать, как показано ниже.
Мы будем использовать утилиту text_dataset_from_directory
для создания помеченного tf.data.Dataset
. Tf.data - мощный набор инструментов для работы с данными.
При проведении эксперимента с машинным обучением рекомендуется разделить набор данных на три части: обучение, проверка и тестирование.
Комплект поезда:
batch_size = 32 seed = 42 raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory( train_dir, batch_size=batch_size, validation_split=0.2, subset='training', seed=seed) Found 8000 files belonging to 4 classes. Using 6400 files for training.
Набор для проверки:
raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory( train_dir, batch_size=batch_size, validation_split=0.2, subset='validation', seed=seed) Found 8000 files belonging to 4 classes. Using 1600 files for validation.
Поскольку имена классов не включены в набор данных, мы напечатаем их для удобства чтения, как показано ниже.
for i, label in enumerate(raw_train_ds.class_names): print("Label", i, "corresponds to", label) Label 0 corresponds to csharp Label 1 corresponds to java Label 2 corresponds to javascript Label 3 corresponds to python
Следующим шагом является стандартизация, токенизация и векторизация данных с использованием слоя preprocessing.TextVectorization
.
- Стандартизация относится к предварительной обработке текста, обычно для удаления знаков препинания или элементов HTML для упрощения набора данных.
- Токенизация относится к разделению строк на токены (например, разделение предложения на отдельные слова путем разделения на пробелы).
- Векторизация относится к преобразованию токенов в числа, чтобы их можно было передать в нейронную сеть.
Все эти задачи могут быть выполнены с TextVectorization
layer. Вы можете узнать больше о каждом из них в API doc.
- При стандартизации по умолчанию текст преобразуется в нижний регистр и удаляются знаки препинания.
- Токенизатор по умолчанию разбивается на пробелы.
- Режим векторизации по умолчанию -
int
. Это выводит целочисленные индексы (по одному на токен). Этот режим можно использовать для построения моделей, учитывающих порядок слов. Вы также можете использовать другие режимы, напримерbinary
, для построения простых моделей.
Вы создадите два режима, чтобы узнать о них больше. Сначала вы воспользуетесь моделью binary
, чтобы построить модель набора слов. Затем вы будете использовать режим int
с 1D ConvNet.
VOCAB_SIZE = 10000 binary_vectorize_layer = TextVectorization( max_tokens=VOCAB_SIZE, output_mode='binary') MAX_SEQUENCE_LENGTH = 250
int_vectorize_layer = TextVectorization( max_tokens=VOCAB_SIZE, output_mode='int', output_sequence_length=MAX_SEQUENCE_LENGTH)
adapt
соответствует состоянию уровня предварительной обработки набору данных для построения индекса строк для целых чисел.
# Make a text-only dataset (without labels), then call adapt train_text = raw_train_ds.map(lambda text, labels: text) binary_vectorize_layer.adapt(train_text) int_vectorize_layer.adapt(train_text)
Пришло время обучить нашу модель, поэтому мы создадим наборы данных для обучения, тестирования и проверки, чтобы
def binary_vectorize_text(text, label): text = tf.expand_dims(text, -1) return binary_vectorize_layer(text), label def int_vectorize_text(text, label): text = tf.expand_dims(text, -1) return int_vectorize_layer(text), label binary_train_ds = raw_train_ds.map(binary_vectorize_text) binary_val_ds = raw_val_ds.map(binary_vectorize_text) binary_test_ds = raw_test_ds.map(binary_vectorize_text) int_train_ds = raw_train_ds.map(int_vectorize_text) int_val_ds = raw_val_ds.map(int_vectorize_text) int_test_ds = raw_test_ds.map(int_vectorize_text)
Некоторые конфигурации в наборе данных для повышения производительности
Есть два важных метода, которые вы должны использовать при загрузке данных, чтобы убедиться, что ввод-вывод не становится блокирующим.
.cache()
сохраняет данные в памяти после их загрузки с диска. Это гарантирует, что набор данных не станет узким местом при обучении вашей модели. Если ваш набор данных слишком велик, чтобы уместиться в памяти, вы также можете использовать этот метод для создания производительного кеша на диске, который более эффективен для чтения, чем многие небольшие файлы..prefetch()
перекрывает предварительную обработку данных и выполнение модели во время обучения.
Вы можете узнать больше об обоих методах, а также о том, как кэшировать данные на диск, в Руководстве по производительности данных.
AUTOTUNE = tf.data.AUTOTUNE def configure_dataset(dataset): return dataset.cache().prefetch(buffer_size=AUTOTUNE) binary_train_ds = configure_dataset(binary_train_ds) binary_val_ds = configure_dataset(binary_val_ds) binary_test_ds = configure_dataset(binary_test_ds) int_train_ds = configure_dataset(int_train_ds) int_val_ds = configure_dataset(int_val_ds) int_test_ds = configure_dataset(int_test_ds)
Ура! Наконец, пришло время создать и обучить нашу модель нейронной сети.
Дляbinary
векторизованных данных мы собираемся обучить простую линейную модель набора слов:
Обучающая бинарная модель
model.fit()
возвращает объект History
, содержащий словарь со всем, что произошло во время обучения:
binary_model = tf.keras.Sequential([layers.Dense(4)]) binary_model.compile( loss=losses.SparseCategoricalCrossentropy(from_logits=True), optimizer='adam', metrics=['accuracy']) history = binary_model.fit( binary_train_ds, validation_data=binary_val_ds, epochs=10) Epoch 1/10 200/200 [==============================] - 7s 33ms/step - loss: 1.1201 - accuracy: 0.6403 - val_loss: 0.9171 - val_accuracy: 0.7769 ... Epoch 10/10 200/200 [==============================] - 1s 6ms/step - loss: 0.2921 - accuracy: 0.9495 - val_loss: 0.4917 - val_accuracy: 0.8413
Создайте график точности и потерь с течением времени для двоичной модели
model.fit()
возвращает объект History
, содержащий словарь со всем, что произошло во время обучения:
# Creating a plot of accuracy and loss over time history_dict = history.history acc = history_dict['accuracy'] val_acc = history_dict['val_accuracy'] loss = history_dict['loss'] val_loss = history_dict['val_loss'] epochs = range(1, len(acc) + 1) # "bo" is for "blue dot" plt.plot(epochs, loss, 'bo', label='Training loss') # b is for "solid blue line" plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show()
plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend(loc='lower right') plt.show()
Обучающая модель Conv1D
def create_model(vocab_size, num_labels): model = tf.keras.Sequential([ layers.Embedding(vocab_size, 64, mask_zero=True), layers.Conv1D(64, 5, padding="valid", activation="relu", strides=2), layers.GlobalMaxPooling1D(), layers.Dense(num_labels) ]) return model # vocab_size is VOCAB_SIZE + 1 since 0 is used additionally for padding. int_model = create_model(vocab_size=VOCAB_SIZE + 1, num_labels=4) int_model.compile( loss=losses.SparseCategoricalCrossentropy(from_logits=True), optimizer='adam', metrics=['accuracy']) history = int_model.fit(int_train_ds, validation_data=int_val_ds, epochs=5) Epoch 1/5 200/200 [==============================] - 12s 56ms/step - loss: ... Epoch 5/5 200/200 [==============================] - 7s 34ms/step - loss: 0.1009 - accuracy: 0.9837 - val_loss: 0.4826 - val_accuracy: 0.8194
Создайте график точности и потерь с течением времени для двоичной модели
model.fit()
возвращает объект History
, содержащий словарь со всем, что произошло во время обучения:
# Creating a plot of accuracy and loss over time history_dict = history.history acc = history_dict['accuracy'] val_acc = history_dict['val_accuracy'] loss = history_dict['loss'] val_loss = history_dict['val_loss'] epochs = range(1, len(acc)+1) # "bo" is for "blue dot" plt.plot(epochs, loss, 'bo', label='Training loss') # b is for "solid blue line" plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show()
plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Traininf and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend(loc='lower right') plt.show()
Подведем итог модели бинарного мешка слов.
print("Linear model on binary vectorized data:") print(binary_model.summary()) Linear model on binary vectorized data: Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 4) 40004 ================================================================= Total params: 40,004 Trainable params: 40,004 Non-trainable params: 0 _________________________________________________________________ None
Подведем итоги модели ConvNet.
print("ConvNet model on int vectorized data:") print(int_model.summary()) ConvNet model on int vectorized data: Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding (Embedding) (None, None, 64) 640064 _________________________________________________________________ conv1d (Conv1D) (None, None, 64) 20544 _________________________________________________________________ global_max_pooling1d (Global (None, 64) 0 _________________________________________________________________ dense_1 (Dense) (None, 4) 260 ================================================================= Total params: 660,868 Trainable params: 660,868 Non-trainable params: 0 _________________________________________________________________ None
Точность оценки сравнивает, как модель работает с тестовым набором данных:
# Evaluating the model binary_loss, binary_accuracy = binary_model.evaluate(binary_test_ds) int_loss, int_accuracy = int_model.evaluate(int_test_ds) print("Binary model accuracy: {:2.2%}".format(binary_accuracy)) print("Int model accuracy: {:2.2%}".format(int_accuracy)) 250/250 [==============================] - 7s 25ms/step - loss: 0.5181 - accuracy: 0.8146 2s - los - ETA: 250/250 [==============================] - 8s 32ms/step - loss: 0.5112 - accuracy: 0.8170 Binary model accuracy: 81.46% Int model accuracy: 81.70%
Экспорт модели
В приведенном выше коде мы применили слой TextVectorization
к набору данных перед подачей текста в модель. Если мы хотим сделать вашу модель способной обрабатывать необработанные строки (например, чтобы упростить ее развертывание), мы можем включить слой TextVectorization
в вашу модель. Для этого мы можем создать новую модель, используя только что обученные веса.
export_model = tf.keras.Sequential([ binary_vectorize_layer, binary_model, layers.Activation('sigmoid')]) export_model.compile( loss=losses.SparseCategoricalCrossentropy(from_logits=False), optimizer='adam', metrics=['accuracy']) # Test it with `raw_test_ds`, which yields raw strings loss, accuracy = export_model.evaluate(raw_test_ds) print("Accuracy: {:2.2%}".format(binary_accuracy)) 250/250 [==============================] - 9s 32ms/step - loss: 0.5181 - accuracy: 0.8146 1s - l Accuracy: 81.46%
Вывод по новым данным
Чтобы получить прогнозы для новых примеров, вы можете просто позвонить model.predict()
.
# Inferencing on new data def get_string_labels(predicted_scores_batch): predicted_int_labels = tf.argmax(predicted_scores_batch, axis=1) predicted_labels = tf.gather(raw_train_ds.class_names, predicted_int_labels) return predicted_labels inputs = [ "how do I extract keys from a dict into a list?", # python "debug public static void main(string[] args) {...}", # java ] predicted_scores = export_model.predict(inputs) predicted_labels = get_string_labels(predicted_scores) for input, label in zip(inputs, predicted_labels): print("Question: ", input) print("Predicted label: ", label.numpy()) Question: how do I extract keys from a dict into a list? Predicted label: b'python' Question: debug public static void main(string[] args) {...} Predicted label: b'java'
Полный код можно скачать снизу.
Кредиты:
Вдохновение:
Глубокие сети в моем мозгу были повреждены, когда я переживший COVID-19, что привело к мозговому туману, потере кратковременной памяти, спутанности сознания, неспособности сосредоточиться и просто по-другому чувствовать.
Затем я решил построить новые сети с помощью изучаю сети в Tensorflow и изучаю немецкую лингвистику.
Надеюсь, многие выздоравливают, удерживая всех в молитвах :)