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

Я экспериментировал с созданием классификатора KNN и сравню его с нейронной сетью с использованием Tensorflow в своей следующей статье.

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

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

Набор данных

Чтобы смоделировать это упражнение, я выбрал набор данных розничных продавцов, загрузив список 100 крупнейших розничных продавцов в США. Я создал кучу методов для приписывания случайных символов и чисел. Чтобы сделать его действительно расплывчатым, я также рандомизировал количество импутаций для одной строки, просто чтобы гарантировать, что данные не станут невозможными для понимания, я ограничил количество импутаций от 0 до 50% длины строки.

import string
import random 
import numpy as np 
import pandas as pd

# Using seen hash table to generate all unique values
# Main goal here is to ensure there is no cross category contamination
seen = {}

def gen_impurities(n,word):
    for i in range(n):
        index = random.randint(0,len(word)-1)

        if index%2 == 0:
            symbol = random.choice(string.punctuation)
            word = word[:index] + symbol + word[index:]

        if index%7 == 0:
            num = random.choice(string.digits)
            #print(f"Number = {num}")
            word = word[:index] + str(num) + word[index:]

        if index%3 == 0:
            word.replace(word[index],"")
    return word

def obfuscate(word):
    n = int(len(word)*0.5)
    iteration = random.randint(1,n)
    label = word
    for i in range(5):
        imputed_word = gen_impurities(iteration,label)
        if imputed_word not in seen:
            seen[imputed_word] = 1
            return imputed_word
        seen[imputed_word] = seen[imputed_word]+1
    return word

def gen_gibberish(min_l,max_l):
    l = random.randint(min_l,max_l)
    res = ''.join(random.choices(string.ascii_uppercase +
                                 string.punctuation +
                                 string.digits, k=l))
    while res not in seen:    
        seen[res] = 1
        return res
    seen[res] = seen[res]+1

def get_obfuscated_targets(targets,n):
    obfuscate_target = targets * n
    obfuscate_targets.sort()
    df_obfuscated = pd.DataFrame({"targets": obfuscate_targets})
    df_obfuscated['bad_names'] = df_obfuscated["targets"].apply(lambda x: obfuscate(x) )
    return df_obfuscated
    
def get_catch_gibberish(instance_count,min_l,max_l):
    others = ['Other'] * instance_count
    df_other = pd.DataFrame({'targets':others})
    df_other['bad_names'] = df_other['targets'].apply(lambda x : gen_gibberish(min_l,max_l)) 
    return df_other

def get_good_names(targets,n):
    good_captures = targets*  n
    df_good_captures = pd.DataFrame({'targets':good_captures})
    df_good_captures['bad_names'] = df_good_captures["targets"]
    return df_good_captures

def get_data(impute_n,instance,min_l,max_l): 
    n = impute_n
    instance_count = instance
    min_l = min_l
    max_l = max_l

    df_obfuscated = get_obfuscated_targets(targets,n)
    df_other = get_catch_gibberish(instance_count,min_l,max_l)
    df_good_captures = repeat_good_names(targets,n,instance_count)
    df = pd.concat([df_obfuscated,df_good_captures,df_other])
    return df

K-ближайший сосед

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

Классы

В этом конкретном случае использования есть 100 классов + 1 класс тарабарских строк или класс мусора, чтобы гарантировать, что какая-то случайная строка не классифицируется как допустимый класс. Я следовал стандартной практике разделения набора данных в тесте и обучении с использованием разделения 80:20.

def read_data():
    df = pd.read_csv("Targets.csv")
    del df['empty']
    df = df.set_index('no')
    df.head()
    return df


def get_train_test_data(split_ratio,impute_ratio,instance,min_l,max_l):
    test_size = (instance*split_ratio)
    train_size = (instance*(1-split_ratio))
    print(f"test_size = {test_size} & train_size = {train_size} & split = {split_ratio}")
    test_impute_n = int(test_size*impute_ratio)
    test_good_n = int(test_size - test_impute_n)
    print(f"test impute = {test_impute_n} & instances = {test_good_n}")
    df = read_data()
    targets = list(df["target"])
    # get imputed
    df_obfuscated = get_obfuscated_targets(targets,test_impute_n)
    # get good names
    df_good_names = get_good_names(targets, test_good_n)
    # get others 
    df_other = get_catch_gibberish(test_impute_n+test_good_n,min_l,max_l)
    df_test = pd.concat([df_obfuscated,df_good_names,df_other])
    
    del df_obfuscated,df_good_names,df_other
    train_impute_n = int(train_size*impute_ratio)
    train_good_n = int(train_size - train_impute_n)
    # get imputed
    df_obfuscated = get_obfuscated_targets(targets,train_impute_n)
    # get good names
    df_good_names = get_good_names(targets, train_good_n)
    # get others 
    df_other = get_catch_gibberish(train_impute_n+train_good_n,min_l,max_l)
    
    df_train = pd.concat([df_obfuscated,df_good_names,df_other])
    
    del df_obfuscated,df_good_names,df_other
    return df_train, df_test

def get_train_test_split(df_train,df_test,one_hot_encode_labels=False):
    X_train = df_train["bad_names"].values
    y_train = df_train["targets"].values
    X_test  = df_test["bad_names"].values
    y_test  = df_test["targets"].values
    
    if one_hot_encode_labels:
        df_labels = pd.concat([df_train['targets'],df_test['targets']])
        print(f"Labels = {df_labels.shape})")
        lables = pd.get_dummies(df_labels)
        lookup = list(lables.columns)
        print(len(lookup))
        del df_labels
        y_test_labels = pd.get_dummies(y_test)
        y_test_encoded = y_test_labels.astype('float32').values
        y_train_labels = pd.get_dummies(y_train)
        y_train_encoded = y_train_labels.astype('float32').values
        return X_train,y_train_encoded,X_test,y_test_encoded, lookup
    else:
        return X_train, y_train, X_test, y_test, None

