paint-brush
Пример классификации текста с помощью машинного обучения с учетом особенностей продуктак@bemorelavender
29,438 чтения
29,438 чтения

Пример классификации текста с помощью машинного обучения с учетом особенностей продукта

к Maria K17m2024/03/12
Read on Terminal Reader

Слишком долго; Читать

Это тематическое исследование машинного обучения с акцентом на продукт: мы собираемся представить, что у нас есть реальный продукт, который нужно улучшить. Мы изучим набор данных и опробуем различные модели, такие как логистическая регрессия, рекуррентные нейронные сети и преобразователи, проверив, насколько они точны, как они собираются улучшить продукт, насколько быстро они работают и легко ли их отлаживать. и масштабировать.
featured image - Пример классификации текста с помощью машинного обучения с учетом особенностей продукта
Maria K HackerNoon profile picture


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


Вы можете прочитать полный код тематического исследования на GitHub и просмотреть блокнот анализа с интерактивными диаграммами в Jupyter Notebook Viewer .


Взволнованный? Давайте займемся этим!

Постановка задачи

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


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


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


Хорошо или плохо? Как выбрать лучшую модель

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


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


Конечно, типичным финальным этапом тестирования кандидата является AB-тестирование — онлайн-этап, который дает лучшее представление о том, как изменения повлияют на клиентов. Обычно AB-тестирование занимает больше времени, чем оффлайн-тестирование, поэтому тестируются только лучшие кандидаты с оффлайн-этапа. Это тематическое исследование, и у нас нет реальных пользователей, поэтому мы не будем рассматривать AB-тестирование.


Что еще нам следует учитывать, прежде чем продвигать кандидата на AB-тестирование? О чем мы можем подумать на этапе автономного тестирования, чтобы сэкономить время на онлайн-тестирование и убедиться, что мы действительно тестируем наилучшее возможное решение?


Превращение технических показателей в показатели, ориентированные на воздействие

Сбалансированная точность — это здорово, но эта оценка не отвечает на вопрос: «Как именно модель повлияет на продукт?». Чтобы найти более ориентированную на продукт оценку, мы должны понять, как мы собираемся использовать модель.


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


Чтобы избежать этого, мы выберем пороговые значения для оценки модели так, чтобы позволить себе только 1% ошибок. Тогда метрика, ориентированная на продукт, может быть установлена следующим образом:


Какой процент элементов мы можем классифицировать автоматически, если наша устойчивость к ошибкам составляет всего 1%?


Ниже мы будем называть это Automatic categorisation percentage при выборе лучшей модели. Полный код выбора порога можно найти здесь .


Время вывода

Сколько времени модели требуется для обработки одного запроса?


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


Масштабируемость

Когда наш продукт будет расти, насколько легко будет управлять ростом, используя данную архитектуру?


Под ростом мы можем понимать:

  • больше категорий, более высокая детализация категорий
  • более длинные описания
  • большие наборы данных
  • и т. д.

Придется ли нам переосмыслить выбор модели, чтобы справиться с ростом, или будет достаточно простого переобучения?


Интерпретируемость

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


Размер модели

Размер модели имеет значение, если:

  • мы хотим, чтобы наша модель оценивалась на стороне клиента
  • он настолько велик, что не помещается в оперативную память


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

Исследование и очистка набора данных

С чем мы работаем? Давайте посмотрим на данные и посмотрим, нужно ли их очищать!


Набор данных содержит 2 столбца: описание элемента и категория, всего 50,5 тыс. строк.

 file_name = "ecommerceDataset.csv" data = pd.read_csv(file_name, header=None) data.columns = ["category", "description"] print("Rows, cols:", data.shape) # >>> Rows, cols: (50425, 2)


