我有一个机器学习问题,我正在计算 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"
我们将使用以下模块/函数/类:
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-gram 创建器。你应该找到很多这样的。此外,CountVectorizer 可以进行广泛的调整(例如转换为小写、删除空格等)
我们连接所有数据:
all_data = np.concatenate((df.text.to_numpy(),np.array(list(sample_dict.values()))))
我们这样做是因为我们的向量化器需要为所有出现的标记有一个通用的索引方案。
现在让我们拟合计数向量化器并相应地转换数据:
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
是 bool 数组,用于测量两个可比较的 n 元语法的并集。仅当两个可比对象中都存在标记时,intersection
才为 True。请注意,.A1
表示一个矩阵
对象作为底层基本数组。
现在
pd.concat(jaccard_similarities, axis=1)
给出
r1 r2 r3
0 0.631579 0.444444 0.500000
1 0.480000 0.333333 0.384615
您也可以将 is 与 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
关于python - 如何使用 pandas 列和字典有效地构建特征?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58250720/