В последние недели была опубликована интригующая статья о классификации текстов. Эта статья призвана обеспечить простую, но эффективную классификацию текста с использованием 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)

https://github.com/bazingagin/npc_gzip/tree/main