Каждому товару присвоена 1 из 4 доступных категорий: Household », Books , Electronics или Clothing & Accessories . Вот 1 пример описания товара для каждой категории:


  • Бытовой SPK Домашний декор Глиняная настенная подвеска ручной работы «Лицо» (разноцветная, В35хШ12см) Сделайте свой дом красивее с помощью этой настенной подвески ручной работы «Терракотовая индийская маска для лица», никогда прежде вы не сможете поймать эту вещь ручной работы на рынке. Вы можете добавить это в свою гостиную / входной вестибюль.


  • Книги BEGF101/FEG1-Базовый курс на английском языке-1 (Neeraj Publications, издание 2018 г.) BEGF101/FEG1-Базовый курс на английском языке-1


  • Одежда и аксессуары Женский джинсовый комбинезон Broadstar Получите пропуск на полный доступ в комбинезонах Broadstar от Broadstar. В этих комбинезонах из джинсовой ткани вам будет комфортно. Сочетайте их с верхом белого или черного цвета, чтобы завершить повседневный образ.


  • Electronics Caprigo Heavy Duty — кронштейн для потолочного крепления проектора премиум-класса высотой 2 фута (регулируемый — белый — вес 15 кг)


Отсутствующие значения

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

 print(data.info()) # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 50425 entries, 0 to 50424 # Data columns (total 2 columns): # # Column Non-Null Count Dtype # --- ------ -------------- ----- # 0 category 50425 non-null object # 1 description 50424 non-null object # dtypes: object(2) # memory usage: 788.0+ KB data.dropna(inplace=True)


Дубликаты

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

 repeated_messages = data \ .groupby("description", as_index=False) \ .agg( n_repeats=("category", "count"), n_unique_categories=("category", lambda x: len(np.unique(x))) ) repeated_messages = repeated_messages[repeated_messages["n_repeats"] > 1] print(f"Count of repeated messages (unique): {repeated_messages.shape[0]}") print(f"Total number: {repeated_messages['n_repeats'].sum()} out of {data.shape[0]}") # >>> Count of repeated messages (unique): 13979 # >>> Total number: 36601 out of 50424


После удаления дубликатов у нас осталось 55% исходного набора данных. Набор данных хорошо сбалансирован.

 data.drop_duplicates(inplace=True) print(f"New dataset size: {data.shape}") print(data["category"].value_counts()) # New dataset size: (27802, 2) # Household 10564 # Books 6256 # Clothing & Accessories 5674 # Electronics 5308 # Name: category, dtype: int64


Описание Язык

Обратите внимание, что согласно описанию набора данных,

Набор данных был взят с индийской платформы электронной коммерции.


Описания не обязательно написаны на английском языке. Некоторые из них написаны на хинди или других языках с использованием символов, отличных от ASCII, или транслитерированы латинским алфавитом, или используют смесь языков. Примеры из категории Books :


  • यू जी सी – नेट जूनियर रिसर्च फैलोशिप एवं सहायक प्रोफेसर योग्यता …
  • Prarambhik Bhartiy Itihas
  • History of NORTH INDIA/வட இந்திய வரலாறு/ …


Чтобы оценить наличие неанглийских слов в описаниях, посчитаем 2 балла:


  • ASCII-оценка: процент символов, отличных от ASCII, в описании.
  • Оценка действительных английских слов: если рассматривать только латинские буквы, какой процент слов в описании являются допустимыми на английском языке? Допустим, допустимые английские слова — это те, которые присутствуют в Word2Vec-300 , обученном на корпусе английского языка.


Используя ASCII-оценку, мы узнаем, что только 2,3% описаний состоят из более чем 1% символов, отличных от ASCII.

 def get_ascii_score(description): total_sym_cnt = 0 ascii_sym_cnt = 0 for sym in description: total_sym_cnt += 1 if sym.isascii(): ascii_sym_cnt += 1 return ascii_sym_cnt / total_sym_cnt data["ascii_score"] = data["description"].apply(get_ascii_score) data[data["ascii_score"] < 0.99].shape[0] / data.shape[0] # >>> 0.023


