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

Анализ тональности – это метод, который может вывести/предсказать общее ощущение фрагментов текста, комментариев, статей и любого текстового объекта. Учитывая, что репутация и имидж компании являются первоочередными задачами для большинства предприятий, анализ настроений может предотвратить и уменьшить нежелательную шумиху, вызванную любым комментарием или статьей, видимой в Интернете.

Определить, является ли комментарий, относящийся к нашей компании, положительным или отрицательным, можно с помощью анализа настроений и моделей ИИ. На следующих шагах мы сравним некоторые из наиболее часто используемых моделей в этом случае.

Предварительная обработка вашего текста

Это один из самых важных шагов, даже в контексте, отличном от НЛП, вам необходимо сначала изучить и предварительно обработать ваши данные. Нашим моделям потребуются чистые и значимые данные. Однако это не основная цель данной статьи, поэтому мы будем краткими.

Мы будем использовать набор данных Sentiment140 с 1,6 миллионами твитов, доступных в TensorFlow и Kaggle. Имейте в виду, что вы можете взять любой набор текстовых данных из своей компании и адаптировать следующий процесс к своим потребностям.

Токенизаторы

Токенизация — это наш первый шаг очистки. Токенизаторы разделят текст на токены.

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

  • Нижний регистр
  • Удаление стоп-слов
  • Замена эмодзи на «Эмодзи» + значение одним словом
  • Замена упоминания на «ПОЛЬЗОВАТЕЛЬ»
  • Удаление не буквенно-цифровых символов

На следующем шаге мы выберем способ создания функций из наших списков токенов между векторизаторами и встраиванием.

Создавайте объекты с помощью векторизаторов

Наш первый метод создания признаков — использование векторизаторов. Для простоты мы будем использовать только 2 векторизатора:

  • Подсчет: он просто подсчитывает, сколько раз токен присутствует в документе.
  • Tf-idf: работает аналогично счетчику, но уравновешивается количеством документов в корпусе, где присутствует токен.

Создание функций с помощью встраивания Word

Наш второй метод создания функций — это встраивание слов. Встраивание слов может фиксировать контекст, семантическое и синтаксическое сходство (пол, синонимы…). В нашем примере мы будем использовать только два вложения:

  • Spacy. Хотя Spacy хорошо известен своим надежным токенизатором, его также можно использовать для встраивания слов.
  • Doc2Vec: я рекомендую вам ознакомиться с этой статьей, чтобы получить представление о Doc2Vec.
  • Слой внедрения: Keras реализует полнофункциональный способ встраивания слов.

Оценка модели

Мы можем разделить наш набор данных, используя следующую пропорцию: 80% для обучения и 20% для тестирования.

Для оценки производительности модели используются 2 метрики:

  • ROC AUC: площадь под кривой ROC дает нам меру разделимости между нашими двумя классами (отрицательным и положительным).
  • AP: Средняя точность дает нам общее представление о производительности модели.

Чтобы проверить, не переоснащается ли модель, мы рассчитаем обе эти метрики в наборе данных для обучения и тестирования. Если метрики обучения намного выше, чем метрики тестирования, мы делаем вывод, что модель переобучается.

1-я модель: модель логистической регрессии

Эта модель является менее сложной в этой статье и будет использоваться в качестве базовой.

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

from sklearn.linear_model import LogisticRegressionCV

# Define model
model = LogisticRegressionCV(solver='saga', random_state=42)
# Train model
model.fit(X_train, y_train)
# Compute predictions
y_pred = model.predict(X_test)

Время тренировки: 51 минута

Время предсказания: 0 секунд

Результаты испытаний поезда

AP = 0.93

ROC AUC = 0,93

Результаты набора тестов

AP = 0.87

ROC AUC = 0,87

2-й тип моделей: модели FNN

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

Со слоем векторизации: количество

Как и CountVectorizer из библиотеки scikit-learn, наш уровень векторизации будет генерировать функции на основе количества токенов.

import tensorflow as tf

from keras.models import Sequential
from keras.layers import Input, TextVectorization, Dense, Dropout, Activation
from keras.callbacks import TensorBoard, EarlyStopping
from keras.metrics import AUC

model_name = "fnn_count"
vocabulary_size = 10000

# define vectorization layer
vectorize_layer = TextVectorization(
    output_mode="count",
    max_tokens=vocabulary_size,
    pad_to_max_tokens=True,
)
vectorize_layer.adapt(
    df.text,
    batch_size=128,
)

# define NN model
model = Sequential(name=model_name)
model.add(Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)
model.add(Dense(100, input_shape=(vocabulary_size,), activation="relu"))
model.add(Dropout(0.2))
model.add(Dense(10, activation="relu"))
model.add(Dense(1, activation="sigmoid"))

