Trabalhos paralelos não terminam no GridSearchCV do scikit-learn

No script a seguir, estou descobrindo que os trabalhos iniciados pelo GridSearchCV parecem travar.

import json
import pandas as pd
import numpy as np
import unicodedata
import re
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.decomposition import TruncatedSVD
from sklearn.linear_model import SGDClassifier
import sklearn.cross_validation as CV
from sklearn.grid_search import GridSearchCV
from nltk.stem import WordNetLemmatizer

# Seed for randomization. Set to some definite integer for debugging and set to None for production
seed = None


### Text processing functions ###

def normalize(string):#Remove diacritics and whatevs
    return "".join(ch.lower() for ch in unicodedata.normalize('NFD', string) if not unicodedata.combining(ch))

wnl = WordNetLemmatizer()
def tokenize(string):#Ignores special characters and punct
    return [wnl.lemmatize(token) for token in re.compile('\w\w+').findall(string)]

def ngrammer(tokens):#Gets all grams in each ingredient
    max_n = 2
    return [":".join(tokens[idx:idx+n]) for n in np.arange(1,1 + min(max_n,len(tokens))) for idx in range(len(tokens) + 1 - n)]

print("Importing training data...")
with open('/Users/josh/dev/kaggle/whats-cooking/data/train.json','rt') as file:
    recipes_train_json = json.load(file)

# Build the grams for the training data
print('\nBuilding n-grams from input data...')
for recipe in recipes_train_json:
    recipe['grams'] = [term for ingredient in recipe['ingredients'] for term in ngrammer(tokenize(normalize(ingredient)))]

# Build vocabulary from training data grams. 
vocabulary = list({gram for recipe in recipes_train_json for gram in recipe['grams']})

# Stuff everything into a dataframe. 
ids_index = pd.Index([recipe['id'] for recipe in recipes_train_json],name='id')
recipes_train = pd.DataFrame([{'cuisine': recipe['cuisine'], 'ingredients': " ".join(recipe['grams'])} for recipe in recipes_train_json],columns=['cuisine','ingredients'], index=ids_index)


# Extract data for fitting
fit_data = recipes_train['ingredients'].values
fit_target = recipes_train['cuisine'].values

# extracting numerical features from the ingredient text
feature_ext = Pipeline([('vect', CountVectorizer(vocabulary=vocabulary)),
                        ('tfidf', TfidfTransformer(use_idf=True)),
                        ('svd', TruncatedSVD(n_components=1000))
])
lsa_fit_data = feature_ext.fit_transform(fit_data)

# Build SGD Classifier
clf =  SGDClassifier(random_state=seed)
# Hyperparameter grid for GRidSearchCV. 
parameters = {
    'alpha': np.logspace(-6,-2,5),
}

# Init GridSearchCV with k-fold CV object
cv = CV.KFold(lsa_fit_data.shape[0], n_folds=3, shuffle=True, random_state=seed)
gs_clf = GridSearchCV(
    estimator=clf,
    param_grid=parameters,
    n_jobs=-1,
    cv=cv,
    scoring='accuracy',
    verbose=2    
)
# Fit on training data
print("\nPerforming grid search over hyperparameters...")
gs_clf.fit(lsa_fit_data, fit_target)

A saída do console é:

Importing training data...

Building n-grams from input data...

Performing grid search over hyperparameters...
Fitting 3 folds for each of 5 candidates, totalling 15 fits
[CV] alpha=1e-06 .....................................................
[CV] alpha=1e-06 .....................................................
[CV] alpha=1e-06 .....................................................
[CV] alpha=1e-05 .....................................................
[CV] alpha=1e-05 .....................................................
[CV] alpha=1e-05 .....................................................
[CV] alpha=0.0001 ....................................................
[CV] alpha=0.0001 .................................................... 

E então ele simplesmente trava. Se eu definirn_jobs=1 noGridSearchCV, o script é concluído conforme o esperado com a saída:

Importing training data...

Building n-grams from input data...