Оценка допустимых английских слов показывает, что только 1,5% описаний содержат менее 70% допустимых английских слов среди слов ASCII.

 w2v_eng = gensim.models.KeyedVectors.load_word2vec_format(w2v_path, binary=True) def get_valid_eng_score(description): description = re.sub("[^az \t]+", " ", description.lower()) total_word_cnt = 0 eng_word_cnt = 0 for word in description.split(): total_word_cnt += 1 if word.lower() in w2v_eng: eng_word_cnt += 1 return eng_word_cnt / total_word_cnt data["eng_score"] = data["description"].apply(get_valid_eng_score) data[data["eng_score"] < 0.7].shape[0] / data.shape[0] # >>> 0.015


Поэтому большинство описаний (около 96%) на английском или преимущественно на английском языке. Мы можем удалить все остальные описания, но вместо этого давайте оставим их как есть и посмотрим, как каждая модель с ними справится.

Моделирование

Давайте разделим наш набор данных на 3 группы:

  • Train 70% - за обучение моделей (19к сообщений)

  • Тест 15% - на выбор параметров и порогов (4,1 тыс. сообщений)

  • Eval 15% - за выбор финальной модели (4,1к сообщений)


 from sklearn.model_selection import train_test_split data_train, data_test = train_test_split(data, test_size=0.3) data_test, data_eval = train_test_split(data_test, test_size=0.5) data_train.shape, data_test.shape, data_eval.shape # >>> ((19461, 3), (4170, 3), (4171, 3))


Базовая модель: мешок слов + логистическая регрессия

Полезно сначала сделать что-нибудь простое и тривиальное, чтобы получить хорошую основу. В качестве основы давайте создадим структуру «мешок слов» на основе набора данных поезда.


Давайте также ограничим размер словаря 100 словами.

 count_vectorizer = CountVectorizer(max_features=100, stop_words="english") x_train_baseline = count_vectorizer.fit_transform(data_train["description"]) y_train_baseline = data_train["category"] x_test_baseline = count_vectorizer.transform(data_test["description"]) y_test_baseline = data_test["category"] x_train_baseline = x_train_baseline.toarray() x_test_baseline = x_test_baseline.toarray()


Я планирую использовать логистическую регрессию в качестве модели, поэтому перед обучением мне нужно нормализовать функции счетчика.

 ss = StandardScaler() x_train_baseline = ss.fit_transform(x_train_baseline) x_test_baseline = ss.transform(x_test_baseline) lr = LogisticRegression() lr.fit(x_train_baseline, y_train_baseline) balanced_accuracy_score(y_test_baseline, lr.predict(x_test_baseline)) # >>> 0.752


Многоклассовая логистическая регрессия показала сбалансированную точность 75,2%. Это отличная база!


Хотя общее качество классификации невелико, модель все же может дать нам некоторую информацию. Давайте посмотрим на матрицу путаницы, нормализованную по количеству предсказанных меток. Ось X обозначает прогнозируемую категорию, а ось Y — реальную категорию. Глядя на каждый столбец, мы можем увидеть распределение реальных категорий, когда определенная категория была предсказана.


Матрица неточностей для базового решения.


Например, Electronics часто путают с Household . Но даже эта простая модель может довольно точно отобразить Clothing & Accessories .


Вот важные функции при прогнозировании категории Clothing & Accessories :

Важность функций для базового решения для этикетки «Одежда и аксессуары»


Топ-6 самых популярных слов в пользу и против категории Clothing & Accessories :

 women 1.49 book -2.03 men 0.93 table -1.47 cotton 0.92 author -1.11 wear 0.69 books -1.10 fit 0.40 led -0.90 stainless 0.36 cable -0.85


РНС

Теперь рассмотрим более продвинутые модели, созданные специально для работы с последовательностями — рекуррентные нейронные сети . GRU и LSTM — это общие расширенные уровни для борьбы с взрывными градиентами, возникающими в простых RNN.


Мы будем использовать библиотеку pytorch для токенизации описаний, а также построения и обучения модели.


Во-первых, нам нужно преобразовать тексты в числа:

  1. Разделить описания на слова
  2. Присвойте индекс каждому слову в корпусе на основе набора обучающих данных.
  3. Зарезервируйте специальные индексы для неизвестных слов и дополнений.
  4. Преобразуйте каждое описание в наборах обучающих и тестовых данных в векторы индексов.


