Взгляд на мультиклассовую классификацию вопросов о переполнении стека с помощью 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 для упрощения набора данных.
  • Токенизация относится к разделению строк на токены (например, разделение предложения на отдельные слова путем разделения на пробелы).
  • Векторизация относится к преобразованию токенов в числа, чтобы их можно было передать в нейронную сеть.

Все эти задачи могут быть выполнены с TextVectorizationlayer. Вы можете узнать больше о каждом из них в 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)

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

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

  1. .cache() сохраняет данные в памяти после их загрузки с диска. Это гарантирует, что набор данных не станет узким местом при обучении вашей модели. Если ваш набор данных слишком велик, чтобы уместиться в памяти, вы также можете использовать этот метод для создания производительного кеша на диске, который более эффективен для чтения, чем многие небольшие файлы.
  2. .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 и изучаю немецкую лингвистику.
Надеюсь, многие выздоравливают, удерживая всех в молитвах :)