split_ratio = 0.2
impute_ratio = 0.5
instances = 50
min_l = 5
max_l = 20

df_train, df_test = get_train_test_data(split_ratio,
                                        impute_ratio,
                                        instances,
                                        min_l,
                                        max_l)

X_train, y_train,X_test, y_test, lable_lookup = get_train_test_split(
                                                df_train,
                                                df_test,
                                                one_hot_encode_labels=False)

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

Здесь я решил использовать векторизатор Tfidf в форме обучения scikit, предполагая, что комбинация двух символов может дать лучшее отношение к прогнозированию целей. TfidfVectorizer — это метод извлечения текстовых объектов, который преобразует набор текстовых документов в числовое представление. TF-IDF расшифровывается как Term Frequency-Inverse Document Frequency, что является широко используемой статистической мерой при поиске информации и анализе текста.

Я подгоняю векторизатор к моему корпусу плохих имен, который генерирует n-граммы для длины 2 (2-грамма). Затем я преобразовываю каждое наблюдение, применяя векторизацию для получения преобразованного массива.

from sklearn.feature_extraction.text import TfidfVectorizer
Text = "".join(X_train)
vectorizer = TfidfVectorizer(analyzer='char',strip_accents ="ascii" , ngram_range=(2,2)).fit(Text)

X_train_vector = vectorizer.transform(X_train).toarray()
X_test_vector = vectorizer.transform(X_test).toarray()

Классификатор К-НН

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

from sklearn.neighbors import KNeighborsClassifier
from scipy.spatial import distance      

classifier = KNeighborsClassifier(
                  n_neighbors=20,
                  n_jobs=10, 
                  metric = distance.correlation)

classifier.fit(X_train_vector, y_train)

Вот разбивка того, как классификатор будет работать с заданными параметрами:

  1. n_neighbors=20: этот параметр определяет количество соседей, рассматриваемых для классификации. В этом случае классификатор будет рассматривать 20 ближайших соседей к точке запроса.
  2. n_jobs=10: этот параметр указывает количество параллельных заданий, которые будут выполняться во время поиска соседей. Это позволяет использовать параллельную обработку для ускорения вычислений. В этом случае классификатор будет использовать 10 параллельных заданий для поиска соседей.
  3. metric=distance.correlation: этот параметр определяет показатель расстояния, используемый для расчета расстояния между точками данных. Метрика distance.correlation представляет собой корреляционное расстояние, которое измеряет несходство между двумя образцами на основе их коэффициента корреляции. Это допустимая метрика для алгоритмов KNN.

Учитывая новую точку данных, классификатор выполнит следующие шаги:

  1. Вычислить расстояния: классификатор вычисляет корреляционное расстояние между новой точкой данных и всеми другими точками данных в обучающем наборе.
  2. Выбрать соседей: выбирает 20 ближайших соседей на основе вычисленных корреляционных расстояний.
  3. Классифицировать точку данных: для классификации метка класса большинства среди выбранных соседей назначается в качестве прогнозируемой метки для новой точки данных.

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

Получение прогнозов

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

def my_classifier(w:list,vectorizer,classifier):
    w_vector = vectorizer.transform(w).toarray()
    pred = classifier.predict(w_vector)
    return pred

Данные, которые я использовал, относились к ведущим ритейлерам, которые функции прогнозирования могут предсказать, как показано ниже:

test_stores = ["Targe1", 
                "Larget", 
                "!a7g3T",
                "'7argay'",
                "7- 11",
                "Ac3 Hw"]
my_classifier(test_stores
              ,vectorizer,classifier)
OP : array(['Target', 'Target', 'Other', 'Gap', '7-Eleven', 'Ace Hardware'],
      dtype=object)

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

Оценка модели

from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score

conf_mat = pd.DataFrame({'test_names':X_test,'actual':y_test})
conf_mat["predicted"] = conf_mat["test_names"].apply(
                                          lambda x : my_classifier([x],
                                                              vectorizer,
                                                              classifier)[0])



result = accuracy_score(
                  conf_mat['actual'].values,
                  conf_mat['predicted'].values)
print("Accuracy:",result) 
OP: Accuracy: 0.9910891089108911

Обучение

  1. Классификатор KNN будет потреблять много памяти по мере роста размера обучающих данных.
  2. Количество классов и размер данных также будут влиять на производительность предиктора.
  3. Может быть точным с меньшими тренировочными наборами.
  4. Чтобы выбрать правильную функцию расстояния и количество соседей, потребуются пробы и ошибки с некоторыми обоснованными предположениями.
  5. Лучше всего оптимизировать производительность классификатора с наименьшим количеством классов и размером обучающих данных.
  6. KNN Classifier не требует горячего кодирования меток и может предсказывать метки классов.
  7. Оценка займет время, пропорциональное количеству записей в тестовом наборе.
  8. Может быть эффективным для получения одиночных прогнозов.

Оформить блокнот на Github

https://github.com/mihirsaurkar/ML-apps/blob/c3b2e8a3391fcb11dee5143aab7887d5ad754f9a/Store_Classification/Store%20Classifier%20KNN.ipynb