Классификация текста, также известная как категоризация текста, является фундаментальной задачей обработки естественного языка (NLP), которая включает в себя присвоение заданным текстовым документам предопределенных категорий или меток. Цель состоит в том, чтобы автоматически анализировать и классифицировать неструктурированные текстовые данные по значимым классам на основе их содержания, тональности, темы или любых других предопределенных критериев.
Предположение Маркова, названное в честь русского математика Андрея Маркова, является фундаментальным предположением в теории вероятностей и статистике. В контексте генерации текста или языкового моделирования предположение Маркова часто применяется для упрощения процесса моделирования.
Предположение Маркова гласит, что будущее состояние или событие в последовательности зависит только от текущего состояния или события, а не от прошлых состояний или событий. Другими словами, предполагается, что будущее не зависит от прошлого при наличии настоящего.
В контексте генерации текста предположение Маркова подразумевает, что вероятность обнаружения определенного слова в последовательности зависит только от предшествующего слова или небольшого фиксированного окна предыдущих слов. Это допущение упрощает процесс моделирования, поскольку позволяет оценить распределение вероятности появления следующего слова исключительно на основе текущего контекста без учета всей истории последовательности.
Например, в марковской модели первого порядка вероятность слова зависит от непосредственно предшествующего ему слова. В марковской модели второго порядка вероятность слова зависит от двух предыдущих слов и так далее.
Применение предположения Маркова позволяет эффективно моделировать и генерировать последовательности, включая текст. Он широко используется в различных задачах НЛП, таких как моделирование языка, маркировка частей речи и машинный перевод. Однако важно отметить, что марковское предположение чрезмерно упрощает сложность языка и может не охватывать долгосрочные зависимости или контекст, выходящий за пределы фиксированного окна слов.
Уравнение Маркова можно упростить, вероятно, следующим образом:
Математика, стоящая за уравнением, немного сложна и выходит за рамки этой статьи. В Интернете доступно множество статей и бесплатных курсов.
Код
#### downloading spam dataset ##### !wget https://lazyprogrammer.me/course_files/spam.csv from sklearn.model_selection import train_test_split import numpy as np import pandas as pd spam_df = pd.read_csv('spam.csv', encoding = "ISO-8859-1") spam_df = spam_df.drop(['Unnamed: 2', 'Unnamed: 3', 'Unnamed: 4'], axis = 1) # mapping the category names to labels # map_dict = {'ham': 0, 'spam': 1} spam_df['labels'] = spam_df['v1'].map(map_dict) input_text = spam_df['v2'] label = spam_df['labels'] # splitting the data into test and train # train_text, test_text, Ytrain, Ytest = train_test_split(input_text, label) ##### starting point for word indexing ##### ##### we are setting aside 0 for unknown values, i.e. the tokens that are present in ##### test but not in train. idx = 1 word2idx = {'<unk>': 0} # populate word2idx for text in train_text: tokens = text.split() for token in tokens: if token not in word2idx: word2idx[token] = idx idx += 1 # convert data into integer format train_text_int = [] test_text_int = [] for text in train_text: tokens = text.split() line_as_int = [word2idx[token] for token in tokens] train_text_int.append(line_as_int) for text in test_text: tokens = text.split() line_as_int = [word2idx.get(token, 0) for token in tokens] test_text_int.append(line_as_int) # initialize A and pi matrices - for both classes. The number of A and pi matrices depends on the number of classes or categories we have # V = len(word2idx) A0 = np.ones((V, V)) pi0 = np.ones(V) A1 = np.ones((V, V)) pi1 = np.ones(V) # compute counts for A and pi def compute_counts(text_as_int, A, pi): for tokens in text_as_int: last_idx = None for idx in tokens: if last_idx is None: # it's the first word in a sentence pi[idx] += 1 else: # the last word exists, so count a transition A[last_idx, idx] += 1 # update last idx last_idx = idx compute_counts([t for t, y in zip(train_text_int, Ytrain) if y == 0], A0, pi0) compute_counts([t for t, y in zip(train_text_int, Ytrain) if y == 1], A1, pi1) # normalize A and pi so they are valid probability matrices A0 /= A0.sum(axis=1, keepdims=True) pi0 /= pi0.sum() A1 /= A1.sum(axis=1, keepdims=True) pi1 /= pi1.sum() # log A and pi since we don't need the actual probs logA0 = np.log(A0) logpi0 = np.log(pi0) logA1 = np.log(A1) logpi1 = np.log(pi1) # compute priors for both categories # count0 = sum(y == 0 for y in Ytrain) count1 = sum(y == 1 for y in Ytrain) total = len(Ytrain) p0 = count0 / total p1 = count1 / total logp0 = np.log(p0) logp1 = np.log(p1) p0, p1
Теперь, когда у нас есть полная матрица A и пи, мы можем приступить к созданию классификатора.
# build a classifier class Classifier: def __init__(self, logAs, logpis, logpriors): self.logAs = logAs self.logpis = logpis self.logpriors = logpriors self.K = len(logpriors) # number of classes def _compute_log_likelihood(self, input_, class_): logA = self.logAs[class_] logpi = self.logpis[class_] last_idx = None logprob = 0 for idx in input_: if last_idx is None: # it's the first token logprob += logpi[idx] else: logprob += logA[last_idx, idx] # update last_idx last_idx = idx return logprob def predict(self, inputs): predictions = np.zeros(len(inputs)) for i, input_ in enumerate(inputs): posteriors = [self._compute_log_likelihood(input_, c) + self.logpriors[c] \ for c in range(self.K)] pred = np.argmax(posteriors) predictions[i] = pred return predictions # each array must be in order since classes are assumed to index these lists clf = Classifier([logA0, logA1], [logpi0, logpi1], [logp0, logp1])
Обучение и тестирование модели
## training data ## Ptrain = clf.predict(train_text_int) print(f"Train acc: {np.mean(Ptrain == Ytrain)}") Train acc: 0.99 Ptest = clf.predict(test_text_int) print(f"Test acc: {np.mean(Ptest == Ytest)}") Test acc: 0.95
Несмотря на то, что точность как для поезда, так и для теста одинакова, у нас несбалансированный набор данных, и, следовательно, оценка F1 будет более полезной метрикой для принятия во внимание, чем точность.
from sklearn.metrics import f1_score f1_score(Ytrain, Ptrain) ans: 0.99 f1_score(Ytest, Ptest) ans: 0.82
Это дает нам лучшее представление о том, как работает наша модель. Даже с таким простым предположением мы можем получить высокий балл F1 на наших тестовых данных.