【问题标题】:How to build features using pandas column and a dictionary efficiently?如何有效地使用 pandas 列和字典构建功能?
【发布时间】:2020-02-03 15:18:00
【问题描述】:

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

以下是我目前正在遵循的步骤: 对于dict中的每个键: 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"

预期输出:

【问题讨论】:

  • 你能分享一些数据和想要的输出吗?感觉就像您基本上可以使用 scikit-learns jaccard-similarity 而不是真正使用 pandas。除了每行应用很慢 :) Vectorize!
  • @Quickbeam2k1 :我已经添加了示例输入和输出。你能提供一个矢量化的例子吗?

标签: python pandas dataframe machine-learning feature-extraction


【解决方案1】:

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

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

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-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 的映射。

接下来,我们要将示例字典中的每个grammes 条目与我们的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 仅在两个可比较中都存在令牌时为 True。请注意,.A1 表示 matrix-object 作为底层基本数组。

现在

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

【讨论】:

  • 谢谢@Quickbeam2k1!几个快速澄清: 1. 首先,当我将jaccard_score(texts.T, repeated_row_matrix.T, average=None) 用于只有一行的数据帧时,它会引发错误。 2. 给出 ngram_range =(2,2) 不会产生与我预期的输出相同的结果。我们如何解决这些问题?
  • 好的,我想我解决了两个问题。首先,我意识到你想创建基于角色的n_grams。顺便说一句,我认为您对n_grams 的实现是错误的。在n=1 的情况下验证这一点。如果不。为什么叫它 n_grams。此外,计数多重性存在问题,我现在将其删除(这是>0),您会在上面找到。希望这会有所帮助
  • 我能够修复 n_gram 计算,但 jaccard_score(texts.T, repeated_row_matrix.T, average=None) 在将它用于只有一行的数据帧时仍然抛出错误 TypeError: len() of unsized object
  • 它是否运行多行文本?这里发生了一些奇怪的事情(我正试图找出错误在哪里)
  • 非常感谢您为 Quickbeam2k1 所做的努力!终于成功了,现在需要检查延迟改进:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-15
  • 2012-01-16
  • 1970-01-01
  • 2020-08-03
  • 2018-05-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多