python - 在多核机器上对 sklearn.naive_bayes.MultinomialNB 执行网格搜索不会使用所有可用的 CPU 资源

标签 python linux scikit-learn

我目前正在尝试使用 Python 和 Scikit-learn 构建一些文本分类工具。

我的文本不是英文的,因此不受词干分解或其他基于英文的降维的通常处理。

结果,TfIdf矩阵变得非常大(150,000x150,000)它可以使用普通PC进行处理,但是对它们运行网格搜索太多了,所以我求助于亚马逊网络服务来运行网格搜索. (我的参数集也很大)

这是我的代码:

 # coding: utf-8  
    import os, json, codecs, nltk  
    import numpy as np  
    from sklearn.feature_extraction.text import TfidfVectorizer,  CountVectorizer,TfidfTransformer  
    from sklearn.grid_search import GridSearchCV  
    from time import time  
    from sklearn.pipeline import Pipeline  
    from sklearn.naive_bayes import MultinomialNB  
    print("Importing dataset...")  
    with open('y_data.json','r') as fp:  
        y = json.load(fp)  
    with open('dataset.json','r') as fp:  
        dataset = json.load(fp)  
    print("Importing stop words...")  
    with codecs.open('stopword.txt','r','utf-8') as fp:  
    stopword = []  
    for w in fp:  
        stopword.append(w.strip())  
    light_st = set(stopword)  
    with codecs.open('st_data.txt','r','cp874') as fp:  
    for w in fp:  
        stopword.append(w.strip())  
    heavy_st = set(stopword)  
    def pre_process_1(text):  
        return text.replace("|"," ")  
    def tokenize_1(text):  
        return text.split()  
    pipeline = Pipeline([('vec', CountVectorizer(encoding='cp874', preprocessor=pre_process_1, tokenizer=tokenize_1, stop_words=heavy_st, token_pattern=None)),('tfidf', TfidfTransformer()), ('clf',       MultinomialNB())])
    parameters = {  
    'vec__max_df': (0.5, 0.625, 0.75, 0.875, 1.0),  
    'vec__max_features': (None, 5000, 10000, 20000),  
    'vec__min_df': (1, 5, 10, 20, 50),  
    'tfidf__use_idf': (True, False),  
    'tfidf__sublinear_tf': (True, False),  
    'vec__binary': (True, False),  
    'tfidf__norm': ('l1', 'l2'),  
    'clf__alpha': (1, 0.1, 0.01, 0.001, 0.0001, 0.00001)  
    }  
    if __name__ == "__main__":  
        grid_search = GridSearchCV(pipeline, parameters, n_jobs=-1, verbose=2)  
        t0 = time()  
        grid_search.fit(dataset, y)  
        print("done in {0}s".format(time() - t0))  
        print("Best score: {0}".format(grid_search.best_score_))  
        print("Best parameters set:")  
        best_parameters = grid_search.best_estimator_.get_params()  
        for param_name in sorted(list(parameters.keys())):  
            print("\t{0}: {1}".format(param_name, best_parameters[param_name]))

