如何有效地使用 CountVectorizer 来组合目录中所有文件的 ngram 计数?

Posted

技术标签:

【中文标题】如何有效地使用 CountVectorizer 来组合目录中所有文件的 ngram 计数?【英文标题】:How to efficiently use CountVectorizer to get ngram counts for all files in a directory combined? 【发布时间】:2020-01-09 16:13:22 【问题描述】:

我的目录中有大约 10k .bytes 文件,我想使用计数矢量化器来获取 n_gram 计数(即适合训练并转换测试集)。 在这 10k 个文件中,我有 8k 个文件作为训练文件,2k 个文件作为测试文件。

files = 
['bfiles/GhHS0zL9cgNXFK6j1dIJ.bytes',
 'bfiles/8qCPkhNr1KJaGtZ35pBc.bytes',
 'bfiles/bLGq2tnA8CuxsF4Py9RO.bytes',
 'bfiles/C0uidNjwV8lrPgzt1JSG.bytes',
 'bfiles/IHiArX1xcBZgv69o4s0a.bytes',
    ...............................
    ...............................]

print(open(files[0]).read())
    'A4 AC 4A 00 AC 4F 00 00 51 EC 48 00 57 7F 45 00 2D 4B 42 45 E9 77 51 4D 89 1D 19 40 30 01 89 45 E7 D9 F6 47 E7 59 75 49 1F ....'

我无法执行以下操作并将所有内容都传递给 CountVectorizer

file_content = []
for file in file:
    file_content.append(open(file).read())

我不能将每个文件文本附加到一个大的嵌套文件列表中,然后使用CountVectorizer,因为所有组合的文本文件大小超过 150gb。我没有资源来做这件事,因为CountVectorizer 使用大量内存。

我需要一种更有效的方法来解决这个问题,有没有其他方法可以实现我想要的,而无需一次将所有内容加载到内存中。非常感谢任何帮助。

我所能实现的只是读取一个文件,然后使用CountVectorizer,但我不知道如何实现我想要的。

cv = CountVectorizer(ngram_range=(1, 4))
temp = cv.fit_transform([open(files[0]).read()])
temp
<1x451500 sparse matrix of type '<class 'numpy.int64'>'
    with 335961 stored elements in Compressed Sparse Row format>

【问题讨论】:

很确定fit 接受任意可迭代,所以不要实现一个巨大的列表,只需使用生成器 @juanpa.arrivillaga 我不知道如何使用生成器来解决这个问题。如果不是太多,您可以提供一个我可以使用的示例答案吗? 试试 HashingVectorizer.... @qaiser HashingVectorizer 存在一个问题,即“不同的标记可以映射到相同的特征索引。但是在实践中,如果 n_features 足够大(例如 2 ** 18 用于文本分类),这很少会成为问题问题”我的 n_features 不够大。您可以在HashingVectorizer 的 scikit-docs 中阅读有关此问题的信息。 @user_12,由您来设置 n_features。只需将其设置为 2**18 就可以了。 【参考方案1】:

sklearn 文档指出.fit_transform 可以采用可生成 str、unicode 或文件对象的迭代。因此,您可以创建一个生成器,逐个生成文件并将其传递给 fit 方法。您可以通过将路径传递给文件来创建生成器,如下所示:

def gen(path):
    A = os.listdir(path)
    for i in A:
        yield (i)

现在您可以创建生成器并将其传递给 CountVectorizer,如下所示:

q = gen("/path/to/your/file/")

from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(ngram_range=(1, 4))
cv.fit_transform(q)

这对你有帮助!

【讨论】:

它是如何工作的?它会一个接一个地适应一个文件并最终返回输出吗?内存要求是什么?我的意思是我有大约 150GB 的数据?这不会需要巨大的内存吗?【参考方案2】:

您可以使用以下流程构建解决方案:

1) 遍历您的文件并在您的文件中创建一组所有令牌。在下面的示例中,这是使用 Counter 完成的,但您可以使用 python 集来实现相同的结果。这里的好处是 Counter 还会为您提供每个术语的出现总数。

2) 使用标记集/列表拟合 CountVectorizer。您可以使用 ngram_range=(1, 4) 实例化 CountVectorizer。为了限制 df_new_data 中的特征数量,避免低于此值。

