scikit的GridSearch和Python通常不会释放内存

时间:2014-12-16 16:05:02

标签: python memory scikit-learn

我做了一些奇怪的观察,我的GridSearches在几个小时后仍然失败,我最初无法弄清楚原因。我随后监视了内存使用情况,发现它以几千兆字节(~6 Gb)启动并持续增加,直到它达到最大值时崩溃。硬件可以承受128 Gb。 我正在尝试使用随机森林来分类大量的文本文档。为了简单起见 - 弄清楚发生了什么 - 我回到了天真的贝叶斯。

我使用的版本是

  • Python 3.4.2
  • scikit-learn 0.15.2

我在GitHub的scikit-issue列表上找到了一些关于这个主题的相关讨论:https://github.com/scikit-learn/scikit-learn/issues/565https://github.com/scikit-learn/scikit-learn/pull/770

听起来它已经成功解决了!

所以,我使用的相关代码是

grid_search = GridSearchCV(pipeline, 
                           parameters, 
                           n_jobs=1, # 
                           cv=5, 
                           scoring='roc_auc',
                           verbose=2,
                           pre_dispatch='2*n_jobs',
                           refit=False)  # tried both True and False

grid_search.fit(X_train, y_train)  
print('Best score: {0}'.format(grid_search.best_score_))  
print('Best parameters set:') 

出于好奇,我后来决定做网格搜索快速&通过嵌套for循环的脏方法

for p1 in parameterset1:
    for p2 in parameterset2:
        ...
            pipeline = Pipeline([
                        ('vec', CountVectorizer(
                                   binary=True,
                                   tokenizer=params_dict[i][0][0],
                                   max_df=params_dict[i][0][1],
                                   max_features=params_dict[i][0][2],
                                   stop_words=params_dict[i][0][3],
                                   ngram_range=params_dict[i][0][4],)),
                         ('tfidf', TfidfTransformer(
                                      norm=params_dict[i][0][5],
                                      use_idf=params_dict[i][0][6],
                                      sublinear_tf=params_dict[i][0][7],)),
                         ('clf', MultinomialNB())])

            scores = cross_validation.cross_val_score(
                                        estimator=pipeline,
                                        X=X_train, 
                                        y=y_train, 
                                        cv=5, 
                                        scoring='roc_auc',
                                        n_jobs=1)

           params_dict[i][1] = '%s,%0.4f,%0.4f' % (params_dict[i][1], scores.mean(), scores.std())
           sys.stdout.write(params_dict[i][1] + '\n')

到目前为止一切顺利。网格搜索运行并将结果写入stdout。但是,经过一段时间后,它再次超过128 Gb的内存上限。与scikit中的GridSearch相同的问题。经过一些实验,我终于发现了

gc.collect()
len(gc.get_objects()) # particularly this part!
for循环中的

解决了问题,并且在~10小时的运行时间内内存使用率始终保持在6.5 Gb。

最终,我让它与上述解决方案一起工作,但是,我很想知道您可能导致此问题的原因以及您的提示和问题。建议!

3 个答案:

答案 0 :(得分:1)

0.15.2中的RandomForest不支持稀疏输入。

升级sklearn并再试一次...希望这将允许最终制作的多个副本消耗更少的内存。 (并加快速度)

答案 1 :(得分:0)

我无法看到您的确切代码,但现在我遇到了类似的问题。 值得一试。 当我们将值从可变数组或类似对象的列表复制到另一个变量创建原始副本的副本然后我们使用append或类似的东西来修改新数组或列表时,类似的内存爆炸很容易发生它同时也在后台增加原始对象。

所以这是一个指数过程,所以一段时间后我们就失去了记忆。我能够,也许你可以通过deepcopy()传递一个值的原始对象来避免这种现象。

我有类似的问题,我用类似的过程炸毁了内存,然后我设法保持10%的内存负载。

<强>更新 现在我看到了带有pandas DataFrame的代码片段。很容易出现这样的价值观问题。

答案 2 :(得分:0)

我不熟悉GridSearch先生,但我建议在内存和大型列表出现问题时写一个小型自定义生成器。它可以重复用于您的所有项目,只需使用任何列表。如果在这里实现超出较低解决方案,首先阅读本文,我发现的最好的生成器文章。我把它全部输入并一块一块地写完,你读完之后有任何问题我也可以试试

https://www.jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/

不需要: for p1 in parameterset1:

尝试

 def listerator(this_list):
    i = 0
    while True:
       yield this_list[i]
       i += 1

'yield'一词(声明中的任何地方)使这成为一个生成器,而不是常规函数。这贯穿并且说我等于0,而真我必须做的事,他们希望我得到this_list [0],在这里你去我会在i += 1等你,如果你再次需要我。下次调用它时,它会拾取并执行i += 1,并注意到它仍处于while循环中并给出this_list [1],并再次记录其位置(i += 1 ...它将等待直到再次呼叫)。请注意,当我将列表提供一次并生成一个生成器(此处为x)时,它将耗尽您的列表。

In [141]: x = listerator([1,2,3])

In [142]: next(x)
Out[142]: 1

In [143]: next(x)
Out[143]: 2

In [144]: next(x)
Out[144]: 3

In [148]: next(x)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-148-5e4e57af3a97> in <module>()
----> 1 next(x)

<ipython-input-139-ed3d6d61a17c> in listerator(this_list)
      2     i = 0
      3     while True:
----> 4             yield this_list[i]
      5             i += 1
      6 

IndexError: list index out of range

让我们看看我们是否可以在for:

中使用它
In [221]: for val in listerator([1,2,3,4]):
   .....:     print val
   .....:     
1
2
3
4
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-221-fa4f59138165> in <module>()
----> 1 for val in listerator([1,2,3,4]):
      2     print val
      3 

<ipython-input-220-263fba1d810b> in listerator(this_list, seed)
      2         i = seed or 0
      3         while True:
----> 4             yield this_list[i]
      5             i += 1

IndexError: list index out of range

不。让我们试着解决这个问题:

def listerator(this_list):
   i = 0
   while True:
       try:
           yield this_list[i]
       except IndexError:
           break
       i += 1

In [223]: for val in listerator([1,2,3,4]):
    print val
   .....:     
1
2
3
4

有效。现在它不会盲目地尝试返回列表元素,即使它不存在。根据你的说法,我几乎可以保证你需要能够种下它(从某个地方拿起,或从某个地方新鲜开始):

def listerator(this_list, seed=None):
   i = seed or 0
   while True:
       try:
           yield this_list[i]
       except IndexError:
           break
       i += 1

In [150]: l = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

In [151]: x = listerator(l, 8)

In [152]: next(x)
Out[152]: 9

In [153]: next(x)
Out[153]: 10

In [154]: next(x)
Out[154]: 11

i = seed or 0是寻找种子的东西,但是种子默认为无,所以通常只从逻辑位置开始,0,列表的开头

如何在不使用(几乎)任何记忆的情况下使用这种野兽?

parameterset1 = [1,2,3,4]
parameterset2 = ['a','b','c','d']

In [224]: for p1 in listerator(parameterset1):
    for p2 in listerator(parameterset2):
        print p1, p2
   .....:         
1 a
1 b
1 c
1 d
2 a
2 b
2 c
2 d
3 a
3 b
3 c
3 d
4 a
4 b
4 c
4 d
看起来很熟悉吧?现在,您可以逐个处理万亿个值,选择重要的值写入磁盘,而不会炸毁您的系统。享受!