有重复项时如何合并两个countvectorizers?

how to merge two countvectorizers when there are duplicates?

考虑这个简单的例子

data = pd.DataFrame({'text1' : ['hello world', 'hello universe'],
                     'text2': ['good morning', 'hello two three']})
    
data
Out[489]: 
            text1            text2
0     hello world     good morning
1  hello universe  hello two three

如您所见,text1text2 有一个完全相同的词:hello。我正在尝试为 text1text2 分别创建 ngram,我想将结果连接到一个 countvectorizer 对象中。

我的想法是,我想为这两个变量分别创建 ngram,并将它们用作 ML 算法中的特征。但是,我确实想要通过将字符串连接在一起而创建的额外 ngram,例如 hello world good morning 中的 world good。这就是我将 ngram 创建分开的原因。

问题是这样做后,生成的(稀疏)向量将包含重复的 hello 列。

看这里:

vector = CountVectorizer(ngram_range=(1, 2))

v1 = vector.fit_transform(data.text1.values) 
print(vector.get_feature_names())

['hello', 'hello universe', 'hello world', 'universe', 'world']

v2 = vector.fit_transform(data.text2.values)
print(vector.get_feature_names())

['good', 'good morning', 'hello', 'hello two', 'morning', 'three', 'two', 'two three']

现在连接 v1v2 得到 13 列

from scipy.sparse import hstack
print(hstack((v1, v2)).toarray())

[[1 0 1 0 1 1 1 0 0 1 0 0 0]
 [1 1 0 1 0 0 0 1 1 0 1 1 1]]

正确的文本特征应该是 12:

hello,word,hello word,good,morning,good morning,hello universe,universetwothreehello twotwo three

我可以在这里做些什么来获得合适的唯一词作为特征? 谢谢!

免责声明:这个答案可能不是很复杂,但如果我理解正确你的问题,它应该能完成它的工作。

# create an additional column by chaining the two text columns with a fake word
data['text3'] = data['text1'] + ' xxxxxxxxxx ' + data['text2']
print(data)
#             text1            text2                                      text3
# 0     hello world     good morning        hello world xxxxxxxxxx good morning
# 1  hello universe  hello two three  hello universe xxxxxxxxxx hello two three

# instantiate CountVectorizer and fit it
vector = CountVectorizer(ngram_range=(1, 2))
v3 = vector.fit_transform(data.text3.values)

# have a look at the resulting column names
all_colnames = vector.get_feature_names()
print(all_colnames)
# ['good', 'good morning', 'hello', 'hello two', 'hello universe', 'hello world', 'morning', 'three', 'two', 'two three', 'universe', 'universe xxxxxxxxxx', 'world', 'world xxxxxxxxxx', 'xxxxxxxxxx', 'xxxxxxxxxx good', 'xxxxxxxxxx hello']

# select only column names of interest
correct_colnames = [e for e in vector.get_feature_names() if 'xxxxxxxxxx' not in e]
print(correct_colnames)
# ['good', 'good morning', 'hello', 'hello two', 'hello universe', 'hello world', 'morning', 'three', 'two', 'two three', 'universe', 'world']

print(len(all_colnames))
# 17
print(len(correct_colnames))
# 12   # the desired length

# select only the array columns where the fake word is absent
arr = v3.toarray()[:, ['xxxxxxxxxx' not in e for e in colnames]]
print(arr.shape)
print(arr)
# (2, 12)
# [[1 1 1 0 0 1 1 0 0 0 0 1]
#  [0 0 2 1 1 0 0 1 1 1 1 0]]

# if you need a pandas.DataFrame as result
new_df = pd.DataFrame(arr, columns=correct_colnames)
print(new_df)
#    good  good morning  hello  hello two  hello universe  hello world  morning  three  two  two three  universe  world
# 0     1             1      1          0               0            1        1      0    0          0         0      1
# 1     0             0      2          1               1            0        0      1    1          1         1      0

其背后的基本原理是:我们插入了一个假词,如 'xxxxxxxxxx',这几乎不可能在文本字符串中遇到。该算法会将其视为一个真实的词,因此将用它创建 1-gram 和 2-gram。

然而,我们可以在之后消除那些 n-grams,并且所有相等的词(如本例中的 'hello')将不会被单独计算为两个文本列 - 事实上,你可以看到在生成的数据框中,单词 'hello' 在第二行中出现了两次,并且没有重复。

我认为解决这个问题的最佳方法是创建一个使用 CountVectorizer.

的自定义 Transformer

我会这样做:

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

class MultiRowsCountVectorizer(BaseEstimator, TransformerMixin):
    def __init__(self):
        self.verctorizer = CountVectorizer(ngram_range=(1, 2))
    
    def fit(self, X, y = None):
        #concatenate all textual columns into one column
        X_ = np.reshape(X.values, (-1,))
        self.verctorizer.fit(X_)
        return self
    

    def transform(self, X, y = None):
        #join all the textual columns into one column
        X_ = X.apply(' '.join, axis=1)
        return self.verctorizer.transform(X_)
    
    def get_feature_names(self):
        return self.verctorizer.get_feature_names()
    
    
transformer = MultiRowsCountVectorizer()
X_ = transformer.fit_transform(data)
transformer.get_feature_names()

fit() 方法通过独立处理列来拟合 CountVectorizer,而 transform() 将列视为同一行文本。

np.reshape(X.values, (-1,)) 正在将形状为 (N, n_columns) 的矩阵转换为大小为 (N*n_columns,) 的一维数组。这确保每个文本字段在 fit() 期间被独立处理。之后,通过将样本的所有文本特征连接在一起,将转换应用于样本的所有文本特征。

这个自定义转换器正在返回所需的 12 个特征:

['good', 'good morning', 'hello', 'hello two', 'hello universe', 'hello world', 'morning', 'three', 'two', 'two three', 'universe', 'world']

并返回以下功能:

[[1 1 1 0 0 1 1 0 0 0 0 1]
 [0 0 2 1 1 0 0 1 1 1 1 0]]

NOTES:这个自定义转换器假定 X 是一个 pd.DataFramen 文本列。

编辑:文本字段需要在 transform().

期间与 space 连接