3) 像往常一样转换新数据。

以下示例适用于小数据。我希望您可以调整代码以满足您的需求。

import glob
import pandas as pd
import numpy as np
from collections import Counter
from sklearn.feature_extraction.text import CountVectorizer

# Create a list of file names
pattern = 'C:\\Bytes\\*.csv'
csv_files = glob.glob(pattern)

# Instantiate Counter and loop through the files chunk by chunk 
# to create a dictionary of all tokens and their number of occurrence
counter = Counter()
c_size = 1000
for file in csv_files:
    for chunk in pd.read_csv(file, chunksize=c_size, index_col=0, header=None):
        counter.update(chunk[1])

# Fit the CountVectorizer to the counter keys
vectorizer = CountVectorizer(lowercase=False)
vectorizer.fit(list(counter.keys()))

# Loop through your files chunk by chunk and accummulate the counts
counts = np.zeros((1, len(vectorizer.get_feature_names())))
for file in csv_files:
    for chunk in pd.read_csv(file, chunksize=c_size,
                             index_col=0, header=None):
        new_counts = vectorizer.transform(chunk[1])
        counts += new_counts.A.sum(axis=0)

# Generate a data frame with the total counts
df_new_data = pd.DataFrame(counts, columns=vectorizer.get_feature_names())

df_new_data
Out[266]: 
      00     01     0A     0B     10     11     1A     1B     A0     A1  \
0  258.0  228.0  286.0  251.0  235.0  273.0  259.0  249.0  232.0  233.0   

      AA     AB     B0     B1     BA     BB  
0  248.0  227.0  251.0  254.0  255.0  261.0  

数据生成代码:

import numpy as np
import pandas as pd

def gen_data(n): 
    numbers = list('01')
    letters = list('AB')
    numlet = numbers + letters
    x = np.random.choice(numlet, size=n)
    y = np.random.choice(numlet, size=n)
    df = pd.DataFrame('X': x, 'Y': y)
    return df.sum(axis=1)

n = 2000
df_1 = gen_data(n)
df_2 = gen_data(n)

df_1.to_csv('C:\\Bytes\\df_1.csv')
df_2.to_csv('C:\\Bytes\\df_2.csv')

df_1.head()
Out[218]: 
0    10
1    01
2    A1
3    AB
4    1A
dtype: object

【讨论】:

这是最有效的方法吗?我所有的文件合并了 150gb 的数据?这些文件是 .bytes 文件(即文本数据)而不是 .csv 文件?另外,您不认为将数据加载到 pandas 数据帧中会浪费大量内存吗? 该示例仅旨在展示一般方法,因此使用 pandas 进行数据生成和读取。你如何阅读文件的细节是你应该能够解决的。如有必要,我很乐意提供帮助。稍后我将使用以下内容扩展答案 - 在第 3 步中,您可以迭代循环遍历文件、转换和累积单个矩阵/数据框中的计数,以获得一个矩阵中整个数据集的计数。 我已经修改了答案以演示您如何累积计数。如果不清楚,请告诉我。 问题是我的转换数据也是文件。我该怎么做? 查看修改后的答案。它适用于 csv 文件。您可能需要调整代码以使用您的特定文件格式。【参考方案3】:

通过使用生成器而不是列表,您的代码不会将文件的值存储到内存中。相反,它将产生一个值并让它忘记它,然后产生下一个,依此类推。在这里,我将使用您的代码并进行简单的调整以将列表更改为生成器。您可以只使用() 而不是[]

cv = CountVectorizer(ngram_range=(1, 4))
temp = cv.fit_transform((open(file).read() for file in files))

【讨论】:

这种方法(或)上述建议(即使用计数器)在计算和内存方面哪个更有效?

以上是关于如何有效地使用 CountVectorizer 来组合目录中所有文件的 ngram 计数?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 sklearn CountVectorizer and() 来获取包含任何标点符号作为单独标记的 ngram?

如何使用带有 countVectorizer.fit_transform() 的腌制分类器来标记数据

如何从 CountVectorizer 保存和加载词汇表?

我可以在 scikit-learn 中使用 CountVectorizer 来计算未用于提取标记的文档的频率吗?

如何更有效地使用 RunApp 功能来更改页面

Spark CountVectorizer返回udt而不是向量[重复]