10 июня 2017 г. Митап победителей открытого курса в Московском офисе Mail.ru Group. Видео-трансляция.
Для многих компаний важно знать, что о них пишут в интернете. Поэтому одна из их основных задач – классификация отзывов в интернете на позитивные и негативные, на языке машинного обучения это будет означать решение задачи бинарной классификации.
Данные могут быть взяты из нескольких источников:
На этом семинаре мы будем использовать данные IMDb и RT, в которых оценки 1-2 – негативные отзывы (0), а оценки 4-5 – позитивные(1).
Поскольку эта простая задача бинарной классификации, ее можно решать массой разных способов:
Мы рассмотрим только решение, основанное на нейронных сетях, а туториалы по остальным подходам вы можете найти в данном репозитории, видео также прилагается.
Скачать можно отсюда.
import os
import numpy as np
from sklearn.model_selection import train_test_split
# инициализируем пути к файлам
BASE_DIR = '../'
TEXT_DATA_DIR = BASE_DIR + 'data/'
TEXT_DATA_FILE = "movie_reviews.csv"
HEADER = True
# инициализируем параметры
VALIDATION_SPLIT = 0.1
RANDOM_SEED = 42
def load_data():
# функция для загрузки данных
x = []
y = []
with open(os.path.join(TEXT_DATA_DIR, TEXT_DATA_FILE), "r", encoding="utf-8") as f:
if HEADER:
_ = next(f)
for line in f:
temp_y, temp_x = line.rstrip("\n").split(",", 1)
x.append(temp_x)
y.append(temp_y)
return x, y
data, labels = load_data()
labels = np.asarray(labels, dtype='int8')
# разделение выборки на тренировочную и тестовую
data_train, data_test, labels_train, labels_test = \
train_test_split(data, np.asarray(labels, dtype='int8'),
test_size=VALIDATION_SPLIT, random_state=RANDOM_SEED, stratify=labels)
В "сыром" виде нельзя подавать данные в модель, их нужно правильно преобразовать. Для начала рассмотрим нейронные сети на словах.
Давайте представим каждое слово как вектор чисел, и чем меньше векторное расстояние между данными словами, тем сильнее они похожи. Для такого представления существует несколько подходов, но самые известные из них – это word2vec и glove.
Word2Vec – это простая нейронная сеть с одним скрытым слоем, которая имеет две спецификации:
Источник: Efficient Estimation of Word Representations in Vector Space
В отличие от word2vec, GloVe – это алгоритм обучения "без учителя" для получения векторных представлений для слов. Обучение проводится по совокупной статистике матрицы совпадений по словам, а полученные представления показывают интересные линейные подструктуры векторного пространства слов.
Источник: GloVe: Global Vectors for Word Representations
Word2Vec и GloVe изучают геометрические кодировки (векторы) слов из их информации о совпадении (как часто они появляются вместе в больших текстовых корпусах). Они отличаются тем, что word2vec является «предиктивной» моделью, тогда как GloVe является моделью на основе «подсчета статистик».
Пример векторной репрезентации:
Источник: Making Sense of Everything with words2map Данные векторные репрезентации принято называть эмбеддингами. Эмбеддинги можно натренировать самостоятельно на вашем корпусе текстов (например, с помощью библотеки gensim), а также можно взять уже предобученые модели.
В этом воркшопе мы будем использовать предобученые вектора, так как наш корпус текста не достаточно большой для тренировки своей модели. Для скорости вычислений воспользуемся векторами glove размерностью 50.
Преобразуем слова в векторы.
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
# инициализируем параметры словаря и эмбеддингов
MAX_NB_WORDS = 10000
MAX_SEQUENCE_LENGTH = 40
print("Предложение до предобработки:\n", data_train[0])
# с помощью Tokenizer создаем словарь
tokenizer = Tokenizer(num_words=MAX_NB_WORDS, filters='#$%&()*+-/:;<=>@[\\]^{|}~\t\n,.!"')
tokenizer.fit_on_texts(data_train)
# заменяем слова на их индексы в нашем словаре
X_train = tokenizer.texts_to_sequences(data_train)
X_test = tokenizer.texts_to_sequences(data_test)
print("Предложение после замены слов на индексы:\n", X_train[0])
# обрезаем каждое предложение приводим к нужной длинне
X_train = pad_sequences(X_train, maxlen=MAX_SEQUENCE_LENGTH)
X_test = pad_sequences(X_test, maxlen=MAX_SEQUENCE_LENGTH)
print("Предложение после приведения к единой длинне:\n", X_train[0])
Using TensorFlow backend.
Предложение до предобработки: "Filled with sentimentality, pretensions, unfulfilled ambitions and a host of dull characters faced with life threatening problems that verge on the ludicrous." Предложение после замены слов на индексы: [953, 13, 4013, 8235, 6794, 3, 2, 2902, 4, 670, 99, 2275, 13, 108, 4091, 714, 11, 7471, 19, 1, 2508] Предложение после приведения к единой длинне: [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 953 13 4013 8235 6794 3 2 2902 4 670 99 2275 13 108 4091 714 11 7471 19 1 2508]
Объект Tokenizer
хранит в себе всю информацию про наш словарь. Нужно найти индекс слова "super" и сколько раз оно встречалось в нашей выборке.
# ЗАМЕНИТЕ 0 НА ПРАВИЛЬНЫЙ ОТВЕТ
print("Индекс слова 'super' – {}.".format(0))
print("Слова 'super' встречалось {} раз.".format(0))
Индекс слова 'super' – 0. Слова 'super' встречалось 0 раз.
print("Индекс слова 'super' – {}.".format(tokenizer.word_index['super']))
print("Слова 'super' встречалось {} раз.".format(tokenizer.word_counts['super']))
# путь к файлу с эмбеддингами
EMBEDDINGS_DIR = BASE_DIR + 'embeddings'
EMBEDDINGS_FILE = 'glove.6B.50d.txt'
EMBEDDING_DIM = 50
# выберем только первые 10000 слов из нашего словаря
first_10000 = {k: v for k, v in tokenizer.word_index.items() if v < 10000}
# загрузим вектора эмбеддингов
embeddings = {}
with open(os.path.join(EMBEDDINGS_DIR, EMBEDDINGS_FILE)) as f:
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings[word] = coefs
del values, word, coefs, line
print("Количество слов в векторном представлении:", len(embeddings))
Количество слов в векторном представлении: 400000
Посмотреть, сколько из наиболее встречаемых 10000 слов из нашего словаря нет в словаре эмбеддингов. Как можно данное к-во уменьшить?
# ВАШ КОД
len(set(first_10000.keys()).difference(embeddings.keys()))
Добавить больше фильтров в аргумент `filters` объекта `Tokenizer`.
# подготовим матрицу эмбеддингов, где по строкам будут индексы слов
embedding_matrix = np.zeros((tokenizer.num_words, EMBEDDING_DIM))
for word, i in first_10000.items():
embedding_vector = embeddings.get(word)
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
Рекуррентные нейронные сети помогают уловить/понять закономерность, которая зависит от времени или порядка. Например, когда мы пытаемся классифицировать какой-то эпизод из фильма, то нам важно знать что было пару эпизодов ранее, или чтобы понять смысл определенного слова, нам нужно знать контекст, который был до него.
Простая рекуррентная нейронная сеть имеет следующее математическое представление:
$$\large h_t = \phi(Wx_t + Uh_{t-1})$$
$$\large y = Vh_t$$
Илюстрация к формуле:
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.layers import Embedding
from keras.layers import SimpleRNN
from keras.callbacks import ModelCheckpoint, TensorBoard, EarlyStopping
NAME = "simple_rnn"
# инициализируем слой эмбеддингов
embedding_layer = Embedding(tokenizer.num_words,
EMBEDDING_DIM,
weights=[embedding_matrix],
input_length=MAX_SEQUENCE_LENGTH,
trainable=False)
model = Sequential()
model.add(embedding_layer)
model.add(SimpleRNN(100))
model.add(Dense(1))
model.add(Activation('sigmoid'))
# инициализируем коллбеки
# автоматическое построение кривых обучения
callback_1 = TensorBoard(log_dir='./logs/logs_{}'.format(NAME), histogram_freq=0,
write_graph=False, write_images=False)
# остановка обучения при не увеличении точности в течении 5 эпох
callback_2 = EarlyStopping(monitor='val_acc', min_delta=0, patience=5, verbose=0, mode='auto')
# сохранение лучшей модели
callback_3 = ModelCheckpoint("../models/model_{}.hdf5".format(NAME), monitor='val_acc',
save_best_only=True, verbose=1)
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
#model.fit(X_train, labels_train, validation_data=[X_test, labels_test],
# batch_size=1024, epochs=100, callbacks=[callback_1, callback_2, callback_3])
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_2 (Embedding) (None, 40, 50) 500000 _________________________________________________________________ simple_rnn_2 (SimpleRNN) (None, 100) 15100 _________________________________________________________________ dense_2 (Dense) (None, 1) 101 _________________________________________________________________ activation_2 (Activation) (None, 1) 0 ================================================================= Total params: 515,201 Trainable params: 15,201 Non-trainable params: 500,000 _________________________________________________________________
Если натренировать данную модель, то точность на тренировочной выборке получится 77.13%, а на валидации – 75.60%.
LSTM имеет ряд приемуществ над простой рекуррентной нейронной сетью. LSTM умеет хранить нужную информацию про определенный объект и не обращать внимание на неактуальную информацию. Например, сцена без упоминания главного героя не будет менять информацию про него и, наоборот, при упоминании она будет фокусироваться.
Добавление механизма забывания. Если эпизод заканчивается, например, модель должна забыть текущее местоположение, время суток и сбросить любую информацию о конкретной сцене. Однако если персонаж умирает в эпизоде, сеть должна должна продолжать помнить, что он больше не жив. Таким образом, мы хотим, чтобы модель изучила отдельный механизм забывания/запоминания: когда появляются новые входные данные, она должна знать, какие факты сохранить или выбросить.
Добавление механизма сохранения. Когда модель увидит новый эпизод, ей необходимо решить, стоит ли использовать и сохранять какую-либо информацию о ней. Ты увидел какой-то новый мем вконтакте, но зачем его помнить?
Поэтому когда приходит новый вход, модель сначала забывает долгосрочную информацию, которая, как она решает, больше не нужна. Затем она узнает, какую часть новых данных стоит использовать, и сохраняет ее в своей долгострочной памяти.
Фокусировка с долгосрочной памяти в рабочую память. Наконец, модель должна узнать, какие части ее долговременной памяти сейчас полезны. Например, возраст героя может быть полезной информацией для сохранения в долгосрочной перспективе (дети с большей вероятностью будут ползать, взрослые скорее всего будут работать), но, вероятно, не имеет значения, если он не находится в текущей сцене. Таким образом, вместо того чтобы использовать полную долгосрочную память все время, она узнает, на каких частях стоит сосредоточиться.
То есть преимущество LSTM над RNN в том, что RNN может только перезаписывать память, а LSTM более гибкая в этом плане и может хранить долгосрочную информацию, фокусируясь на нужных ее частях.
Рассмотрим теперь это все со стороны математики.
Начнем с долгосрочной памяти. Во-первых, нам нужно знать, какие фрагменты долгосрочной памяти продолжать помнить, а какие забывать, поэтому нам нужно на основе нового входа и нашей рабочей памяти понять, какая часть долгосрочной должна сохраниться.
Таким образом, мы на выходе получаем значение от 0 до 1, где 0 – мы все забываем, 1 – все помним.
Теперь необходимо решить, какую новую информацию запомнить и какую ее часть добавить в долгосрочную:
Данный шаг состоит из двух частей: первая – это какую именно информацию мы хотим обновить ($ \large i_t $); а вторая – кандидат на добавление в долгосрочную память ( $\large \tilde{C_t}$).
Теперь мы должны обновить нашу долгосрочную память:
После того как мы обновили нашу долгосрочную память, необходимо сфокусироваться на нужной информации для конкретного примера.
Простыми словами, если мы фокусируемся на какой-то информации, то активация сигмоиды возвращает 1, а если нам какая-то информация сейчас не нужна, то мы возращаем 0.
Давайте рассмотрим игрушечный пример для закрепления понимания. Будем тренироваться на покемонах :)
Что "думает" полносвязная нейронная сеть:
Что "думает" простая рекуррентная сеть:
Как видно из рисунка, рекуррентная сеть помнит, что случилось пару секунд назад, и может примерно понять, что стало причиной появления воды в следующем кадре.
Что "думает" LSTM:
LSTM вспоминает, что было в предыдущем эпизоде, а также подтягивает долгосрочную память и фокусируется только на нужной для конкретного эпизода информации.
Источники:
from keras.layers import LSTM
# инициализируем слой эмбеддингов
NAME = "simple_lstm"
embedding_layer = Embedding(tokenizer.num_words,
EMBEDDING_DIM,
weights=[embedding_matrix],
input_length=MAX_SEQUENCE_LENGTH,
trainable=False)
model = Sequential()
model.add(embedding_layer)
model.add(LSTM(100))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
#model.fit(X_train, labels_train, validation_data=[X_test, labels_test],
# batch_size=1024, epochs=100, callbacks=[callback_1, callback_2, callback_3])
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_4 (Embedding) (None, 40, 50) 500000 _________________________________________________________________ lstm_2 (LSTM) (None, 100) 60400 _________________________________________________________________ dense_4 (Dense) (None, 1) 101 _________________________________________________________________ activation_4 (Activation) (None, 1) 0 ================================================================= Total params: 560,501 Trainable params: 60,501 Non-trainable params: 500,000 _________________________________________________________________
Параметров в простой LSTM практически в 4 раза больше, математическое подверждение данному факту можно увидеть из формул выше.
При замене простой RNN на LSTM точность на тренировочной выборке возросла до 82.49%, а на валидации – до 77.71%.
Теперь давайте попробуем улучшить нашу простую модель LSTM, добавив в нее следующие модификации (к-во параметров не должно измениться):
1. Дропауты (меньше переобучаемся на определенные слова). 2. Маскинг (данный параметр стоит добавить в инициализацию эмбеддингов, чтобы функция потерь не учитывала 0, когда наш отзыв меньше максимальной длинны). 3. Регуляризация (этот подход часто работает, но не в случае с LSTM, так как l1/l2 регуляризация для предотвращения взрывания градиентов, но ячейка LSTM построена таким образом, чтобы этого взрывания не было, так что использование l1/l2 регуляризации нецелесообразно и ухудшает результаты).
from keras.layers import Dropout
# инициализируем слой эмбеддингов
NAME = "modified_lstm"
embedding_layer = Embedding(tokenizer.num_words,
EMBEDDING_DIM,
weights=[embedding_matrix],
input_length=MAX_SEQUENCE_LENGTH,
trainable=False,
mask_zero=True)
model = Sequential()
model.add(embedding_layer)
model.add(Dropout(0.2))
model.add(LSTM(100, dropout=0.1, recurrent_dropout=0.1))
model.add(Dropout(0.2))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
#model.fit(X_train, labels_train, validation_data=[X_test, labels_test],
# batch_size=1024, epochs=100, callbacks=[callback_1, callback_2, callback_3])
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_6 (Embedding) (None, 40, 50) 500000 _________________________________________________________________ dropout_3 (Dropout) (None, 40, 50) 0 _________________________________________________________________ lstm_4 (LSTM) (None, 100) 60400 _________________________________________________________________ dropout_4 (Dropout) (None, 100) 0 _________________________________________________________________ dense_6 (Dense) (None, 1) 101 _________________________________________________________________ activation_6 (Activation) (None, 1) 0 ================================================================= Total params: 560,501 Trainable params: 60,501 Non-trainable params: 500,000 _________________________________________________________________
Данная модель показала себя ожидаемо лучше: на тренировочной выборке – 78.99%, на валидационной – 79.81%.
Варианты улучшения точности для LSTM модели в задаче сентимент анализа:
Больше про реализацию различных видов LSTM можно посмотреть в этом докладе.
Предобработка данных для сверточных нейронных сетей на словах ничем не отличается от предобработки для LSTM. Разве что на небольших данных лучше натренировать свои эмбеддинги, а не использовать заранее предобученные.
from keras.layers import Conv1D, MaxPooling1D, GlobalMaxPooling1D
# инициализируем слой эмбеддингов
NAME = "words_cnn"
EMBEDDING_DIM = 50
# initialize model
model = Sequential()
model.add(Embedding(tokenizer.num_words, EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH, trainable=True))
model.add(Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid"))
model.add(Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid"))
model.add(Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid"))
model.add(GlobalMaxPooling1D())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(100, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
#model.fit(X_train, labels_train, validation_data=[X_test, labels_test],
# batch_size=1024, epochs=100, callbacks=[callback_1, callback_2, callback_3])
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_8 (Embedding) (None, 40, 50) 500000 _________________________________________________________________ conv1d_4 (Conv1D) (None, 37, 100) 20100 _________________________________________________________________ conv1d_5 (Conv1D) (None, 34, 100) 40100 _________________________________________________________________ conv1d_6 (Conv1D) (None, 31, 100) 40100 _________________________________________________________________ global_max_pooling1d_2 (Glob (None, 100) 0 _________________________________________________________________ dense_10 (Dense) (None, 100) 10100 _________________________________________________________________ dropout_6 (Dropout) (None, 100) 0 _________________________________________________________________ dense_11 (Dense) (None, 100) 10100 _________________________________________________________________ dense_12 (Dense) (None, 1) 101 ================================================================= Total params: 620,601 Trainable params: 620,601 Non-trainable params: 0 _________________________________________________________________
На наших данных результат сверточной нейронной сети на тренировочной выборке – 81.75, на валидации – 79.88% (причем всего за 2 итерации). И дальше идет сильное переобучение. На небольших данных сверточная сеть очень склонна к переобучению.
Существует два основных вида препроцессинга для нейронных сетей на символах:
Для начала разберем, какие символы включать. Включать символы можно любые, но по общей практике для английского языка используют 70 символов: буквы нижнего регистра, цифры и пунктуация. В зависимости от задачи или языка можно варьировать число символов.
Рассмотрим пример с эмбеддингами с помощью небольшого задания.
Создать словарь из 70 символов и заменить в нашей выборке символы на их индексы в словаре. Подсказка: используйте библиотеку string
и метод from keras.preprocessing.sequence import pad_sequences
.
import string
from keras.preprocessing.sequence import pad_sequences
def create_vocab_set():
#1. ВАШ КОД
return vocab, vocab_size
def text2sequence(text, vocab):
temp = []
#2. ВАШ КОД
return temp
vocab, vocab_size = create_vocab_set()
X_train = text2sequence(data_train, vocab)
X_test = text2sequence(data_test, vocab)
#3. ВАШ КОД
X_train =
X_test =
1. alphabet = (list(string.ascii_lowercase) + list(string.digits) +
list(string.punctuation) + [' ', '\n'])
vocab_size = len(alphabet)
vocab = {}
for ix, t in enumerate(alphabet):
vocab[t] = ix + 1
2. for review in text:
temp.append([])
for i in review:
char = vocab.get(i,0)
if char != 0:
temp[-1].append(char)
3. X_train = pad_sequences(X_train, maxlen=MAX_SEQUENCE_LENGTH, value=0)
X_val = pad_sequences(X_val, maxlen=MAX_SEQUENCE_LENGTH, value=0)
Такой же подход, как и для сверток на словах. Мы случайно инициализируем веса эмбеддингов и обновляем их во время обучения модели.
Мы должны поэкспериментировать с ними и попытаться найти лучшую архитектуру для нашей задачи.
В GlobalMaxPooling1D мы пытаемся выбрать самую важную фичу для каждого фильтра и передать ее на полносвязный слой.
Это полносвязные слои с дропаутом между ними для уменьшения переобучения.
from keras.layers import GlobalMaxPooling1D
NAME = "char_cnn_emb"
EMBEDDING_DIM = 50
# инициализируем модель
model = Sequential()
model.add(Embedding(vocab_size+1, EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH, trainable=True))
model.add(Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid"))
model.add(Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid"))
model.add(Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid"))
model.add(GlobalMaxPooling1D())
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(100, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
#model.fit(X_train, labels_train, validation_data=[X_test, labels_test],
# batch_size=1024, epochs=100, callbacks=[callback_1, callback_2, callback_3])
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_11 (Embedding) (None, 40, 50) 3550 _________________________________________________________________ conv1d_13 (Conv1D) (None, 37, 100) 20100 _________________________________________________________________ conv1d_14 (Conv1D) (None, 34, 100) 40100 _________________________________________________________________ conv1d_15 (Conv1D) (None, 31, 100) 40100 _________________________________________________________________ global_max_pooling1d_5 (Glob (None, 100) 0 _________________________________________________________________ dense_19 (Dense) (None, 100) 10100 _________________________________________________________________ dropout_9 (Dropout) (None, 100) 0 _________________________________________________________________ dense_20 (Dense) (None, 100) 10100 _________________________________________________________________ dense_21 (Dense) (None, 1) 101 ================================================================= Total params: 124,151 Trainable params: 124,151 Non-trainable params: 0 _________________________________________________________________
Точность данной модели на валидации – 77.76%, что примерно равняется точности простой LSTM.
Главный недостаток данного подхода в том, что мы никак не учитываем порядок слов (выполняются нелинейные преобразования, а потом выбираются лучшие признаки, и на них уже применяется несколько полносвязных слоев).
Теперь рассмотрим второй подход к символьным моделям – One-Hot-Encoding (OHE). Допустим у нас есть словарь из трех символов "а", "б", "в". OHE представление "абва" будет $$а – 0 0 \\ б – 1 0 \\ в – 0 1 \\ а – 0 0$$
Для реализации воспользуемся дополнительными функциями из API Keras и TensorFlow.
import tensorflow as tf
# ohe функция
def ohe(x, sz):
return tf.to_float(tf.one_hot(x, sz, on_value=1, off_value=0, axis=-1))
from keras.models import Model
from keras.layers import Input, Lambda
from keras.layers import MaxPooling1D
NAME = "char_cnn_ohe"
# инициализация входа
in_sentence = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int64')
# Lambda слой для ohe преобразования
embedded = Lambda(ohe, output_shape=lambda x: (x[0], x[1], vocab_size), arguments={"sz": vocab_size})(in_sentence)
block = embedded
# свертки с MaxPooling
for i in range(3):
block = Conv1D(activation="relu", filters=100, kernel_size=4, padding="valid")(block)
if i == 0:
block = MaxPooling1D(pool_size=5)(block)
# LSTM ячейка
block = LSTM(128, dropout=0.1, recurrent_dropout=0.1)(block)
block = Dense(100, activation='relu')(block)
block = Dense(1, activation='sigmoid')(block)
# собираем модель
model = Model(inputs=in_sentence, outputs=block)
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
#model.fit(X_train, labels_train, validation_data=[X_test, labels_test],
# batch_size=1024, epochs=100, callbacks=[callback_1, callback_2, callback_3])
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_5 (InputLayer) (None, 40) 0 _________________________________________________________________ lambda_4 (Lambda) (None, 40, 70) 0 _________________________________________________________________ conv1d_25 (Conv1D) (None, 37, 100) 28100 _________________________________________________________________ max_pooling1d_4 (MaxPooling1 (None, 7, 100) 0 _________________________________________________________________ conv1d_26 (Conv1D) (None, 4, 100) 40100 _________________________________________________________________ conv1d_27 (Conv1D) (None, 1, 100) 40100 _________________________________________________________________ lstm_8 (LSTM) (None, 128) 117248 _________________________________________________________________ dense_28 (Dense) (None, 100) 12900 _________________________________________________________________ dense_29 (Dense) (None, 1) 101 ================================================================= Total params: 238,549 Trainable params: 238,549 Non-trainable params: 0 _________________________________________________________________
Точность на тренировочной выборке – 80.86%, на валидационной – 78.27%.
У сверточных нейронных сетях есть несколько преимуществ перед LSTM:
Эти подходы являются базовыми в применении глубокого обучения для задачи сентимент-анализа. Улучшать точность можно следующими модификациями:
Личные наблюдения:
Лучшей моделью на этих данных оказалась наша модифицированная LSTM с предобученным w2v от гугла размерностью 300, словарем размера 50000 и большей размерности выхода LSTM ячейки. Точность на валидации составила больше 86.85%. Больше про решение задачи сентимент-анализа можно узнать из этого GitHub-репозитория и видео.