Это 18-я статья в моей серии статей по Python для НЛП. В моей предыдущей статье я объяснил , как создать модель анализа настроений фильмов на основе глубокого обучения с использованием библиотеки Python Keras . В этой статье мы увидели, как мы можем выполнить анализ настроений отзывов пользователей о различных фильмах на IMDB. Мы использовали текст обзора the review для прогнозирования настроений.
Однако в задачах классификации текста мы также можем использовать нетекстовую информацию для классификации текста. Например, гендерный фактор может оказать влияние на настроения участников обзора. Кроме того, национальности могут влиять на общественное мнение о том или ином фильме. Таким образом, эта связанная информация, также известная как метаданные, также может быть использована для повышения точности статистической модели.
В этой статье мы будем опираться на концепции, которые мы изучали в последних двух статьях, и увидим, как создать систему классификации текста, которая классифицирует отзывы пользователей о различных видах бизнеса в одну из трех предопределенных категорий: “хорошие”, “плохие” и “средние”. Однако в дополнение к тексту обзора мы будем использовать соответствующие метаданные обзора для выполнения классификации. Поскольку у нас есть два разных типа входных данных, то есть текстовый ввод и числовой ввод, нам нужно создать модель с несколькими входами. Мы будем использовать функциональный API Keras, так как он поддерживает несколько входных и несколько выходных моделей.
Прочитав эту статью, вы сможете создать модель глубокого обучения в Корее, которая способна принимать несколько входных данных, объединять два выходных и затем выполнять классификацию или регрессию с использованием агрегированных входных данных.
- Набор данных
- Создание модели только с текстовыми вводами
- Создание модели только с метаинформацией
- Создание модели с несколькими входами
- Заключительные мысли и улучшения
Прежде чем мы углубимся в детали создания такой модели, давайте сначала кратко рассмотрим набор данных, который мы собираемся использовать.
Набор данных
Набор данных для этой статьи можно скачать по ссылке Kaggle . Набор данных содержит несколько файлов, но нас интересует только файл yelp_review.csv
. Файл содержит более 5,2 миллиона отзывов о различных предприятиях, включая рестораны, бары, стоматологов, врачей, салоны красоты и т. Д. Для наших целей мы будем использовать только первые 50 000 записей для обучения вашей модели. Загрузите набор данных на свой локальный компьютер.
Давайте сначала импортируем все библиотеки, которые мы будем использовать в этой статье, прежде чем импортировать набор данных.
from numpy import array from keras.preprocessing.text import one_hot from keras.preprocessing.sequence import pad_sequences from keras.models import Sequential from keras.layers.core import Activation, Dropout, Dense from keras.layers import Flatten, LSTM from keras.layers import GlobalMaxPooling1D from keras.models import Model from keras.layers.embeddings import Embedding from sklearn.model_selection import train_test_split from keras.preprocessing.text import Tokenizer from keras.layers import Input from keras.layers.merge import Concatenate import pandas as pd import numpy as np import re
В качестве первого шага нам нужно загрузить набор данных. Это делает следующий сценарий:
yelp_reviews = pd.read_csv("/content/drive/My Drive/yelp_review_short.csv")
Набор данных содержит столбец Stars
, содержащий рейтинги для различных компаний. Столбец “Звезды” может иметь значения от 1 до 5. Мы упростим нашу задачу, преобразовав числовые значения для обзоров в категориальные. Мы добавим новый столбец reviews_score
в наш набор данных. Если отзыв пользователя имеет значение 1 в столбце Stars
, то столбец reviews_score
будет иметь строковое значение bad
. Если рейтинг равен 2 или 3 в столбце Stars
, то столбец reviews_score
будет содержать значение average
. Наконец, рейтинг обзора 4 или 5 будет иметь соответствующее значение good
в столбце reviews_score
.
Следующий сценарий выполняет эту предварительную обработку:
bins = [0,1,3,5] review_names = ['bad', 'average', 'good'] yelp_reviews['reviews_score'] = pd.cut(yelp_reviews['stars'], bins, labels=review_names)
Затем мы удалим все нулевые значения из вашего фрейма данных и напечатаем форму и заголовок набора данных.
yelp_reviews.isnull().values.any() print(yelp_reviews.shape) yelp_reviews.head()
В выходных данных вы увидите (50000,10)
, что означает, что наш набор данных содержит 50 000 записей с 10 столбцами. Заголовок фрейма данных yelp_reviews
выглядит следующим образом:
Вы можете увидеть 10 столбцов, которые содержит наш фрейм данных, включая недавно добавленный столбец reviews_score
. Столбец text
содержит текст рецензии, а столбец useful
содержит числовое значение, представляющее количество людей, которые сочли рецензию полезной. Аналогично, столбцы funny
и cool
содержат количество людей , которые нашли отзывы funny
или cool
соответственно.
Давайте случайным образом выберем отзыв. Если вы посмотрите на 4-й отзыв (отзыв с индексом 3), то он имеет 4 звезды и, следовательно, помечен как хороший
. Давайте посмотрим полный текст этого обзора:
print(yelp_reviews["text"][3])
Вывод выглядит следующим образом:
Love coming here. Yes the place always needs the floor swept but when you give out peanuts in the shell how won't it always be a bit dirty. The food speaks for itself, so good. Burgers are made to order and the meat is put on the grill when you order your sandwich. Getting the small burger just means 1 patty, the regular is a 2 patty burger which is twice the deliciousness. Getting the Cajun fries adds a bit of spice to them and whatever size you order they always throw more fries (a lot more fries) into the bag.
Вы можете ясно видеть, что это положительный отзыв.
Давайте теперь построим график количества хороших
, средних
и плохих
отзывов.
import seaborn as sns sns.countplot(x='reviews_score', data=yelp_reviews)
Из приведенного выше сюжета видно, что большинство отзывов хорошие, а за ними следуют средние отзывы. Количество отрицательных отзывов очень мало.
Мы предварительно обработали наши данные и теперь создадим три модели в этой статье. Первая модель будет использовать только текстовые входные данные для прогнозирования того, является ли обзор хорошим
, средним
или плохим
. Во второй модели мы не будем использовать текст. Мы будем использовать только метаинформацию, такую как полезно
, смешно
и круто
, чтобы предсказать настроение обзора. Наконец, мы создадим модель, которая принимает несколько входных данных, то есть текст и метаинформацию для классификации текста.
Создание модели только с текстовыми вводами
Первым шагом является определение функции, которая очищает текстовые данные.
def preprocess_text(sen): # Remove punctuations and numbers sentence = re.sub('[^a-zA-Z]', ' ', sen) # Single character removal sentence = re.sub(r"\s+[a-zA-Z]\s+", ' ', sentence) # Removing multiple spaces sentence = re.sub(r'\s+', ' ', sentence) return sentence
Поскольку в этой модели мы используем только текст, мы отфильтруем все текстовые отзывы и сохраним их в списке. Текстовые обзоры будут очищены с помощью функции preprocess_text
, которая удаляет знаки препинания и цифры из текста.
X = [] sentences = list(yelp_reviews["text"]) for sen in sentences: X.append(preprocess_text(sen)) y = yelp_reviews['reviews_score']
Наша переменная X
здесь содержит текстовые отзывы, в то время как переменная y
содержит соответствующие значения reviews_score
. Столбец reviews_score
содержит данные в текстовом формате. Нам нужно преобразовать текст в один горячий кодированный вектор. Мы можем использовать метод to_categorical
из модуля keras.utils
. Однако сначала мы должны преобразовать текст в целочисленные метки с помощью функции LabelEncoder
из модуля sklearn.preprocessing
.
from sklearn import preprocessing # label_encoder object knows how to understand word labels. label_encoder = preprocessing.LabelEncoder() # Encode labels in column 'species'. y = label_encoder.fit_transform(y)
Давайте теперь разделим наши данные на тестовые и обучающие наборы:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
Теперь мы можем преобразовать как обучающие, так и тестовые метки в однократно закодированные векторы:
from keras.utils import to_categorical y_train = to_categorical(y_train) y_test = to_categorical(y_test)
Я объяснил в своей статье о встраивании слов, что текстовые данные должны быть преобразованы в некоторую числовую форму, прежде чем они могут быть использованы статистическими алгоритмами, такими как модели машинного и глубокого обучения. Один из способов преобразования текста в числа-это встраивание слов. Если вы не знаете, как реализовать встраивание слов с помощью Kara, я настоятельно рекомендую вам прочитать эту статью, прежде чем переходить к следующим разделам кода.
Первый шаг встраивания слов состоит в преобразовании слов в соответствующие им числовые индексы. Для этого мы можем использовать класс Tokenizer
из модуля Keras.preprocessing.text
.
tokenizer = Tokenizer(num_words=5000) tokenizer.fit_on_texts(X_train) X_train = tokenizer.texts_to_sequences(X_train) X_test = tokenizer.texts_to_sequences(X_test)
Предложения могут иметь разную длину, и поэтому последовательности, возвращаемые классом Tokenizer
, также состоят из переменных длин. Мы указываем, что максимальная длина последовательности будет равна 200 (хотя вы можете попробовать любое число). Для предложений длиной менее 200 остальные индексы будут дополнены нулями. Для предложений, длина которых превышает 200, остальные индексы будут усечены.
Посмотрите на следующий сценарий:
vocab_size = len(tokenizer.word_index) + 1 maxlen = 200 X_train = pad_sequences(X_train, padding='post', maxlen=maxlen) X_test = pad_sequences(X_test, padding='post', maxlen=maxlen)
Далее нам нужно загрузить встроенные перчатки встраивания слов.
from numpy import array from numpy import asarray from numpy import zeros embeddings_dictionary = dict() for line in glove_file: records = line.split() word = records[0] vector_dimensions = asarray(records[1:], dtype='float32') embeddings_dictionary [word] = vector_dimensions glove_file.close()
Наконец, мы создадим матрицу встраивания, где строки будут равны количеству слов в словаре (плюс 1). Количество столбцов будет равно 100, так как каждое слово в вложениях перчаток, которые мы загрузили, представлено в виде 100-мерного вектора.
embedding_matrix = zeros((vocab_size, 100)) for word, index in tokenizer.word_index.items(): embedding_vector = embeddings_dictionary.get(word) if embedding_vector is not None: embedding_matrix[index] = embedding_vector
Как только шаг встраивания слова будет завершен, мы готовы создать нашу модель. Мы будем использовать функциональный API Keras для создания нашей модели. Хотя модели с одним входом, подобные той, которую мы создаем сейчас, также могут быть разработаны с использованием последовательного API, но поскольку в следующем разделе мы собираемся разработать модель с несколькими входами, которая может быть разработана только с использованием функционального API Keras, мы будем придерживаться функционального API и в этом разделе.
Мы создадим очень простую модель с одним входным слоем (embedding layer), одним LSTM-слоем со 128 нейронами и одним плотным слоем, который также будет выступать в качестве выходного слоя. Поскольку у нас есть 3 возможных выхода, число нейронов будет равно 3, а функция активации будет softmax
. Мы будем использовать categorical_cross entropy
в качестве нашей функции потерь и adam
в качестве функции оптимизации.
deep_inputs = Input(shape=(maxlen,)) embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(deep_inputs) LSTM_Layer_1 = LSTM(128)(embedding_layer) dense_layer_1 = Dense(3, activation='softmax')(LSTM_Layer_1) model = Model(inputs=deep_inputs, outputs=dense_layer_1) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
Давайте распечатаем резюме нашей модели:
print(model.summary())
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 200) 0 _________________________________________________________________ embedding_1 (Embedding) (None, 200, 100) 5572900 _________________________________________________________________ lstm_1 (LSTM) (None, 128) 117248 _________________________________________________________________ dense_1 (Dense) (None, 3) 387 ================================================================= Total params: 5,690,535 Trainable params: 117,635 Non-trainable params: 5,572,900
Наконец, давайте напечатаем блок-схему нашей нейронной сети:
from keras.utils import plot_model plot_model(model, to_file='model_plot1.png', show_shapes=True, show_layer_names=True)
Файл model_plot1.png
будет создан в вашем локальном пути к файлу. Если вы откроете изображение, оно будет выглядеть так:
Вы можете видеть, что модель имеет 1 входной слой, 1 слой встраивания, 1 LSTM и один плотный слой, который также служит выходным слоем.
Давайте теперь потренируем нашу модель:
history = model.fit(X_train, y_train, batch_size=128, epochs=10, verbose=1, validation_split=0.2)
Модель будет обучена на 80% данных о поездах и будет проверена на 20% данных о поездах. Результаты для 10 эпох следующие:
Train on 32000 samples, validate on 8000 samples Epoch 1/10 32000/32000 [==============================] - 81s 3ms/step - loss: 0.8640 - acc: 0.6623 - val_loss: 0.8356 - val_acc: 0.6730 Epoch 2/10 32000/32000 [==============================] - 80s 3ms/step - loss: 0.8508 - acc: 0.6618 - val_loss: 0.8399 - val_acc: 0.6690 Epoch 3/10 32000/32000 [==============================] - 84s 3ms/step - loss: 0.8461 - acc: 0.6647 - val_loss: 0.8374 - val_acc: 0.6726 Epoch 4/10 32000/32000 [==============================] - 82s 3ms/step - loss: 0.8288 - acc: 0.6709 - val_loss: 0.7392 - val_acc: 0.6861 Epoch 5/10 32000/32000 [==============================] - 82s 3ms/step - loss: 0.7444 - acc: 0.6804 - val_loss: 0.6371 - val_acc: 0.7311 Epoch 6/10 32000/32000 [==============================] - 83s 3ms/step - loss: 0.5969 - acc: 0.7484 - val_loss: 0.5602 - val_acc: 0.7682 Epoch 7/10 32000/32000 [==============================] - 82s 3ms/step - loss: 0.5484 - acc: 0.7623 - val_loss: 0.5244 - val_acc: 0.7814 Epoch 8/10 32000/32000 [==============================] - 86s 3ms/step - loss: 0.5052 - acc: 0.7866 - val_loss: 0.4971 - val_acc: 0.7950 Epoch 9/10 32000/32000 [==============================] - 84s 3ms/step - loss: 0.4753 - acc: 0.8032 - val_loss: 0.4839 - val_acc: 0.7965 Epoch 10/10 32000/32000 [==============================] - 82s 3ms/step - loss: 0.4539 - acc: 0.8110 - val_loss: 0.4622 - val_acc: 0.8046
Вы можете видеть, что конечная точность обучения модели составляет 81,10%, а точность проверки-80,46. Разница очень мала, и поэтому мы предполагаем, что наша модель не слишком подходит для обучающих данных.
Теперь давайте оценим производительность нашей модели на тестовом наборе:
score = model.evaluate(X_test, y_test, verbose=1) print("Test Score:", score[0]) print("Test Accuracy:", score[1])
Вывод выглядит следующим образом:
10000/10000 [==============================] - 37s 4ms/step Test Score: 0.4592904740810394 Test Accuracy: 0.8101
Наконец, давайте построим график значений потерь и точности как для обучающих, так и для тестовых наборов:
import matplotlib.pyplot as plt plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train','test'], loc='upper left') plt.show() plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train','test'], loc='upper left') plt.show()
Вы должны увидеть следующие два графика:
Вы можете видеть, что линии как для обучения, так и для тестирования точности и потерь довольно близки друг к другу, что означает, что модель не переоснащена.
Создание модели только с метаинформацией
В этом разделе мы создадим классификационную модель, использующую информацию из полезных
, забавных
и крутых
колонок обзоров yelp. Поскольку данные для этих столбцов хорошо структурированы и не содержат никаких последовательных или пространственных паттернов, мы можем использовать простые плотносвязанные нейронные сети для прогнозирования.
Давайте построим средние значения для полезных
, забавных
и крутых
отзывов против оценки обзора.
import seaborn as sns sns.barplot(x='reviews_score', y='useful', data=yelp_reviews)
Из выходных данных вы можете видеть, что среднее количество отзывов, отмеченных как полезные
, является самым высоким для плохих отзывов, за которыми следуют средние отзывы и хорошие отзывы.
Давайте теперь построим средний счет для смешных
отзывов:
sns.barplot(x='reviews_score', y='funny', data=yelp_reviews)
Вывод показывает, что опять же среднее количество отзывов, отмеченных как funny
, является самым высоким для плохих отзывов.
Наконец, давайте построим среднее значение для столбца cool
против столбца reviews_score
. Мы ожидаем, что средний показатель для колонки cool
будет самым высоким для хороших отзывов, так как люди часто отмечают положительные или хорошие отзывы как классные:
sns.barplot(x='reviews_score', y='cool', data=yelp_reviews)
Как и ожидалось, среднее количество крутых отзывов для хороших отзывов самое высокое. Из этой информации мы можем с уверенностью предположить, что значения count для столбцов useful
, funny
и cool
имеют некоторую корреляцию со столбцами reviews_score
. Поэтому мы попытаемся использовать данные из этих трех столбцов для обучения нашего алгоритма, который предсказывает значение для столбца reviews_score
.
Давайте отфильтруем эти три столбца из нашего набора данных:
yelp_reviews_meta = yelp_reviews[['useful', 'funny', 'cool']] X = yelp_reviews_meta.values y = yelp_reviews['reviews_score']
Затем мы преобразуем наши метки в однократно закодированные значения, а затем разделим наши данные на обучающие и тестовые наборы:
from sklearn import preprocessing # label_encoder object knows how to understand word labels. label_encoder = preprocessing.LabelEncoder() # Encode labels in column 'species'. y = label_encoder.fit_transform(y) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42) from keras.utils import to_categorical y_train = to_categorical(y_train) y_test = to_categorical(y_test)
Следующий шаг-создание нашей модели. Наша модель будет состоять из четырех слоев (вы можете попробовать любое количество): входной слой, два плотных скрытых слоя с 10 нейронами и функциями активации relu и, наконец, выходной плотный слой с 3 нейронами и функцией активации softmax. Функция потерь и оптимизатор будут иметь значения categorical_cross entropy
и adam
соответственно.
Следующий сценарий определяет модель:
input2 = Input(shape=(3,)) dense_layer_1 = Dense(10, activation='relu')(input2) dense_layer_2 = Dense(10, activation='relu')(dense_layer_1) output = Dense(3, activation='softmax')(dense_layer_2) model = Model(inputs=input2, outputs=output) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
Давайте напечатаем краткое описание модели:
print(model.summary())
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 3) 0 _________________________________________________________________ dense_1 (Dense) (None, 10) 40 _________________________________________________________________ dense_2 (Dense) (None, 10) 110 _________________________________________________________________ dense_3 (Dense) (None, 3) 33 ================================================================= Total params: 183 Trainable params: 183 Non-trainable params: 0
Наконец, блок-схема модели может быть создана с помощью следующего скрипта:
from keras.utils import plot_model plot_model(model, to_file='model_plot2.png', show_shapes=True, show_layer_names=True)
Теперь, если вы откроете файл model_plot2.png
из своего локального пути к файлу, он будет выглядеть следующим образом:
Теперь давайте обучим модель и выведем значения точности и потерь для каждой эпохи:
history = model.fit(X_train, y_train, batch_size=16, epochs=10, verbose=1, validation_split=0.2)
Train on 32000 samples, validate on 8000 samples Epoch 1/10 32000/32000 [==============================] - 8s 260us/step - loss: 0.8429 - acc: 0.6649 - val_loss: 0.8166 - val_acc: 0.6734 Epoch 2/10 32000/32000 [==============================] - 7s 214us/step - loss: 0.8203 - acc: 0.6685 - val_loss: 0.8156 - val_acc: 0.6737 Epoch 3/10 32000/32000 [==============================] - 7s 217us/step - loss: 0.8187 - acc: 0.6685 - val_loss: 0.8150 - val_acc: 0.6736 Epoch 4/10 32000/32000 [==============================] - 7s 220us/step - loss: 0.8183 - acc: 0.6695 - val_loss: 0.8160 - val_acc: 0.6740 Epoch 5/10 32000/32000 [==============================] - 7s 227us/step - loss: 0.8177 - acc: 0.6686 - val_loss: 0.8149 - val_acc: 0.6751 Epoch 6/10 32000/32000 [==============================] - 7s 219us/step - loss: 0.8175 - acc: 0.6686 - val_loss: 0.8157 - val_acc: 0.6744 Epoch 7/10 32000/32000 [==============================] - 7s 216us/step - loss: 0.8172 - acc: 0.6696 - val_loss: 0.8145 - val_acc: 0.6733 Epoch 8/10 32000/32000 [==============================] - 7s 214us/step - loss: 0.8175 - acc: 0.6689 - val_loss: 0.8139 - val_acc: 0.6734 Epoch 9/10 32000/32000 [==============================] - 7s 215us/step - loss: 0.8169 - acc: 0.6691 - val_loss: 0.8160 - val_acc: 0.6744 Epoch 10/10 32000/32000 [==============================] - 7s 216us/step - loss: 0.8167 - acc: 0.6694 - val_loss: 0.8138 - val_acc: 0.6736
Из выходных данных вы можете видеть, что наша модель не сходится, и значения точности остаются между 66 и 67 во всех эпохах.
Давайте посмотрим, как модель работает на тестовом наборе:
score = model.evaluate(X_test, y_test, verbose=1) print("Test Score:", score[0]) print("Test Accuracy:", score[1])
10000/10000 [==============================] - 0s 34us/step Test Score: 0.8206425309181213 Test Accuracy: 0.6669
Мы можем распечатать значения потерь и точности для обучающих и тестовых наборов с помощью следующего скрипта:
import matplotlib.pyplot as plt plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train','test'], loc='upper left') plt.show() plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train','test'], loc='upper left') plt.show()
Из выходных данных видно, что значения точности относительно ниже. Следовательно, мы можем сказать, что наша модель недостаточно приспособлена. Точность может быть увеличена путем увеличения числа плотных слоев или путем увеличения числа эпох, однако я оставлю это вам.
Давайте перейдем к последнему и самому важному разделу этой статьи, где мы будем использовать несколько входных данных различных типов для обучения нашей модели.
Создание модели с несколькими входами
В предыдущих разделах мы видели, как обучать модели глубокого обучения с использованием текстовых данных или метаинформации. Что делать, если мы хотим объединить текстовую информацию с метаинформацией и использовать ее в качестве входных данных для нашей модели? Мы можем сделать это с помощью функционального API Keras. В этом разделе мы создадим две подмодели.
Первая подмодель будет принимать текстовый ввод в виде текстовых обзоров. Эта подмодель будет состоять из входного слоя формы, слоя встраивания и слоя LSTM из 128 нейронов. Вторая подмодель будет принимать входные данные в виде метаинформации из столбцов полезно
, смешно
и круто
. Вторая подмодель также состоит из трех слоев. Входной слой и два плотных слоя.
Выход из слоя LSTM первой подмодели и выход из второго плотного слоя второй подмодели будут объединены вместе и будут использоваться в качестве объединенного входа в другой плотный слой с 10 нейронами. Наконец, выходной плотный слой будет иметь три нейрона, соответствующих каждому типу обзора.
Давайте посмотрим, как мы можем создать такую объединенную модель.
Сначала мы должны создать два различных типа входных данных. Для этого мы разделим наши данные на набор функций и набор меток, как показано ниже:
X = yelp_reviews.drop('reviews_score', axis=1) y = yelp_reviews['reviews_score']
Переменная X
содержит набор признаков, в то время как переменная y
содержит набор меток. Нам нужно преобразовать наши метки в однократно закодированные векторы. Мы можем сделать это с помощью кодировщика меток и функции to_categorical
модуля keras.utils
. Мы также разделим наши данные на обучение и набор функций.
from sklearn import preprocessing # label_encoder object knows how to understand word labels. label_encoder = preprocessing.LabelEncoder() # Encode labels in column 'species'. y = label_encoder.fit_transform(y) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42) from keras.utils import to_categorical y_train = to_categorical(y_train) y_test = to_categorical(y_test)
Теперь наш набор этикеток находится в нужном виде. Поскольку выход будет только один, поэтому нам не нужно обрабатывать наш набор меток. Однако в модели будет несколько входов. Поэтому нам нужно предварительно обработать наш набор функций.
Давайте сначала создадим preprocess_text
функцию, которая будет использоваться для предварительной обработки нашего набора данных:
def preprocess_text(sen): # Remove punctuations and numbers sentence = re.sub('[^a-zA-Z]', ' ', sen) # Single character removal sentence = re.sub(r"\s+[a-zA-Z]\s+", ' ', sentence) # Removing multiple spaces sentence = re.sub(r'\s+', ' ', sentence) return sentence
В качестве первого шага мы создадим текстовый ввод для обучающего и тестового набора. Посмотрите на следующий сценарий:
X1_train = [] sentences = list(X_train["text"]) for sen in sentences: X1_train.append(preprocess_text(sen))
Теперь X1_train
содержит текстовый ввод для обучающего набора. Аналогично, следующий сценарий предварительно обрабатывает текстовые входные данные для тестового набора:
X1_test = [] sentences = list(X_test["text"]) for sen in sentences: X1_test.append(preprocess_text(sen))
Теперь нам нужно преобразовать текстовый ввод для обучающих и тестовых наборов в числовую форму с помощью вложений слов. Это делает следующий сценарий:
tokenizer = Tokenizer(num_words=5000) tokenizer.fit_on_texts(X1_train) X1_train = tokenizer.texts_to_sequences(X1_train) X1_test = tokenizer.texts_to_sequences(X1_test) vocab_size = len(tokenizer.word_index) + 1 maxlen = 200 X1_train = pad_sequences(X1_train, padding='post', maxlen=maxlen) X1_test = pad_sequences(X1_test, padding='post', maxlen=maxlen)
Мы снова будем использовать встраивания слов перчаток для создания векторов слов:
from numpy import array from numpy import asarray from numpy import zeros embeddings_dictionary = dict() glove_file = open('/content/drive/My Drive/glove.6B.100d.txt', encoding="utf8") for line in glove_file: records = line.split() word = records[0] vector_dimensions = asarray(records[1:], dtype='float32') embeddings_dictionary[word] = vector_dimensions glove_file.close() embedding_matrix = zeros((vocab_size, 100)) for word, index in tokenizer.word_index.items(): embedding_vector = embeddings_dictionary.get(word) if embedding_vector is not None: embedding_matrix[index] = embedding_vector
Мы предварительно обработали наш текстовый ввод. Второй тип ввода-это метаинформация в столбцах полезно
, смешно
и круто
. Мы отфильтруем эти столбцы из набора функций, чтобы создать мета-вход для обучения алгоритмов. Посмотрите на следующий сценарий:
X2_train = X_train[['useful', 'funny', 'cool']].values X2_test = X_test[['useful', 'funny', 'cool']].values
Теперь давайте создадим два входных слоя. Первый входной слой будет использоваться для ввода текстового ввода, а второй входной слой будет использоваться для ввода метаинформации из трех столбцов.
input_1 = Input(shape=(maxlen,)) input_2 = Input(shape=(3,))
Вы можете видеть, что первый входной слой input_1
используется для текстового ввода. Размер фигуры был установлен в соответствии с формой входного предложения. Для второго входного слоя форма соответствует трем столбцам.
Теперь давайте создадим первую подмодель, которая принимает данные из первого входного слоя:
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(input_1) LSTM_Layer_1 = LSTM(128)(embedding_layer)
Аналогично, следующий сценарий создает вторую подмодель, которая принимает входные данные от второго входного слоя:
dense_layer_1 = Dense(10, activation='relu')(input_2) dense_layer_2 = Dense(10, activation='relu')(dense_layer_1)
Теперь у нас есть две подмодели. То, что мы хотим сделать, – это объединить выходные данные из первой подмодели с выходными данными из второй подмодели. Выход из первой подмодели-это выход из LSTM_Layer_1
и аналогично выход из второй подмодели-это выход из dense_layer_2
. Мы можем использовать класс Concatenate
из keras.layers.merge
модуль для объединения двух входных данных.
Следующий сценарий создает нашу окончательную модель:
concat_layer = Concatenate()([LSTM_Layer_1, dense_layer_2]) dense_layer_3 = Dense(10, activation='relu')(concat_layer) output = Dense(3, activation='softmax')(dense_layer_3) model = Model(inputs=[input_1, input_2], outputs=output)
Вы можете видеть, что теперь наша модель имеет список входных данных с двумя элементами. Следующий сценарий компилирует модель и печатает ее сводку:
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc']) print(model.summary())
Краткое описание модели выглядит следующим образом:
Layer (type) Output Shape Param # Connected to ================================================================================================== input_1 (InputLayer) (None, 200) 0 __________________________________________________________________________________________________ input_2 (InputLayer) (None, 3) 0 __________________________________________________________________________________________________ embedding_1 (Embedding) (None, 200, 100) 5572900 input_1[0][0] __________________________________________________________________________________________________ dense_1 (Dense) (None, 10) 40 input_2[0][0] __________________________________________________________________________________________________ lstm_1 (LSTM) (None, 128) 117248 embedding_1[0][0] __________________________________________________________________________________________________ dense_2 (Dense) (None, 10) 110 dense_1[0][0] __________________________________________________________________________________________________ concatenate_1 (Concatenate) (None, 138) 0 lstm_1[0][0] dense_2[0][0] __________________________________________________________________________________________________ dense_3 (Dense) (None, 10) 1390 concatenate_1[0][0] __________________________________________________________________________________________________ dense_4 (Dense) (None, 3) 33 dense_3[0][0] ================================================================================================== Total params: 5,691,721 Trainable params: 118,821 Non-trainable params: 5,572,900
Наконец, мы можем построить полную сетевую модель, используя следующий сценарий:
from keras.utils import plot_model plot_model(model, to_file='model_plot3.png', show_shapes=True, show_layer_names=True)
Если вы откроете файл model_plot3.png
, то увидите следующую сетевую диаграмму:
Приведенный выше рисунок ясно объясняет, как мы объединили несколько входов в один вход для создания нашей модели.
Давайте теперь потренируем нашу модель и посмотрим результаты:
history = model.fit(x=[X1_train, X2_train], y=y_train, batch_size=128, epochs=10, verbose=1, validation_split=0.2)
Вот результат для 10 эпох:
Train on 32000 samples, validate on 8000 samples Epoch 1/10 32000/32000 [==============================] - 155s 5ms/step - loss: 0.9006 - acc: 0.6509 - val_loss: 0.8233 - val_acc: 0.6704 Epoch 2/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.8212 - acc: 0.6670 - val_loss: 0.8141 - val_acc: 0.6745 Epoch 3/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.8151 - acc: 0.6691 - val_loss: 0.8086 - val_acc: 0.6740 Epoch 4/10 32000/32000 [==============================] - 155s 5ms/step - loss: 0.8121 - acc: 0.6701 - val_loss: 0.8039 - val_acc: 0.6776 Epoch 5/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.8027 - acc: 0.6740 - val_loss: 0.7467 - val_acc: 0.6854 Epoch 6/10 32000/32000 [==============================] - 155s 5ms/step - loss: 0.6791 - acc: 0.7158 - val_loss: 0.5764 - val_acc: 0.7560 Epoch 7/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.5333 - acc: 0.7744 - val_loss: 0.5076 - val_acc: 0.7881 Epoch 8/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.4857 - acc: 0.7973 - val_loss: 0.4849 - val_acc: 0.7970 Epoch 9/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.4697 - acc: 0.8034 - val_loss: 0.4709 - val_acc: 0.8024 Epoch 10/10 32000/32000 [==============================] - 154s 5ms/step - loss: 0.4479 - acc: 0.8123 - val_loss: 0.4592 - val_acc: 0.8079
Чтобы оценить нашу модель, нам придется передать оба тестовых входа в функцию evaluate
, как показано ниже:
score = model.evaluate(x=[X1_test, X2_test], y=y_test, verbose=1) print("Test Score:", score[0]) print("Test Accuracy:", score[1])
Вот результат:
10000/10000 [==============================] - 18s 2ms/step Test Score: 0.4576087875843048 Test Accuracy: 0.8053
Точность нашего теста составляет 80,53%, что немного меньше, чем у нашей первой модели, которая использует только текстовый ввод. Это показывает, что метаинформация в yelp_reviews
не очень полезна для прогнозирования настроений.
В любом случае, теперь вы знаете, как создать множественную входную модель для классификации текста в Keras!
Наконец, давайте теперь напечатаем потери и точность для обучающих и тестовых наборов:
import matplotlib.pyplot as plt plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train','test'], loc='upper left') plt.show() plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train','test'], loc='upper left') plt.show()
Вы можете видеть, что различия в значениях потерь и точности минимальны между обучающим и тестовым наборами, следовательно, наша модель не переоснащена.
Заключительные мысли и улучшения
В этой статье мы построили очень простую нейронную сеть, так как цель статьи-объяснить, как создать модель глубокого обучения, которая принимает несколько входных данных разных типов.
Ниже приведены некоторые советы, которым вы можете следовать для дальнейшего повышения производительности модели классификации текста:
- Мы использовали только 50 000 из 5,2 миллиона записей в этой статье, так как у нас было аппаратное ограничение. Вы можете попробовать обучить свою модель на большем количестве записей и посмотреть, сможете ли вы добиться лучшей производительности.
- Попробуйте добавить в модель больше LSTM и плотных слоев. Если модель слишком подходит, попробуйте добавить отсев.
- Попробуйте изменить функцию оптимизатора и обучить модель с большим количеством эпох.
Пожалуйста, поделитесь своими результатами вместе с конфигурацией нейронной сети в разделе комментариев. Мне бы очень хотелось посмотреть, насколько хорошо вы справились.