# compile NN network
model.compile(
    loss="binary_crossentropy",
    optimizer="adam",
    metrics=[
        "accuracy",
        AUC(curve="ROC", name="ROC_AUC"),
        AUC(curve="PR", name="AP"),
    ],
)

# fit NN model
model.fit(
    X_train,
    y_train,
    epochs=10,
    batch_size=128,
    validation_split=0.2,
    callbacks=[
        TensorBoard(log_dir=f"logs/{model.name}"),
        EarlyStopping(monitor="val_loss", patience=2),
    ],
    workers=4,
    use_multiprocessing=True,
)

Обучить результаты

Средняя точность = 0,94

ROC AUC = 0,94

Результаты тестирования

Средняя точность = 0,89

ROC AUC = 0,89

С встраиванием SpaCy

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

from tqdm import tqdm
import spacy

!python -m spacy download en_core_web_lg
nlp = spacy.load("en_core_web_lg")

X = [nlp(doc).vector for doc in tqdm(df.text)]

Используемая модель такая же, как и раньше, за исключением части слоя векторизации:

from keras.models import Sequential
from keras.layers import (
    Input,
    TextVectorization,
    Dense,
    Dropout,
    Activation,
)
from keras.callbacks import TensorBoard, EarlyStopping
from keras.metrics import AUC


model_name = "fnn_spacy_embedded"
vector_size = len(X[0])


# define NN model
model = Sequential(name=model_name)
model.add(Dense(100, input_shape=(vector_size,), activation="relu"))
model.add(Dropout(0.2))
model.add(Dense(10, activation="relu"))
model.add(Dense(1, activation="sigmoid"))

# compile NN network
model.compile(
    loss="binary_crossentropy",
    optimizer="adam",
    metrics=[
        "accuracy",
        AUC(curve="ROC", name="ROC_AUC"),
        AUC(curve="PR", name="AP"),
    ],
)

# fit NN model
model.fit(
    np.stack(X_train, axis=0),
    y_train,
    epochs=10,
    batch_size=128,
    validation_split=0.2,
    callbacks=[
        TensorBoard(log_dir=f"logs/{model.name}"),
        EarlyStopping(monitor="val_loss", patience=2),
    ],
    workers=4,
    use_multiprocessing=True,
)

Обучить результаты

Средняя точность = 0,87

ROC AUC = 0,86

Результаты тестирования

Средняя точность = 0,86

ROC AUC = 0,86

С встраиванием Doc2Vec

Модель Doc2Vec будет генерировать векторы признаков. Doc2Vec основан на Word2Vec, и к логике добавлено встраивание абзаца.

Код модели остается прежним, меняется только встраиваемая часть:

!pip install gensim

from gensim.models.doc2vec import TaggedDocument, Doc2Vec
from gensim.utils import simple_preprocess

# Tag documents for training
corpus = [
    TaggedDocument(words=simple_preprocess(doc), tags=[i])
    for i, doc in enumerate(df.text)
]

# Train doc2vec model
doc2vec = Doc2Vec()
doc2vec.build_vocab(corpus)
doc2vec.train(corpus, total_examples=doc2vec.corpus_count,
  epochs=doc2vec.epochs)

# Vectorize text
X = [doc2vec.infer_vector(doc.words) for doc in corpus]

Обучить результаты

Средняя точность = 0,82

ROC AUC = 0,83

Результаты тестирования

Средняя точность = 0,82

ROC AUC = 0,82

3-й тип моделей: модели RNN

Мы будем использовать две продвинутые модели рекуррентной нейронной сети: LSTM и двунаправленный LSTM. Эти архитектуры рекомендуются для последовательных данных и широко используются в обработке естественного языка (NLP).

Использование RNN предоставит модели возможность хранить информацию о прошлых шагах.

LSTM-модель

Мы добавим слой Embedding, который позаботится о создании функций для модели LSTM. Векторы объектов ограничены 10 000, чтобы ускорить выполнение.

from keras.models import Sequential
from keras.layers import (
    Input,
    TextVectorization,
    Dense,
    Dropout,
    Activation,
    Embedding,
    LSTM,
)
from keras.callbacks import TensorBoard, EarlyStopping
from keras.metrics import AUC


# Model constants.
model_name = "lstm_embedded"
max_features = 10000
sequence_length = 30
embedding_dim = 100
rnn_units = 100


# Define vectorizer
vectorize_layer = TextVectorization(
    output_mode="int",
    max_tokens=max_features,
    output_sequence_length=sequence_length,
)
vectorize_layer.adapt(
    df.text,
    batch_size=128,
)


