Вопрос или проблема
Я пытаюсь составить скрипт, который классифицирует комментарии на адекватные и неадекватные. Я ранее задавал вопрос здесь с полным кодом, но думаю, что изолировал проблему в настройке модели, поэтому я его удалил и надеюсь, что это более упрощенный и понятный вариант. Пример, который я пытаюсь воспроизвести, – это классический комментарий IMDB, где комментарии либо положительные, либо отрицательные, но в моем случае – адекватные или нет. Мой токенизатор и очистка текста, а также дополнение работают хорошо, и у меня есть обучающий набор данных, который корректно возвращает токенизированные последовательности. Я думаю, что моя основная ошибка заключается в следующем:
model = tf.keras.Sequential([
tf.keras.layers.Embedding(10000, 300),
tf.keras.layers.GlobalAveragePooling1D(),
tf.keras.layers.Dense(1, activation = 'sigmoid')])
model.summary()
model.compile(optimizer="adam", loss="binary_crossentropy", metrics = ['accuracy'])
es = tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", mode="max")
callbacks = [es]
history = model.fit(train_seqs, train_df['adq'].values,
batch_size = BATCH_SIZE,
epochs = EPOCHS,
verbose = 2,
validation_split = 0.2,
callbacks = callbacks)
model.evaluate(test_seqs, test_df['adq'].values)
Главная проблема с выводом заключается в том, что когда я запускаю модель для предсказания комментариев без классификации, модель возвращает одно и то же значение для каждого отдельного комментария. Я провел некоторые исследования, и люди предлагали нормализовать пакет, и я пробовал добавить слой нормализации пакета в свою модель, но это не помогло. Можете ли вы, пожалуйста, взглянуть и показать мне, где я ошибаюсь? Большое спасибо за вашу помощь!
Вот весь мой скрипт согласно моему комментарию ниже:
import pandas as pd
import tensorflow as tf
import pickle
import string
import re
NUM_WORDS = 10000
SEQ_LEN = 512
EMBEDDING_SIZE = 300
BATCH_SIZE = 70
EPOCHS = 20
HIGHEST_PROTOCOL = 3
THRESHOLD = 0.60
train_df = pd.read_csv(r'C:\Users\peter\OneDrive\Documents\IMDBtrain.csv')
test_df = pd.read_csv(r'C:\Users\peter\OneDrive\Documents\IMDBtest.csv')
def clean_text(text, remove_stopwords=True):
'''Очистить текст с опцией удаления стоп-слов'''
# Привести слова к нижнему регистру и разбить их
text = text.lower().split()
# При желании, удалить стоп-слова
if remove_stopwords:
stops = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours',
'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself',
'its', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themslves', 'what', 'which',
'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be',
'been', 'be', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a',
'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at',
'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before',
'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over',
'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why',
'how', 'all', 'any', 'both', 'each', 'few', 'most', 'more', 'other', 'some', 'such', 'no',
'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will',
'just', 'don', 'should', 'now']
text = [w for w in text if not w in stops]
text = " ".join(text)
# Очистить текст
text = re.sub(r"<br />", " ", text)
text = re.sub(r"[^a-z]", " ", text)
text = re.sub(r" ", " ", text) # Удалить лишние пробелы
text = re.sub(r" ", " ", text)
# Вернуть список слов
return(text)
train_df["text"] = train_df["text"].apply(lambda x: clean_text(x))
test_df["text"] = test_df["text"].apply(lambda x: clean_text(x))
train_df = train_df.sample(frac = 1).reset_index(drop = True)
test_df = test_df.sample(frac = 1).reset_index(drop = True)
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words = NUM_WORDS, oov_token = '<UNK>')
tokenizer.fit_on_texts(train_df['text'])
train_seqs = tokenizer.texts_to_sequences(train_df['text'])
test_seqs = tokenizer.texts_to_sequences(test_df['text'])
train_seqs = tf.keras.preprocessing.sequence.pad_sequences(train_seqs, maxlen = SEQ_LEN, padding = 'post')
test_seqs = tf.keras.preprocessing.sequence.pad_sequences(test_seqs, maxlen = SEQ_LEN, padding = 'post')
model = tf.keras.Sequential([
tf.keras.layers.Embedding(NUM_WORDS, EMBEDDING_SIZE),
tf.keras.layers.GlobalAveragePooling1D(),
tf.keras.layers.Dense(1, activation = 'sigmoid')])
model.summary()
model.compile(optimizer="adam", loss="binary_crossentropy", metrics = ['accuracy'])
es = tf.keras.callbacks.EarlyStopping(monitor="val_accuracy", mode="max")
callbacks = [es]
history = model.fit(train_seqs, train_df['adq'].values,
batch_size = BATCH_SIZE,
epochs = EPOCHS,
validation_split = 0.2,
callbacks = callbacks)
model.evaluate(test_seqs, test_df['adq'].values)
model.save('model.ps1')
with open('tokenizer.pickle', 'wb') as handle:
pickle.dump(tokenizer, handle, protocol = pickle.HIGHEST_PROTOCOL)
del model
del tokenizer
loaded_model = tf.keras.models.load_model('model.ps1')
with open('tokenizer.pickle', 'rb') as f:
loaded_tokenizer = pickle.load(f)
def prepare_predict_data(tokenizer, comments):
seqs = tokenizer.texts_to_sequences(comments)
seqs = tf.keras.preprocessing.sequence.pad_sequences(seqs, maxlen = SEQ_LEN, padding = 'post')
return seqs
comments_to_pred = pd.read_csv(r'C:\Users\peter\OneDrive\Documents\IMDBload.csv')
my_comments = comments_to_pred.to_numpy().tolist()
my_seqs = prepare_predict_data(loaded_tokenizer, my_comments)
preds = loaded_model.predict(my_seqs)
pred_df = pd.DataFrame(columns = ['text', 'adq'])
pred_df['text'] = my_comments
pred_df['adq'] = preds
print(pred_df.head(20))
pred_df['adq'] = pred_df['adq'].apply(lambda x: 'pos' if x > THRESHOLD else 'neg')
#print(pred_df.head(40))
```
Вы применяете глобальное усреднение к своему слою встраивания. Убедитесь, что это делается по словам, а не по оси встраивания. Если выходные данные вашего встраивания имеют форму (Пакет, Слова, 300), вам нужно применить GlobalAveragePool1D к оси 1 (слова).
Однако одна проблема может заключаться в том, что, усредняя по словам, вы искажаете данные из-за дополнения вашего ввода. Если вы дополните 10-словеый ввод до 25, а затем возьмете среднее на всех 25, где 15 из 25 – нули, вы будете искажать усреднение почти случайно (случайно в зависимости от длины ввода), что ваша не-рекуррентная модель не сможет понять. Вам следует добавить дополнительный слой Lambda, чтобы усреднять только по длине ввода и передавать длину ввода в качестве дополнительного входного значения.
Ответ или решение
Конечно! Давайте разберём вашу модель и поймём, как оптимизировать её для классификации комментариев как адекватных или неадекватных. Ниже приведены ключевые моменты, которые могут помочь улучшить результаты вашей модели.
Проблема с выходными данными
Если ваша модель возвращает одно и то же значение для всех комментариев, это может быть связано с несколькими факторами:
-
Проблема с обработкой данных: Когда вы используете
GlobalAveragePooling1D
, он объединяет информацию со всех слов в последовательности, включая нулевые значения, вставленные вpad_sequences
. Таким образом, в среднем это может давать вам слишком много нуля, если вы работаете с последовательностями разной длины, что, в свою очередь, затрудняет обучение модели. -
Неоптимальные параметры модели: Размерность встраивания и число эпох могут быть не оптимальными для вашего набора данных.
Решение
1. Устранение проблемы с GlobalAveragePooling1D
Чтобы избежать проблемы объединения с нулевыми значениями после паддинга, вы можете использовать Masking
или Lambda
слой для маскировки паддингов или же воспользоваться Keras Functional API
вместо Sequential API
. Вот пример, как это реализовать:
from tensorflow.keras.layers import Input, Embedding, GlobalAveragePooling1D, Dense, Masking
from tensorflow.keras.models import Model
inputs = Input(shape=(SEQ_LEN,))
embedding = Embedding(NUM_WORDS, EMBEDDING_SIZE)(inputs)
masked = Masking(mask_value=0)(embedding)
pooled = GlobalAveragePooling1D()(masked)
outputs = Dense(1, activation='sigmoid')(pooled)
model = Model(inputs, outputs)
2. Использование более сложной архитектуры
Вы можете добавить больше слоёв и сложности в вашу модель для повышения её способности к обучению:
from tensorflow.keras.layers import Dropout
model = tf.keras.Sequential([
Embedding(NUM_WORDS, EMBEDDING_SIZE, input_length=SEQ_LEN),
GlobalAveragePooling1D(),
Dense(128, activation='relu'),
Dropout(0.5),
Dense(1, activation='sigmoid')
])
3. Изменение параметров обучения
Обратите внимание на параметры, такие как размер пакета и количество эпох. Иногда большая размерность пакета может приводить к тому, что модель не обучается достаточно эффективно:
history = model.fit(train_seqs, train_df['adq'].values,
batch_size=32, # уменьшите размер пакета
epochs=10, # попробуйте уменьшить количество эпох
validation_split=0.2,
callbacks=callbacks)
4. Проверка Подготовленных Данных
Обратите внимание, что ваша функция обработки комментариев и токенизация корректно работают. Убедитесь, что ваши настраиваемые функции действительно очищают текст и что нет никаких остатков, которые могут влиять на токенизацию.
5. Визуализация и анализ
Используйте визуализацию истории обучения, чтобы понять, происходит ли переобучение. Если точность на обучающем наборе высока, а на проверочном – низка, возможно, стоит уменьшить сложность модели или использовать регуляризацию.
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label='val_accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
Заключение
Проблема, с которой вы столкнулись, скорее всего, связана с обработкой входных данных и архитектурой вашей модели. Улучшив эти аспекты, вы должны увидеть более вариативные и точные результаты модели. Если у вас будут ещё вопросы или вам потребуется дополнительная помощь, не стесняйтесь задавать!