为给定文档选择前n个TFIDF特征

10

我正在使用TFIDF稀疏矩阵进行文档分类,并希望每个文档仅保留前n个(例如50)按TFIDF得分排名的词项。参见下面的编辑

import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer

tfidfvectorizer = TfidfVectorizer(analyzer='word', stop_words='english', 
                              token_pattern='[A-Za-z][\w\-]*', max_df=0.25)
n = 50

df = pd.read_pickle('my_df.pickle')
df_t = tfidfvectorizer.fit_transform(df['text'])

df_t
Out[15]: 
<21175x201380 sparse matrix of type '<class 'numpy.float64'>'
    with 6055621 stored elements in Compressed Sparse Row format>

我尝试按照这篇帖子中的示例操作,虽然我的目标不是显示功能,而只是在训练之前为每个文档选择前n个。但由于我的数据太大而无法转换为密集矩阵,所以出现了内存错误。

df_t_sorted = np.argsort(df_t.toarray()).flatten()[::1][n]
Traceback (most recent call last):

  File "<ipython-input-16-e0a74c393ca5>", line 1, in <module>
    df_t_sorted = np.argsort(df_t.toarray()).flatten()[::1][n]

  File "C:\Users\Me\AppData\Local\Continuum\anaconda3\lib\site-packages\scipy\sparse\compressed.py", line 943, in toarray
    out = self._process_toarray_args(order, out)

  File "C:\Users\Me\AppData\Local\Continuum\anaconda3\lib\site-packages\scipy\sparse\base.py", line 1130, in _process_toarray_args
    return np.zeros(self.shape, dtype=self.dtype, order=order)

MemoryError

是否有方法能够满足我的需求,不使用密集表示(即不使用toarray()函数),且不会将特征空间缩小得比我已经做的更多(使用了min_df参数)?

注意:max_features参数并不能满足我的需求,因为它只考虑“整个语料库中按术语频率排序的前max_features个”(文档见此处),而我需要的是基于文档的排名。

编辑:我想知道解决这个问题的最佳方法是否是将除了前n项之外的所有特征值都设为零。我说这话是因为词汇表已经计算过了,所以特征索引必须保持不变,因为我还要将它们用于其他目的(例如,可视化与前n个特征相对应的实际单词)。

一位同事编写了一些代码来检索前n个高排名特征的索引:

n = 2
tops = np.zeros((df_t.shape[0], n), dtype=int) # store the top indices in a new array
for ind in range(df_t.shape[0]):
    tops[ind,] = np.argsort(-df_t[ind].toarray())[0, 0:n] # for each row (i.e. document) sort the (inversed, as argsort is ascending) list and slice top n

但是在那之后,我需要做以下两步中的一步:

  1. 检索剩余(即排名最低)索引列表并“原地”修改值,或者
  2. 循环遍历原矩阵(df_t)并将所有值设置为0,除了tops中排名最高的n个索引。

这里有一篇文章介绍了如何使用csr_matrix,但我不确定如何实践以获得我想要的结果。

3个回答

6
from nltk.tokenize import word_tokenize
from sklearn.feature_extraction.text import TfidfVectorizer
vect = TfidfVectorizer(tokenizer=word_tokenize,ngram_range=(1,2), binary=True, max_features=50)
TFIDF=vect.fit_transform(df['processed_cv_data'])

TfidfVectorizer中传递的max_features参数将按照它们的词频而不是它们的Tf-idf分数来选择前50个特征。您可以使用以下命令查看这些特征:

print(vect.get_feature_names())

3

就像你提到的一样,TfidfVectorizer 的 max_features 参数是选择特征的一种方式。

如果你正在寻找一种考虑与目标变量关系的替代方法,可以使用 sklearn 的 SelectKBest。通过设置 k=50,这将为你筛选出最佳特征。用于选择的度量标准可以在参数 score_func 中指定。

示例:

from sklearn.feature_selection import SelectKBest

tfidfvectorizer = TfidfVectorizer(analyzer='word', stop_words='english', 
                          token_pattern='[A-Za-z][\w\-]*', max_df=0.25)

df_t = tfidfvectorizer.fit_transform(df['text'])
df_t_reduced = SelectKBest(k=50).fit_transform(df_t, df['target'])

您还可以在管道中链接它:

pipeline = Pipeline([("vectorizer", TfidfVectorizer()),
                     ("feature_reduction", SelectKBest(k=50)),
                     ("classifier", classifier)])

1
谢谢您提供的信息,但是(除非我弄错了),SelectKBest 似乎不是我需要的,因为它似乎是在整个语料库中计算 k 个最佳特征(因此不包含任何 k 个术语的文档仅用零表示)。我想做的是按 TFIDF 分数降序排列每个文档的特征,然后选择前 k 个特征(就像对列表进行排序然后切片一样)。 - ongenz
1
@ogenz,抱歉我没有理解你想要做什么。我会保留我的答案以便帮助其他人。 - Glyph

1
你可以将numpy数组分成多个部分以释放内存。然后只需将它们连接起来即可。
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.datasets import fetch_20newsgroups

data = fetch_20newsgroups(subset='train').data

tfidfvectorizer = TfidfVectorizer(analyzer='word', stop_words='english', 
                                  token_pattern='[A-Za-z][\w\-]*', max_df=0.25)
df_t = tfidfvectorizer.fit_transform(data)

n = 10

df_t = tfidfvectorizer.fit_transform(data)

df_top = [np.argsort(df_t[i: i+500, :].toarray(), axis=1)[:, :n]
          for i in range(0, df_t.shape[0], 500)]

np.concatenate(df_top, axis=0).shape
>> (11314, 10)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接