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

提问

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

我的文字不是英语,因此不受茎分解或其他基于英语的降维处理的常规处理.

结果,TfIdf矩阵变得很大(150,000×150,000)可以使用常规PC进行处理,但是在它们上运行网格搜索会太多,因此我寻求Amazon Web Service的帮助来运行网格搜索. (我的参数集也很大)

这是我的代码:

 # 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负载(观察-n 5正常运行时间)…时,我发现即使将其运行一段时间,平均CPU负载也不会超过9. (据我了解,一台32核机器在充分利用其所有核时应为32左右).

我尝试改变

n_job

参数到具有相同结果的各种数字(8、32、128). (但是我认为该脚本尝试运行所指示的尽可能多的作业,因为当我终止该过程时,我会看到类似“ Process ForkPoolWorker-30:”的消息,并且其回溯信息会越过屏幕)

使用ps x -C python3.4命令进行进一步检查,得出只有8个python进程正在运行.我推断这可能是python或OS的某种限制(我使用没有很多内核的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使用时间进行计时,以找出瓶颈的发生位置.

实验1:仅执行向量化和转换.

使用我的真实数据作为输入(150,000个文本文档;每个文档包含大约130个单词)
参数空间约为400.
多线程由Joblib(与Scikit-learn使用的模块相同)完成.我有:
使用8个线程:完成841.017783164978 s,并使用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 s完成,并使用49.83674999999993%的CPU.
使用所有32个线程:在1319.416403055191中完成,并使用89.90074999999997%的CPU.

从8线程到16线程的过渡显示出巨大的进步.但是,随着线程数增加到32,完成的总时间只会略微缩短,而CPU使用率却大大增加.这一点我不太了解.

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

使用8个线程:在3385.3253166675568中完成,并使用25.68999999999995%的CPU.
使用16个线程:完成2066.499200105667 s,并使用49.359249999999996%的CPU.
使用所有32个线程:在2018.8800330162048中完成,并使用54.55375000000004%的CPU.

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

结论

根据我的测试,我得出结论. (如果我错了,请纠正我)

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

我认为现在最好的选择是进行集群计算. ?

最佳答案

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

朴素贝叶斯是一个非常简单的模型,其训练算法由一个(稀疏)矩阵乘法和几个和组成.同样,tf-idf的计算非常简单:它对输入进行求和,计算一些日志并存储结果.

实际上,NB非常简单,以至于该程序的瓶颈几乎可以肯定在CountVectorizer中,它可以多次转换内存中的数据结构,直到将其所有术语计数塞满正确的矩阵格式为止.如果并行执行很多操作,则很可能会遇到内存带宽瓶颈.

(所有这些都是有根据的猜测,但这是基于我参与scikit-learn开发的结果.我是MultinomialNB的作者之一,也是许多使用CountVectorizer来加快速度的人之一.)