Создание модели предсказания кода МКБ-10 на основе текста описания болезни

Привет, Хабр! Решила с вами поделиться одной простой работой, которая привела к неплохим результатам. Расскажу о всем подробно и очень просто:) Интересно тем, кто еще не решал задачи NLP до этого момента.

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

Проконсультировавшись с медицинскими работниками  ФГБОУ ВО "ПИМУ" Минздрава России Института травматологии, я смогла структурировать цепочку действий, совершаемые врачами с целью постановки диагноза. Можно выделить два основных направления работы с пациентом: технический и человеческий. К человеческим я отношу: знакомство с пациентом, получение симптоматики, составление анамнеза, получение данных дополнительных диагностик, постановка диагноза и назначение лечения. Так как все современные медицинские учреждения оснащены компьютерными средствами и собственными программами в них, к техническим аспектам работы врача я отношу: создание карточки пациента, объективное описание анамнеза, присвоения кода болезни. Существуют несколько видов кодирования болезни, чаще всего используется международная классификация болезней десятого пересмотра или МКБ-10. Согласно этому коду определяется диагноз. Он сопоставляется с реестром ( работает ли медицинское учреждение с данным видом болезней), подходит ли данная болезнь под ОМС (обязательное медицинское страхование в России), то есть сможет пациент лечиться бесплатно или нет или иначе говоря финансируется лечение данной болезни за счет ОМС или собственных средств пациента. Из всего выше сказанного можно выделить цепь возможной технической ошибки: правильность заполнения карточки, достаточное и точное описание анамнеза, правильность выбора кода МКБ-10.

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

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

Задача понятна, теперь практика:)

Для решения данной задачи я использую собственные данные (ссылку на файлик ищи в комментариях). Источник -  https://ru.wikipedia.org/wiki/Категория:Заболевания_по_алфавиту. Этот набор данных содержит все тексты, описаний болезней с указанного источника, если у болезни имелась ссылка на код МКБ-10.  Данные разбиты на абзацы в качестве наблюдений и размечены соответствующим кодом и названием диагноза. Каждый из текстов содержит информацию на любую тему, которая могла бы быть связана с текущей болезнью, например: симптомы, лечение, историю открытия болезни, этиологию, распространенность, профилактику и прочее. Наполненность информации по любо из болезней зависит в том числе от ее изученности, по этому некоторые из классов имеют малое содержание, например один текст на диагноз. Для обработки данных используется язык программирования python.

import pandas as pd
import numpy as np
import nltk
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.stem.porter import PorterStemmer
import re
import matplotlib.pyplot as plt
import seaborn as sns
from IPython.display import Image

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import balanced_accuracy_score, classification_report, accuracy_score
from fuzzywuzzy import process
from nltk.stem.snowball import RussianStemmer

from tqdm.notebook import tqdm
from pymystem3 import Mystem
from gensim.corpora import Dictionary

from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC

import fasttext.util
from timeit import default_timer as timer

Обзор данных

df = pd.read_csv('Итоговая_БАЗА_болезней.csv')
df.sample(5)


Index

Симптомы

МКБ_10

56

Альвеолярный протеиноз

Физикальное обследование:Аускультация: ослабле...

J84.0

2194

Свиной грипп

Клинически течение данного заболевания в целом...

J09.0

2078

Сахарный диабет

