Feature: Out-Of-Fold Predictions from a Multi-Layer Perceptron (+Magic Inputs)

In addition to the MLP architecture, we'll append some of the leaky features to the intermediate feature layer.

Imports

This utility package imports numpy, pandas, matplotlib and a helper kg module into the root namespace.


In [1]:
from pygoose import *

In [2]:
import gc

In [3]:
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import *

In [5]:
from keras import backend as K
from keras.models import Model, Sequential
from keras.layers import *
from keras.optimizers import *
from keras.callbacks import EarlyStopping, ModelCheckpoint


Using TensorFlow backend.

Config

Automatically discover the paths to various data folders and compose the project structure.


In [6]:
project = kg.Project.discover()

Identifier for storing these features on disk and referring to them later.


In [7]:
feature_list_id = 'oofp_nn_mlp_with_magic'

Make subsequent NN runs reproducible.


In [8]:
RANDOM_SEED = 42

In [9]:
np.random.seed(RANDOM_SEED)

Read data

Word embedding lookup matrix.


In [10]:
embedding_matrix = kg.io.load(project.aux_dir + 'fasttext_vocab_embedding_matrix.pickle')

Padded sequences of word indices for every question.


In [11]:
X_train_q1 = kg.io.load(project.preprocessed_data_dir + 'sequences_q1_fasttext_train.pickle')
X_train_q2 = kg.io.load(project.preprocessed_data_dir + 'sequences_q2_fasttext_train.pickle')

In [12]:
X_test_q1 = kg.io.load(project.preprocessed_data_dir + 'sequences_q1_fasttext_test.pickle')
X_test_q2 = kg.io.load(project.preprocessed_data_dir + 'sequences_q2_fasttext_test.pickle')

In [13]:
y_train = kg.io.load(project.features_dir + 'y_train.pickle')

Magic features.


In [14]:
magic_feature_lists = [
    'magic_frequencies',
    'magic_cooccurrence_matrix',
]

In [15]:
X_train_magic, X_test_magic, _ = project.load_feature_lists(magic_feature_lists)

In [16]:
X_train_magic = X_train_magic.values
X_test_magic = X_test_magic.values

In [17]:
scaler = StandardScaler()
scaler.fit(np.vstack([X_train_magic, X_test_magic]))
X_train_magic = scaler.transform(X_train_magic)
X_test_magic = scaler.transform(X_test_magic)

Word embedding properties.


In [18]:
EMBEDDING_DIM = embedding_matrix.shape[-1]
VOCAB_LENGTH = embedding_matrix.shape[0]
MAX_SEQUENCE_LENGTH = X_train_q1.shape[-1]

In [19]:
print(EMBEDDING_DIM, VOCAB_LENGTH, MAX_SEQUENCE_LENGTH)


300 101564 30

Define models


In [20]:
def create_model_question_branch():
    input_q = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32')
    
    embedding_q = Embedding(
        VOCAB_LENGTH,
        EMBEDDING_DIM,
        weights=[embedding_matrix],
        input_length=MAX_SEQUENCE_LENGTH,
        trainable=False,
    )(input_q)

    timedist_q = TimeDistributed(Dense(
        EMBEDDING_DIM,
        activation='relu',
    ))(embedding_q)

    lambda_q = Lambda(
        lambda x: K.max(x, axis=1),
        output_shape=(EMBEDDING_DIM, )
    )(timedist_q)
    
    output_q = lambda_q
    return input_q, output_q

In [21]:
def create_model(params):
    q1_input, q1_output = create_model_question_branch()
    q2_input, q2_output = create_model_question_branch()
    magic_input = Input(shape=(X_train_magic.shape[-1], ))
    
    merged_inputs = concatenate([q1_output, q2_output, magic_input])

    dense_1 = Dense(params['num_dense_1'])(merged_inputs)
    bn_1 = BatchNormalization()(dense_1)
    relu_1 = Activation('relu')(bn_1)

    dense_2 = Dense(params['num_dense_2'])(relu_1)
    bn_2 = BatchNormalization()(dense_2)
    relu_2 = Activation('relu')(bn_2)

    dense_3 = Dense(params['num_dense_3'])(relu_2)
    bn_3 = BatchNormalization()(dense_3)
    relu_3 = Activation('relu')(bn_3)

    dense_4 = Dense(params['num_dense_4'])(relu_3)
    bn_4 = BatchNormalization()(dense_4)
    relu_4 = Activation('relu')(bn_4)

    bn_final = BatchNormalization()(relu_4)
    output = Dense(1, activation='sigmoid')(bn_final)
    
    model = Model(
        inputs=[q1_input, q2_input, magic_input],
        outputs=output,
    )

    model.compile(
        loss='binary_crossentropy', 
        optimizer=Adam(lr=0.01),
        metrics=['accuracy']
    )

    return model

