В первую неделю года работа шла медленно, поэтому я решил впервые попробовать свои силы в конкурсе Kaggle (да, я знаю, что опаздываю на вечеринку). После регистрации и осмотра я оказался на Задаче классификации токсичных комментариев Jigsaw. Если вы просматриваете только Medium и не знаете, что означает токсичный комментарий, вот и все:
В этом сообщении описывается моя (вроде) успешная попытка обучить ConvNet классифицировать комментарий по одному или нескольким типам токсичности: угроза, непристойность, оскорбление и т. Д. (Всего 6 классов). По сравнению с лидером log-loss 0,022, моя простая модель набрала ~ 0,055 - Не удивительно, но довольно хорошо для ‹100 строк кода с Keras! В конце поста я также упомяну некоторые мета-уроки о моем первом опыте соревновательного машинного обучения :-).
Предварительная обработка текста
Данные для обучения были предоставлены в виде файла CSV с ~ 100 тыс. Строк. Каждая строка содержала уникальный идентификатор, текст и 1/0 для каждого класса, обозначающего классификацию.
from backports import csv import numpy as np # Helps in reading long texts csv.field_size_limit(sys.maxsize) def get_texts_and_targets(filename): texts = [] targets = [] with io.open(filename, encoding='utf-8') as csvfile: readCSV = csv.reader(csvfile) for i, row in enumerate(readCSV): if i == 0: # Header row continue texts.append(row[1].strip().encode('ascii', 'replace')) targets.append(np.array([float(x) for x in row[2:]])) print("Total number of texts: %s" % len(texts)) return texts, targets
Будучи новичком в области глубокого обучения, я начал писать классный код предварительной обработки в NLTK. Оказывается, Keras предоставляет удобный класс Tokenizer для решения всех основных задач, таких как удаление специальных символов и преобразование в нижний регистр. Так что я поленился и просто использовал это:
from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences # Max number of input words in any sample MAX_SEQUENCE_LENGTH = 200 VALIDATION_SPLIT = 0.1 def get_datasets(texts, targets, tokenizer=None): if tokenizer is None: tokenizer = Tokenizer() tokenizer.fit_on_texts(texts) sequences = tokenizer.texts_to_sequences(texts) word_index = tokenizer.word_index data = pad_sequences(sequences, maxlen=MAX_SEQUENCE_LENGTH) targets = np.asarray(targets) indices = np.arange(data.shape[0]) np.random.shuffle(indices) data = data[indices] targets = targets[indices] nb_validation_samples = int(VALIDATION_SPLIT * data.shape[0]) x_train = data[:-nb_validation_samples] y_train = targets[:-nb_validation_samples] x_val = data[-nb_validation_samples:] y_val = targets[-nb_validation_samples:] return tokenizer, word_index, x_train, y_train, x_val, y_val
Вложения слов
Для встраивания слов я использовал 100-размерные векторы Glove Twitter. Другими вариантами были предварительно обученные векторы из Word2Vec или Fasttext. Я попробовал Word2Vec, и, как и другие, Glove у меня работала лучше. Мне не удалось применить Fasttext, что является многообещающей перспективой - в основном потому, что Fasttext имеет векторы для дробей слов, и это может быть полезно для слов с орфографическими ошибками (или других терминов OOV), которые часто встречаются в комментариях.
Если вы, как и я, привыкли к пакету Python Gensim, вы можете использовать их скрипт для преобразования вложений Glove в формат word2vec. Как только это будет сделано, векторы можно будет довольно легко загрузить:
from gensim.models import KeyedVectors def load_glove_model(): word2vec = KeyedVectors.load_word2vec_format( os.path.join(WORD2VEC_FOLDER, 'word2vec_twitter_glove.txt'), binary=False) return word2vec
Слой внедрения можно определить в Keras как:
def get_embedding_layer(word_index, gensim_model): embedding_dim = len(gensim_model.wv['apple']) embedding_matrix = np.zeros((len(word_index) + 1, embedding_dim)) for word, i in word_index.items(): if word in gensim_model.wv.vocab: embedding_matrix[i] = gensim_model.wv[word] embedding_layer = Embedding(len(word_index) + 1, embedding_dim, weights=[embedding_matrix], input_length=MAX_SEQUENCE_LENGTH, trainable=True) return embedding_layer
Обратите внимание на часть trainable = True в приведенном выше фрагменте - мы могли бы использовать вложения как они есть, но их тонкая настройка во время обучения изменяет их семантическое местоположение для нашего конкретного приложения. По сути, это форма трансферного обучения.
CNN
В этот момент вам может быть интересно, почему я использовал CNN для задачи понимания текста. Во-первых, потому что я никогда не тренировал CNN в Керасе (а я хотел). Но что ж… эта статья дает хорошее представление о том, как одномерные свертки могут быть полезны для обработки текста. В одномерных свертках вы, по сути, перебираете участки слов, а не пиксели (подумайте о скользящем окне слов, например, чтении). Для визуального восприятия (и чтобы сделать этот пост более привлекательным) я добавил это полностью оригинальное изображение:
CNN не особенно хороши для большинства задач НЛП, поскольку они теряют в последовательном потоке информации. Но поскольку цель здесь сводится к распознаванию «блоков» настроений, разбросанных по тексту, они работают прилично хорошо!
Мы используем 2 блока Convolutional + Max-Pooling, за которыми следуют 3 плотных слоя:
from keras.layers import * from keras.models import Model N_TARGET_CLASSES = 6 def get_convnet_model(embedding_layer): sequence_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32') embedded_sequences = embedding_layer(sequence_input) x = Conv1D(128, 5, activation='relu')(embedded_sequences) x = MaxPooling1D(5)(x) x = Conv1D(128, 5, activation='relu')(x) x = MaxPooling1D(5)(x) x = Flatten()(x) x = Dense(128, activation='relu')(x) x = Dense(64, activation='relu')(x) preds = Dense(N_TARGET_CLASSES, activation='sigmoid')(x) model = Model(sequence_input, preds) return model
Сигмоид (а не Softmax) является здесь более подходящей целевой функцией, поскольку каждый образец может принадлежать нескольким классам (комментарий может быть оскорблением и непристойным одновременно) .
Я попытался использовать Dropout для регуляризации, но, похоже, это не помогло с оценками. Поэтому я отказался от этой идеи.
Обучение
Я обнаружил, что Adagrad (с его настройками по умолчанию) лучше всего подходит для этого варианта использования. В Keras есть поддержка различных оптимизаторов, и я не пробовал настраивать такие параметры, как затухание (что могло бы еще больше уменьшить ошибку). Краткий обзор различных техник оптимизации можно найти в потрясающем сообщении в блоге Рудера.
texts, targets = get_texts_and_targets('train.csv') tokenizer, word_index, x_train, y_train, x_val, y_val = get_datasets(texts, targets) word2vec = load_word2vec_model() embedding_layer = get_embedding_layer(word_index, word2vec) model = get_convnet_model(embedding_layer) model.compile(loss='binary_crossentropy', optimizer='adagrad', metrics=['accuracy']) model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=2, batch_size=32, verbose=1)
Цель binary_crossentropy - это версия Keras потери журнала (так что вы получите то же значение). Поскольку я использовал предварительно обученные векторы и набор данных из ~ 85 тыс. Экземпляров, 2 эпохи достаточно (на основе журналов Keras, потеря, похоже, выходит на плато в последней половине самой второй эпохи).
Для краткости я не буду писать код, который использовал для вывода и построения выходного файла (вы можете легко сделать это с помощью model.predict). Общие потери журнала, вычисленные Керасом, оказались около 0,055, что неплохо, учитывая этот подход, основанный на использовании одной модели.
А теперь несколько случайных размышлений о потере девственности Kaggle:
- Сложный не всегда лучше: я начал с Адама, но Адаград оказался лучше. Существует множество дискуссий на StackOverflow о том, как Adagrad временами работает лучше, чем его расширение Adadelta. В любом случае вначале не стоит беспокоиться о точном оптимизаторе, поскольку большинство из них обычно сходятся к достаточно хорошему значению.
- Сначала начните с больших изменений: это не пришло мне в голову естественным образом, и я признаю, что должно было прийти. До сих пор я в основном копировал фрагменты TensorFlow из других сообщений в блогах, поэтому мне никогда не приходилось настраивать свои собственные нейронные сети. Для настройки гиперпараметров (когда вы не используете что-то вроде hyperopt) всегда лучше сначала поиграться с теми изменениями, которые окажут максимальное влияние: например, Количество слоев ›Значение момента в оптимизаторе. Возможно, это не всегда так, но это хорошее практическое правило.
- Ансамбли - король (для лучших результатов): большинство лидеров Kaggle используют структуры ансамблей (например, XGBoost) или усредняют результаты нескольких сложных моделей (кто-то на досках обсуждений использовал LSTM + CNN).
- Kaggle - хорошее упражнение в обучении: хотя существует обоснованный скептицизм по поводу того, насколько опыт Kaggle актуален для отраслевой науки о данных, это, несомненно, хороший опыт обучения. Испытание нескольких алгоритмов, чтение форумов и просто тонкая настройка параметров (и наблюдение за журналами обучения) многое расскажут о том, как глубокое обучение ведет себя на практике. Фактически, всего за один раз в Kaggle я выучил довольно много практических правил, о которых я бы не знал иначе (например, размер партии = 32 - обычно хорошее место для начала. Для меня 16 был слишком медленным, и 128 никогда не сходился).
- Это вызывает привыкание: может быть, дело только в мне, но я не мог удержаться от того, чтобы снова и снова заглядывать в журналы обучения, чтобы увидеть, как меняются значения потерь для каждого эксперимента. Это в первую очередь причина того, что я не буду использовать Kaggle во время серьезной работы в офисе.
- Для глубокого обучения не нужна теория: Мои знания о глубоком обучении улучшились за последние несколько месяцев, но я все еще обнаружил, что использую классы / методы, не имея представления о том, как именно они работают (например, вся концепция одномерных сверток). В каком-то смысле это хорошо, поскольку делает обучение доступным для всех, у кого есть хороший компьютер и знание Python. При этом знание теории полезно для того, чтобы начать движение в правильном направлении и иметь возможность применять алгоритмы машинного обучения к неочевидным (читай: доступным в Интернете) сценариям использования.
Общепринятое мнение гласит, что я должен сделать призыв к действию прямо сейчас. Но что меня беспокоит, так это то, что есть некоторая возможная шутка о том, как плохие комментарии предоставят мне больше данных для этой модели, но я не могу понять это :-(