# define NN model
model = Sequential(name=model_name)
model.add(Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)

# Embedding layer
model.add(
    Embedding(
        max_features,
        embedding_dim,
        input_length=sequence_length,
    )
)

# LSTM layer
model.add(LSTM(units=rnn_units, dropout=0.2))

# Dense layers
model.add(Dense(100, input_shape=(max_features,), activation="relu"))
model.add(Dropout(0.2))
model.add(Dense(10, activation="relu"))

# Classification layer
model.add(Dense(1, activation="sigmoid"))

# compile NN network
model.compile(
    loss="binary_crossentropy",
    optimizer="adam",
    metrics=[
        "accuracy",
        AUC(curve="ROC", name="ROC_AUC"),
        AUC(curve="PR", name="AP"),
    ],
)

# fit NN model
model.fit(
    X_train,
    y_train,
    epochs=10,
    batch_size=128,
    validation_split=0.2,
    callbacks=[
        EarlyStopping(monitor="val_loss", patience=2),
    ],
    workers=4,
    use_multiprocessing=True,
)

Обучить результаты

Средняя точность = 0,94

ROC AUC = 0,93

Результаты тестирования

Средняя точность = 0,91

ROC AUC = 0,91

Двунаправленная модель LSTM

Эта модель почти такая же, как и предыдущая. Слой LSTM заменяется двунаправленным слоем LSTM.

from keras.models import Sequential
from keras.layers import (
    Input,
    TextVectorization,
    Dense,
    Dropout,
    Activation,
    Embedding,
    Bidirectional,
    LSTM,
)
from keras.callbacks import TensorBoard, EarlyStopping
from keras.metrics import AUC


# Model constants
model_name = "bidirectional_lstm_embedded"
max_features = 10000
sequence_length = 30
embedding_dim = 100
rnn_units = 100


# Define vectorizer
vectorize_layer = TextVectorization(
    output_mode="int",
    max_tokens=max_features,
    output_sequence_length=sequence_length,
)
vectorize_layer.adapt(
    df.text,
    batch_size=128,
)

# define NN model
model = Sequential(name=model_name)
model.add(Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)

# Embedding layer
model.add(
    Embedding(
        max_features,
        embedding_dim,
        input_length=sequence_length,
    )
)

# Bidirectional LSTM layer
model.add(Bidirectional(LSTM(units=rnn_units, dropout=0.2)))

# Dense layers
model.add(Dense(100, input_shape=(max_features,), activation="relu"))
model.add(Dropout(0.2))
model.add(Dense(10, activation="relu"))

# Classification layer
model.add(Dense(1, activation="sigmoid"))

# compile NN network
model.compile(
    loss="binary_crossentropy",
    optimizer="adam",
    metrics=[
        "accuracy",
        AUC(curve="ROC", name="ROC_AUC"),
        AUC(curve="PR", name="AP"),
    ],
)

# fit NN model
model.fit(
    X_train,
    y_train,
    epochs=10,
    batch_size=128,
    validation_split=0.2,
    callbacks=[
        TensorBoard(log_dir=f"logs/{model.name}"),
        EarlyStopping(monitor="val_loss", patience=2),
    ],
    workers=4,
    use_multiprocessing=True,
)

Результаты обучения

Средняя точность = 0,94

ROC AUC = 0,93

Результаты теста

Средняя точность = 0,91

ROC AUC = 0,91

4-й тип моделей: модели Bert

Модель BERT (представление двунаправленного кодировщика для преобразователя) была представлена ​​в этой статье и впервые опубликована в этом репозитории командой Google Research в 2018 году.

BERT — это модель-трансформер, предварительно обученная на огромном количестве данных на английском языке. Учебная часть была выполнена с самоконтролем, что означает, что тренировочные данные не маркировались.

Он использует моделирование маскированного языка (MLM), которое отличается от RNN, которое может принимать слова только одно за другим. Он может понимать шаблоны данных и обобщать их для нескольких задач НЛП.

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

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

Прежде чем использовать модели BERT, мы должны токенизировать и подготовить все необходимые векторы (input_ids, warning_mask и token_type_ids) следующим образом:

%pip install transformers
# Tokenizers, Stemmers and Lemmatizers
from transformers import AutoTokenizer

BERT_MODEL = "bert-base-uncased"  #or "vinai/bertweet-base"
MAX_LENGTH = 50

tokenizer = AutoTokenizer.from_pretrained(BERT_MODEL, do_lower_case=True)