这是我的软件环境的详细信息:
  • Python3.4.2
  • scikit-learn 0.15.2(与 Pip 一起安装)
  • Ubuntu Server14.04 LTS,64 位(使用 HVM)
  • 在 ec2 r3.8xlarge 实例上试过

  • 起初,我使用一个小得多的实例(r3.2xlarge;8 核)运行我的模型,但从计算中发现它需要很长时间(2 天)。所以,我决定扩大我的机器并使用最大的实例(我使用 r3,因为我的脚本非常占用内存);然而,它并没有我想象的那么快。

    当我尝试监控 CPU 负载时​​(watch -n 5 uptime)...我发现即使我让它运行一段时间,平均 CPU 负载也不会超过 9。 (据我了解,一台 32 核的机器,当充分利用其所有内核时,应该在 32 左右)。

    我试着改变

    n_job



    参数到不同的数字 (8, 32, 128) 具有相同的结果。 (但是,我认为该脚本会尝试按照指示运行尽可能多的作业,因为当我终止该进程时,我会看到类似...“Process ForkPoolWorker-30:”的内容,并且它们的回溯会掠过屏幕)

    使用 ps x -C python3.4 命令进一步检查发现只有 8 个 python 进程正在运行。我推断这可能是 python 或操作系统的某种限制(我使用没有很多内核的 t2.micro 实例构建我的 AMI)所以,我决定重做我从头开始重建环境的工作,包括编译 Python使用 c3.4xlarge,并将操作系统更改为 Amazon Linux(我认为是 Fedora 的一个分支)以更好地与硬件兼容。

    但是,我的脚本仍然没有超过 8 个内核。
    最后,使用来自 Scikit-learn 网站的演示文本分类代码:http://scikit-learn.org/stable/auto_examples/grid_search_text_feature_extraction.html
    (使用 SGDClassifier 而不是 MultinomialNB)它可以与所有 32 个内核完美运行!

    所以......也许,与网格搜索算法和朴素贝叶斯分类器有关?

    我正在考虑提交错误,但首先想知道这是朴素贝叶斯的预期行为还是我的代码有问题?

    更新

    我找不到直接测试内存带宽是否是罪魁祸首的方法。但是我尝试以各种方式对我的并行代码和 CPU 使用进行计时,以找出瓶颈发生的确切位置。

    实验一:只进行向量化和变换。

    使用我的真实数据作为输入(150,000 个文本文档;每个包含大约 130 个单词)
    参数空间约为400。
    多线程由 Joblib(与 Scikit-learn 使用的模块相同)完成。我有:
    使用 8 个线程:在 841.017783164978 秒内完成并使用 24.636999999999993 % 的 CPU。
    使用 16 个线程:在 842.9525656700134 秒内完成并使用 24.700749999999985 % 的 CPU。
    使用所有 32 个线程:在 857.024197101593 秒内完成并使用 24.242250000000013 % 的 CPU。

    结果清楚地表明矢量化过程无法随着处理能力的增加而扩展。

    实验 2:这次我只对预先矢量化的数据执行 MultinomialNB。

    像以前一样使用大约400的参数空间,我得到:
    使用 8 个线程:在 2102.0565922260284 秒内完成并使用 25.486000000000054 % 的 CPU。
    使用 16 个线程:在 1385.6887295246124 秒内完成并使用 49.83674999999993 % 的 CPU。
    使用所有 32 个线程:在 1319.416403055191 秒内完成并使用 89.90074999999997 % 的 CPU。

    从 8 个线程到 16 个线程的过渡显示出巨大的改进。但是,随着线程数增加到 32,完成的总时间只会稍微缩短一点,而 CPU 使用率却大幅增加。这点我不太明白。

    实验 3:我将这两个过程结合在一起。

    使用 8 个线程:在 3385.3253166675568 秒内完成并使用 25.68999999999995 % 的 CPU。
    使用 16 个线程:在 2066.499200105667 秒内完成并使用 49.359249999999996 % 的 CPU。
    使用所有 32 个线程:在 2018.8800330162048 秒内完成并使用 54.55375000000004 % 的 CPU。

    我从自己的并行代码中获得的时间与从 GridsearchCV 获得的时间之间存在一些差异,但这可能是因为我在代码中进行了简化(我没有进行交叉验证或完整参数迭代,例如网格搜索)

    结论

    根据我的测试,我得出结论。 (如果我错了,请纠正我)
  • 矢量化阶段使用的内存要密集得多;因此,最有可能使带宽饱和。这可以从完成时间和 CPU 利用率观察到它遇到了某种瓶颈并且没有扩展。然而,这是一个相对较快的过程。 (我消除了 IO-bound,因为所有数据都存储在 RAM 上,这段时间的内存使用量约为 30%)
  • MultinomialNB 使用的内存比向量化器要少;大多数计算似乎是在内核中处理的。因此,它可以比矢量化器(8 > 16)更好地扩展,但在那之后,它也遇到了某种瓶颈,MultinomialNB 比矢量化器花费更多的时间。
  • 当将两个进程组合​​在一起时,完成时间显示出与 MultinomialNB 相同的趋势,因为在我看来,内存带宽可能是矢量化阶段的瓶颈,但与 MultinomialNB 相比,该阶段相对较短。因此,如果并发任务数较少,则可以同时进行这两个阶段而不会使带宽饱和,但是当进程数足够多时,将有足够多的并发进程执行向量化以达到饱和带宽;从而迫使操作系统减少运行进程。 (仅解释我之前发现的 8-9 个正在运行的 python 进程)
  • 我不太确定,但我认为 SGDClassifier 可以使用 100% CPU 的原因是因为 SGDClassifier 的核内处理时间比 MultinomialNB 长得多。因此,在每次迭代中,大部分时间都用于在核心计算 SGDClassifier 而不是进行向量化,并且 SGDClassifier 需要很长时间来计算的事实减少了许多工作人员同时进入向量化阶段的机会(因为每个向量化任务相对较短但内存密集)

  • 我认为我现在最好的选择是进行集群计算。 :)

    最佳答案

    看起来您的工作都是受内存限制的。

    朴素贝叶斯是一个极其简单的模型,其训练算法由单个(稀疏)矩阵乘法和一些求和组成。同样,tf-idf 计算起来也很简单:它对输入求和,计算一些日志,然后存储结果。

    其实NB就是这么简单,这个程序的瓶颈几乎肯定在CountVectorizer ,它会多次转换内存中的数据结构,直到将所有术语计数塞入正确的矩阵格式。如果并行执行大量操作,则可能会遇到内存带宽瓶颈。

    (这都是有根据的猜测,但这是基于我参与 scikit-learn 开发。我是 MultinomialNB 的作者之一,也是许多入侵 CountVectorizer 以加快速度的人之一。)

    关于python - 在多核机器上对 sklearn.naive_bayes.MultinomialNB 执行网格搜索不会使用所有可用的 CPU 资源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26569478/

    相关文章:

    c - 为什么在 XWindows 中绘图很重要?

    python - 类型错误 : ufunc 'true_divide' output (typecode 'd' ) could not be coerced to provided output parameter (typecode 'q' )

    python - 如何将 .pyc 文件转换为 .exe 文件

    python - 用转义引号替换字符串中的所有引号?

    python - 从矩阵中删除特定值

    linux - .desktop 文件在 centos 8 上不可执行

    python - 如何同时更改和移动 tkinter Canvas 上的图像?

    linux - `tce-load -wi stress` 不起作用?

    python - 堆叠两个不同维度的稀疏矩阵

    python - 计数向量化器中是否可以有无序二元组