将语料库流式传输到管道中的矢量化器

Streaming corpus to a vectorizer in a pipeline

我有一个大型语言语料库,我使用 sklearn tfidf vectorizer 和 gensim Doc2Vec 来计算语言模型。我的语料库总数大约有 100,000 个文档,我意识到一旦超过某个阈值,我的 Jupyter notebook 就会停止计算。我猜想应用网格搜索和交叉验证步骤后内存已满。

即使是下面的示例脚本也已经在某些时候停止了 Doc2Vec:

%%time
import pandas as pd
import numpy as np
from tqdm import tqdm
from sklearn.externals import joblib

from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from gensim.sklearn_api import D2VTransformer

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from gensim.utils import simple_preprocess

np.random.seed(1)

data = pd.read_csv('https://pastebin.com/raw/dqKFZ12m')
X_train, X_test, y_train, y_test = train_test_split([simple_preprocess(doc) for doc in data.text],
                                                    data.label, random_state=1)

model_names = [
               'TfidfVectorizer',
               'Doc2Vec_PVDM',
              ]

models = [
    TfidfVectorizer(preprocessor=' '.join, tokenizer=None, min_df = 5),
    D2VTransformer(dm=0, hs=0, min_count=5, iter=5, seed=1, workers=1),
]

parameters = [
              {
              'model__smooth_idf': (True, False),
              'model__norm': ('l1', 'l2', None)
              },
              {
              'model__size': [200],
              'model__window': [4]
              }
              ]

for params, model, name in zip(parameters, models, model_names):

    pipeline = Pipeline([
      ('model', model),
      ('clf', LogisticRegression())
      ])

    grid = GridSearchCV(pipeline, params, verbose=1, cv=5, n_jobs=-1)
    grid.fit(X_train, y_train)
    print(grid.best_params_)

    cval = cross_val_score(grid.best_estimator_, X_train, y_train, scoring='accuracy', cv=5, n_jobs=-1)
    print("Cross-Validation (Train):", np.mean(cval))

print("Finished.")

有没有办法 "stream" 文档中的每一行,而不是将完整数据加载到内存中?或者另一种提高内存效率的方法?我阅读了一些关于该主题的文章,但没有发现任何包含管道示例的文章。

只有 100,000 个文档,除非它们很大,否则导致问题的原因不一定是将数据加载到内存中。特别注意:

  • 在您开始 scikit-learn pipelines/grid-search 之前,文档的加载和标记化就已经成功了 pipelines/grid-search,并且内存使用量的进一步增加是在必须重复的替代模型中,不是 原始文档
  • scikit-learn API 倾向于假设训练数据完全在内存中——所以即使最里面的 gensim classes (Doc2Vec) 对任意大小的流数据感到满意,它更难将其改编为 scikit-learn

所以你应该去别处看看,你显示的代码还有其他问题。

我经常遇到 scikit-learn 的并行性尝试(通过类似 n_jobs 的参数启用)的内存或锁定问题,尤其是在 Jupyter 笔记本中。它会分叉完整的 OS 个进程,这往往会增加内存使用量。 (每个子进程都获得父进程内存的完整副本,这可能会被有效地共享——直到子进程开始 moving/changing 事情。)有时一个进程或进程间通信失败,而主进程只是等待响应——这似乎让 Jupyter notebooks 感到特别困惑。

因此,除非您有大量内存并且绝对需要 scikit-learn 并行性,否则我建议您先尝试使用 n_jobs=1 进行操作 – 然后再尝试更多作业。

相比之下,Doc2Vecclass(和D2VTransformer)的workers使用了更轻量级的线程,你至少应该使用workers=3,也许是 8 个(如果您至少有那么多内核,而不是您现在使用的 workers=1

而且:您在代码中执行了一堆价值不明确的冗余操作。从未使用过来自初始训练测试拆分的测试集。 (也许你想把它放在一边作为最终验证集?这是对你的最终结果在未来看不见的数据上的表现进行良好估计的最严格的方法,但在许多情况下,数据是有限的,而且这个估计并不像重要的是用有限的数据做到最好。)

GridSearchCV 本身进行 5 向 train/test 拆分作为其工作的一部分,完成后其最佳结果会在其属性中记住。

因此您无需再次执行 cross_val_score() - 您可以阅读 GridSearchCV 的结果。