Как эффективно создавать функции, используя колонку панд и словарь? - PullRequest
1 голос
/ 05 октября 2019

У меня проблема с машинным обучением, когда я вычисляю сходство биграммы по Джакарду текстового столбца пандасного фрейма со значениями словаря. В настоящее время я храню их в виде списка, а затем преобразую их в столбцы. Это оказывается очень медленным в производстве. Есть ли более эффективный способ сделать это?

Ниже приведены шаги, которые я сейчас выполняю: Для каждого ключа в dict: 1. Получите биграммы для столбца панд и dict [ключ] 2. Вычислите сходство с Жакаром3. Добавьте к пустому списку 4. Сохраните список в кадре данных 5. Преобразуйте список в столбцы

from itertools import tee, islice

def count_ngrams(lst, n):
    tlst = lst
    while True:
        a, b = tee(tlst)
        l = tuple(islice(a, n))
        if len(l) == n:
            yield l
            next(b)
            tlst = b
        else:
            break

def n_gram_jaccard_similarity(str1, str2,n):

    a = set(count_ngrams(str1.split(),n))
    b = set(count_ngrams(str2.split(),n))

    intersection = a.intersection(b)
    union = a.union(b)

    try:
        return len(intersection) / float(len(union))

    except:
        return np.nan

def jc_list(sample_dict,row,n):
    sim_list = []
    for key in sample_dict:
       sim_list.append(n_gram_jaccard_similarity(sample_dict[key],row["text"],n))

    return str(sim_list)

Используя вышеупомянутые функции для построения функций подобия биграммы Jaccard, выполните следующие действия:

df["bigram_jaccard_similarity"]=df.apply(lambda row: jc_list(sample_dict,row,2),axis=1)
df["bigram_jaccard_similarity"] = df["bigram_jaccard_similarity"].map(lambda x:[float(i) for i in [a for a in [s.replace(',','').replace(']', '').replace('[','') for s in x.split()] if a!='']])
df[[i for i in sample_dict]] = pd.DataFrame(df["bigram_jaccard_similarity"].values.tolist(), index= df.index)

Пример ввода:

df = pd.DataFrame(columns=["id","text"],index=None)
df.loc[0] = ["1","this is a sample text"]

import collections

sample_dict = collections.defaultdict()
sample_dict["r1"] = "this is sample 1" 
sample_dict["r2"] = "is sample" 
sample_dict["r3"] = "sample text 2"

Ожидаемый результат:

enter image description here

1 Ответ

1 голос
/ 05 октября 2019

Итак, это сложнее, чем я, из-за некоторых проблем вещания разреженных матриц. Кроме того, за короткий период времени я не смог полностью векторизовать его.

Я добавил дополнительную строку текста во фрейм:

df = pd.DataFrame(columns=["id","text"],index=None)
df.loc[0] = ["1","this is a sample text"]
df.loc[1] = ["2","this is a second sample text"]

import collections

sample_dict = collections.defaultdict()
sample_dict["r1"] = "this is sample 1" 
sample_dict["r2"] = "is sample" 
sample_dict["r3"] = "sample text 2"

Мы будем использовать следующие модули / функции / классы:

from sklearn.feature_extraction.text import CountVectorizer
from scipy.sparse import csr_matrix
import numpy as np

и определим CountVectorizer длясоздавать символьные n_grams

ngram_vectorizer = CountVectorizer(ngram_range=(2, 2), analyzer="char")

не стесняйтесь выбирать n-граммы, которые вам нужны. Я бы посоветовал взять существующий токенизатор и создатель n-грамм. Вы должны найти много таких. Кроме того, CountVectorizer можно настраивать (например, преобразовывать в нижний регистр, избавляться от пробелов и т. Д.)

Мы объединяем все данные:

all_data = np.concatenate((df.text.to_numpy(),np.array(list(sample_dict.values()))))

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

Теперь давайте установим векторизатор Count и соответствующим образом преобразуем данные:

ngrammed = ngram_vectorizer.fit_transform(all_data) >0

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

Далее мы хотим сравнить каждую запись в граммах из образца dict с каждой строкой наших текстовых данных ngrammed. Нам нужно немного магии:

texts = ngrammed[:len(df)]
samples = ngrammed[len(df):]
text_rows = len(df)

jaccard_similarities = []
for key, ngram_sample in zip(sample_dict.keys(), samples):
    repeated_row_matrix = (csr_matrix(np.ones([text_rows,1])) * ngram_sample).astype(bool)
    support = texts.maximum(repeated_row_matrix)
    intersection = texts.multiply(repeated_row_matrix).todense()
    jaccard_similarities.append(pd.Series((intersection.sum(axis=1)/support.sum(axis=1)).A1, name=key))

support - это логический массив, который измеряет объединение n-грамм для обоих сопоставимых. intersection только True, если токен присутствует в обоих сопоставимых. Обратите внимание, что .A1 представляет matrix -объект в качестве базового базового массива.

Теперь

pd.concat(jaccard_similarities, axis=1)

дает

         r1        r2        r3
0  0.631579  0.444444  0.500000
1  0.480000  0.333333  0.384615

, которые вы можете использовать такжедо df и получить с

pd.concat([df, pd.concat(jaccard_similarities, axis=1)], axis=1)
  id                          text        r1        r2        r3
0  1         this is a sample text  0.631579  0.444444  0.500000
1  2  this is a second sample text  0.480000  0.333333  0.384615
...