В последние недели была опубликована интригующая статья о классификации текстов. Эта статья призвана обеспечить простую, но эффективную классификацию текста с использованием k ближайших соседей (kNN) и gzip, в отличие от глубоких нейронных сетей (DNN). Хотя DNN дают высокие результаты в классификации текстов, они требуют обширной обработки, миллионов параметров и большого количества размеченных данных. Однако в этой статье предлагается облегченный метод, который дает хорошие результаты без каких-либо параметров или обучения. В этом посте мы протестируем это приложение, используя набор данных, состоящий примерно из 300 турецких вопросов и ответов, связанных с законом.
Связанные исследования и подход
В статье также упоминаются предыдущие исследования в этой области, которые проводились с использованием минимальной перекрестной энтропии, но не дали эффективных результатов. Пояснения, формулы и диаграммы в этом разделе взяты непосредственно из статьи.
Классификацию текста с помощью компрессоров можно разделить на два основных подхода:
Использование теории информации Шеннона для оценки энтропии и аппроксимации колмогоровской сложности, а также использование информационного расстояния. В этом подходе в первую очередь используется метод сжатия текста, называемый прогнозированием путем частичного сопоставления (PPM), и он применяется для классификации тем. Этот подход оценивает перекрестную энтропию между распределением вероятностей, созданным с данным документом d для определенного класса c: Hc(d).
Подход статьи к этой теме состоит из компрессора без потерь, метрики расстояния на основе компрессора и классификатора k-ближайших соседей. Компрессоры без потерь пытаются представить информацию с использованием меньшего количества битов, назначая более короткие коды символам с большей вероятностью.
Например, x1 относится к той же категории, что и x2, но относится к другой категории, чем x3. Предполагая, что C(·) представляет собой сжатую длину, мы найдем C(x1x2) — C(x1) ‹ C(x1x3) — C(x1); здесь C(x1x2) обозначает сжатую длину комбинации x1 и x2.
Чтобы измерить содержание общей информации между двумя объектами, Bennett et al. (1998) определяют информационное расстояние E(x,y) следующим образом:
E(x,y) приравнивает сходство к кратчайшей длине программы, необходимой для преобразования одного объекта в другой.
Невычислимость колмогоровской сложности препятствует вычислимости E(x,y), поэтому Li et al. (2004) предлагают нормированное расстояние сжатия (NCD), используя сжатую длину C (x) реальных компрессоров для аппроксимации колмогоровской сложности K (x). Формула выглядит следующим образом:
Здесь C(x) представляет длину после того, как x был сжат с максимальной скоростью компрессором. Как правило, чем выше степень сжатия, тем ближе C(x) к K(x).
Мы можем использовать kNN с матрицей расстояний, предоставленной NCD для классификации. Наш метод можно реализовать с помощью 12 строк кода Python:
for (x1, _) in test_set: Cx1 = len(gzip.compress(x1.encode())) distance_from_x1 = [] for (x2, _) in train_set: Cx2 = len(gzip.compress(x2.encode())) x1x2 = " ".join([x1, x2]) Cx1x2 = len(gzip.compress(x1x2.encode())) ncd = (Cx1x2 - min(Cx1, Cx2)) / max(Cx1, Cx2) distance_from_x1.append(ncd) sorted_idx = np.argsort(np.array(distance_from_x1)) top_k_class = [train_set[idx][1] for idx in sorted_idx[:k]] predict_class = max(set(top_k_class), key=top_k_class.count)
Подготовка данных и тестирование подхода
Эта часть поста немного экспериментальная и, я думаю, скорее любительская. Сначала я загрузил свои данные ChatGPT, а затем разделил их на вопросы и ответы. Я назначил свои данные как вопросы, а ответы из GPT как ответы и преобразовал их в файл csv. Затем я разделил его на обучающие и тестовые наборы, но мой подход здесь был таким, как если бы я обучал DNN. Затем я провел тест, и результаты были несколько успешными.
import gzip import numpy as np import csv dataset = [] with open('dataset.csv', 'r', newline='', encoding='utf-8') as file: csv_reader = csv.reader(file) next(csv_reader) for row in csv_reader: question = row[0] answer = row[1] dataset.append((question, answer)) train_size = int(0.8 * len(dataset)) training_set = dataset[:train_size] test_set = dataset[train_size:] def normalized_compression_distance(x1, x2): #C(x) and C(y) are the compressed sizes of strings x and y, respectively. Cx1 = len(gzip.compress(x1.encode())) Cx2 = len(gzip.compress(x2.encode())) x1x2 = " ".join([x1, x2]) #C(xy) is the compressed size of the concatenated strings x and y. Cx1x2 = len(gzip.compress(x1x2.encode())) #min{C(x), C(y)} represents the smallest compressed size between x and y. #max{C(x), C(y)} represents the largest compressed size between x and y. ncd = (Cx1x2 - min(Cx1, Cx2)) / max(Cx1, Cx2) return ncd # Function to predict the class for a given text using KNN def predict_class_knn(text, dataset, k=3): distances = [] for (question, answer) in dataset: distance = normalized_compression_distance(text, question) distances.append((distance, answer)) distances.sort(key=lambda x: x[0]) top_k_class = [distances[i][1] for i in range(k)] predicted_class = max(set(top_k_class), key=top_k_class.count) return predicted_class
Я пробовал случайные вещи, так как я не знал и не мог точно вспомнить, о чем были мои разговоры, но я не мог получить полностью эффективный результат. Однако в какой-то момент я был удивлен, потому что, когда я оставлял пустое место или вводил бессмысленные буквы или цифры, я получал такой ответ
После этого я собрал около 300 точек данных с юридического веб-сайта и решил попробовать, и результат меня удивил. Мне казалось, что я выполняю своего рода поиск, но результат был хорошим. Это дало много неправильных результатов в некоторых местах, но пока это было удовлетворительно.
Я также протестировал модель для анализа настроений с набором данных для анализа настроений в Турции от winvoker, и результаты оказались очень хорошими. Работает быстро и эффективно.
import gzip import numpy as np import datasets dataset = datasets.load_dataset("winvoker/turkish-sentiment-analysis-dataset", split="train") questions = dataset['text'] answers = dataset['label'] data_tuples = list(zip(questions, answers)) train_size = int(0.8 * len(data_tuples)) training_set = data_tuples[:train_size] test_set = data_tuples[train_size:] def normalized_compression_distance(x1, x2): #C(x) and C(y) are the compressed sizes of strings x and y, respectively. Cx1 = len(gzip.compress(x1.encode())) Cx2 = len(gzip.compress(x2.encode())) x1x2 = " ".join([x1, x2]) #C(xy) is the compressed size of the concatenated strings x and y. Cx1x2 = len(gzip.compress(x1x2.encode())) #min{C(x), C(y)} represents the smallest compressed size between x and y. #max{C(x), C(y)} represents the largest compressed size between x and y. ncd = (Cx1x2 - min(Cx1, Cx2)) / max(Cx1, Cx2) return ncd # Function to predict the class for a given text using KNN def predict_class_knn(text, dataset, k=3): distances = [] for (question, answer) in dataset: distance = normalized_compression_distance(text, question) distances.append((distance, answer)) distances.sort(key=lambda x: x[0]) top_k_class = [distances[i][1] for i in range(k)] predicted_class = max(set(top_k_class), key=top_k_class.count) return predicted_class
Статья
[Малоресурсная текстовая классификация: безпараметрический метод классификации с компрессорами] (https://aclanthology.org/2023.findings-acl.426) (Jiang et al., Findings 2023)