Чтобы построить эту модель, мы будем использовать набор данных, найденный на этой странице (https://www.kaggle.com/datasets/andrewmvd/trip-advisor-hotel-reviews), который содержит около 20491 отзыва плюс соответствующий рейтинг (от 1 –5) оставили на TripAdvisor информацию об опыте проживания разных людей в соответствующих гостиничных номерах.
trip = pd.read_csv("tripadvisor_hotel_reviews.csv")
trip.shape
(20491, 2)
trip.head()
Предварительная обработка и исследовательский анализ
В этом разделе мы подготовим данные в таком формате, который можно использовать для построения желаемой модели глубокого обучения.
Во-первых, важно отметить, что мы ориентируемся на то, есть ли у человека, оставившего отзыв, жалобы или нет. Для этого мы выберем отзывы с оценкой 5, оставленные человеком, у которого нет жалоб (код 0), а оценки ниже 5 будут считаться отзывами людей, у которых есть жалобы на свой номер в отеле (код 0). по 1).
trip["Complaints"] = np.where(trip["Rating"] < 5, 1, 0)
Еще одна характеристика, представляющая интерес для каждого из этих обзоров, — это количество слов, используемых для описания опыта, возможно, более длинные обзоры, как правило, хуже, чем более короткие обзоры.
trip["length"] = trip["Review"].apply(lambda x: len(x.split(" ")))
Теперь давайте посмотрим на распределение оценки рецензента (жалобы или нет).
plt.figure(figsize = (12,8)) sns.set_style("whitegrid") sns.histplot(x = "Complaints", data = trip) plt.title("Reviewer's grade distribution") plt.xlabel("Grade")
Text(0.5, 0, 'Grade')
Как мы видим, распределение кажется довольно сбалансированным: около 44% наблюдений имеют те или иные жалобы, а остальные 56% считают, что сервис был идеальным (нет жалоб).
Точно так же теперь мы хотим взглянуть на распределение длины обзоров, как показано ниже.
plt.figure(figsize = (12,8))
sns.set_style("whitegrid")
sns.histplot(x = "length", data = trip, kde = True, bins = 50)
plt.title("Review's length distribution")
plt.xlabel("Length")
plt.show()
trip.describe()
Мы видим, что абсолютное большинство обзоров, как правило, относительно короткие. В частности, около 75% полученных отзывов содержат 130 или менее слов, что немного.
Мы также видим, что очень немногие подборки отзывов очень длинные. Например, ясно, что самое длинное из сообщений состоит из 1933 слов, что можно считать чрезвычайно большим для отзыва об отеле.
Посмотрим, были ли жалобы у человека, оставившего самый длинный отзыв, или нет.
print(trip[trip["length"] == 1933][["Review","Complaints"]])
Review Complaints 7072 honest review visit 5/21-5/28 let begin saying... 1
Похоже, что этот человек был не очень доволен предоставленной услугой (или, по крайней мере, имел какие-то претензии) и оставил обширный отзыв с подробным описанием того, почему ему это не понравилось.
Для остальной части проекта нас не будут интересовать чрезвычайно длинные обзоры. По этой причине в наших данных будут выбраны и сохранены только те, у которых меньше 80 слов.
trip = trip[trip["length"] < 80]
trip.shape
(10330, 4)
Делая это, мы получаем около 10330 отзывов в наборе данных.
trip.hist(column = "length", by = "Complaints",figsize = (12,8), bins = 20)
plt.show()
Теперь, если мы сравним распределения длины для идеальных обзоров и отзывов с некоторыми жалобами, мы ясно увидим, что в целом длина обзора не является определяющим фактором для настроения рецензента.
Это разумно, так как гистограмма длины в обоих случаях практически неотличима одна от другой.
Предварительная обработка для отзывов
В этом разделе нас особенно интересует форматирование обзоров таким образом, чтобы наша модель могла лучше давать результаты. Эта предварительная обработка включает в себя:
- Удаление знаков препинания.
- Удаление номеров.
- Удаление URL.
- Удаление стоп-слов (очень распространенных слов, которые не добавляют никакой ценности)
- Лемматизация (преобразование слов в их простейшую форму, например, бег и бег становятся бегом)
import nltk from nltk.corpus import stopwords import re import string from textblob import Word, TextBlob
def preprocess_reviews(review): pre_review = review pre_review = "".join(word for word in pre_review if word not in string.punctuation) #Remove punctuation pre_review = "".join(word for word in pre_review if not word.isdigit()) pre_review = " ".join(Word(word).lemmatize() for word in pre_review.split()) #Take every word to root pre_review = re.sub(r'http\S+', '', pre_review) #Remove URL's pre_review = " ".join(word for word in pre_review.split() if word.lower() not in [stopwords.words("english"), "hotel","room"]) return pre_review
trip["ReviewPre"] = trip["Review"].apply(preprocess_reviews)
После выполнения упомянутых шагов некоторые обзоры в наборе данных показаны ниже:
trip.head()
Если мы сравним получившиеся отзывы с исходными, то ясно увидим, что некоторые слова, такие как гостиница и комната, которые были очень частыми, были удалены, а также знаки препинания и числа, как мы упоминали ранее.
Создание счетчика
Затем мы хотим проверить, насколько часто встречается каждое слово, в основном, сколько раз каждое слово используется во всех доступных обзорах.
from collections import Counter
def word_counter(x):
count = Counter()
for text in x.values:
for word in text.split():
count[word] += 1
return count
counter = word_counter(trip["ReviewPre"])
Давайте теперь посмотрим на некоторые из наиболее распространенных слов в обзорах нашего набора данных.
counter.most_common(5)
[('great', 8331), ('not', 6546), ('staff', 6405), ('stay', 6141), ('location', 5658)]
Самое распространенное слово — «отлично», что является очень хорошим признаком, поскольку это означает, что подавляющее большинство отзывов, как правило, положительно отзываются об отелях на tripadvisor. С другой стороны, также очень ясно, что слово «не» чрезвычайно распространено, а это означает, что в некоторых обзорах, в которых упоминается что-то «хорошее», на самом деле может упоминаться что-то «плохое».
Наконец, одними из наиболее распространенных тем, упоминаемых в отзывах, являются персонал и расположение отеля.
Разделение данных
Чтобы обеспечить точную оценку производительности нашей модели, необходимо разделить данные на обучение и тестирование. Первая часть будет данными, которые мы используем для построения нашей модели, в то время как второе подмножество будет использоваться для расчета производительности модели, это важно, поскольку мы хотим протестировать модель с данными, которые не были видел раньше (в разделе обучения).
from sklearn.model_selection import train_test_split
X = trip["ReviewPre"] y = trip["Complaints"]
В частности, мы будем использовать 80% доступных данных для обучающей части и оставшиеся 20% для тестовой части.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 1902)
Токенизация слов
Следующий шаг включает в себя кодирование каждого из слов, используемых в обзорах. Этот процесс известен как токенизация, и в нашем случае мы будем делать это, индексируя указанные слова.
Таким образом, каждое конкретное слово будет обозначаться числом, представляющим собой индекс отсортированных слов от наиболее часто встречающихся к наименее часто встречающимся, а это означает, что, как мы видели ранее, слово «великолепный», являющееся наиболее распространенным, будет сокращаться. отождествляется с цифрой 1.
from tensorflow.keras.preprocessing.text import Tokenizer
num_unique_words = len(counter) tokenizer = Tokenizer(num_words = num_unique_words) tokenizer.fit_on_texts(X_train)
word_index = tokenizer.word_index
Далее мы видим, например, данный индекс как для слова «отличный», так и для слова «персонал».
print(word_index["great"]) print(word_index["staff"])
1 3
Преобразование текстов в последовательности и использование заполнения
Теперь, когда мы успешно токенизировали слова, нам интересно изменить слова в обзорах для их соответствующих индексов.
X_train_seq = tokenizer.texts_to_sequences(X_train) X_test_seq = tokenizer.texts_to_sequences(X_test)
print(X_train[20:25]) print(X_train_seq[20:25])
10006 great based tripadvisor review booked threenig... 18058 lovely paper wall stay du petit moulin weekend... 2484 not satisfied price dissapointed stay year ago... 20293 comfortable central stayed night really enjoye... 10963 luxury nt think ok provides amenity good clean... Name: ReviewPre, dtype: object [[1, 483, 489, 54, 63, 6775, 4, 3770, 128, 116, 43, 3, 15, 1, 10, 219, 195, 145, 412, 179, 344, 354, 9, 57, 241, 987, 16, 23, 2, 223, 153, 2, 153, 89, 3766, 670, 16, 3, 406, 16, 11, 3771, 1508, 2, 2289, 4, 2478, 569, 11, 63, 99, 167], [52, 1249, 250, 4, 1482, 2479, 5544, 112, 52, 4, 3, 14, 17, 44, 1078, 511, 1, 5, 41, 8, 57, 51, 82, 4175, 1450, 1321, 610, 1249, 250, 30], [2, 922, 34, 1483, 4, 83, 671, 19, 710, 19, 36, 5545, 80, 19, 11, 482, 16, 1981, 65, 2, 298, 14, 24, 22, 363, 79, 519, 189, 2193, 548, 5546, 608, 244, 21, 956, 10, 1849, 163, 748, 2739, 259, 104, 20, 2048, 1117, 174, 19], [26, 102, 7, 10, 31, 95, 26, 102, 6, 58, 4755, 3, 17, 956, 697, 3772, 9459, 374, 757, 9460, 4, 33], [377, 11, 154, 134, 1229, 305, 6, 9, 26, 25, 8, 12, 1000, 89, 34, 2, 158, 184, 222, 385, 377, 377, 563, 2049, 68, 216, 311, 399, 1118, 460, 630, 283, 617, 1030, 117, 923, 1290, 141, 217, 3231, 2194, 2864, 68, 398, 399, 377, 116, 214, 140, 2864, 141, 517, 185, 1290, 134, 2195, 586, 20, 82, 617, 207, 182, 583, 2196, 642, 549]]
Поскольку наша модель должна иметь одинаковый размер ввода для каждого из обзоров, необходимо добавить максимальное количество слов, которые можно передать в прогнозную модель.
По этой причине мы будем использовать «заполнение», таким образом, для нашего конкретного случая размер ввода будет равен 70 словам в каждом обзоре. Если в каком-то тексте не так много слов, пустые места будут заменены нулями.
# In order to have the same length for every sequence, we use padding from tensorflow.keras.preprocessing.sequence import pad_sequences max_length = 70 train_padded = pad_sequences(X_train_seq, maxlen = max_length, padding = "post", truncating = "post") test_padded = pad_sequences(X_test_seq, maxlen = max_length, padding = "post", truncating = "post") train_padded.shape, test_padded.shape
((8264, 70), (2066, 70))
Давайте посмотрим на пример полученных последовательностей.
train_padded[5]
array([ 1, 1320, 188, 7, 170, 1289, 582, 215, 18, 219, 97, 17, 224, 216, 22, 403, 9430, 9431, 531, 7, 81, 2378, 154, 56, 32, 1076, 1159, 899, 24, 14, 359, 872, 71, 127, 647, 596, 11, 75, 361, 72, 85, 10, 592, 15, 29, 104, 984, 144, 405, 294, 762, 199, 203, 814, 130, 1225, 187, 15, 84, 376, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
X_train.index = range(0, X_train.shape[0]) y_train.index = range(0, X_train.shape[0])
Более конкретно, давайте посмотрим на ту же последовательность через 3 основных шага предварительной обработки, которые мы предприняли. Первый - это фактическое предложение после удаления знаков препинания и стоп-слов; второй - индексированная последовательность с идентификационными номерами; и последний — это последняя последовательность после заполнения, где мы добавляем нули, пока у нас не будет 70 чисел в последовательности.
print(X_train[15]) print(" ") print(X_train_seq[15]) print(" ") print(train_padded[15])
small wonderful super location selected based trip advisor review not disappointed elegant staff outstanding alessandro called friend drive town replace lost camerea helping reservation practical suggestion liked canal having drink outside watching passing scene fixture murano glass added elegant touch minute [24, 37, 331, 5, 1449, 483, 43, 562, 54, 2, 241, 862, 3, 351, 6770, 385, 155, 901, 220, 3476, 832, 9454, 1573, 216, 3768, 822, 368, 369, 199, 166, 150, 1022, 3041, 1507, 1793, 5542, 506, 807, 862, 321, 40] [ 24 37 331 5 1449 483 43 562 54 2 241 862 3 351 6770 385 155 901 220 3476 832 9454 1573 216 3768 822 368 369 199 166 150 1022 3041 1507 1793 5542 506 807 862 321 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Подгонка модели классификации
В следующем разделе мы сосредоточимся на подборе и обучении прогностической модели для классификации с использованием обучающих данных.
Прежде чем погрузиться в построение модели, давайте кратко рассмотрим ее структуру. Первым слоем нашего классификатора будет встраиваемый слой. Встраивание — это более современный способ представления слов с использованием плотного вектора, который представляет собой проекцию слова в непрерывное векторное пространство. Положение слова в векторе узнается из заданных текстов и основано на словах, которые его окружают, когда это слово используется. Эту позицию мы называем «встраиванием».
Поскольку мы работаем с предварительной обработкой текста, кажется разумным использовать нейронную сеть с долговременной кратковременной памятью (LTSM), которая, проще говоря, представляет собой тип рекуррентной нейронной сети, отличающийся «запоминания» только той информации, которая становится контекстуально релевантной на каждом этапе обучения.
from tensorflow.keras import layers from tensorflow.keras.callbacks import EarlyStopping
model = keras.models.Sequential() model.add(layers.Embedding(num_unique_words, 16, input_length = max_length)) model.add(layers.LSTM(32)) model.add(layers.Dropout(0.15)) model.add(layers.Dense(1)) model.add(layers.Activation("sigmoid")) model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding (Embedding) (None, 70, 16) 425984 _________________________________________________________________ lstm (LSTM) (None, 32) 6272 _________________________________________________________________ dropout (Dropout) (None, 32) 0 _________________________________________________________________ dense (Dense) (None, 1) 33 _________________________________________________________________ activation (Activation) (None, 1) 0 ================================================================= Total params: 432,289 Trainable params: 432,289 Non-trainable params: 0 _________________________________________________________________
Чтобы контролировать возможную переобучение, мы также включим обратный вызов для ранней остановки, который остановит обучение модели, если потеря проверки не улучшится после 10 итераций подгонки.
early_stop = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=20)
model.compile(loss = "binary_crossentropy",
optimizer = "adam",
metrics = ["accuracy"])
Первоначально мы будем использовать 100 эпох, а также размер пакета 100.
history = model.fit(train_padded, y_train, batch_size = 100, epochs = 100, validation_data=(test_padded, y_test), callbacks = [early_stop])
Train on 8264 samples, validate on 2066 samples Epoch 1/100 8264/8264 [==============================] - 2s 269us/sample - loss: 0.0281 - accuracy: 0.9936 - val_loss: 1.3494 - val_accuracy: 0.7338 Epoch 2/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0160 - accuracy: 0.9976 - val_loss: 1.4349 - val_accuracy: 0.7270 Epoch 3/100 8264/8264 [==============================] - 1s 76us/sample - loss: 0.0140 - accuracy: 0.9982 - val_loss: 1.5659 - val_accuracy: 0.7260 Epoch 4/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0135 - accuracy: 0.9982 - val_loss: 1.5703 - val_accuracy: 0.7280 Epoch 5/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0136 - accuracy: 0.9978 - val_loss: 1.6140 - val_accuracy: 0.7246 Epoch 6/100 8264/8264 [==============================] - 1s 74us/sample - loss: 0.0143 - accuracy: 0.9976 - val_loss: 1.5129 - val_accuracy: 0.7231 Epoch 7/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0228 - accuracy: 0.9959 - val_loss: 1.5931 - val_accuracy: 0.7231 Epoch 8/100 8264/8264 [==============================] - 1s 79us/sample - loss: 0.0472 - accuracy: 0.9877 - val_loss: 1.1907 - val_accuracy: 0.7285 Epoch 9/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0158 - accuracy: 0.9975 - val_loss: 1.5240 - val_accuracy: 0.7241 Epoch 10/100 8264/8264 [==============================] - 1s 74us/sample - loss: 0.0119 - accuracy: 0.9984 - val_loss: 1.5472 - val_accuracy: 0.7188 Epoch 11/100 8264/8264 [==============================] - 1s 74us/sample - loss: 0.0108 - accuracy: 0.9987 - val_loss: 1.6002 - val_accuracy: 0.7241 Epoch 12/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0101 - accuracy: 0.9988 - val_loss: 1.6486 - val_accuracy: 0.7188 Epoch 13/100 8264/8264 [==============================] - 1s 74us/sample - loss: 0.0098 - accuracy: 0.9988 - val_loss: 1.6749 - val_accuracy: 0.7173 Epoch 14/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0095 - accuracy: 0.9988 - val_loss: 1.6863 - val_accuracy: 0.7178 Epoch 15/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0100 - accuracy: 0.9988 - val_loss: 1.6933 - val_accuracy: 0.7178 Epoch 16/100 8264/8264 [==============================] - 1s 74us/sample - loss: 0.0099 - accuracy: 0.9988 - val_loss: 1.6784 - val_accuracy: 0.7173 Epoch 17/100 8264/8264 [==============================] - 1s 76us/sample - loss: 0.0097 - accuracy: 0.9988 - val_loss: 1.6861 - val_accuracy: 0.7193 Epoch 18/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0102 - accuracy: 0.9988 - val_loss: 1.6802 - val_accuracy: 0.7193 Epoch 19/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0099 - accuracy: 0.9988 - val_loss: 1.7047 - val_accuracy: 0.7197 Epoch 20/100 8264/8264 [==============================] - 1s 74us/sample - loss: 0.0100 - accuracy: 0.9988 - val_loss: 1.6641 - val_accuracy: 0.7212 Epoch 21/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0089 - accuracy: 0.9989 - val_loss: 1.6858 - val_accuracy: 0.7202 Epoch 22/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0090 - accuracy: 0.9989 - val_loss: 1.6959 - val_accuracy: 0.7217 Epoch 23/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0088 - accuracy: 0.9989 - val_loss: 1.6894 - val_accuracy: 0.7217 Epoch 24/100 8264/8264 [==============================] - 1s 76us/sample - loss: 0.0088 - accuracy: 0.9989 - val_loss: 1.6489 - val_accuracy: 0.7227 Epoch 25/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0089 - accuracy: 0.9989 - val_loss: 1.7599 - val_accuracy: 0.7217 Epoch 26/100 8264/8264 [==============================] - 1s 75us/sample - loss: 0.0095 - accuracy: 0.9988 - val_loss: 1.5357 - val_accuracy: 0.7197 Epoch 27/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0079 - accuracy: 0.9990 - val_loss: 1.6903 - val_accuracy: 0.7202 Epoch 28/100 8264/8264 [==============================] - 1s 73us/sample - loss: 0.0073 - accuracy: 0.9992 - val_loss: 1.7159 - val_accuracy: 0.7246 Epoch 00028: early stopping
Используя ранее полученные данные тестирования, мы получаем расчетную точность около 72%. Это означает, что мы можем правильно предсказать, если у клиента есть жалобы на конкретный отель, используя слова, которые он использует для описания этого отеля, примерно в 72% случаев.
def plot_metric(history, metric): train_metrics = history.history[metric] val_metrics = history.history['val_'+metric] epochs = range(1, len(train_metrics) + 1) plt.plot(epochs, train_metrics) plt.plot(epochs, val_metrics) plt.title('Training and validation '+ metric) plt.xlabel("Epochs") plt.ylabel(metric) plt.legend(["train_"+metric, 'val_'+metric]) plt.show()
plt.figure(figsize = (12,8)) plot_metric(history, "loss")
predictions = model.predict(test_padded)
pred_0_1 = [round(x[0]) for x in predictions]
from sklearn.metrics import classification_report, confusion_matrix
print(confusion_matrix( y_test,pred_0_1)) print(classification_report(y_test,pred_0_1))
[[735 278] [302 751]] precision recall f1-score support 0 0.71 0.73 0.72 1013 1 0.73 0.71 0.72 1053 accuracy 0.72 2066 macro avg 0.72 0.72 0.72 2066 weighted avg 0.72 0.72 0.72 2066
Кроме того, взглянув на соответствующую матрицу путаницы, а также на отчет о классификации, мы можем увидеть, что наша исходная модель, по-видимому, так же хорошо предсказывает людей с жалобами, как и предсказывает людей без жалоб. Однако мы могли бы быть более заинтересованы в более надежной модели, когда дело доходит до классификации людей, у которых могут быть жалобы (чувствительность или отзыв для положительного класса), даже если это заставляет нас терять часть отзыва при прогнозировании людей без жалоб (специфичность).
В последнем разделе основное внимание будет уделено построению модели, которая пытается выполнить это предыдущее условие, чтобы более точно классифицировать людей, у которых есть жалобы, а не тех, у кого их нет.
model2 = keras.models.Sequential() model2.add(layers.Embedding(num_unique_words, 60, input_length = max_length)) model2.add(layers.LSTM(40)) model2.add(layers.Dropout(0.2)) model2.add(layers.Dense(32)) model2.add(layers.Dropout(0.2)) model2.add(layers.Dense(16)) model2.add(layers.Dense(1)) model2.add(layers.Activation("sigmoid")) model2.summary()
Model: "sequential_12" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_12 (Embedding) (None, 70, 60) 1597440 _________________________________________________________________ lstm_12 (LSTM) (None, 40) 16160 _________________________________________________________________ dropout_18 (Dropout) (None, 40) 0 _________________________________________________________________ dense_28 (Dense) (None, 32) 1312 _________________________________________________________________ dropout_19 (Dropout) (None, 32) 0 _________________________________________________________________ dense_29 (Dense) (None, 16) 528 _________________________________________________________________ dense_30 (Dense) (None, 1) 17 _________________________________________________________________ activation_12 (Activation) (None, 1) 0 ================================================================= Total params: 1,615,457 Trainable params: 1,615,457 Non-trainable params: 0 _________________________________________________________________
early_stop = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=20) model2.compile(loss = "binary_crossentropy", optimizer = "adam", metrics = ["accuracy"])
history2 = model2.fit(train_padded, y_train, batch_size = 30, epochs = 100, validation_data=(test_padded, y_test), callbacks = [early_stop])
Train on 8264 samples, validate on 2066 samples Epoch 1/100 8264/8264 [==============================] - 7s 788us/sample - loss: 0.6901 - accuracy: 0.5120 - val_loss: 0.6919 - val_accuracy: 0.5237 Epoch 2/100 8264/8264 [==============================] - 4s 532us/sample - loss: 0.5841 - accuracy: 0.6971 - val_loss: 0.4801 - val_accuracy: 0.7778 Epoch 3/100 8264/8264 [==============================] - 4s 528us/sample - loss: 0.3681 - accuracy: 0.8473 - val_loss: 0.4948 - val_accuracy: 0.7788 Epoch 4/100 8264/8264 [==============================] - 4s 520us/sample - loss: 0.2301 - accuracy: 0.9155 - val_loss: 0.5594 - val_accuracy: 0.7536 Epoch 5/100 8264/8264 [==============================] - 5s 553us/sample - loss: 0.1493 - accuracy: 0.9471 - val_loss: 0.6004 - val_accuracy: 0.7536 Epoch 6/100 8264/8264 [==============================] - 5s 545us/sample - loss: 0.1074 - accuracy: 0.9660 - val_loss: 0.8424 - val_accuracy: 0.7202 Epoch 7/100 8264/8264 [==============================] - 4s 536us/sample - loss: 0.0702 - accuracy: 0.9774 - val_loss: 1.2820 - val_accuracy: 0.7236 Epoch 8/100 8264/8264 [==============================] - 4s 543us/sample - loss: 0.0641 - accuracy: 0.9820 - val_loss: 0.8639 - val_accuracy: 0.7401 Epoch 9/100 8264/8264 [==============================] - 5s 549us/sample - loss: 0.0472 - accuracy: 0.9867 - val_loss: 1.1572 - val_accuracy: 0.7357 Epoch 10/100 8264/8264 [==============================] - 4s 533us/sample - loss: 0.0343 - accuracy: 0.9910 - val_loss: 1.1266 - val_accuracy: 0.7193 Epoch 11/100 8264/8264 [==============================] - 4s 524us/sample - loss: 0.0438 - accuracy: 0.9877 - val_loss: 1.5101 - val_accuracy: 0.7318 Epoch 12/100 8264/8264 [==============================] - 4s 511us/sample - loss: 0.0287 - accuracy: 0.9925 - val_loss: 1.2244 - val_accuracy: 0.7348 Epoch 13/100 8264/8264 [==============================] - 4s 501us/sample - loss: 0.0234 - accuracy: 0.9950 - val_loss: 1.6815 - val_accuracy: 0.7299 Epoch 14/100 8264/8264 [==============================] - 4s 504us/sample - loss: 0.0177 - accuracy: 0.9965 - val_loss: 1.5120 - val_accuracy: 0.7352 Epoch 15/100 8264/8264 [==============================] - 4s 504us/sample - loss: 0.0237 - accuracy: 0.9946 - val_loss: 1.3144 - val_accuracy: 0.7328 Epoch 16/100 8264/8264 [==============================] - 4s 511us/sample - loss: 0.0351 - accuracy: 0.9924 - val_loss: 1.1855 - val_accuracy: 0.7309 Epoch 17/100 8264/8264 [==============================] - 4s 505us/sample - loss: 0.0332 - accuracy: 0.9912 - val_loss: 1.3751 - val_accuracy: 0.7280 Epoch 18/100 8264/8264 [==============================] - 4s 503us/sample - loss: 0.0269 - accuracy: 0.9930 - val_loss: 1.4329 - val_accuracy: 0.7328 Epoch 19/100 8264/8264 [==============================] - 4s 500us/sample - loss: 0.0207 - accuracy: 0.9959 - val_loss: 1.2505 - val_accuracy: 0.7289 Epoch 20/100 8264/8264 [==============================] - 4s 506us/sample - loss: 0.0088 - accuracy: 0.9983 - val_loss: 1.7137 - val_accuracy: 0.7241 Epoch 21/100 8264/8264 [==============================] - 4s 500us/sample - loss: 0.0172 - accuracy: 0.9967 - val_loss: 1.5518 - val_accuracy: 0.7425 Epoch 22/100 8264/8264 [==============================] - 4s 503us/sample - loss: 0.0195 - accuracy: 0.9946 - val_loss: 1.8774 - val_accuracy: 0.7328 Epoch 00022: early stopping
predictions2 = model2.predict(test_padded)
pred2_0_1 = [round(x[0]) for x in predictions2]
print(confusion_matrix( y_test,pred2_0_1)) print(classification_report(y_test,pred2_0_1))
[[673 340] [212 841]] precision recall f1-score support 0 0.76 0.66 0.71 1013 1 0.71 0.80 0.75 1053 accuracy 0.73 2066 macro avg 0.74 0.73 0.73 2066 weighted avg 0.74 0.73 0.73 2066
С этой новой моделью мы достигли именно того, к чему стремились. В отчете о классификации четко указывается значение чувствительности около 80% без потери какой-либо «глобальной» предсказательной силы (поскольку точность, по сути, такая же, как у предыдущей модели), что в этом контексте означает, что наша модель была в состоянии определить 80% людей, которые были недовольны каким-либо аспектом отеля за счет выявления только 66% людей, которые дали идеальный отзыв.
Это очень важно, поскольку конкретный отель может изменять вещи, которые могут вызывать дискомфорт у определенного клиента, в режиме реального времени, чтобы в конечном итоге обеспечить почти идеальное обслуживание для указанного клиента, что может привести к положительному отзыву от их.
Аналогичное упражнение можно выполнить, чтобы найти модель, ориентированную на правильную классификацию удовлетворенных людей, чтобы выявить людей, которые с большей готовностью вернутся в отель в будущем.