Словарный запас, который мы получаем в результате простой токенизации набора данных о поездах, огромен — почти 90 тысяч слов. Чем больше слов у нас есть, тем больше места для встраивания должна изучить модель. Чтобы упростить обучение, удалим из него самые редкие слова и оставим только те, которые встречаются не менее чем в 3% описаний. Это сократит словарный запас до 340 слов.

(полную реализацию CorpusDictionary можно найти здесь )


 corpus_dict = util.CorpusDictionary(data_train["description"]) corpus_dict.truncate_dictionary(min_frequency=0.03) data_train["vector"] = corpus_dict.transform(data_train["description"]) data_test["vector"] = corpus_dict.transform(data_test["description"]) print(data_train["vector"].head()) # 28453 [1, 1, 1, 1, 12, 1, 2, 1, 6, 1, 1, 1, 1, 1, 6,... # 48884 [1, 1, 13, 34, 3, 1, 1, 38, 12, 21, 2, 1, 37, ... # 36550 [1, 60, 61, 1, 62, 60, 61, 1, 1, 1, 1, 10, 1, ... # 34999 [1, 34, 1, 1, 75, 60, 61, 1, 1, 72, 1, 1, 67, ... # 19183 [1, 83, 1, 1, 87, 1, 1, 1, 12, 21, 42, 1, 2, 1... # Name: vector, dtype: object


Следующее, что нам нужно решить, — это общая длина векторов, которые мы собираемся подавать в качестве входных данных в RNN. Мы не хотим использовать полные векторы, поскольку самое длинное описание содержит 9,4 тыс. токенов.


Однако 95% описаний в наборе данных поезда не длиннее 352 токенов — это хорошая длина для обрезки. Что произойдет с более короткими описаниями?


Они будут дополнены индексом заполнения до общей длины.

 print(max(data_train["vector"].apply(len))) # >>> 9388 print(int(np.quantile(data_train["vector"].apply(len), q=0.95))) # >>> 352


Далее нам нужно преобразовать целевые категории в векторы 0-1, чтобы вычислить потери и выполнить обратное распространение ошибки на каждом этапе обучения.

 def get_target(label, total_labels=4): target = [0] * total_labels target[label_2_idx.get(label)] = 1 return target data_train["target"] = data_train["category"].apply(get_target) data_test["target"] = data_test["category"].apply(get_target)


Теперь мы готовы создать собственный набор данных и загрузчик данных pytorch для использования в модели. Полную реализацию PaddedTextVectorDataset можно найти здесь .

 ds_train = util.PaddedTextVectorDataset( data_train["description"], data_train["target"], corpus_dict, max_vector_len=352, ) ds_test = util.PaddedTextVectorDataset( data_test["description"], data_test["target"], corpus_dict, max_vector_len=352, ) train_dl = DataLoader(ds_train, batch_size=512, shuffle=True) test_dl = DataLoader(ds_test, batch_size=512, shuffle=False)


Наконец, давайте построим модель.


Минимальная архитектура:

  • слой внедрения
  • Уровень РНС
  • линейный слой
  • слой активации


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


 class GRU(nn.Module): def __init__(self, vocab_size, embedding_dim, n_hidden, n_out): super().__init__() self.vocab_size = vocab_size self.embedding_dim = embedding_dim self.n_hidden = n_hidden self.n_out = n_out self.emb = nn.Embedding(self.vocab_size, self.embedding_dim) self.gru = nn.GRU(self.embedding_dim, self.n_hidden) self.dropout = nn.Dropout(0.3) self.out = nn.Linear(self.n_hidden, self.n_out) def forward(self, sequence, lengths): batch_size = sequence.size(1) self.hidden = self._init_hidden(batch_size) embs = self.emb(sequence) embs = pack_padded_sequence(embs, lengths, enforce_sorted=True) gru_out, self.hidden = self.gru(embs, self.hidden) gru_out, lengths = pad_packed_sequence(gru_out) dropout = self.dropout(self.hidden[-1]) output = self.out(dropout) return F.log_softmax(output, dim=-1) def _init_hidden(self, batch_size): return Variable(torch.zeros((1, batch_size, self.n_hidden)))