При недостаточности инсулина (сахарный диабет ...

E10

2745

Торсионная дистония

Гиперкинезы уменьшаются с помощью коррегирующи...

G24

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

df.Index.value_counts()
pd.DataFrame(df.Index.value_counts()).Index.hist()
plt.title('Распределение болезней по количеству наблюдений')
plt.show()

Как мы видим из графика в  большинстве классов текстов содержится менее 20 текстов, удаление малонаполненных классов приведет к потере большей части имеющихся данных. Будем считать нерелевантными классы с менее, чем 10 текстами.  Также из-за большой разницы в количествах текстов будем подавать разное количество болезней в модель, чтобы изучить зависимость результатов от количества классов.  После фильтрации мы получили новый дотасет, состоящий из 2327 наблюдений для 108 классов

df = df[df['МКБ_10'].isin(df['МКБ_10'].value_counts()[:108].index)]
simps = df['Симптомы'].values.tolist()

Обработка текстов

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

# Пример текста без обработки
simps[5]

'Клиническая картина во многом напоминает симптомы цитостатической болезни и проявляется в виде поражения организма различными инфекциями в результате снижения эффективности иммунного ответа. Характерны (особенно при лекарственном агранулоцитозе) острое начало и быстрое нарастание клинических симптомов. Иногда возникновению основных признаков агранулоцитоза предшествует короткий скрытый период, характеризующийся слабостью, недомоганием, головными болями[4]. К первым клиническим проявлениям агранулоцитоза относятся лихорадка, афтозный стоматит, ангина[4], озноб, артралгия[2]. Развиваются язвенно-некротические поражения слизистой оболочки полости рта в виде изъязвлений, покрытых сероватым налётом, некротических бляшек и язв с грязно-серым налётом на миндалинах (агранулоцитарная ангина)'

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

lemmatizer = WordNetLemmatizer()
#Слова паразиты
stopwords = nltk.corpus.stopwords.words('russian')
stopwords.append(['править','это'])
#список названий болезней
all_bad = []
for txt in [w.lower() for w in df.Index.unique()]:
    for word in txt.split():
        all_bad.append(word)
#список однокорневых слов болезней
all_bad_2 = []
for i in all_bad:
    all_bad_2.append(i[0:4])
    
alphabet = list('abcdefghijklmnopqrstuvwxyz')
def preproc(simps, all_bad, all_bad_2):
    corpus = []
    stemmer = Mystem()
    for simp in tqdm(simps):
        tokens = nltk.word_tokenize(simp.lower()) # преобразование к нижнему регистру
        tokens = [w for w in tokens if w.isalpha()] # выбор только алфавитных значений
        tokens = [w for w in tokens if w not in stopwords] # удаление слов паразитов и предлогов
        #return tokens
        #tokens = list(filter(lambda w: not re.match(r'[a-zA-Z]+', w), t))# удаление букв английского алфавита
        tokens = [w for w in tokens for i in w if i not in alphabet]
        
        tokens = [stemmer.lemmatize(w) for w in tokens] # преобразование к начальной форме слова
        tokens = [tok[0] for tok in tokens]
        tokens = [w for w in tokens if w not in all_bad] # удаление названий болезней
        tokens = [w for w in tokens if w[0:4] not in all_bad_2] # удаление однокорневых слов с названиями болезней
        tokens = [w for w in tokens if len(w)>2] # удление слов длиной меньше 3 сиволов
        
        tokens = set(tokens) # оставляем только уникальные значения
        corpus.append(' '.join(tokens))
    return corpus
corpus = preproc(simps, all_bad, all_bad_2)
# Пример того же текста после обработки
corpus[5]

'первый напоминать рот лекарственный относиться короткий эффективность афтозный бляшка слабость некротический проявление ответ вид стоматит многое миндалина ангина различный предшествовать нарастание покрывать сероватый проявляться симптом слизистый начало характерный налет картина изъязвление полость иммунный период особенно скрытый снижение основной боль озноб быстрый признак результат возникновение оболочка поражение язва клинический организм характеризоваться развиваться'

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

Векторизация текстов

Для выполнения задачи классификации необходимо преобразовать неструктурированные данные в векторное пространство. Для этого можно использовать представления, основанные на неконтролируемых методах, которые допускают векторизацию слов, таких как CountVectorizer, TF-IDF, HashingVectorizer или аналогичные подходы. Давайте посмотрим как применить каждый из них, сравним и выберем наилучший.

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

corpus_cvec = corpus.copy()
cv = CountVectorizer(min_df=2, max_df=1.)
cv.fit(corpus_cvec)
transformed_cvec = cv.transform(corpus_cvec)
dense_cvec = transformed_cvec.todense()
dense_cvec.shape

(2327, 4962)

dense_cvec[0]

matrix([[0, 0, 0, ..., 0, 0, 0]])

Мы получили матрицу, размерностью 2327 (количество наблюдений) на 4962 (количество уникальных слов), чисел, означающие количество встречаний данного слова в тексте.

# Разбивка выборки на тренировочную и тестовою
X_tr, X_te, y_tr, y_te = train_test_split(np.array(dense_cvec), df['МКБ_10'], random_state=203)
# Логистическая регрессия
lr = LogisticRegression()
lr.fit(X_tr, y_tr)
print(balanced_accuracy_score(lr.predict(X_tr), y_tr))
print(balanced_accuracy_score(lr.predict(X_te), y_te))

0.9988108599219709

0.5881197323399867

Разбив выборку на тренировочную и тестовую и передав элементы в модель логистической регрессии, мы получили результаты (balanced accuracy): 99,9% на тренировочном и 58,8 на тестовом.

Необходимые комментарии.

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

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

Balanced Accuracy полезен для мультиклассовой классификации. Здесь BA — это среднее значение отзыва, полученное в каждом классе, т.е. макросреднее значение отзыва для каждого класса. Таким образом, для сбалансированного набора данных оценки, как правило, совпадают с точностью.

Сбалансированная точность это среднее арифметическое от метри recall, она расчитывается отдельно для каждого класса количество верно предсказанных элементов истинной принадлежности (TP) / (TP) + ложно предсказанные этим классом (FN). Из математического обоснования мы можем говорить о том, что данная метрика является оптимальной для оценки несбалансированных данных, мы отдаем предпочтение ей.

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

Еще несколько способов векторизации

Метод TF-IDF основан на вычислении двух параметров для каждого токена. TF - это частотность термина, которая измеряет, насколько часто термин встречается в документе (как в CountVectorizer). Логично предположить, что в длинных документах термин может встретиться в больших количествах, чем в коротких. Поэтому применяют относительные, своего рода нормировка — делят количество раз, когда нужный термин встретился в тексте, на общее количество слов в тексте. IDF - это обратная частотность документов. Она измеряет непосредственно важность термина. Когда мы считали TF, все термины считались условно равными по важности друг другу. Но всем известно, что, например, предлоги встречаются очень часто, хотя практически не влияют на смысл текста. IDF считается как логарифм от общего количества документов, делённого на количество документов, в которых встречается термин. А затем мы умножаем TF на IDF и получаем TF-IDF.

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

# векторизация TF/IDF
corpus_tf = corpus.copy()

tf = TfidfVectorizer(min_df=2, max_df=1.)
tf.fit(corpus_tf)
transformed_tf = tf.transform(corpus_tf)
dense_tf = transformed_tf.todense()
dense_tf.shape

(2327, 4962)

pd.DataFrame(dense_tf)[1].unique()

array([0. , 0.15152024, 0.12289763, 0.2538578 , 0.18524786])

# Разбивка выборки на тренировочную и тестовою
X_tr, X_te, y_tr, y_te = train_test_split(np.array(dense_tf), df['МКБ_10'], random_state=203)
# Логистическая регрессия
lr = LogisticRegression()
lr.fit(X_tr, y_tr)
print(balanced_accuracy_score(lr.predict(X_tr), y_tr))
print(balanced_accuracy_score(lr.predict(X_te), y_te))

0.9495468027236211

0.6754541916289634

Разбив выборку на тренировочную и тестовую и передав элементы в модель логистической регрессии, мы получили результаты (balanced accuracy): 94,9% на тренировочном и 67,5% на тестовом.

При масштабной обработке текстов, размерность полученных данных может стать огромной. В современное время, часто требуется уменьшение размерности, и метод векторизации, описанный в предыдущем разделе, нельзя использовать напрямую. Наиболее часто используемый метод уменьшения размерности текста - это Hash Trick или HashingVectorizer. Значение Hash здесь аналогично известному определению из структуры данных (Википедия: Хеш-табли́ца (англ. hash-table) — структура данных, реализующая интерфейс ассоциативного массива. В отличие от деревьев поиска, реализующих тот же интерфейс, обеспечивают меньшее время отклика в среднем. Представляет собой эффективную структуру данных для реализации словарей, а именно, она позволяет хранить пары (ключ, значение) и выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу).

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

Значения закодированного документа соответствуют нормализованному количеству слов по умолчанию в диапазоне от -1 до 1, но могут быть сделаны простые целочисленные счетчики путем изменения конфигурации по умолчанию.

# векторизация каждого метод HashingVectorizer
corpus_hv = corpus.copy()

hv = HashingVectorizer(n_features=20)
hv.fit(corpus_hv)
transformed_hv = hv.transform(corpus_hv)
dense_hv = transformed_hv.todense()
dense_hv.shape

(2327, 20)

dense_hv[0]
# Разбивка выборки на тренировочную и тестовою
X_tr, X_te, y_tr, y_te = train_test_split(np.array(dense_hv), df['МКБ_10'], random_state=203)
# Логистическая регрессия
lr = LogisticRegression()
lr.fit(X_tr, y_tr)
print(balanced_accuracy_score(lr.predict(X_tr), y_tr))
print(balanced_accuracy_score(lr.predict(X_te), y_te))

0.2509569568858903

0.04868499685846096

Разбив выборку на тренировочную и тестовую и передав элементы в модель логистической регрессии, мы получили результаты (balanced accuracy): 25,1% на тренировочном и 4,8% на тестовом.

Так как мы видим, наилучший результат классификации на основе логистической регрессии показала векторизация на основе TF-IDF. 67,5%, при условии мультиклассификации в 108 классов, хороший результат. Тексты на медицинские темы всегда содержат множество терминов, а термины помогают точно формулировать смысл, по этому, в теории, мы можем обучить хороший медицинский классификатор, даже при небольшом количестве наблюдений. Далее мы оценили результаты использования других алгоритмов машинного обучения.

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

# Разбивка выборки на тренировочную и тестовою
X_tr, X_te, y_tr, y_te = train_test_split(np.array(dense_tf), df['МКБ_10'], random_state=203)
# Наивный Байес
nb = GaussianNB()
nb.fit(X_tr, y_tr)
print(balanced_accuracy_score(nb.predict(X_tr), y_tr))
print(balanced_accuracy_score(nb.predict(X_te), y_te))

0.9797164650460575

0.5644975477681466

svc = SVC()
svc.fit(X_tr, y_tr)
print(balanced_accuracy_score(svc.predict(X_tr), y_tr))
print(balanced_accuracy_score(svc.predict(X_te), y_te))

0.9942186225425265

0.7848107770320787

Разбив выборку на тренировочную и тестовую и передав элементы ( TF-IDF) в модель наивного Байеса, мы получили результаты (balanced accuracy): 98,0% на тренировочном и 56,4% на тестовом. А с помощью метода опорных векторов удалось получить наилучшие оценки качества : 99,4% и 78,4% соответственно.

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

Аугментация данных

FastText — это решение для предоставления готовых векторных представлений слов, для решения различных задач в области ML и NLP. Но у данных моделей, есть недостаток. На текущий момент обученная модель FastText на русскоязычном корпусе текстов Википедии занимает более 16Гигабайт, что к сожалению, сужает область применения данной тихнологии и замедляет процесс обучения. С помощью этой модели, можно получить наиболее схожие слова по смыслу. Пример ниже:

fasttext.util.download_model('ru', if_exists='ignore')
ft = fasttext.load_model('cc.ru.300.bin')

Ох, большая и долгая модель.... Может есть еще что-то, что работает с русским языком?

ft.get_nearest_neighbors('смех',k=1)

[(0.8175902366638184, 'хохот')]

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

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

# Отбор тощих классов
skinny = pd.DataFrame(df['МКБ_10'].value_counts())
skinny = skinny[skinny.МКБ_10 < 20].reset_index()
df1 = df[df['МКБ_10'].isin(skinny['index'])]
simps_1 = df1['Симптомы'].values.tolist()
corpus_1 = preproc(simps_1, all_bad, all_bad_2)
# Приводим к формату, с которым сможет работать Fasttext
words = []
new_tokens = []

for simp in corpus_1:
    words = []
    tokens = nltk.word_tokenize(simp)
    for word in tokens:
        words.append(word)
    new_tokens.append(words)
#Подбираем 10 ближайших слов, проверяем совпадает ли слово с изначальным,
#если нет - добавляем новое слово в наблюдение, иначе - оставляем прежнее
new_corpus = []
for corp in tqdm(new_tokens[622:]):   
    symptoms = []
    for word in corp:
        cort = ft.get_nearest_neighbors(word,k=10)
        syn_list = []
        end_syn_list = []
        for i in cort:
            x,y = i       
            y = preproc([y], all_bad, all_bad_2)
            if y[0] != '' and word[:3] not in y[0]:
                syn_list.append(y[0])         
        if  len(syn_list) != 0:               
            end_syn_list.append(syn_list[0])
        else:
            end_syn_list.append(word)
        symptoms.append(end_syn_list)
    new_corpus.append(symptoms)

Нам удалось получить новые наблюдения для классов у которых менее 20 наблюдений, путем подбора слов синонимов. К сожалению компьютер не всегда бесперебойно работает с этой библиотекой, нам не удалось точно зафиксировать потраченное время на аугментацию, так как функция несколько раз "вылетала" после 6-7 часов непрерывной работы, но нам удалось преобразовать 843 текста (ПРИМЕРНО ЗА 30 ЧАСОВ!!!!) Так как генерация данных не конечная цель работы, а метод для улучшения точности предсказаний модели, используем только то, что удалось получить. Повторим действия по векторизации и обучению.

# Распаковка списка списков, приведение к формату удобного для работы далее.
a=[]
for z in new_corpus:
    tok= []
    for item in z:   
        for i in item: 
            tok.append(i)
    a.append(tok)
b = []
for item in a:
    tok= []
    tok.append(' '.join(item))
    b += tok 

Сравним текст до - после.

print('Начальная форма: ', new_tokens[4])
print('Измененная форма: ', b[4])

Начальная форма: ['происходить', 'гибель', 'препарат', 'изучать', 'реакция', 'гранулоцит', 'поражение', 'гаптен', 'индивидуальный', 'повторяться', 'возникать', 'результат', 'механизм', 'неизменно', 'организм', 'патогенез', 'многий', 'введение', 'конец', 'гаптеновый', 'однажды', 'форма', 'медикамент']

Измененная форма: совершаться смерть антибиотик исследовать агрессия тромбоцит разгромный никотинамидадениндинуклеотид персональный описываться появляться итог принцип изменно кишечник этиология бесчисленный внедрение начало иммуноглобулиновый единожды структура препарат

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

corpus += b
predict = list(df['МКБ_10'].append(df1['МКБ_10']).reset_index()['МКБ_10'])

Ну, конечно, я вас не заставлю ждать 30 часов генерации данных LoL. Второй файлик по ссылке это они))

