Набор данных Toxic Comments Kaggle — это хорошо известный набор данных и тестовый набор для классификации текста с несколькими метками. Ознакомьтесь с моим предыдущим сообщением в блоге об изучении набора данных о токсичных комментариях. В этой работе я реализую модели глубокого обучения с использованием фреймворка Tensorflow и Pytorch, а также сравниваю другие разные методологии, чтобы подойти к задаче классификации текста с несколькими метками.
Импорт и очистка набора данных
Функцию очистки текста можно найти в моей предыдущей работе над набором данных токсичных комментариев. Я удобно импортирую эти функции, чтобы упростить предварительную обработку текста. Затем я использую scikit-learn CountVectorizer для базовых вложений. Я выбрал CountVectorizer в качестве базового встраивания текста. Определенно есть более продвинутые вложения текста, которые могут лучше работать для классификации текста.
# importing dataset from local directory dataset_directory = ".../toxic-comments-datasets/" train_df = pd.read_csv(dataset_directory + "train.csv.zip", usecols = ['comment_text', 'toxic', 'severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate']) train_df = train_df.astype({'toxic':'int16', 'severe_toxic':'int16', 'obscene':'int16', 'threat':'int16', 'insult':'int16', 'identity_hate':'int16'}) train_df = train_df.dropna() # I found 5 nan rows in cleaned_text, so I just drop those rows altogether # importing custom text_cleaner from text_cleaning_functions import text_cleaner train_df['cleaner_text'] = train_df['comment_text'].map(lambda comments : text_cleaner(comments)) # create text embeddings with scikit-learn CountVectorizer LABELS = ['toxic', 'severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate'] train_text = train_df['cleaner_text'] train_labels = train_df[LABELS] train_text_countvec = CountVectorizer(max_features=10000).fit_transform(train_text) X_train, X_test, y_train, y_test = train_test_split(train_text_countvec, train_labels.values, test_size=0.2) print(X_train.shape) print(X_test.shape)
Модель TensorFlow MLP
Архитектура, которую я использую, представляет собой очень общий многослойный персептрон (MLP) с несколькими полностью связанными слоями, одним отсеваемым слоем и слоем пакетной нормализации. Топология этой архитектуры сужается от большого количества вложений N-векторов текста, который нужно классифицировать, к 6 бинарным классам задачи классификации с несколькими метками.
import tensorflow as tf from tensorflow.keras import layers l2_regularizer = tf.keras.regularizers.L2(l2=0.001) # default l2 = glorot_initializer = tf.keras.initializers.GlorotNormal(seed=32) model2 = tf.keras.Sequential([ layers.Input(shape=X_train.shape[1:], name="Input_Layer"), layers.Dense(256, activation="relu", kernel_initializer=glorot_initializer, kernel_regularizer=l2_regularizer, bias_regularizer=l2_regularizer, name="FC1"), layers.Dense(128, activation="relu", kernel_initializer=glorot_initializer, kernel_regularizer=l2_regularizer, bias_regularizer=l2_regularizer, name="FC2"), layers.Dropout(rate=0.3), layers.Dense(64, activation="relu", kernel_initializer=glorot_initializer, kernel_regularizer=l2_regularizer, bias_regularizer=l2_regularizer, name="FC3"), layers.BatchNormalization(), layers.Dense(32, activation="relu", kernel_initializer=glorot_initializer, kernel_regularizer=l2_regularizer, bias_regularizer=l2_regularizer, name="FC4"), layers.Dense(y_train.shape[1], activation="sigmoid", kernel_initializer=tf.keras.initializers.GlorotNormal(seed=3), kernel_regularizer=l2_regularizer, bias_regularizer=l2_regularizer, name="Output") ]) #compile model2.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=["binary_accuracy"])
Объяснение гиперпараметров:
`kernel_initializer` — это параметр, используемый в нейронных сетях для определения метода инициализации весов (также называемых «ядрами») слоев. Правильная инициализация веса имеет решающее значение для обучения нейронной сети, поскольку она может повлиять на скорость сходимости и окончательную производительность модели.
Существует несколько доступных методов инициализации веса, и некоторые из наиболее распространенных включают в себя:
1. Случайная инициализация: случайная инициализация весов небольшими значениями, обычно выбираемыми из равномерного или нормального распределения.
2. Инициализация Xavier/Glorot: Инициализация весов путем взятия образцов из равномерного распределения с определенным диапазоном. Этот диапазон зависит от количества входных и выходных единиц в слое. Идея инициализации Xavier состоит в том, чтобы поддерживать одинаковую дисперсию как для входных, так и для выходных активаций во время прямого прохода, что помогает при обучении глубоких сетей.
3. Инициализация He: аналогична инициализации Xavier, но использует другой диапазон для равномерного распределения, что делает его более подходящим для сетей, использующих функции активации ReLU (выпрямленная линейная единица).
`kernel_regularizer` – это параметр, используемый в нейронных сетях для применения регуляризации к весам (также называемым "ядрами") слоев. Регуляризация — это метод, используемый для предотвращения переобучения путем добавления штрафного члена к функции потерь, что препятствует изучению моделью слишком сложных или зашумленных шаблонов в обучающих данных. Основная цель регуляризации — улучшить способность модели к обобщению, чтобы она хорошо работала с невидимыми данными.
Существует два основных типа регуляризации, применяемых к весам:
1. Регуляризация L1 (лассо): добавляет абсолютные значения весов к функции потерь, масштабированные с помощью коэффициента регуляризации (обычно обозначаемого лямбда или альфа). Этот тип регуляризации способствует разреженности модели, поскольку некоторые веса могут быть сведены к нулю.
2. Регуляризация L2 (хребет): Добавляет квадраты значений весов к функции потерь, масштабированные с помощью коэффициента регуляризации. Регуляризация L2 не поощряет большие значения веса, но не обязательно сводит их к нулю. Обычно используется в нейронных сетях.
Обучение скомпилированной модели NN
# this step is necessary to convert the sparse matrix to a sparse tensor def convert_sparse_matrix_to_sparse_tensor(X): coo = X.tocoo() indices = np.mat([coo.row, coo.col]).transpose() return tf.SparseTensor(indices, coo.data, coo.shape) train_tensor = convert_sparse_matrix_to_sparse_tensor(X_train) train_tensor = tf.sparse.reorder(train_tensor) test_tensor = convert_sparse_matrix_to_sparse_tensor(X_test) test_tensor= tf.sparse.reorder(test_tensor) # train for 10 epochs history = model2.fit(train_tensor, y_train, validation_data=(test_tensor, y_test), epochs=10)
Модель Pytorch MLP
Для сравнения и обучения я создал эквивалентную нейронную сеть с PyTorch.
import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.nn import init class CustomModel(nn.Module): def __init__(self, input_shape, output_shape): super(CustomModel, self).__init__() self.fc1 = nn.Linear(input_shape, 256) self.fc2 = nn.Linear(256, 128) self.fc3 = nn.Linear(128, 64) self.bn = nn.BatchNorm1d(64) self.fc4 = nn.Linear(64, 32) self.fc5 = nn.Linear(32, output_shape) self.dropout = nn.Dropout(p=0.3) self.init_weights() def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.dropout(x) x = F.relu(self.fc3(x)) x = self.bn(x) x = F.relu(self.fc4(x)) x = torch.sigmoid(self.fc5(x)) return x def init_weights(self): for m in self.modules(): if isinstance(m, nn.Linear): init.xavier_normal_(m.weight, gain=1) init.constant_(m.bias, 0) # Apply L2 regularization to weights and biases # Note that the equivalent is handled in the optimizer in PyTorch # See the optimizer definition below input_shape = X_train.shape[1] output_shape = y_train.shape[1] model = CustomModel(input_shape, output_shape) # Define an optimizer with L2 regularization (weight_decay) optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.001) criterion = nn.BCELoss() # Use Binary Cross Entropy Loss for binary classification optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.001) # Define the optimizer with L2 regularization # convert the training and test data to usable torch tensors X_train_dense = X_train.toarray() X_test_dense = X_test.toarray() X_train_tensor = torch.tensor(X_train_dense, dtype=torch.float32) y_train_tensor = torch.tensor(y_train, dtype=torch.float32) X_test_tensor = torch.tensor(X_test_dense, dtype=torch.float32) y_test_tensor = torch.tensor(y_test, dtype=torch.float32) from torch.utils.data import TensorDataset, DataLoader batch_size = 64 train_dataset = TensorDataset(X_train_tensor, y_train_tensor) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) test_dataset = TensorDataset(X_test_tensor, y_test_tensor) test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False) epochs = 10 for epoch in range(epochs): model.train() # Set the model to training mode # Training loop for i, (inputs, targets) in enumerate(train_loader): optimizer.zero_grad() # Reset the gradients outputs = model(inputs) # Forward pass loss = criterion(outputs, targets) # Compute the loss loss.backward() # Backward pass optimizer.step() # Update the weights # Validation loop model.eval() # Set the model to evaluation mode validation_loss = 0 for inputs, targets in test_loader: with torch.no_grad(): outputs = model(inputs) # Forward pass loss = criterion(outputs, targets) # Compute the loss validation_loss += loss.item() validation_loss /= len(test_loader) print(f"Epoch {epoch+1}/{epochs}, Validation Loss: {validation_loss:.4f}")
Заключение
Различия между Tensorflow и Pytorch
- Первое отличие заключается в API и синтаксисе кода для реализации обоих пакетов. Типичный рабочий процесс для реализации моделей глубокого обучения:
создать модель → скомпилировать → подогнать/обучить → предсказать.
Я считаю, что Tensorflow API немного более интуитивно понятен, однако я м уверен, что это дело вкуса. - Pytorch был значительно быстрее при обучении по сравнению с Tensorflow.
- В Pytorch есть больше кода для реализации той же модели NN.
Точность и потери обеих моделей были очень близки, что неудивительно, поскольку архитектуры НС были одинаковыми. Разница в первую очередь в реализации и скорости обработки.