Performing grid search over hyperparameters...
Fitting 3 folds for each of 5 candidates, totalling 15 fits
[CV] alpha=1e-06 .....................................................
[CV] ............................................ alpha=1e-06 -   6.5s
[Parallel(n_jobs=1)]: Done   1 jobs       | elapsed:    6.6s
[CV] alpha=1e-06 .....................................................
[CV] ............................................ alpha=1e-06 -   6.6s
[CV] alpha=1e-06 .....................................................
[CV] ............................................ alpha=1e-06 -   6.7s
[CV] alpha=1e-05 .....................................................
[CV] ............................................ alpha=1e-05 -   6.7s
[CV] alpha=1e-05 .....................................................
[CV] ............................................ alpha=1e-05 -   6.7s
[CV] alpha=1e-05 .....................................................
[CV] ............................................ alpha=1e-05 -   6.6s
[CV] alpha=0.0001 ....................................................
[CV] ........................................... alpha=0.0001 -   6.6s
[CV] alpha=0.0001 ....................................................
[CV] ........................................... alpha=0.0001 -   6.7s
[CV] alpha=0.0001 ....................................................
[CV] ........................................... alpha=0.0001 -   6.7s
[CV] alpha=0.001 .....................................................
[CV] ............................................ alpha=0.001 -   7.0s
[CV] alpha=0.001 .....................................................
[CV] ............................................ alpha=0.001 -   6.8s
[CV] alpha=0.001 .....................................................
[CV] ............................................ alpha=0.001 -   6.6s
[CV] alpha=0.01 ......................................................
[CV] ............................................. alpha=0.01 -   6.7s
[CV] alpha=0.01 ......................................................
[CV] ............................................. alpha=0.01 -   7.3s
[CV] alpha=0.01 ......................................................
[CV] ............................................. alpha=0.01 -   7.1s
[Parallel(n_jobs=1)]: Done  15 out of  15 | elapsed:  1.7min finished

A execução de thread único é concluída rapidamente, portanto, tenho certeza de que estou dando tempo suficiente ao caso paralelo da tarefa para fazer o cálculo.

Especificações do ambiente: MacBook Pro (15 polegadas, meados de 2010), 2,4 GHz Intel Core i5, 8 GB 1067 MHz DDR3, OSX 10.10.5, python 3.4.3, ipython 3.2.0, numpy v1.9.3, scipy 0.16.0 , scikit-learn v0.16.1 (python e pacotes todos da distro do anaconda)

Alguns comentários adicionais:

eu uson_jobs=-1 comGridSearchCV o tempo todo nesta máquina sem problemas, então minha plataforma suporta a funcionalidade. Geralmente, há 4 trabalhos por vez, pois tenho 4 núcleos nesta máquina (2 físicos, mas 4 "núcleos virtuais" devido ao hyperthreading do Mac). Mas, a menos que eu entenda mal a saída do console, nesse caso, ele terá 8 trabalhos sem retorno. Observando o uso da CPU no Activity Monitor em tempo real, quatro trabalhos são iniciados, funcionam um pouco e terminam (ou morrem?), Seguidos por mais quatro que iniciam, funcionam um pouco e ficam completamente ociosos, mas ficam por perto.

Em nenhum momento eu vejo pressão significativa na memória. O processo principal atinge cerca de 1 GB de memória real, o filho processa cerca de 600 MB. Quando eles param, a memória real é insignificante.

O script funciona bem com vários trabalhos se um remover oTruncatedSVD passo do pipeline de extração de recursos. Observe, porém, que esse pipeline atua antes da pesquisa na grade e não faz parte doGridSearchCV empregos).

Este script é para a competição kaggleO que está cozinhando? então, se você quiser executá-lo com os mesmos dados que estou usando, poderá obtê-lo a partir daí. Os dados vêm como uma matriz de objetos JSON. Cada objeto representa uma receita e contém uma lista de trechos de texto que são os ingredientes. Como cada amostra é uma coleção de documentos em vez de um único documento, acabei tendo que escrever um pouco da minha própria lógica de n-gramática e tokenização, pois não conseguia descobrir como obter os transformadores internos do scikit-learn. faça exatamente o que eu quero. Duvido que isso importe, mas apenas um FYI.

Normalmente, executo scripts na CLI do iPython com% run, mas obtenho o mesmo comportamento do terminal OSX bash com python (3.4.3) diretamente.

questionAnswers(3)

yourAnswerToTheQuestion