Мы будем использовать оптимизатор Adam и cross_entropy в качестве функции потерь.


 vocab_size = len(corpus_dict.word_to_idx) emb_dim = 4 n_hidden = 15 n_out = len(label_2_idx) model = GRU(vocab_size, emb_dim, n_hidden, n_out) opt = optim.Adam(model.parameters(), 1e-2) util.fit( model=model, train_dl=train_dl, test_dl=test_dl, loss_fn=F.cross_entropy, opt=opt, epochs=35 ) # >>> Train loss: 0.3783 # >>> Val loss: 0.4730 

Потери при обучении и тестировании за эпоху, модель RNN

Эта модель показала сбалансированную точность 84,3% в наборе оценочных данных. Ух ты, какой прогресс!


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

Основным недостатком обучения модели RNN с нуля является то, что ей приходится самой изучать значение слов — это задача слоя внедрения. Предварительно обученные модели word2vec доступны для использования в качестве готового слоя внедрения, что уменьшает количество параметров и придает токенам гораздо больше смысла. Давайте воспользуемся одной из моделей word2vec , доступных в pytorchglove, dim=300 .


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

 ds_emb_train = util.PaddedTextVectorDataset( data_train["description"], data_train["target"], emb=glove, max_vector_len=max_len, ) ds_emb_test = util.PaddedTextVectorDataset( data_test["description"], data_test["target"], emb=glove, max_vector_len=max_len, ) dl_emb_train = DataLoader(ds_emb_train, batch_size=512, shuffle=True) dl_emb_test = DataLoader(ds_emb_test, batch_size=512, shuffle=False)
 import torchtext.vocab as vocab glove = vocab.GloVe(name='6B', dim=300) class LSTMPretrained(nn.Module): def __init__(self, n_hidden, n_out): super().__init__() self.emb = nn.Embedding.from_pretrained(glove.vectors) self.emb.requires_grad_ = False self.embedding_dim = 300 self.n_hidden = n_hidden self.n_out = n_out self.lstm = nn.LSTM(self.embedding_dim, self.n_hidden, num_layers=1) self.dropout = nn.Dropout(0.5) self.out = nn.Linear(self.n_hidden, self.n_out) def forward(self, sequence, lengths): batch_size = sequence.size(1) self.hidden = self.init_hidden(batch_size) embs = self.emb(sequence) embs = pack_padded_sequence(embs, lengths, enforce_sorted=True) lstm_out, (self.hidden, _) = self.lstm(embs) lstm_out, lengths = pad_packed_sequence(lstm_out) dropout = self.dropout(self.hidden[-1]) output = self.out(dropout) return F.log_softmax(output, dim=-1) def init_hidden(self, batch_size): return Variable(torch.zeros((1, batch_size, self.n_hidden)))


И мы готовы тренироваться!

 n_hidden = 50 n_out = len(label_2_idx) emb_model = LSTMPretrained(n_hidden, n_out) opt = optim.Adam(emb_model.parameters(), 1e-2) util.fit(model=emb_model, train_dl=dl_emb_train, test_dl=dl_emb_test, loss_fn=F.cross_entropy, opt=opt, epochs=11) 

Потери при обучении и тестировании за эпоху, модель RNN + предварительно обученные внедрения

Теперь мы получаем сбалансированную точность 93,7% в наборе данных eval. Ву!


БЕРТ

