Существует множество бизнес-кейсов для сопоставления строк. Когда для точек данных не существует уникального идентификатора, существуют некоторые неструктурированные данные, чтобы сделать возможным сопоставление. В нашем случае неструктурированные данные - это текст. Например, некоторые могут соответствовать клиентам, продуктам, данные компании поступают из разных источников данных. Кроме того, банк может сопоставить информацию о клиентах с их аккаунтами в социальных сетях.
Мои работы в основном посвящены бизнес-проблемам и решениям, поэтому сопоставление строк будет использоваться в названиях продуктов на страницах продуктов Getir и CarrefourSA.
Шаги:
- Описание и загрузка набора данных.
- Измерение схожести строк (расстояние Левенштейна и отсортированное расстояние Левенштейна)
- Проблемы с мерами сопоставления строк
- Разработка функций в наборе данных
- Прогнозирование совпадений с машинным обучением (персептрон, логистическая регрессия, машины опорных векторов, многослойный персептрон)
Набор данных
Набор данных представляет собой файл Excel, состоящий из столбцов externalName, FetchName, externalPrice, price, priceRate и Match. GetirName имеет названия продуктов в приложении Getir. Столбцы externalName и externalPrice предназначены для наименования и цены продуктов при поиске значений GetirName на веб-сайте CarrefourSA. Столбец Price предназначен для цен на продукты Getir, а priceRate равно price / externalPrice. Соответствие предназначено для целевой переменной, которая равна 1, если два имени совпадают, и 0, когда они не совпадают.
df= pd.read_excel('getir.xlsx', sheet_name='getir') df.head(25)
Сходство измерительной строки
Для измерения сходства с парой строк построено несколько алгоритмов. Я использую Edit Distance, которое также известно как Levensthein Distance.
Расстояние Левенштейна можно определить как рекурсивную функцию или итеративную функцию. Итерационная версия Levensthein Distance быстрее рекурсивной версии благодаря динамическому программированию.
Рекурсивная версия:
def levenshteinRecursive(seq1, seq2): if seq1 == "": return len(seq2) if seq2 == "": return len(seq1) if seq1[-1] == seq2[-1]: cost = 0 else: cost = 1 res = min([levenshteinRecursive(seq1[:-1], seq2) + 1, levenshteinRecursive(seq1, seq2[:-1]) + 1, levenshteinRecursive(seq1[:-1], seq2[:-1]) + cost]) return res
Динамическая версия:
def levenshtein(seq1, seq2): size_x = len(seq1) + 1 size_y = len(seq2) + 1 matrix = np.zeros ((size_x, size_y)) for x in range(size_x): matrix [x, 0] = x for y in range(size_y): matrix [0, y] = y for x in range(1, size_x): for y in range(1, size_y): if seq1[x-1] == seq2[y-1]: matrix [x,y] = min( matrix[x-1, y] + 1, matrix[x-1, y-1], matrix[x, y-1] + 1 ) else: matrix [x,y] = min( matrix[x-1,y] + 1, matrix[x-1,y-1] + 1, matrix[x,y-1] + 1 ) return (matrix[size_x - 1, size_y - 1])
Заказанная версия расстояния Левенштейна :
Нам нужна заказанная версия Levensthein Distance, потому что некоторые названия продуктов могут быть заказаны иначе, как в приведенном выше примере.
def sorted_levenshtein_rate(seq1, seq2): product1 = ''.join(sorted(seq1)) product2 = ''.join(sorted(seq2)) distance = levenshtein(product1, product2) max_len = max(len(product1), len(product2)) return 1-(distance/max_len) def levenshtein_rate(product1, product2): distance = levenshtein(product1, product2) max_len = max(len(product1), len(product2)) return 1 - (distance / max_len) if __name__ == "__main__": met1 = 'Arko Tıraş Köpüğü 200 ml Cool' met2 = 'Arko Men Cool Tıraş Köpüğü 200 ml' print('Levenshtein Distance: {}, MatchScore: {} '.\ format(levenshtein(met1, met2), levenshtein_rate(met1, met2))) # print('Sorted Levenshtein Distance: ', sorted_levenshtein(met1, met2)) print('Sorted Levenshtein Distance: {}, MatchScore: {} '.\ format(sorted_levenshtein(met1, met2), sorted_levenshtein_rate(met1, met2)))
Результат примера выше:
Расстояние Левенштейна: 14,0, MatchScore: 0,57
Отсортированное расстояние Левенштейна: 4,0, MatchScore: 0,88
Проблемы с мерами сопоставления строк:
Прежде всего, эти меры могут использоваться только как метрики. Использование только этой меры с системой, основанной на правилах, или простой моделью может привести к значительному снижению точности. Пример ниже покажет больше, чем было написано ранее.
if __name__ == "__main__": met1 = "Selpak Kağıt Havlu 8'li" met2 = "Selpak Kağıt Havlu 12'li" print('Levenshtein Distance: {}, MatchScore: {} '.\ format(levenshtein(met1, met2), levenshtein_rate(met1, met2))) # print('Sorted Levenshtein Distance: ', sorted_levenshtein(met1, met2)) print('Sorted Levenshtein Distance: {}, MatchScore: {} '.\ format(sorted_levenshtein(met1, met2), sorted_levenshtein_rate(met1, met2))
Результат приведенного выше кода:
Расстояние Левенштейна: 2,0, MatchScore: 0,916
Сортированное расстояние Левенштейна: 2,0, MatchScore: 0,916
Действительно высокий показатель соответствия, который больше, чем в первом примере, но, в отличие от первого примера, эти 2 продукта не совпадают. Эти проблемы возникают из-за разных размеров порций, поскольку значения чисел больше, чем длина их строковой версии. Так что должно быть сделано?
Разработка функций в наборе данных
Сопоставление чисел следует определять с помощью другого столбца. Сначала мы извлечем список чисел в каждой строке в виде списка и преобразуем их в заданный объект. Затем мы можем получить меру совпадения чисел с len перехвата двух наборов за len объединения двух наборов. Вы можете найти функцию удара.
import re def get_rate(row): product1 = row.externalName product2 = row.GetirName regex = r'[0-9]+' numbers1 = set(re.findall(regex, product1)) numbers2 = set(re.findall(regex, product2)) union = numbers1.union(numbers2) intersection = numbers1.intersection(numbers2) if len(numbers1)==0 and len(numbers2) == 0: rate = 1 else: rate = (len(intersection)/ len(union)) return rate
Я также считаю, что это уникальное число, состоящее из двух строк, что означает len перехвата наборов. Вы также можете найти код этой функции ниже.
import re def get_unique_number_count(row): product1 = row.externalName product2 = row.GetirName regex = r'[0-9]+' numbers1 = set(re.findall(regex, product1)) numbers2 = set(re.findall(regex, product2)) union = numbers1.union(numbers2) return len(union)
Прогнозирование совпадений с машинным обучением (персептрон, логистическая регрессия, машины опорных векторов, многослойный персептрон)
Эта статья в основном не о машинном обучении. Алгоритмы будут использоваться без определения. Давайте сначала извлечем функции.
df= pd.read_excel('getir.xlsx', sheet_name='getir') df['levenshteinDistance'] = df.apply(fe.sorted_levenshtein_apply, axis=1) df['uniqueNumberCount'] = df.apply(fe.get_unique_number_count, axis=1)+1 df['numberMatchRate'] = df.apply(fe.get_rate, axis=1) df['matchScore'] = df.apply(fe.sorted_levenshtein_rate_apply, axis=1) df['normalizedMatchRate'] = (df['numberMatchRate']+2).apply(np.log) df['squaredPriceRate'] = df['priceRate']* df['priceRate'] X = df[['matchScore', 'squaredPriceRate', 'uniqueNumberCount', 'normalizedMatchRate']].values y = df['Match'].values
Как вы можете видеть в приведенном выше коде, мы сначала получаем данные, вычисляем новые функции и нормализуем с помощью журнала или расширяем возможности с помощью функции квадрата.
X = df[['matchScore', 'squaredPriceRate', 'uniqueNumberCount', 'normalizedMatchRate']].values y = df['Match'].values X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20,\ random_state=4, stratify=y) sc = StandardScaler() sc.fit(X_train) X_train_std = sc.transform(X_train) X_test_std = sc.transform(X_test)
Выбираются столбцы функций, а данные делятся на обучающие и тестовые наборы.
import pandas as pd import re import numpy as np import featureExtractors as fe from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import Perceptron from sklearn.metrics import accuracy_score from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.neural_network import MLPClassifier from sklearn.metrics import classification_report X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=4, stratify=y)
Объект словаря Python, представленный ниже, даст нам определения всех моделей, которые мы использовали для классификации.
models = {'Perceptron' : Perceptron(max_iter=40, eta0=0.1, random_state=1), 'LogisticRegression' : LogisticRegression(C=100.0, random_state=1), 'LinearSVC' : SVC(kernel='linear', C=1.0, random_state=1), 'KernelizedSVC' : SVC(kernel='rbf', random_state=1, gamma=5.0, C=1.0), 'MLP' : MLPClassifier(activation='relu', alpha=0.0001, batch_size='auto', beta_1=0.9, beta_2=0.999, early_stopping=False, epsilon=1e-08, hidden_layer_sizes=(20, 20, 20), learning_rate='constant', learning_rate_init=0.001, max_iter=500, momentum=0.9, nesterovs_momentum=True, power_t=0.5, random_state=None, shuffle=True, solver='adam', tol=0.0001, validation_fraction=0.1, verbose=False, warm_start=False) }
Эта статья не только о машинном обучении. Можно искать в Интернете все модели классификации и параметры, которые я использовал. Следующий фрагмент кода применит к набору данных все модели, указанные выше.
for model_name, model in models.items(): model.fit(X_train_std, y_train) y_pred = model.predict(X_test_std) print(model_name) print('Accuracy: %.2f' % accuracy_score(y_test, y_pred)) print('Fscore: %.2f' % fbeta_score(y_test, y_pred, beta=1))
Вывод фрагмента кода:
Персептрон:
Точность: 0,98
Оценка по шкале F: 0,99
LogisticRegression:
Точность: 0,98
Fscore: 0,99
LinearSVC:
Точность: 0,98
Fscore: 0,99
KernelizedSVC
Точность: 0,95
Fscore: 0,97
MLP:
Точность: 1.00
Fscore: 1.00
Распечатаны все значения точности и Fscores. Как видите, MLP выиграла конкурс. Некоторые могут прогнозировать все совпадения с помощью многослойного перцептрона. Вы можете найти набор данных и коды в моем аккаунте на github.
Спасибо за прочтение.