input_ids = np.asarray(
    [
        tokenizer(sent, max_length=MAX_LENGTH, padding="max_length", truncation=True)[
            "input_ids"
        ]
        for sent in tqdm(df.text)
    ]
)
attention_mask = np.asarray(
    [
        tokenizer(sent, max_length=MAX_LENGTH, padding="max_length", truncation=True)[
            "attention_mask"
        ]
        for sent in tqdm(df.text)
    ]
)
token_type_ids = np.asarray(
    [
        tokenizer(sent, max_length=MAX_LENGTH, padding="max_length", truncation=True)[
            "token_type_ids"
        ]
        for sent in tqdm(df.text)
    ]
)

Базовая бескорпусная модель BERT

Поскольку модель предварительно обучена на большом корпусе, мы не будем изучать результаты обучения.

Мы будем использовать bert-base-uncased от Hugging Face.

from transformers import TFAutoModelForSequenceClassification
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.losses import BinaryCrossentropy
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.metrics import BinaryAccuracy


# Define NN model
print("Defining model...")
model = TFAutoModelForSequenceClassification.from_pretrained(BERT_MODEL, num_labels=2)

# compile NN network
print("Compiling model...")
model.compile(
    loss=BinaryCrossentropy(),
    optimizer=Adam(learning_rate=2e-5),  # Value recommended by the Bert team
    metrics=BinaryAccuracy(),
)

# fit NN model
print("Fitting model...")
model.fit(
    [input_ids_train, attention_mask_train, token_type_ids_train],
    labels_train,
    epochs=10,
    batch_size=8,
    validation_split=0.2,
    callbacks=[
        EarlyStopping(monitor="val_loss", patience=3),
    ],
    workers=4,
    use_multiprocessing=True,
)

Результаты тестирования

Confusion Matrix : 
[[17631 82369]
 [  415 99585]]
ROC AUC score :  0.883
Average Precision score :  0.822

BERTweet модель

Поскольку модель предварительно обучена на большом корпусе, мы не будем изучать результаты обучения.

Мы будем использовать vinai/bertweet-base от Hugging Face. Эта модель BERT была обучена на базе 850 млн английских твитов.

Кодовая часть такая же, как и раньше, вам просто нужно изменить название модели, чтобы загрузить нужное.

Результаты тестирования

Confusion Matrix :
[[79351 20649]
[10016 89984]]

ROC AUC score : 0.915
Average Precision score : 0.901

Сравнение моделей

Бенчмарк основных показателей

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

Лучшие модели — LSTM (простая и двунаправленная) и BERTweet. Они хорошо работают на наших тестовых наборах данных.

Худшие модели — FNN с встраиванием Word (Doc2Vec и SpaCy) и базой BERT. Они сложнее нашей базовой модели, но менее эффективны. Это означает, что сложность не обязательно означает эффективность.

Специфичность против чувствительности

Напомню, что специфичность измеряет способность модели предсказывать истинные отрицательные значения. С другой стороны, чувствительность измеряет способность модели предсказывать истинные положительные результаты.

Мы видим, что базовая модель BERT имеет высокую чувствительность. Это означает, что эта модель правильно определяет большинство истинно положительных твитов. В нашем случае это не имеет значения, поскольку мы пытаемся правильно идентифицировать негативные твиты.

Модели BERTweet, FNN с подсчетом слов и LSTM обладают высокой специфичностью. Это означает, что эти модели минимизируют количество негативных твитов, которые неправильно классифицированы. В нашем случае это может быть интересно в том смысле, что мы не хотим, чтобы «плохой» твит классифицировали как положительный.

Стоимость времени

Чем сложнее модели, тем больше времени нам потребуется для их обучения. В нашем наборе моделей мы можем легко сказать, что наша модель логистической регрессии является менее сложной, и для обучения на ЦП требуется всего 45 минут.

Далее у нас есть модели FNN и RNN, которым требуется от 1 до 2 часов для обучения на GPU.

Наконец, моделям BERT и BERTweet требуется огромное количество ресурсов, чтобы иметь хорошие характеристики. Средние показатели достигаются после 6,5 часов обучения на графическом процессоре для BERT, а хорошие показатели — после 11 часов для BERTweet.

Выбор модели

Наконец, в нашем примере компания выберет LSTM в качестве производственной модели. Действительно, у этой модели очень много плюсов:

  • Хорошие выступления
  • Разумно с точки зрения необходимых вычислительных ресурсов
  • Архитектура может быть полностью настроена в соответствии с возможностями наших ресурсов.

Тем не менее, BERTweet может быть очень интересной моделью, если мы получим достаточно данных и ресурсов GPU/TPU. Производительность увеличивается по мере увеличения времени обучения. В долгосрочной перспективе, я думаю, у него будет лучшая производительность, чем у модели LSTM.