Современные современные модели работы с последовательностями — это трансформеры. Однако для обучения трансформатора с нуля нам потребуются огромные объемы данных и вычислительных ресурсов. Здесь мы можем попробовать настроить одну из предварительно обученных моделей для достижения нашей цели. Для этого нам нужно загрузить предварительно обученную модель BERT и добавить отсев и линейный слой, чтобы получить окончательный прогноз. Рекомендуется обучать настроенную модель в течение 4 эпох. Для экономии времени я тренировал всего 2 дополнительные эпохи — на это у меня ушло 40 минут.


 from transformers import BertModel class BERTModel(nn.Module): def __init__(self, n_out=12): super(BERTModel, self).__init__() self.l1 = BertModel.from_pretrained('bert-base-uncased') self.l2 = nn.Dropout(0.3) self.l3 = nn.Linear(768, n_out) def forward(self, ids, mask, token_type_ids): output_1 = self.l1(ids, attention_mask = mask, token_type_ids = token_type_ids) output_2 = self.l2(output_1.pooler_output) output = self.l3(output_2) return output


 ds_train_bert = bert.get_dataset( list(data_train["description"]), list(data_train["target"]), max_vector_len=64 ) ds_test_bert = bert.get_dataset( list(data_test["description"]), list(data_test["target"]), max_vector_len=64 ) dl_train_bert = DataLoader(ds_train_bert, sampler=RandomSampler(ds_train_bert), batch_size=batch_size) dl_test_bert = DataLoader(ds_test_bert, sampler=SequentialSampler(ds_test_bert), batch_size=batch_size)


 b_model = bert.BERTModel(n_out=4) b_model.to(torch.device("cpu")) def loss_fn(outputs, targets): return torch.nn.BCEWithLogitsLoss()(outputs, targets) optimizer = optim.AdamW(b_model.parameters(), lr=2e-5, eps=1e-8) epochs = 2 scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=0, num_training_steps=total_steps ) bert.fit(b_model, dl_train_bert, dl_test_bert, optimizer, scheduler, loss_fn, device, epochs=epochs) torch.save(b_model, "models/bert_fine_tuned")


Журнал тренировок:

 2024-02-29 19:38:13.383953 Epoch 1 / 2 Training... 2024-02-29 19:40:39.303002 step 40 / 305 done 2024-02-29 19:43:04.482043 step 80 / 305 done 2024-02-29 19:45:27.767488 step 120 / 305 done 2024-02-29 19:47:53.156420 step 160 / 305 done 2024-02-29 19:50:20.117272 step 200 / 305 done 2024-02-29 19:52:47.988203 step 240 / 305 done 2024-02-29 19:55:16.812437 step 280 / 305 done 2024-02-29 19:56:46.990367 Average training loss: 0.18 2024-02-29 19:56:46.990932 Validating... 2024-02-29 19:57:51.182859 Average validation loss: 0.10 2024-02-29 19:57:51.182948 Epoch 2 / 2 Training... 2024-02-29 20:00:25.110818 step 40 / 305 done 2024-02-29 20:02:56.240693 step 80 / 305 done 2024-02-29 20:05:25.647311 step 120 / 305 done 2024-02-29 20:07:53.668489 step 160 / 305 done 2024-02-29 20:10:33.936778 step 200 / 305 done 2024-02-29 20:13:03.217450 step 240 / 305 done 2024-02-29 20:15:28.384958 step 280 / 305 done 2024-02-29 20:16:57.004078 Average training loss: 0.08 2024-02-29 20:16:57.004657 Validating... 2024-02-29 20:18:01.546235 Average validation loss: 0.09


Наконец, точно настроенная модель BERT показывает колоссальную сбалансированную точность 95,1% в наборе оценочных данных.


Выбираем нашего победителя

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

Вот диаграммы, показывающие измеряемые параметры:

Показатели производительности моделей


Хотя точно настроенный BERT лидирует по качеству, RNN с предварительно обученным слоем внедрения LSTM+EMB находится на втором месте, отставая всего на 3% от автоматического назначения категорий.


С другой стороны, время вывода точно настроенного BERT в 14 раз больше, чем LSTM+EMB . Это приведет к дополнительным затратам на обслуживание серверной части, которые, вероятно, перевесят преимущества, которые дает точно настроенный BERT по сравнению с LSTM+EMB .


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


Несмотря на то, что BERT кажется суперзвездой благодаря своей высокой точности, в конечном итоге мы выбираем RNN с предварительно обученным слоем внедрения. Почему? Он довольно точен, не слишком медленный и не слишком усложняется, когда дело становится большим.


Надеюсь, вам понравился этот практический пример. Какую модель вы бы выбрали и почему?