# corpus += b
corpus = list(pd.read_csv('gener_data.csv')['0'])
predict = list(df['МКБ_10'].append(df1['МКБ_10']).reset_index()['МКБ_10'])

Только удалите эти строки, маленько порченный файлик.

corpus.pop(1274)
predict.pop(1274)
corpus.pop(1644)
predict.pop(1644)

Далее повторим: обработка, векторизация, обучение.

corpus = preproc(corpus, all_bad, all_bad_2)
tf = TfidfVectorizer(min_df=2, max_df=1.)
tf.fit(corpus)
transformed_tf = tf.transform(corpus)
dense_tf = transformed_tf.todense()

X_tr, X_te, y_tr, y_te = train_test_split(np.array(dense_tf), predict,
                                           random_state=203)

svc.fit(X_tr, y_tr)
                                          
print(balanced_accuracy_score(svc.predict(X_tr), y_tr))
print(balanced_accuracy_score(svc.predict(X_te), y_te)) 

0.9987236196386523

0.8213375263292584

Разбив выборку на тренировочную и тестовую и передав элементы в модель опорных векторов, мы получили результаты (balanced accuracy): 99,9% на тренировочном и 82,1% на тестовом. То есть мы увеличили качество модели с 78,4% до 82,1% ( разница 3,6%).

Вот наглядно наши результаты)


Логистическая регрессия

Наивный Байес

Метод опорных векторов

Метод опорных векторов после аугментации

CountVectorizer

58,8 %

-

-

-

TF-IDF

67,5 %

56,4 %

78,4 %

82,1 %

HashingVectorizer

4,8 %

-

-

-

Можем сделать чат-бота с нашей моделью, интересно?