In [22]:
def predict(model, X_q1, X_q2, X_magic):
    """
    Mirror the pairs, compute two separate predictions, and average them.
    """
    
    y1 = model.predict([X_q1, X_q2, X_magic], batch_size=1024, verbose=1).reshape(-1)   
    y2 = model.predict([X_q2, X_q1, X_magic], batch_size=1024, verbose=1).reshape(-1)    
    return (y1 + y2) / 2

Partition the data


In [23]:
NUM_FOLDS = 5

In [24]:
kfold = StratifiedKFold(
    n_splits=NUM_FOLDS,
    shuffle=True,
    random_state=RANDOM_SEED
)

Create placeholders for out-of-fold predictions.


In [25]:
y_train_oofp = np.zeros_like(y_train, dtype='float64')

In [26]:
y_test_oofp = np.zeros((len(X_test_q1), NUM_FOLDS))

Define hyperparameters


In [27]:
BATCH_SIZE = 2048

In [28]:
MAX_EPOCHS = 200

In [29]:
model_params = {
    'num_dense_1': 400,
    'num_dense_2': 200,
    'num_dense_3': 400,
    'num_dense_4': 100,
}

The path where the best weights of the current model will be saved.


In [30]:
model_checkpoint_path = project.temp_dir + 'fold-checkpoint-' + feature_list_id + '.h5'

Fit the folds and compute out-of-fold predictions


In [31]:
%%time

# Iterate through folds.
for fold_num, (ix_train, ix_val) in enumerate(kfold.split(X_train_q1, y_train)):
    
    # Augment the training set by mirroring the pairs.
    X_fold_train_q1 = np.vstack([X_train_q1[ix_train], X_train_q2[ix_train]])
    X_fold_train_q2 = np.vstack([X_train_q2[ix_train], X_train_q1[ix_train]])
    X_fold_train_magic = np.vstack([X_train_magic[ix_train], X_train_magic[ix_train]])

    X_fold_val_q1 = np.vstack([X_train_q1[ix_val], X_train_q2[ix_val]])
    X_fold_val_q2 = np.vstack([X_train_q2[ix_val], X_train_q1[ix_val]])
    X_fold_val_magic = np.vstack([X_train_magic[ix_val], X_train_magic[ix_val]])

    # Ground truth should also be "mirrored".
    y_fold_train = np.concatenate([y_train[ix_train], y_train[ix_train]])
    y_fold_val = np.concatenate([y_train[ix_val], y_train[ix_val]])
    
    print()
    print(f'Fitting fold {fold_num + 1} of {kfold.n_splits}')
    print()
    
    # Compile a new model.
    model = create_model(model_params)

    # Train.
    model.fit(
        [X_fold_train_q1, X_fold_train_q2, X_fold_train_magic], y_fold_train,
        validation_data=([X_fold_val_q1, X_fold_val_q2, X_fold_val_magic], y_fold_val),

        batch_size=BATCH_SIZE,
        epochs=MAX_EPOCHS,
        verbose=1,
        
        callbacks=[
            # Stop training when the validation loss stops improving.
            EarlyStopping(
                monitor='val_loss',
                min_delta=0.001,
                patience=3,
                verbose=1,
                mode='auto',
            ),
            # Save the weights of the best epoch.
            ModelCheckpoint(
                model_checkpoint_path,
                monitor='val_loss',
                save_best_only=True,
                verbose=2,
            ),
        ],
    )
        
    # Restore the best epoch.
    model.load_weights(model_checkpoint_path)
    
    # Compute out-of-fold predictions.
    y_train_oofp[ix_val] = predict(model, X_train_q1[ix_val], X_train_q2[ix_val], X_train_magic[ix_val])
    y_test_oofp[:, fold_num] = predict(model, X_test_q1, X_test_q2, X_test_magic)
    
    # Clear GPU memory.
    K.clear_session()
    del X_fold_train_q1, X_fold_train_q2, X_fold_train_magic
    del X_fold_val_q1, X_fold_val_q2, X_fold_val_magic
    del model
    gc.collect()


