如何有效地使用 pandas 列和字典构建特征?

How to build features using pandas column and a dictionary efficiently?

我有一个机器学习问题,我正在计算 pandas 数据框文本列与字典值的二元组 Jaccard 相似度。目前我将它们存储为列表,然后将它们转换为列。事实证明,这在生产中非常缓慢。有没有更有效的方法呢?

以下是我目前正在执行的步骤: 对于字典中的每个键: 1. 获取 pandas 列和 dict[key] 的二元语法 2.计算Jaccard相似度 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"

预期输出:

因此,由于稀疏矩阵的一些广播问题,这比我想象的要困难。此外,在短时间内我无法完全矢量化它。

我在框架中添加了一个额外的文本行:

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"

我们将使用以下 modules/functions/classes:

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-gram。我建议采用现有的分词器和 n-gram 创建器。你应该找到很多这样的。还可以对 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 并找到从标记到列 ID 的映射。

接下来我们要将示例字典中的每个语法条目与 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-gram 的并集。 intersection 仅当令牌在两个可比较对象中都存在时才为真。请注意,.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