Tutorial de Scikit-Learn: ejemplos de aprendizaje automático en Python
¿Qué es Scikit-learn?
Scikit-learn es una biblioteca de Python de código abierto para el aprendizaje automático. La biblioteca soporta algoritmos de última generación como KNN, XGBoost, bosque aleatorio, SVM entre otros. Está construido en la parte superior de Numpy. Scikit-learn es ampliamente utilizado en la competencia kaggle, así como en empresas tecnológicas prominentes. Scikit-Learn ayuda en el preprocesamiento, la reducción de dimensionalidad (selección de parámetros), la clasificación, la regresión, la agrupación y la selección de modelos.
Scikit-learn tiene la mejor documentación de todas las bibliotecas de código abierto. Le proporciona un gráfico interactivo en http://scikit-learn.org/stable/tutorial/machine_learning_map/index.html.
Scikit-learn no es muy difícil de usar y proporciona excelentes resultados. Sin embargo, scikit learn no admite cálculos paralelos. Es posible ejecutar un algoritmo de aprendizaje profundo con él, pero no es una solución óptima, especialmente si sabe cómo usar TensorFlow.
En este tutorial, usted aprenderá.
- ¿Qué es Scikit-learn?
- Descargar e instalar scikit-learn
- Aprendizaje automático con scikit-learn
- Importar los datos
- Crear el tren o conjunto de pruebas
- Construir la tubería
- Uso de nuestra tubería en una búsqueda de cuadrícula
- Modelo XGBoost con scikit-learn
- Crear DNN con MLPClassifier en scikit-learn
- LIME: Confíe en su modelo
Descargar e instalar scikit-learn
Opción 1: AWS
scikit-learn se puede utilizar sobre AWS. Consulte La imagen del docker tiene scikit-learn preinstalado.
Para usar la versión de desarrollador, use el comando en Jupyter
1 2 |
import sys !{sys.executable} -m pip install git+git://github.com/scikit-learn/scikit-learn.git |
Opción 2: Mac o Windows con Anaconda
Para obtener más información sobre la instalación de Anaconda consulte https://www.guru99.com/download-install-tensorflow.html
Recientemente, los desarrolladores de scikit han lanzado una versión de desarrollo que aborda el problema común que enfrenta la versión actual. Nos pareció más conveniente usar la versión del desarrollador en lugar de la versión actual.
Si instaló scikit-learn con el entorno conda, siga el paso para actualizar a la versión 0.20
Paso 1) Activar el entorno de tensorflow
1 |
source activate hello-tf |
Paso 2) Eliminar scikit lean usando el comando conda
1 |
conda remove scikit-learn |
Paso 3) Instale la versión del desarrollador scikit learn junto con las bibliotecas necesarias.
1 2 3 4 |
conda install -c anaconda git pip install Cython pip install h5py pip install git+git://github.com/scikit-learn/scikit-learn.git |
NOTA: Windows utilizado necesitará instalar Microsoft Visual C++ 14. Puedes obtenerlo desde aquí .
Aprendizaje automático con scikit-learn
Este tutorial se divide en dos partes:
1. Aprendizaje automático con scikit-learn
2. Cómo confiar en su modelo con LIME
La primera parte detalla cómo construir una tubería, crear un modelo y ajustar los hiperparámetros, mientras que la segunda parte proporciona tecnología avanzada en términos de selección del modelo.
Paso 1) Importar los datos
Durante este tutorial, utilizará el conjunto de datos para adultos. Para obtener información sobre este conjunto de datos, consulte Si está interesado en obtener más información sobre las estadísticas descriptivas, utilice las herramientas de buceo y visión general. Consulte este tutorial para obtener más información sobre buceo y visión general
Importar el dataset con Pandas. Tenga en cuenta que necesita convertir el tipo de las variables continuas en formato flotante.
Este conjunto de datos incluye ocho variables categóricas:
Las variables categóricas se enumeran en CATE_FACTIES
- clase de trabajo
- educación
- conyugal
- ocupación
- relación
- raza
- sexo
- native_country
además, seis variables continuas:
Las variables continuas se enumeran en CONTI_FACTIES
- edad
- Fnlwgt
- núm_educación_de_educación
- capital_gain
- capital_loss
- horas_semana
Tenga en cuenta que cumplimentamos la lista a mano para que tenga una mejor idea de qué columnas estamos usando. Una forma más rápida de construir una lista de categóricos o continuos es usar:
1 2 3 4 5 6 7 |
## List Categorical CATE_FEATURES = df_train.iloc[:,:-1].select_dtypes('object').columns print(CATE_FEATURES) ## List continuous CONTI_FEATURES = df_train._get_numeric_data() print(CONTI_FEATURES) |
Aquí está el código para importar los datos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# Import dataset import pandas as pd ## Define path data COLUMNS = ['age','workclass', 'fnlwgt', 'education', 'education_num', 'marital', 'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss', 'hours_week', 'native_country', 'label'] ### Define continuous list CONTI_FEATURES = ['age', 'fnlwgt','capital_gain', 'education_num', 'capital_loss', 'hours_week'] ### Define categorical list CATE_FEATURES = ['workclass', 'education', 'marital', 'occupation', 'relationship', 'race', 'sex', 'native_country'] ## Prepare the data features = ['age','workclass', 'fnlwgt', 'education', 'education_num', 'marital', 'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss', 'hours_week', 'native_country'] PATH = "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data" df_train = pd.read_csv(PATH, skipinitialspace=True, names = COLUMNS, index_col=False) df_train[CONTI_FEATURES] =df_train[CONTI_FEATURES].astype('float64') df_train.describe() |
age | fnlwgt | education_num | capital_gain | capital_loss | hours_week | |
count | 32561.000000 | 3.256100e+04 | 32561.000000 | 32561.000000 | 32561.000000 | 32561.000000 |
mean | 38.581647 | 1.897784e+05 | 10.080679 | 1077.648844 | 87.303830 | 40.437456 |
std | 13.640433 | 1.055500e+05 | 2.572720 | 7385.292085 | 402.960219 | 12.347429 |
min | 17.000000 | 1.228500e+04 | 1.000000 | 0.000000 | 0.000000 | 1.000000 |
25% | 28.000000 | 1.178270e+05 | 9.000000 | 0.000000 | 0.000000 | 40.000000 |
50% | 37.000000 | 1.783560e+05 | 10.000000 | 0.000000 | 0.000000 | 40.000000 |
75% | 48.000000 | 2.370510e+05 | 12.000000 | 0.000000 | 0.000000 | 45.000000 |
max | 90.000000 | 1.484705e+06 | 16.000000 | 99999.000000 | 4356.000000 | 99.000000 |
Puede comprobar el recuento de valores únicos de las entidades native_country. Se puede ver que sólo una casa viene de Holanda-Países Bajos. Esta casa no nos traerá ninguna información, pero lo hará a través de un error durante el entrenamiento.
1 |
df_train.native_country.value_counts() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
United-States 29170 Mexico 643 ? 583 Philippines 198 Germany 137 Canada 121 Puerto-Rico 114 El-Salvador 106 India 100 Cuba 95 England 90 Jamaica 81 South 80 China 75 Italy 73 Dominican-Republic 70 Vietnam 67 Guatemala 64 Japan 62 Poland 60 Columbia 59 Taiwan 51 Haiti 44 Iran 43 Portugal 37 Nicaragua 34 Peru 31 France 29 Greece 29 Ecuador 28 Ireland 24 Hong 20 Cambodia 19 Trinadad&Tobago 19 Thailand 18 Laos 18 Yugoslavia 16 Outlying-US(Guam-USVI-etc) 14 Honduras 13 Hungary 13 Scotland 12 Holand-Netherlands 1 Name: native_country, dtype: int64 |
Puede excluir esta fila no informativa del conjunto de datos
1 2 |
## Drop Netherland, because only one row df_train = df_train[df_train.native_country != "Holand-Netherlands"] |
A continuación, almacena la posición de las entidades continuas en una lista. Lo necesitará en el siguiente paso para construir la tubería.
El siguiente código recorrerá todos los nombres de columnas en CONTI_FACTIES y obtendrá su ubicación (es decir, su número) y luego lo anexará a una lista llamada conti_features
1 2 3 4 5 6 |
## Get the column index of the categorical features conti_features = [] for i in CONTI_FEATURES: position = df_train.columns.get_loc(i) conti_features.append(position) print(conti_features) |
1 |
[0, 2, 10, 4, 11, 12] |
El siguiente código hace el mismo trabajo que el anterior, pero para la variable categórica. El siguiente código repite lo que has hecho anteriormente, excepto con las características categóricas.
1 2 3 4 5 6 |
## Get the column index of the categorical features categorical_features = [] for i in CATE_FEATURES: position = df_train.columns.get_loc(i) categorical_features.append(position) print(categorical_features) |
1 |
[1, 3, 5, 6, 7, 8, 9, 13] |
Puede echar un vistazo al conjunto de datos. Tenga en cuenta que, cada característica categórica es una cadena. No se puede alimentar un modelo con un valor de cadena. Debe transformar el conjunto de datos utilizando una variable ficticia.
1 |
df_train.head(5) |
De hecho, debe crear una columna para cada grupo en la entidad. En primer lugar, puede ejecutar el siguiente código para calcular la cantidad total de columnas necesarias.
1 2 |
print(df_train[CATE_FEATURES].nunique(), 'There are',sum(df_train[CATE_FEATURES].nunique()), 'groups in the whole dataset') |
1 2 3 4 5 6 7 8 9 |
workclass 9 education 16 marital 7 occupation 15 relationship 6 race 5 sex 2 native_country 41 dtype: int64 There are 101 groups in the whole dataset |
Todo el conjunto de datos contiene 101 grupos como se muestra anteriormente. Por ejemplo, las características de la clase de trabajo tienen nueve grupos. Puede visualizar el nombre de los grupos con los siguientes códigos
unique () devuelve los valores únicos de las entidades categóricas.
1 2 |
for i in CATE_FEATURES: print(df_train[i].unique()) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
['State-gov' 'Self-emp-not-inc' 'Private' 'Federal-gov' 'Local-gov' '?' 'Self-emp-inc' 'Without-pay' 'Never-worked'] ['Bachelors' 'HS-grad' '11th' 'Masters' '9th' 'Some-college' 'Assoc-acdm' 'Assoc-voc' '7th-8th' 'Doctorate' 'Prof-school' '5th-6th' '10th' '1st-4th' 'Preschool' '12th'] ['Never-married' 'Married-civ-spouse' 'Divorced' 'Married-spouse-absent' 'Separated' 'Married-AF-spouse' 'Widowed'] ['Adm-clerical' 'Exec-managerial' 'Handlers-cleaners' 'Prof-specialty' 'Other-service' 'Sales' 'Craft-repair' 'Transport-moving' 'Farming-fishing' 'Machine-op-inspct' 'Tech-support' '?' 'Protective-serv' 'Armed-Forces' 'Priv-house-serv'] ['Not-in-family' 'Husband' 'Wife' 'Own-child' 'Unmarried' 'Other-relative'] ['White' 'Black' 'Asian-Pac-Islander' 'Amer-Indian-Eskimo' 'Other'] ['Male' 'Female'] ['United-States' 'Cuba' 'Jamaica' 'India' '?' 'Mexico' 'South' 'Puerto-Rico' 'Honduras' 'England' 'Canada' 'Germany' 'Iran' 'Philippines' 'Italy' 'Poland' 'Columbia' 'Cambodia' 'Thailand' 'Ecuador' 'Laos' 'Taiwan' 'Haiti' 'Portugal' 'Dominican-Republic' 'El-Salvador' 'France' 'Guatemala' 'China' 'Japan' 'Yugoslavia' 'Peru' 'Outlying-US(Guam-USVI-etc)' 'Scotland' 'Trinadad&Tobago' 'Greece' 'Nicaragua' 'Vietnam' 'Hong' 'Ireland' 'Hungary'] |
Por lo tanto, el conjunto de datos de capacitación contendrá 101 + 7 columnas. Las últimas siete columnas son las entidades continuas.
Scikit-learn puede encargarse de la conversión. Se realiza en dos pasos:
- Primero, debe convertir la cadena a ID. Por ejemplo, State-Gov tendrá el ID 1, Self-emp-no-Inc ID 2 y así sucesivamente. La función LabelEncoder hace esto por usted
- Transpone cada ID a una nueva columna. Como se mencionó anteriormente, el conjunto de datos tiene 101 ID de grupo. Por lo tanto, habrá 101 columnas que capturan todos los grupos de entidades categóricas. Scikit-learn tiene una función llamada OneHotEncoder que realiza esta operación
Paso 2) Crear el tren o conjunto de pruebas
Ahora que el conjunto de datos está listo, podemos dividirlo 80/20. 80 por ciento para el conjunto de entrenamiento y 20 por ciento para el conjunto de pruebas.
Puede usar train_test_split. El primer argumento es el marco de datos es las entidades y el segundo argumento es el marco de datos de etiqueta. Puede especificar el tamaño del conjunto de pruebas con test_size.
1 2 3 4 5 6 7 |
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(df_train[features], df_train.label, test_size = 0.2, random_state=0) X_train.head(5) print(X_train.shape, X_test.shape) |
1 |
(26048, 14) (6512, 14) |
Paso 3) Construir la tubería
La canalización facilita la alimentación del modelo con datos consistentes. La idea detrás es poner los datos sin procesar en una ‘canalización’ para realizar operaciones. Por ejemplo, con el dataset actual, necesita estandarizar las variables continuas y convertir los datos categóricos. Tenga en cuenta que puede realizar cualquier operación dentro de la tubería. Por ejemplo, si tiene ‘NA’ en el dataset, puede reemplazarlos por la media o mediana. También puede crear nuevas variables.
Tiene la opción: codificar los dos procesos o crear una canalización. La primera opción puede provocar fugas de datos y crear inconsistencias a lo largo del tiempo. Una mejor opción es usar la tubería.
1 2 3 4 |
from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder from sklearn.compose import ColumnTransformer, make_column_transformer from sklearn.pipeline import make_pipeline from sklearn.linear_model import LogisticRegression |
El gasoducto realizará dos operaciones antes de alimentar el clasificador logístico:
1. Estandarizar la variable: StandardScaler ()
`
2. Convertir las características categóricas: OneHotEncoder (Sparse=false)
Puede realizar los dos pasos utilizando make_column_transformer. Esta función no está disponible en la versión actual de scikit-learn (0.19). Con la versión actual no es posible realizar el codificador de etiquetas y un codificador en caliente en la tubería. Es una de las razones por las que decidimos usar la versión para desarrolladores.
make_column_transformer es fácil de usar. Debe definir qué columnas aplicar la transformación y qué transformación operar. Por ejemplo, para estandarizar la función continua, puede hacer lo siguiente:
- conti_features, standardScaler () dentro make_column_transformer.
- conti_features: lista con la variable continua
- StandardScaler: estandarizar la variable
El objeto OneHotEncoder dentro de make_column_transformer codifica automáticamente la etiqueta.
1 2 3 4 5 |
preprocess = make_column_transformer( (conti_features, StandardScaler()), ### Need to be numeric not string to specify columns name (categorical_features, OneHotEncoder(sparse=False)) ) |
Puede probar si la tubería funciona con fit_transform. El conjunto de datos debe tener la siguiente forma: 26048, 107
1 |
preprocess.fit_transform(X_train).shape |
1 |
(26048, 107) |
El transformador de datos está listo para usar. Puede crear la canalización con make_pipeline. Una vez que los datos se transforman, puede alimentar la regresión logística.
1 2 3 |
model = make_pipeline( preprocess, LogisticRegression()) |
El entrenamiento de un modelo con scikit-learn es trivial. Debe usar el ajuste del objeto precedido por la canalización, es decir, modelo. Puede imprimir la precisión con el objeto de partitura desde la biblioteca scikit-learn
1 2 |
model.fit(X_train, y_train) print("logistic regression score: %f" % model.score(X_test, y_test)) |
1 |
logistic regression score: 0.850891 |
Finalmente, puede predecir las clases con predict_proba. Devuelve la probabilidad de cada clase. Tenga en cuenta que se suma a uno.
1 |
model.predict_proba(X_test) |
1 2 3 4 5 6 7 |
array([[0.83576663, 0.16423337], [0.94582765, 0.05417235], [0.64760587, 0.35239413], ..., [0.99639252, 0.00360748], [0.02072181, 0.97927819], [0.56781353, 0.43218647]]) |
Paso 4) Uso de nuestra tubería en una búsqueda de cuadrícula
Ajustar el hiperparámetro (variables que determinan la estructura de la red como unidades ocultas) puede ser tedioso y agotador. Una forma de evaluar el modelo podría ser cambiar el tamaño del conjunto de entrenamiento y evaluar el rendimiento. Puede repetir este método diez veces para ver las métricas de puntuación. Sin embargo, es demasiado trabajo.
En su lugar, scikit-learn proporciona una función para llevar a cabo el ajuste de parámetros y la validación cruzada.
Validación cruzada
Validación cruzada significa durante el entrenamiento, el conjunto de entrenamiento es deslizamiento n número de veces en pliegues y luego evalúa el modelo n tiempo. Por ejemplo, si cv se establece en 10, el conjunto de entrenamiento se capacita y evalúa diez veces. En cada ronda, el clasificador elige al azar nueve veces para entrenar el modelo, y el décimo pliegue está destinado a la evaluación.
Búsqueda de cuadrícula Cada clasificador tiene hiperparámetros para ajustar. Puede probar diferentes valores o puede establecer una cuadrícula de parámetros. Si vas a la web oficial scikit-learn, puedes ver que el clasificador logístico tiene diferentes parámetros para afinar. Para que el entrenamiento sea más rápido, elige ajustar el parámetro C. Controla el parámetro de regularización. Debería ser positivo. Un valor pequeño le da más peso al regularizador.
Puede utilizar el objeto GridSearchCV. Necesita crear un diccionario que contenga los hiperparámetros para sintonizar.
Enumere los hiperparámetros seguidos de los valores que desea probar. Por ejemplo, para ajustar el parámetro C, utilice:
- ‘Logisticregresion__c’: [0.1, 1.0, 1.0]: El parámetro está precedido por el nombre, en minúsculas, del clasificador y dos guiones bajos.
El modelo intentará cuatro valores diferentes: 0.001, 0.01, 0.1 y 1.
Entrena el modelo usando 10 pliegues: cv=10
1 2 3 4 5 |
from sklearn.model_selection import GridSearchCV # Construct the parameter grid param_grid = { 'logisticregression__C': [0.001, 0.01,0.1, 1.0], } |
Puede entrenar el modelo usando GridSearchCV con los parámetros gri y cv.
1 2 3 4 5 6 |
# Train the model grid_clf = GridSearchCV(model, param_grid, cv=10, iid=False) grid_clf.fit(X_train, y_train) |
SALIDA
1 2 3 4 5 6 7 8 9 10 11 12 13 |
GridSearchCV(cv=10, error_score='raise-deprecating', estimator=Pipeline(memory=None, steps=[('columntransformer', ColumnTransformer(n_jobs=1, remainder='drop', transformer_weights=None, transformers=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True), [0, 2, 10, 4, 11, 12]), ('onehotencoder', OneHotEncoder (categorical_features=None, categories=None,...ty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False))]), fit_params=None, iid=False, n_jobs=1, param_grid={'logisticregression__C': [0.001, 0.01, 0.1, 1.0]}, pre_dispatch='2*n_jobs', refit=True, return_train_score='warn', scoring=None, verbose=0) |
Para acceder a los mejores parámetros, utilice best_params_
1 |
grid_clf.best_params_ |
SALIDA
1 |
{'logisticregression__C': 1.0} |
Después de entrenar el modelo con cuatro valores de regularización diferentes, el parámetro óptimo es
1 |
print("best logistic regression from grid search: %f" % grid_clf.best_estimator_.score(X_test, y_test)) |
mejor regresión logística de búsqueda en cuadrícula: 0.850891
Para acceder a las probabilidades predecidas:
1 |
grid_clf.best_estimator_.predict_proba(X_test) |
1 2 3 4 5 6 7 |
array([[0.83576677, 0.16423323], [0.9458291 , 0.0541709 ], [0.64760416, 0.35239584], ..., [0.99639224, 0.00360776], [0.02072033, 0.97927967], [0.56782222, 0.43217778]]) |
Modelo XGBoost con scikit-learn
Tratemos de entrenar a uno de los mejores clasificadores del mercado. XGBoost es una mejora sobre el bosque aleatorio. El fondo teórico del clasificador fuera del alcance de este tutorial. Tenga en cuenta que, XGBoost ha ganado muchas competiciones kaggle. Con un tamaño de conjunto de datos promedio, puede funcionar tan bien como un algoritmo de aprendizaje profundo o incluso mejor.
El clasificador es un reto de entrenar porque tiene un gran número de parámetros para afinar. Por supuesto, puede usar GridSearchCV para elegir el parámetro por usted.
En su lugar, veamos cómo usar una mejor manera de encontrar los parámetros óptimos. GridSearchCV puede ser tedioso y muy largo de entrenar si pasa muchos valores. El espacio de búsqueda crece junto con el número de parámetros. Una solución preferible es usar RandomizedSearchCV. Este método consiste en elegir los valores de cada hiperparámetro después de cada iteración aleatoriamente. Por ejemplo, si el clasificador está entrenado en 1000 iteraciones, entonces se evalúan 1000 combinaciones. Funciona más o menos como. GridSearchCV
Necesita importar xgboost. Si la biblioteca no está instalada, utilice pip3 install xgboost o
1 2 |
use import sys !{sys.executable} -m pip install xgboost |
En el entorno de Jupyter
A continuación,
1 2 3 |
import xgboost from sklearn.model_selection import RandomizedSearchCV from sklearn.model_selection import StratifiedKFold |
El siguiente paso incluye especificar los parámetros que desea ajustar. Puede consultar la documentación oficial para ver todos los parámetros a ajustar. Por el bien del tutorial, solo elige dos hiperparámetros con dos valores cada uno. XGBoost toma mucho tiempo para entrenar, cuantos más hiperparámetros haya en la cuadrícula, más tiempo necesitará esperar.
1 2 3 4 |
params = { 'xgbclassifier__gamma': [0.5, 1], 'xgbclassifier__max_depth': [3, 4] } |
Se construye una nueva canalización con el clasificador XGBoost. Usted elige definir 600 estimadores. Tenga en cuenta que n_estimators son un parámetro que puede sintonizar. Un valor alto puede conducir a un exceso de ajuste. Puede probar por sí mismo diferentes valores, pero tenga en cuenta que puede tomar horas. Utilice el valor predeterminado para los otros parámetros
1 2 3 4 5 6 7 8 |
model_xgb = make_pipeline( preprocess, xgboost.XGBClassifier( n_estimators=600, objective='binary:logistic', silent=True, nthread=1) ) |
uede mejorar la validación cruzada con el validador cruzado Stratified K-Folds. Usted construye sólo tres pliegues aquí para acelerar el cálculo pero reducir la calidad. Aumente este valor a 5 o 10 en casa para mejorar los resultados.
Usted elige entrenar el modelo en cuatro iteraciones.
1 2 3 4 5 6 7 8 9 10 11 12 |
skf = StratifiedKFold(n_splits=3, shuffle = True, random_state = 1001) random_search = RandomizedSearchCV(model_xgb, param_distributions=params, n_iter=4, scoring='accuracy', n_jobs=4, cv=skf.split(X_train, y_train), verbose=3, random_state=1001) |
La búsqueda aleatoria está lista para usar, puede entrenar el modelo
1 2 |
#grid_xgb = GridSearchCV(model_xgb, params, cv=10, iid=False) random_search.fit(X_train, y_train) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Fitting 3 folds for each of 4 candidates, totalling 12 fits [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5 ............ [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5 ............ [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5 ............ [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5 ............ [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5, score=0.8759645283888057, total= 1.0min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5 ............ [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5, score=0.8729701715996775, total= 1.0min [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5, score=0.8706519235199263, total= 1.0min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5 ............ [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1 .............. [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5, score=0.8735460094437406, total= 1.3min [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1 .............. [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1, score=0.8722791661868018, total= 57.7s [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1 .............. [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1, score=0.8753886905447426, total= 1.0min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1 .............. [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5, score=0.8697304768486523, total= 1.3min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1 .............. [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5, score=0.8740066797189912, total= 1.4min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1 .............. [CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1, score=0.8707671043538355, total= 1.0min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1, score=0.8729701715996775, total= 1.2min [Parallel(n_jobs=4)]: Done 10 out of 12 | elapsed: 3.6min remaining: 43.5s [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1, score=0.8736611770125533, total= 1.2min [CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1, score=0.8692697535130154, total= 1.2min |
1 2 3 4 5 6 |
[Parallel(n_jobs=4)]: Done 12 out of 12 | elapsed: 3.6min finished /Users/Thomas/anaconda3/envs/hello-tf/lib/python3.6/site-packages/sklearn/ model_selection/_search.py:737: DeprecationWarning: The default of the `iid` parameter will change from True to False in version 0.22 and will be removed in 0.24. This will change numeric results when test-set sizes are unequal. DeprecationWarning) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
RandomizedSearchCV(cv=<generator object _BaseKFold.split at 0x1101eb830>, error_score='raise-deprecating', estimator=Pipeline(memory=None, steps=[('columntransformer', ColumnTransformer(n_jobs=1, remainder='drop', transformer_weights=None, transformers=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True), [0, 2, 10, 4, 11, 12]), ('onehotencoder', OneHotEncoder(categorical_features=None, categories=None,... reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None, silent=True, subsample=1))]), fit_params=None, iid='warn', n_iter=4, n_jobs=4, param_distributions={'xgbclassifier__gamma': [0.5, 1], 'xgbclassifier__max_depth': [3, 4]}, pre_dispatch='2*n_jobs', random_state=1001, refit=True, return_train_score='warn', scoring='accuracy', verbose=3) |
Como puede ver, XGBoost tiene una puntuación mejor que la regresión logisitc anterior.
1 2 3 |
print("Best parameter", random_search.best_params_) print("best logistic regression from grid search: %f" % random_search.best_estimator_.score (X_test, y_test)) |
1 2 |
Best parameter {'xgbclassifier__max_depth': 3, 'xgbclassifier__gamma': 0.5} best logistic regression from grid search: 0.873157 |
1 |
random_search.best_estimator_.predict(X_test) |
1 |
array(['<=50K', '<=50K', '<=50K', ..., '<=50K', '>50K', '<=50K'], dtype=object) |
Crear DNN con MLPClassifier en scikit-learn
Finalmente, puede entrenar un algoritmo de aprendizaje profundo con scikit-learn. El método es el mismo que el otro clasificador. El clasificador está disponible en MLPClassifier.
1 |
from sklearn.neural_network import MLPClassifier |
Defina el siguiente algoritmo de aprendizaje profundo:
- Adam solucionador
- Función de activación Relu
- Alfa = 0.0001
- tamaño de lote de 150
- Dos capas ocultas con 100 y 50 neuronas respectivamente
1 2 3 4 5 6 7 8 |
model_dnn = make_pipeline( preprocess, MLPClassifier(solver='adam', alpha=0.0001, activation='relu', batch_size=150, hidden_layer_sizes=(200, 100), random_state=1)) |
Puede cambiar el número de capas para mejorar el modelo
1 2 |
model_dnn.fit(X_train, y_train) print("DNN regression score: %f" % model_dnn.score(X_test, y_test)) |
Puntuación de regresión DNN: 0.821253
LIME: Confíe en su modelo
Ahora que tienes un buen modelo, necesitas una herramienta para confiar en él. Se sabe que el algoritmo de aprendizaje automático, especialmente el bosque aleatorio y la red neuronal, es un algoritmo blax-box. Di lo contrario, funciona pero nadie sabe por qué.
Tres investigadores han llegado con una gran herramienta para ver cómo el ordenador hace una predicción. El periódico se llama ¿Por qué debería confiar en ti?
Desarrollaron un algoritmo llamado Local Interpretable Model-Agnóstico Explicaciones (LIME).
Tomemos un ejemplo:
a veces no sabes si puedes confiar en una predicción de aprendizaje automático:
Un médico, por ejemplo, no puede confiar en un diagnóstico sólo porque una computadora lo dijo. También necesita saber si puede confiar en el modelo antes de ponerlo en producción.
Imagine que podemos entender por qué cualquier clasificador está haciendo una predicción incluso modelos increíblemente complicados como redes neuronales, bosques aleatorios o svms con cualquier núcleo
será más accesible para confiar en una predicción si podemos entender las razones detrás de ella. A partir del ejemplo con el médico, si el modelo le dijo qué síntomas son esenciales, confiaría en él, también es más fácil averiguar si no debe confiar en el modelo.
Lime puede decirle qué características afectan las decisiones del clasificador
Preparación de datos
Son un par de cosas que necesita cambiar para ejecutar LIME con python. En primer lugar, debe instalar cal en la terminal. Puede usar pip install lime
Lime hace uso del objeto LimeMetabularExplainer para aproximar el modelo localmente. Este objeto requiere:
- un dataset en formato numpy
- El nombre de las entidades: feature_names
- El nombre de las clases: class_names
- El índice de la columna de las entidades categóricas: categorical_features
- El nombre del grupo para cada elemento categórico: categorical_names
Crear conjunto de trenes numpy
Puede copiar y convertir df_train de pandas a numpy muy fácilmente
1 2 3 4 |
df_train.head(5) # Create numpy data df_lime = df_train df_lime.head(3) |
Obtener el nombre de la clase La etiqueta es accesible con el objeto unique (). Deberías ver:
- ‘<=50K’
- ‘>50K’
1 2 3 |
# Get the class name class_names = df_lime.label.unique() class_names |
1 |
array(['<=50K', '>50K'], dtype=object) |
índice de la columna de las entidades categóricas
Puede usar el método que se inclina antes para obtener el nombre del grupo. Codificar la etiqueta con LabelEncoder. Repetirá la operación en todas las entidades categóricas.
1 2 3 4 5 6 7 8 9 |
## import sklearn.preprocessing as preprocessing categorical_names = {} for feature in CATE_FEATURES: le = preprocessing.LabelEncoder() le.fit(df_lime[feature]) df_lime[feature] = le.transform(df_lime[feature]) categorical_names[feature] = le.classes_ print(categorical_names) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
{'workclass': array(['?', 'Federal-gov', 'Local-gov', 'Never-worked', 'Private', 'Self-emp-inc', 'Self-emp-not-inc', 'State-gov', 'Without-pay'], dtype=object), 'education': array(['10th', '11th', '12th', '1st-4th', '5th-6th', '7th-8th', '9th', 'Assoc-acdm', 'Assoc-voc', 'Bachelors', 'Doctorate', 'HS-grad', 'Masters', 'Preschool', 'Prof-school', 'Some-college'], dtype=object), 'marital': array(['Divorced', 'Married-AF-spouse', 'Married-civ-spouse', 'Married-spouse-absent', 'Never-married', 'Separated', 'Widowed'], dtype=object), 'occupation': array(['?', 'Adm-clerical', 'Armed-Forces', 'Craft-repair', 'Exec-managerial', 'Farming-fishing', 'Handlers-cleaners', 'Machine-op-inspct', 'Other-service', 'Priv-house-serv', 'Prof-specialty', 'Protective-serv', 'Sales', 'Tech-support', 'Transport-moving'], dtype=object), 'relationship': array(['Husband', 'Not-in-family', 'Other-relative', 'Own-child', 'Unmarried', 'Wife'], dtype=object), 'race': array (['Amer-Indian-Eskimo', 'Asian-Pac-Islander', 'Black', 'Other', 'White'], dtype=object), 'sex': array(['Female', 'Male'], dtype=object), 'native_country': array(['?', 'Cambodia', 'Canada', 'China', 'Columbia', 'Cuba', 'Dominican-Republic', 'Ecuador', 'El-Salvador', 'England', 'France', 'Germany', 'Greece', 'Guatemala', 'Haiti', 'Honduras', 'Hong', 'Hungary', 'India', 'Iran', 'Ireland', 'Italy', 'Jamaica', 'Japan', 'Laos', 'Mexico', 'Nicaragua', 'Outlying-US(Guam-USVI-etc)', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Puerto-Rico', 'Scotland', 'South', 'Taiwan', 'Thailand', 'Trinadad&Tobago', 'United-States', 'Vietnam', 'Yugoslavia'], dtype=object)} df_lime.dtypes |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
age float64 workclass int64 fnlwgt float64 education int64 education_num float64 marital int64 occupation int64 relationship int64 race int64 sex int64 capital_gain float64 capital_loss float64 hours_week float64 native_country int64 label object dtype: object |
Ahora que el dataset está listo, puede construir el dataset diferente. En realidad, transforma los datos fuera de la canalización para evitar errores con LIME. El conjunto de entrenamiento en el liMetabularExplainer debe ser una matriz numpy sin cadena. Con el método anterior, ya tiene un conjunto de datos de entrenamiento convertido.
1 2 3 4 5 6 |
from sklearn.model_selection import train_test_split X_train_lime, X_test_lime, y_train_lime, y_test_lime = train_test_split(df_lime[features], df_lime.label, test_size = 0.2, random_state=0) X_train_lime.head(5) |
Puede hacer la tubería con los parámetros óptimos de XGBoost
1 2 3 4 5 6 7 8 9 10 |
model_xgb = make_pipeline( preprocess, xgboost.XGBClassifier(max_depth = 3, gamma = 0.5, n_estimators=600, objective='binary:logistic', silent=True, nthread=1)) model_xgb.fit(X_train_lime, y_train_lime) |
1 2 3 4 5 6 7 8 |
/Users/Thomas/anaconda3/envs/hello-tf/lib/python3.6/site-packages/sklearn/ preprocessing/_encoders.py:351: FutureWarning: The handling of integer data will change in version 0.22. Currently, the categories are determined based on the range [0, max(values)], while in the future they will be determined based on the unique values. If you want the future behavior and silence this warning, you can specify "categories='auto'."In case you used a LabelEncoder before this OneHotEncoder to convert the categories to integers, then you can now use the OneHotEncoder directly. warnings.warn(msg, FutureWarning) |
1 2 3 4 5 6 7 8 |
Pipeline(memory=None, steps=[('columntransformer', ColumnTransformer(n_jobs=1, remainder='drop', transformer_weights=None, transformers=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True), [0, 2, 10, 4, 11, 12]), ('onehotencoder', OneHotEncoder (categorical_features=None, categories=None,... reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None, silent=True, subsample=1))]) |
Recibéis una advertencia. La advertencia explica que no es necesario crear un codificador de etiquetas antes de la canalización. Si no desea utilizar LIME, puede utilizar el método de la primera parte del tutorial. De lo contrario, puede seguir con este método, primero crear un dataset codificado, establecer obtener el codificador caliente dentro de la canalización.
1 |
print("best logistic regression from grid search: %f" % model_xgb.score(X_test_lime, y_test_lime)) |
1 |
best logistic regression from grid search: 0.873157 |
1 |
model_xgb.predict_proba(X_test_lime) |
1 2 3 4 5 6 7 |
array([[7.9646105e-01, 2.0353897e-01], [9.5173013e-01, 4.8269872e-02], [7.9344827e-01, 2.0655173e-01], ..., [9.9031430e-01, 9.6856682e-03], [6.4581633e-04, 9.9935418e-01], [9.7104281e-01, 2.8957171e-02]], dtype=float32) |
Antes de usar LIME en acción, vamos a crear una matriz numpy con las características de la clasificación incorrecta. Puede usar esa lista más adelante para tener una idea acerca de lo que confunde al clasificador.
1 2 3 4 5 6 |
temp = pd.concat([X_test_lime, y_test_lime], axis= 1) temp['predicted'] = model_xgb.predict(X_test_lime) temp['wrong']= temp['label'] != temp['predicted'] temp = temp.query('wrong==True').drop('wrong', axis=1) temp= temp.sort_values(by=['label']) temp.shape |
(826, 16)
Cree una función lambda para recuperar la predicción del modelo con los nuevos datos. Lo necesitarás pronto.
1 2 |
predict_fn = lambda x: model_xgb.predict_proba(x).astype(float) X_test_lime.dtypes |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
age float64 workclass int64 fnlwgt float64 education int64 education_num float64 marital int64 occupation int64 relationship int64 race int64 sex int64 capital_gain float64 capital_loss float64 hours_week float64 native_country int64 dtype: object |
1 |
predict_fn(X_test_lime) |
1 2 3 4 5 6 7 |
array([[7.96461046e-01, 2.03538969e-01], [9.51730132e-01, 4.82698716e-02], [7.93448269e-01, 2.06551731e-01], ..., [9.90314305e-01, 9.68566816e-03], [6.45816326e-04, 9.99354184e-01], [9.71042812e-01, 2.89571714e-02]]) |
Convierte el marco de datos pandas a una matriz numpy
1 2 3 |
X_train_lime = X_train_lime.values X_test_lime = X_test_lime.values X_test_lime |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
array([[4.00000e+01, 5.00000e+00, 1.93524e+05, ..., 0.00000e+00, 4.00000e+01, 3.80000e+01], [2.70000e+01, 4.00000e+00, 2.16481e+05, ..., 0.00000e+00, 4.00000e+01, 3.80000e+01], [2.50000e+01, 4.00000e+00, 2.56263e+05, ..., 0.00000e+00, 4.00000e+01, 3.80000e+01], ..., [2.80000e+01, 6.00000e+00, 2.11032e+05, ..., 0.00000e+00, 4.00000e+01, 2.50000e+01], [4.40000e+01, 4.00000e+00, 1.67005e+05, ..., 0.00000e+00, 6.00000e+01, 3.80000e+01], [5.30000e+01, 4.00000e+00, 2.57940e+05, ..., 0.00000e+00, 4.00000e+01, 3.80000e+01]]) |
1 |
model_xgb.predict_proba(X_test_lime) |
1 2 3 4 5 6 7 |
array([[7.9646105e-01, 2.0353897e-01], [9.5173013e-01, 4.8269872e-02], [7.9344827e-01, 2.0655173e-01], ..., [9.9031430e-01, 9.6856682e-03], [6.4581633e-04, 9.9935418e-01], [9.7104281e-01, 2.8957171e-02]], dtype=float32) |
1 2 3 4 |
print(features, class_names, categorical_features, categorical_names) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
['age', 'workclass', 'fnlwgt', 'education', 'education_num', 'marital', 'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss', 'hours_week', 'native_country'] ['<=50K' '>50K'] [1, 3, 5, 6, 7, 8, 9, 13] {'workclass': array(['?', 'Federal-gov', 'Local-gov', 'Never-worked', 'Private', 'Self-emp-inc', 'Self-emp-not-inc', 'State-gov', 'Without-pay'], dtype=object), 'education': array(['10th', '11th', '12th', '1st-4th', '5th-6th', '7th-8th', '9th', 'Assoc-acdm', 'Assoc-voc', 'Bachelors', 'Doctorate', 'HS-grad', 'Masters', 'Preschool', 'Prof-school', 'Some-college'], dtype=object), 'marital': array(['Divorced', 'Married-AF-spouse', 'Married-civ-spouse', 'Married-spouse-absent', 'Never-married', 'Separated', 'Widowed'], dtype=object), 'occupation': array(['?', 'Adm-clerical', 'Armed-Forces', 'Craft-repair', 'Exec-managerial', 'Farming-fishing', 'Handlers-cleaners', 'Machine-op-inspct', 'Other-service', 'Priv-house-serv', 'Prof-specialty', 'Protective-serv', 'Sales', 'Tech-support', 'Transport-moving'], dtype=object), 'relationship': array(['Husband', 'Not-in-family', 'Other-relative', 'Own-child', 'Unmarried', 'Wife'], dtype=object), 'race': array(['Amer-Indian-Eskimo', 'Asian-Pac-Islander', 'Black', 'Other', 'White'], dtype=object), 'sex': array(['Female', 'Male'], dtype=object), 'native_country': array(['?', 'Cambodia', 'Canada', 'China', 'Columbia', 'Cuba', 'Dominican-Republic', 'Ecuador', 'El-Salvador', 'England', 'France', 'Germany', 'Greece', 'Guatemala', 'Haiti', 'Honduras', 'Hong', 'Hungary', 'India', 'Iran', 'Ireland', 'Italy', 'Jamaica', 'Japan', 'Laos', 'Mexico', 'Nicaragua', 'Outlying-US(Guam-USVI-etc)', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Puerto-Rico', 'Scotland', 'South', 'Taiwan', 'Thailand', 'Trinadad&Tobago', 'United-States', 'Vietnam', 'Yugoslavia'], dtype=object)} |
1 2 3 4 5 6 7 8 9 |
import lime import lime.lime_tabular ### Train should be label encoded not one hot encoded explainer = lime.lime_tabular.LimeTabularExplainer(X_train_lime , feature_names = features, class_names=class_names, categorical_features=categorical_features, categorical_names=categorical_names, kernel_width=3) |
Vamos a elegir un hogar aleatorio del conjunto de pruebas y ver la predicción del modelo y cómo el ordenador hizo su elección.
1 2 3 4 5 |
import numpy as np np.random.seed(1) i = 100 print(y_test_lime.iloc[i]) >50K |
1 |
X_test_lime[i] |
1 2 3 |
array([4.20000e+01, 4.00000e+00, 1.76286e+05, 7.00000e+00, 1.20000e+01, 2.00000e+00, 4.00000e+00, 0.00000e+00, 4.00000e+00, 1.00000e+00, 0.00000e+00, 0.00000e+00, 4.00000e+01, 3.80000e+01]) |
Puede usar el explicador con explain_instance para verificar la explicación detrás del modelo
1 2 |
exp = explainer.explain_instance(X_test_lime[i], predict_fn, num_features=6) exp.show_in_notebook(show_all=False) |
Podemos ver que el clasificador predijo el hogar correctamente. El ingreso es, de hecho, por encima de los 50k.
Lo primero que podemos decir es que el clasificador no está tan seguro de las probabilidades pronosticadas. La máquina predice que el hogar tiene un ingreso superior a 50k con una probabilidad del 64%. Este 64% se compone de Ganancias de capital y conyugal. El color azul contribuye negativamente a la clase positiva y la línea naranja, positivamente.
El clasificador se confunde porque la ganancia de capital de este hogar es nula, mientras que la ganancia de capital suele ser un buen predictor de riqueza. Además, el hogar trabaja menos de 40 horas por semana. La edad, la ocupación y el sexo contribuyen positivamente al clasificador.
Si el estado civil fuera soltero, el clasificador habría predicho un ingreso inferior a 50k (0.64-0.18 = 0.46)
Podemos intentarlo con otro hogar que ha sido clasificado erróneamente
1 2 |
temp.head(3) temp.iloc[1,:-2] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
age 58 workclass 4 fnlwgt 68624 education 11 education_num 9 marital 2 occupation 4 relationship 0 race 4 sex 1 capital_gain 0 capital_loss 0 hours_week 45 native_country 38 Name: 20931, dtype: object |
1 2 |
i = 1 print('This observation is', temp.iloc[i,-2:]) |
1 2 3 |
This observation is label <=50K predicted >50K Name: 20931, dtype: object |
1 2 |
exp = explainer.explain_instance(temp.iloc[1,:-2], predict_fn, num_features=6) exp.show_in_notebook(show_all=False) |
El clasificador predijo un ingreso por debajo de 50k mientras que es falso. Esta casa parece extraña. No tiene una ganancia de capital, ni pérdida de capital. Está divorciado y tiene 60 años, y es un pueblo educado, es decir, education_num > 12. De acuerdo con el patrón general, este hogar debería, como explica el clasificador, obtener un ingreso inferior a 50k.
Intentas jugar con LIME. Notará errores graves del clasificador.
Puede verificar el GitHub del propietario de la biblioteca. Proporcionan documentación adicional para la clasificación de imágenes y textos.
Resumen
A continuación se muestra una lista de algunos comandos útiles con scikit learn version >=0.20
create train/test dataset | trainees split |
Build a pipeline | |
select the column and apply the transformation | makecolumntransformer |
type of transformation | |
standardize | StandardScaler |
min max | MinMaxScaler |
Normalize | Normalizer |
Impute missing value | Imputer |
Convert categorical | OneHotEncoder |
Fit and transform the data | fit_transform |
Make the pipeline | make_pipeline |
Basic model | |
logistic regression | LogisticRegression |
XGBoost | XGBClassifier |
Neural net | MLPClassifier |
Grid search | GridSearchCV |
Randomized search | RandomizedSearchCV |
Leave a Reply
Want to join the discussion?Feel free to contribute!