Fitting fold 1 of 5

Train on 646862 samples, validate on 161718 samples
Epoch 1/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.3390 - acc: 0.8494Epoch 00000: val_loss improved from inf to 0.40987, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 17s - loss: 0.3388 - acc: 0.8495 - val_loss: 0.4099 - val_acc: 0.8459
Epoch 2/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.2794 - acc: 0.8741Epoch 00001: val_loss improved from 0.40987 to 0.34372, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 15s - loss: 0.2794 - acc: 0.8742 - val_loss: 0.3437 - val_acc: 0.8561
Epoch 3/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.2526 - acc: 0.8874Epoch 00002: val_loss improved from 0.34372 to 0.30076, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 15s - loss: 0.2526 - acc: 0.8875 - val_loss: 0.3008 - val_acc: 0.8641
Epoch 4/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.2304 - acc: 0.8984Epoch 00003: val_loss improved from 0.30076 to 0.29949, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 16s - loss: 0.2305 - acc: 0.8984 - val_loss: 0.2995 - val_acc: 0.8694
Epoch 5/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.2115 - acc: 0.9075Epoch 00004: val_loss did not improve
646862/646862 [==============================] - 15s - loss: 0.2115 - acc: 0.9075 - val_loss: 0.3067 - val_acc: 0.8655
Epoch 6/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.1938 - acc: 0.9160Epoch 00005: val_loss did not improve
646862/646862 [==============================] - 14s - loss: 0.1938 - acc: 0.9159 - val_loss: 0.3285 - val_acc: 0.8687
Epoch 7/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.1772 - acc: 0.9241Epoch 00006: val_loss did not improve
646862/646862 [==============================] - 15s - loss: 0.1773 - acc: 0.9241 - val_loss: 0.3963 - val_acc: 0.8235
Epoch 8/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.1625 - acc: 0.9306Epoch 00007: val_loss did not improve
646862/646862 [==============================] - 15s - loss: 0.1625 - acc: 0.9306 - val_loss: 0.3367 - val_acc: 0.8573
Epoch 00007: early stopping
2341888/2345796 [============================>.] - ETA: 0s
Fitting fold 2 of 5

Train on 646862 samples, validate on 161718 samples
Epoch 1/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.3408 - acc: 0.8499Epoch 00000: val_loss improved from inf to 0.47372, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 17s - loss: 0.3407 - acc: 0.8499 - val_loss: 0.4737 - val_acc: 0.8016
Epoch 2/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.2794 - acc: 0.8746Epoch 00001: val_loss improved from 0.47372 to 0.41548, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 16s - loss: 0.2794 - acc: 0.8746 - val_loss: 0.4155 - val_acc: 0.8243
Epoch 3/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.2532 - acc: 0.8874Epoch 00002: val_loss improved from 0.41548 to 0.29717, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646862/646862 [==============================] - 16s - loss: 0.2532 - acc: 0.8874 - val_loss: 0.2972 - val_acc: 0.8668
Epoch 4/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.2313 - acc: 0.8980Epoch 00003: val_loss did not improve
646862/646862 [==============================] - 14s - loss: 0.2313 - acc: 0.8980 - val_loss: 0.3021 - val_acc: 0.8680
Epoch 5/200
643072/646862 [============================>.] - ETA: 0s - loss: 0.2129 - acc: 0.9071Epoch 00004: val_loss did not improve
646862/646862 [==============================] - 14s - loss: 0.2128 - acc: 0.9071 - val_loss: 0.3781 - val_acc: 0.8348
Epoch 6/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.1948 - acc: 0.9158Epoch 00005: val_loss did not improve
646862/646862 [==============================] - 15s - loss: 0.1949 - acc: 0.9158 - val_loss: 0.3030 - val_acc: 0.8684
Epoch 7/200
645120/646862 [============================>.] - ETA: 0s - loss: 0.1780 - acc: 0.9232Epoch 00006: val_loss did not improve
646862/646862 [==============================] - 14s - loss: 0.1780 - acc: 0.9232 - val_loss: 0.3978 - val_acc: 0.8614
Epoch 00006: early stopping
2344960/2345796 [============================>.] - ETA: 0s
Fitting fold 3 of 5

