Рубрики
Без рубрики

Python для НЛП: Создание моделей классификации нескольких типов данных с помощью Keras

Автор оригинала: Usman Malik.

Это 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()
голова

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

Заключительные мысли и улучшения

В этой статье мы построили очень простую нейронную сеть, так как цель статьи-объяснить, как создать модель глубокого обучения, которая принимает несколько входных данных разных типов.

Ниже приведены некоторые советы, которым вы можете следовать для дальнейшего повышения производительности модели классификации текста:

  1. Мы использовали только 50 000 из 5,2 миллиона записей в этой статье, так как у нас было аппаратное ограничение. Вы можете попробовать обучить свою модель на большем количестве записей и посмотреть, сможете ли вы добиться лучшей производительности.
  2. Попробуйте добавить в модель больше LSTM и плотных слоев. Если модель слишком подходит, попробуйте добавить отсев.
  3. Попробуйте изменить функцию оптимизатора и обучить модель с большим количеством эпох.

Пожалуйста, поделитесь своими результатами вместе с конфигурацией нейронной сети в разделе комментариев. Мне бы очень хотелось посмотреть, насколько хорошо вы справились.