Train on 646864 samples, validate on 161716 samples
Epoch 1/200
643072/646864 [============================>.] - ETA: 0s - loss: 0.3405 - acc: 0.8490Epoch 00000: val_loss improved from inf to 0.37872, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646864/646864 [==============================] - 16s - loss: 0.3403 - acc: 0.8491 - val_loss: 0.3787 - val_acc: 0.8446
Epoch 2/200
645120/646864 [============================>.] - ETA: 0s - loss: 0.2803 - acc: 0.8739Epoch 00001: val_loss improved from 0.37872 to 0.30225, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646864/646864 [==============================] - 15s - loss: 0.2803 - acc: 0.8739 - val_loss: 0.3023 - val_acc: 0.8646
Epoch 3/200
645120/646864 [============================>.] - ETA: 0s - loss: 0.2531 - acc: 0.8875Epoch 00002: val_loss did not improve
646864/646864 [==============================] - 15s - loss: 0.2531 - acc: 0.8875 - val_loss: 0.3244 - val_acc: 0.8609
Epoch 4/200
645120/646864 [============================>.] - ETA: 0s - loss: 0.2316 - acc: 0.8978Epoch 00003: val_loss did not improve
646864/646864 [==============================] - 15s - loss: 0.2317 - acc: 0.8977 - val_loss: 0.3761 - val_acc: 0.8505
Epoch 5/200
645120/646864 [============================>.] - ETA: 0s - loss: 0.2123 - acc: 0.9071Epoch 00004: val_loss did not improve
646864/646864 [==============================] - 14s - loss: 0.2123 - acc: 0.9071 - val_loss: 0.3054 - val_acc: 0.8679
Epoch 6/200
645120/646864 [============================>.] - ETA: 0s - loss: 0.1937 - acc: 0.9157Epoch 00005: val_loss did not improve
646864/646864 [==============================] - 15s - loss: 0.1936 - acc: 0.9157 - val_loss: 0.4158 - val_acc: 0.8132
Epoch 00005: early stopping
2340864/2345796 [============================>.] - ETA: 0s
Fitting fold 4 of 5

Train on 646866 samples, validate on 161714 samples
Epoch 1/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.3403 - acc: 0.8487Epoch 00000: val_loss improved from inf to 0.37181, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 17s - loss: 0.3402 - acc: 0.8488 - val_loss: 0.3718 - val_acc: 0.8467
Epoch 2/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.2818 - acc: 0.8736Epoch 00001: val_loss improved from 0.37181 to 0.35005, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 16s - loss: 0.2818 - acc: 0.8737 - val_loss: 0.3501 - val_acc: 0.8523
Epoch 3/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.2547 - acc: 0.8865Epoch 00002: val_loss improved from 0.35005 to 0.31893, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 16s - loss: 0.2547 - acc: 0.8865 - val_loss: 0.3189 - val_acc: 0.8567
Epoch 4/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.2336 - acc: 0.8970Epoch 00003: val_loss did not improve
646866/646866 [==============================] - 15s - loss: 0.2336 - acc: 0.8970 - val_loss: 0.3253 - val_acc: 0.8576
Epoch 5/200
643072/646866 [============================>.] - ETA: 0s - loss: 0.2147 - acc: 0.9060Epoch 00004: val_loss improved from 0.31893 to 0.31360, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 16s - loss: 0.2147 - acc: 0.9060 - val_loss: 0.3136 - val_acc: 0.8613
Epoch 6/200
643072/646866 [============================>.] - ETA: 0s - loss: 0.1969 - acc: 0.9143Epoch 00005: val_loss did not improve
646866/646866 [==============================] - 14s - loss: 0.1969 - acc: 0.9143 - val_loss: 0.3318 - val_acc: 0.8528
Epoch 7/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1807 - acc: 0.9219Epoch 00006: val_loss did not improve
646866/646866 [==============================] - 15s - loss: 0.1807 - acc: 0.9219 - val_loss: 0.3349 - val_acc: 0.8568
Epoch 8/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1653 - acc: 0.9290Epoch 00007: val_loss did not improve
646866/646866 [==============================] - 15s - loss: 0.1653 - acc: 0.9290 - val_loss: 0.3466 - val_acc: 0.8613
Epoch 9/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1509 - acc: 0.9358Epoch 00008: val_loss did not improve
646866/646866 [==============================] - 14s - loss: 0.1509 - acc: 0.9358 - val_loss: 0.3473 - val_acc: 0.8648
Epoch 00008: early stopping
2341888/2345796 [============================>.] - ETA: 0s
Fitting fold 5 of 5

Train on 646866 samples, validate on 161714 samples
Epoch 1/200
643072/646866 [============================>.] - ETA: 0s - loss: 0.3346 - acc: 0.8508Epoch 00000: val_loss improved from inf to 0.41002, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 17s - loss: 0.3344 - acc: 0.8508 - val_loss: 0.4100 - val_acc: 0.8383
Epoch 2/200
643072/646866 [============================>.] - ETA: 0s - loss: 0.2783 - acc: 0.8744Epoch 00001: val_loss improved from 0.41002 to 0.30778, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 16s - loss: 0.2783 - acc: 0.8744 - val_loss: 0.3078 - val_acc: 0.8651
Epoch 3/200
643072/646866 [============================>.] - ETA: 0s - loss: 0.2515 - acc: 0.8882Epoch 00002: val_loss did not improve
646866/646866 [==============================] - 15s - loss: 0.2515 - acc: 0.8882 - val_loss: 0.3671 - val_acc: 0.8534
Epoch 4/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.2302 - acc: 0.8986Epoch 00003: val_loss did not improve
646866/646866 [==============================] - 14s - loss: 0.2303 - acc: 0.8985 - val_loss: 0.3649 - val_acc: 0.8574
Epoch 5/200
643072/646866 [============================>.] - ETA: 0s - loss: 0.2113 - acc: 0.9077Epoch 00004: val_loss improved from 0.30778 to 0.30353, saving model to /home/yuriyguts/Projects/kaggle-quora-question-pairs/data/tmp/fold-checkpoint-oofp_nn_mlp_with_magic.h5
646866/646866 [==============================] - 15s - loss: 0.2113 - acc: 0.9077 - val_loss: 0.3035 - val_acc: 0.8638
Epoch 6/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1932 - acc: 0.9167Epoch 00005: val_loss did not improve
646866/646866 [==============================] - 14s - loss: 0.1932 - acc: 0.9166 - val_loss: 0.3573 - val_acc: 0.8384
Epoch 7/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1755 - acc: 0.9246Epoch 00006: val_loss did not improve
646866/646866 [==============================] - 14s - loss: 0.1756 - acc: 0.9246 - val_loss: 0.3867 - val_acc: 0.8227
Epoch 8/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1603 - acc: 0.9311- Epoch 00007: val_loss did not improve
646866/646866 [==============================] - 15s - loss: 0.1603 - acc: 0.9311 - val_loss: 0.3439 - val_acc: 0.8549
Epoch 9/200
645120/646866 [============================>.] - ETA: 0s - loss: 0.1450 - acc: 0.9386Epoch 00008: val_loss did not improve
646866/646866 [==============================] - 14s - loss: 0.1450 - acc: 0.9386 - val_loss: 0.3498 - val_acc: 0.8582
Epoch 00008: early stopping
80857/80857 [==============================] - 0s     
2340864/2345796 [============================>.] - ETA: 0sCPU times: user 7min 23s, sys: 37.5 s, total: 8min 1s
Wall time: 13min 45s

In [32]:
cv_score = log_loss(y_train, y_train_oofp)
print('CV score:', cv_score)


CV score: 0.292694363155

Save features


In [33]:
feature_names = [feature_list_id]

In [34]:
features_train = y_train_oofp.reshape((-1, 1))

In [35]:
features_test = np.mean(y_test_oofp, axis=1).reshape((-1, 1))

In [36]:
project.save_features(features_train, features_test, feature_names, feature_list_id)

Explore


In [37]:
pd.DataFrame(features_test).plot.hist()


Out[37]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fa076daa518>