Facies classification using Convolutional Neural Networks

Team StoDIG - Statoil Deep-learning Interest Group

Eskil Kulseth Dahl, David Wade & John Thurmond

In this python notebook we propose a facies classification model, building on the simple Neural Network solution proposed by LA_Team in order to outperform the prediction model proposed in the predicting facies from well logs challenge.

Given the limited size of the training data set, Deep Learning is not likely to exceed the accuracy of results from refined Machine Learning techniques (such as Gradient Boosted Trees). However, we chose to use the opportunity to advance our understanding of Deep Learning network design, and have enjoyed participating in the contest. With a substantially larger training set and perhaps more facies ambiguity, Deep Learning could be a preferred approach to this sort of problem.

We use three key innovations:

  • Inserting a convolutional layer as the first layer in the Neural Network
  • A convolution layer, a stack of LSTMs (with skip connection) feeding a Maxout layer cf. CLDNN & Maxout
  • Adding Dropout regularization to prevent overfitting

Problem Modeling


The dataset we will use comes from a class excercise from The University of Kansas on Neural Networks and Fuzzy Systems. This exercise is based on a consortium project to use machine learning techniques to create a reservoir model of the largest gas fields in North America, the Hugoton and Panoma Fields. For more info on the origin of the data, see Bohling and Dubois (2003) and Dubois et al. (2007).

The dataset we will use is log data from nine wells that have been labeled with a facies type based on oberservation of core. We will use this log data to train a classifier to predict facies types.

This data is from the Council Grove gas reservoir in Southwest Kansas. The Panoma Council Grove Field is predominantly a carbonate gas reservoir encompassing 2700 square miles in Southwestern Kansas. This dataset is from nine wells (with 4149 examples), consisting of a set of seven predictor variables and a rock facies (class) for each example vector and validation (test) data (830 examples from two wells) having the same seven predictor variables in the feature vector. Facies are based on examination of cores from nine wells taken vertically at half-foot intervals. Predictor variables include five from wireline log measurements and two geologic constraining variables that are derived from geologic knowledge. These are essentially continuous variables sampled at a half-foot sample rate.

The seven predictor variables are:

The nine discrete facies (classes of rocks) are:

  1. Nonmarine sandstone
  2. Nonmarine coarse siltstone
  3. Nonmarine fine siltstone
  4. Marine siltstone and shale
  5. Mudstone (limestone)
  6. Wackestone (limestone)
  7. Dolomite
  8. Packstone-grainstone (limestone)
  9. Phylloid-algal bafflestone (limestone)

These facies aren't discrete, and gradually blend into one another. Some have neighboring facies that are rather close. Mislabeling within these neighboring facies can be expected to occur. The following table lists the facies, their abbreviated labels and their approximate neighbors.

Facies Label Adjacent Facies
1 SS 2
2 CSiS 1,3
3 FSiS 2
4 SiSh 5
5 MS 4,6
6 WS 5,7
7 D 6,8
8 PS 6,7,9
9 BS 7,8

Setup


Check we have all the libraries we need, and import the modules we require. Note that we have used the Theano backend for Keras, and to achieve a reasonable training time we have used an NVidia K20 GPU.


In [ ]:
%%sh
pip install pandas
pip install scikit-learn
pip install keras
pip install sklearn

In [1]:
from __future__ import print_function
import time
import numpy as np
%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from mpl_toolkits.axes_grid1 import make_axes_locatable
from keras.preprocessing import sequence
from keras.models import Model, Sequential
from keras.constraints import maxnorm, nonneg
from keras.optimizers import SGD, Adam, Adamax, Nadam
from keras.regularizers import l1, l2, activity_l2
from keras.layers import Input, Dense, Dropout, Activation, LSTM, GRU, Reshape, MaxoutDense, Convolution1D, Cropping1D, Cropping2D, Permute, Flatten, MaxPooling1D, merge
from keras.wrappers.scikit_learn import KerasClassifier
from keras.utils import np_utils
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold , StratifiedKFold
from classification_utilities import display_cm, display_adj_cm
from sklearn.metrics import confusion_matrix, f1_score
from sklearn import preprocessing
from sklearn.model_selection import GridSearchCV


Using Theano backend.
ERROR (theano.sandbox.cuda): nvcc compiler not found on $PATH. Check your nvcc installation and try again.

Data ingest


We load the training and testing data to preprocess it for further analysis, filling the missing data values in the PE field with zero and proceeding to normalize the data that will be fed into our model. We now incorporate the Imputation from Paolo Bestagini via LA_Team's Submission 5.


In [2]:
data = pd.read_csv('train_test_data.csv')

# Set 'Well Name' and 'Formation' fields as categories
data['Well Name'] = data['Well Name'].astype('category')
data['Formation'] = data['Formation'].astype('category')

def coding(col, codeDict):
    colCoded = pd.Series(col, copy=True)
    for key, value in codeDict.items():
        colCoded.replace(key, value, inplace=True)
    return colCoded

data['Formation_coded'] = coding(data['Formation'], {'A1 LM':1,'A1 SH':2,'B1 LM':3,'B1 SH':4,'B2 LM':5,'B2 SH':6,'B3 LM':7,'B3 SH':8,'B4 LM':9,'B4 SH':10,'B5 LM':11,'B5 SH':12,'C LM':13,'C SH':14})
formation = data['Formation_coded'].values[:,np.newaxis]

# Parameters
feature_names = ['Depth', 'GR', 'ILD_log10', 'DeltaPHI', 'PHIND', 'PE', 'NM_M', 'RELPOS']
facies_labels = ['SS', 'CSiS', 'FSiS', 'SiSh', 'MS','WS', 'D','PS', 'BS']
facies_colors = ['#F4D03F', '#F5B041','#DC7633','#6E2C00', '#1B4F72','#2E86C1', '#AED6F1', '#A569BD', '#196F3D']
well_names_test = ['SHRIMPLIN', 'ALEXANDER D', 'SHANKLE', 'LUKE G U', 'KIMZEY A', 'CROSS H CATTLE', 'NOLAN', 'Recruit F9', 'NEWBY', 'CHURCHMAN BIBLE']
well_names_validate = ['STUART', 'CRAWFORD']

data_vectors = data[feature_names].values
correct_facies_labels = data['Facies'].values

well_labels = data[['Well Name', 'Facies']].values
depth = data['Depth'].values

# Fill missing values and normalize for 'PE' field
imp = preprocessing.Imputer(missing_values='NaN', strategy='mean', axis=0)
imp.fit(data_vectors)
data_vectors = imp.transform(data_vectors)

data_vectors = np.hstack([data_vectors, formation])

scaler = preprocessing.StandardScaler().fit(data_vectors)
scaled_features = scaler.transform(data_vectors)
data_out = np.hstack([well_labels, scaled_features])
print(data_out[0:2,2:11].shape)


(2, 9)

Split data into training data and blind data, and output as Numpy arrays


In [3]:
def preprocess(data_out):
    
    data = data_out
      
    X = data[0:4149,0:11]
    
    y = np.concatenate((data[0:4149,0].reshape(4149,1), np_utils.to_categorical(correct_facies_labels[0:4149]-1)), axis=1)
    X_test = data[4149:,0:11]

    return X, y, X_test

X_train_in, y_train, X_test_in = preprocess(data_out)

print(y_train.shape)
print(X_train_in.shape)


(4149, 10)
(4149, 11)

Data Augmentation


We expand the input data to be acted on by the convolutional layer.


In [4]:
conv_domain = 7

# Reproducibility
np.random.seed(7) 
# Load data

def expand_dims(input):
    r = int((conv_domain-1)/2)
    l = input.shape[0]
    n_input_vars = input.shape[1]
    output = np.zeros((l, conv_domain, n_input_vars))
    for i in range(l):
        for j in range(conv_domain):
            for k in range(n_input_vars):
                output[i,j,k] = input[min(i+j-r,l-1),k]
    return output

X_train = np.empty((0,conv_domain,9), dtype=float)
X_test  = np.empty((0,conv_domain,9), dtype=float)
y_select = np.empty((0,9), dtype=int)

well_names_train = ['SHRIMPLIN', 'ALEXANDER D', 'SHANKLE', 'LUKE G U', 'KIMZEY A', 'CROSS H CATTLE', 'NOLAN', 'NEWBY', 'CHURCHMAN BIBLE']

for wellId in well_names_train:
    X_train_subset = X_train_in[X_train_in[:, 0] == wellId][:,2:11]
    X_train_subset = expand_dims(X_train_subset)
    X_train = np.concatenate((X_train,X_train_subset),axis=0)
    y_select = np.concatenate((y_select, y_train[y_train[:, 0] == wellId][:,1:10]), axis=0)
    
for wellId in well_names_validate:
    X_test_subset = X_test_in[X_test_in[:, 0] == wellId][:,2:11]
    X_test_subset = expand_dims(X_test_subset)
    X_test = np.concatenate((X_test,X_test_subset),axis=0)

y_train = y_select
    
print(X_train.shape)
print(X_test.shape)
print(y_select.shape)


(4069, 7, 9)
(830, 7, 9)
(4069, 9)

Convolutional Long-Short term Memory Fully connected Neural Network

We build a CLDNN with the following layers (no longer using Sequential() model):

  • Dropout layer on input
  • One 1D convolutional layer (7-point radius) feeding the LSTM stack and skipping to the Maxout layer
  • One 1D cropping layer (just take actual log-value of interest), feeding the LSTM branch
  • A Merge layer re-adding result of LSTM stack and the result from the convolution layer
  • L1 regularization is applied to the Convolution filter to avoid noisy kernels.

In [6]:
# Set parameters
input_dim = 9
output_dim = 9
n_per_batch = 512
epochs = 100
crop_factor = int(conv_domain/2)
crop_len=crop_factor
filters_per_log = 7

n_convolutions = input_dim*filters_per_log

# Set parameters
conv_dim = 2*input_dim
#hidden_dim_1 = 128
lstm_dim1=18
lstm_dim2=18
lstm_dim3=18
lstm_dim4=9

output_dim = y_train.shape[1]
n_per_batch = 512
epochs = 100
nb_conv1 = input_dim*conv_domain
conv_length=conv_domain
n_conv=input_dim*nb_conv1

init_drop=0.25
lstm_drop=0.5
dense_drop=0.5


def cld_model():
#Input
    inputs=Input(shape=(conv_length,input_dim), name='Input')
#Conv branch    
    x_conv=Dropout(init_drop, name='Dropout_Conv_1')(inputs)    
    x_conv=Convolution1D(nb_conv1, conv_length, border_mode='same', activation='tanh', W_regularizer=l1(0.001), name='Conv1D_1')(x_conv)
    x_conv=Dropout(dense_drop, name='Dropout_Conv_Dense')(x_conv)
    x_conv=Flatten()(x_conv)
    x_conv=Dense(conv_dim, activation='tanh', W_regularizer=l1(0.001), name='Dense1')(x_conv)
    x_conv_in=Reshape((1,conv_dim))(x_conv)

#LSTM branch
    input_lstm=Cropping1D(cropping=(crop_len,crop_len),name='Crop_Input')(inputs)
    #Merge input+conv-branch
    lstm_in=merge([input_lstm,x_conv_in], mode='concat', concat_axis=2)
    x_lstm_1=LSTM(lstm_dim1, return_sequences=True, init='uniform', dropout_W=lstm_drop, dropout_U=lstm_drop, name='LSTM_1')(lstm_in)
    #Merge LSTM1+inputLSTM1
    lstm_in_2=merge([input_lstm,x_lstm_1], mode='concat', concat_axis=2)
    x_lstm_2=LSTM(lstm_dim2, return_sequences=True, init='uniform',dropout_W=lstm_drop, dropout_U=lstm_drop, name='LSTM_2')(lstm_in_2)
    #Merge LSTM2+inputLSTM2
    lstm_in_3=merge([lstm_in_2,x_lstm_2], mode='concat', concat_axis=2)
    x_lstm_3=LSTM(lstm_dim3, return_sequences=True, init='uniform',dropout_W=lstm_drop, dropout_U=lstm_drop, name='LSTM_3')(lstm_in_3)
    #Merge LSTM3+inoutLSTM3
    lstm_in_4=merge([lstm_in_3,x_lstm_3], mode='concat', concat_axis=2)
    x_lstm=LSTM(lstm_dim4, init='uniform', dropout_W=lstm_drop, dropout_U=lstm_drop, name='LSTM_4')(lstm_in_4)

#Fully connected branch
    #input_x=Reshape((input_dim,))(input_lstm)
    input_dense=merge([x_conv,x_lstm], mode='concat', concat_axis=1)
    x_dense=Dropout(dense_drop)(input_dense)
    x_dense=MaxoutDense(36, nb_feature=6, name='MaxoutDense_1')(x_dense)
    out_dense=Dense(9, activation='softmax', name='Output')(x_dense)

#define model
    model=Model(input=inputs, output=out_dense)

#optimizer and compile
    optimizerNadam = Nadam(lr=0.002, beta_1=0.9, beta_2=0.999, epsilon=1e-08, schedule_decay=0.004)
    model.compile(loss='categorical_crossentropy', optimizer=optimizerNadam, metrics=['accuracy'])
    return model

# Load the model
t0 = time.time()
model_cld = cld_model()
model_cld.summary()
t1 = time.time()
print("Load time = %d" % (t1-t0) )

def plot_weights(n_convs_disp=input_dim):
    layerID=2

    print(model_cld.layers[layerID].get_weights()[0].shape)
    print(model_cld.layers[layerID].get_weights()[1].shape)

    fig, ax = plt.subplots(figsize=(12,10))

    for i in range(n_convs_disp):
        plt.subplot(input_dim,1,i+1)
        plt.imshow(model_cld.layers[layerID].get_weights()[0][:,0,i,:], interpolation='none')

    plt.show()
    
plot_weights(1)


____________________________________________________________________________________________________
Layer (type)                     Output Shape          Param #     Connected to                     
====================================================================================================
Input (InputLayer)               (None, 7, 9)          0                                            
____________________________________________________________________________________________________
Dropout_Conv_1 (Dropout)         (None, 7, 9)          0           Input[0][0]                      
____________________________________________________________________________________________________
Conv1D_1 (Convolution1D)         (None, 7, 63)         4032        Dropout_Conv_1[0][0]             
____________________________________________________________________________________________________
Dropout_Conv_Dense (Dropout)     (None, 7, 63)         0           Conv1D_1[0][0]                   
____________________________________________________________________________________________________
flatten_1 (Flatten)              (None, 441)           0           Dropout_Conv_Dense[0][0]         
____________________________________________________________________________________________________
Dense1 (Dense)                   (None, 18)            7956        flatten_1[0][0]                  
____________________________________________________________________________________________________
Crop_Input (Cropping1D)          (None, 1, 9)          0           Input[0][0]                      
____________________________________________________________________________________________________
reshape_1 (Reshape)              (None, 1, 18)         0           Dense1[0][0]                     
____________________________________________________________________________________________________
merge_1 (Merge)                  (None, 1, 27)         0           Crop_Input[0][0]                 
                                                                   reshape_1[0][0]                  
____________________________________________________________________________________________________
LSTM_1 (LSTM)                    (None, 1, 18)         3312        merge_1[0][0]                    
____________________________________________________________________________________________________
merge_2 (Merge)                  (None, 1, 27)         0           Crop_Input[0][0]                 
                                                                   LSTM_1[0][0]                     
____________________________________________________________________________________________________
LSTM_2 (LSTM)                    (None, 1, 18)         3312        merge_2[0][0]                    
____________________________________________________________________________________________________
merge_3 (Merge)                  (None, 1, 45)         0           merge_2[0][0]                    
                                                                   LSTM_2[0][0]                     
____________________________________________________________________________________________________
LSTM_3 (LSTM)                    (None, 1, 18)         4608        merge_3[0][0]                    
____________________________________________________________________________________________________
merge_4 (Merge)                  (None, 1, 63)         0           merge_3[0][0]                    
                                                                   LSTM_3[0][0]                     
____________________________________________________________________________________________________
LSTM_4 (LSTM)                    (None, 9)             2628        merge_4[0][0]                    
____________________________________________________________________________________________________
merge_5 (Merge)                  (None, 27)            0           Dense1[0][0]                     
                                                                   LSTM_4[0][0]                     
____________________________________________________________________________________________________
dropout_1 (Dropout)              (None, 27)            0           merge_5[0][0]                    
____________________________________________________________________________________________________
MaxoutDense_1 (MaxoutDense)      (None, 36)            6048        dropout_1[0][0]                  
____________________________________________________________________________________________________
Output (Dense)                   (None, 9)             333         MaxoutDense_1[0][0]              
====================================================================================================
Total params: 32,229
Trainable params: 32,229
Non-trainable params: 0
____________________________________________________________________________________________________
Load time = 10
(7, 1, 9, 63)
(63,)

We train the CLDNN and evaluate it on precision/recall.


In [7]:
#Train model
t0 = time.time()
model_cld.fit(X_train, y_train, batch_size=n_per_batch, nb_epoch=epochs, verbose=2)
t1 = time.time()
print("Train time = %d seconds" % (t1-t0) )


# Predict Values on Training set
t0 = time.time()
y_predicted = model_cld.predict( X_train , batch_size=n_per_batch, verbose=2)
t1 = time.time()
print("Test time = %d seconds" % (t1-t0) )

# Print Report

# Format output [0 - 8 ]
y_ = np.zeros((len(y_train),1))
for i in range(len(y_train)):
    y_[i] = np.argmax(y_train[i])

y_predicted_ = np.zeros((len(y_predicted), 1))
for i in range(len(y_predicted)):
    y_predicted_[i] = np.argmax( y_predicted[i] )
    
# Confusion Matrix
conf = confusion_matrix(y_, y_predicted_)

def accuracy(conf):
    total_correct = 0.
    nb_classes = conf.shape[0]
    for i in np.arange(0,nb_classes):
        total_correct += conf[i][i]
    acc = total_correct/sum(sum(conf))
    return acc

adjacent_facies = np.array([[1], [0,2], [1], [4], [3,5], [4,6,7], [5,7], [5,6,8], [6,7]])

def accuracy_adjacent(conf, adjacent_facies):
    nb_classes = conf.shape[0]
    total_correct = 0.
    for i in np.arange(0,nb_classes):
        total_correct += conf[i][i]
        for j in adjacent_facies[i]:
            total_correct += conf[i][j]
    return total_correct / sum(sum(conf))

# Print Results
print ("\nModel Report")
print ("-Accuracy: %.6f" % ( accuracy(conf) ))
print ("-Adjacent Accuracy: %.6f" % ( accuracy_adjacent(conf, adjacent_facies) ))
print ("\nConfusion Matrix")
display_cm(conf, facies_labels, display_metrics=True, hide_zeros=True)


Epoch 1/100
0s - loss: 2.6430 - acc: 0.1010
Epoch 2/100
0s - loss: 2.3007 - acc: 0.3274
Epoch 3/100
0s - loss: 1.9966 - acc: 0.4146
Epoch 4/100
0s - loss: 1.8142 - acc: 0.4227
Epoch 5/100
0s - loss: 1.6910 - acc: 0.4524
Epoch 6/100
0s - loss: 1.5764 - acc: 0.4682
Epoch 7/100
0s - loss: 1.5119 - acc: 0.4822
Epoch 8/100
0s - loss: 1.4455 - acc: 0.4977
Epoch 9/100
0s - loss: 1.4022 - acc: 0.4984
Epoch 10/100
0s - loss: 1.3687 - acc: 0.5014
Epoch 11/100
0s - loss: 1.3332 - acc: 0.5065
Epoch 12/100
0s - loss: 1.3125 - acc: 0.5205
Epoch 13/100
0s - loss: 1.3027 - acc: 0.5154
Epoch 14/100
0s - loss: 1.2771 - acc: 0.5326
Epoch 15/100
1s - loss: 1.2608 - acc: 0.5245
Epoch 16/100
1s - loss: 1.2541 - acc: 0.5311
Epoch 17/100
0s - loss: 1.2402 - acc: 0.5318
Epoch 18/100
1s - loss: 1.2395 - acc: 0.5257
Epoch 19/100
1s - loss: 1.2145 - acc: 0.5370
Epoch 20/100
1s - loss: 1.2099 - acc: 0.5424
Epoch 21/100
1s - loss: 1.2132 - acc: 0.5343
Epoch 22/100
1s - loss: 1.2027 - acc: 0.5281
Epoch 23/100
0s - loss: 1.2064 - acc: 0.5453
Epoch 24/100
0s - loss: 1.2039 - acc: 0.5402
Epoch 25/100
0s - loss: 1.1884 - acc: 0.5421
Epoch 26/100
1s - loss: 1.1839 - acc: 0.5409
Epoch 27/100
1s - loss: 1.1836 - acc: 0.5495
Epoch 28/100
0s - loss: 1.1658 - acc: 0.5476
Epoch 29/100
0s - loss: 1.1590 - acc: 0.5498
Epoch 30/100
0s - loss: 1.1726 - acc: 0.5429
Epoch 31/100
0s - loss: 1.1649 - acc: 0.5478
Epoch 32/100
0s - loss: 1.1700 - acc: 0.5458
Epoch 33/100
0s - loss: 1.1549 - acc: 0.5559
Epoch 34/100
0s - loss: 1.1475 - acc: 0.5571
Epoch 35/100
0s - loss: 1.1473 - acc: 0.5554
Epoch 36/100
0s - loss: 1.1368 - acc: 0.5579
Epoch 37/100
0s - loss: 1.1427 - acc: 0.5606
Epoch 38/100
0s - loss: 1.1338 - acc: 0.5579
Epoch 39/100
0s - loss: 1.1406 - acc: 0.5574
Epoch 40/100
0s - loss: 1.1353 - acc: 0.5552
Epoch 41/100
0s - loss: 1.1358 - acc: 0.5621
Epoch 42/100
0s - loss: 1.1351 - acc: 0.5549
Epoch 43/100
0s - loss: 1.1428 - acc: 0.5522
Epoch 44/100
0s - loss: 1.1336 - acc: 0.5581
Epoch 45/100
0s - loss: 1.1173 - acc: 0.5638
Epoch 46/100
0s - loss: 1.1327 - acc: 0.5581
Epoch 47/100
0s - loss: 1.1298 - acc: 0.5539
Epoch 48/100
0s - loss: 1.1146 - acc: 0.5711
Epoch 49/100
1s - loss: 1.1181 - acc: 0.5648
Epoch 50/100
0s - loss: 1.1211 - acc: 0.5707
Epoch 51/100
0s - loss: 1.1140 - acc: 0.5675
Epoch 52/100
0s - loss: 1.1086 - acc: 0.5569
Epoch 53/100
0s - loss: 1.1085 - acc: 0.5635
Epoch 54/100
0s - loss: 1.1061 - acc: 0.5707
Epoch 55/100
0s - loss: 1.1245 - acc: 0.5630
Epoch 56/100
0s - loss: 1.0967 - acc: 0.5788
Epoch 57/100
0s - loss: 1.1067 - acc: 0.5665
Epoch 58/100
0s - loss: 1.0963 - acc: 0.5748
Epoch 59/100
0s - loss: 1.1168 - acc: 0.5640
Epoch 60/100
1s - loss: 1.1041 - acc: 0.5675
Epoch 61/100
1s - loss: 1.0926 - acc: 0.5662
Epoch 62/100
1s - loss: 1.0980 - acc: 0.5660
Epoch 63/100
1s - loss: 1.1043 - acc: 0.5761
Epoch 64/100
0s - loss: 1.0980 - acc: 0.5748
Epoch 65/100
0s - loss: 1.0927 - acc: 0.5778
Epoch 66/100
0s - loss: 1.1004 - acc: 0.5702
Epoch 67/100
0s - loss: 1.0902 - acc: 0.5650
Epoch 68/100
0s - loss: 1.0931 - acc: 0.5734
Epoch 69/100
0s - loss: 1.0872 - acc: 0.5736
Epoch 70/100
0s - loss: 1.0771 - acc: 0.5748
Epoch 71/100
0s - loss: 1.0946 - acc: 0.5810
Epoch 72/100
0s - loss: 1.0778 - acc: 0.5797
Epoch 73/100
0s - loss: 1.0809 - acc: 0.5748
Epoch 74/100
0s - loss: 1.0853 - acc: 0.5739
Epoch 75/100
0s - loss: 1.0851 - acc: 0.5844
Epoch 76/100
1s - loss: 1.0789 - acc: 0.5817
Epoch 77/100
1s - loss: 1.0969 - acc: 0.5739
Epoch 78/100
1s - loss: 1.0760 - acc: 0.5844
Epoch 79/100
0s - loss: 1.0759 - acc: 0.5797
Epoch 80/100
0s - loss: 1.0811 - acc: 0.5856
Epoch 81/100
0s - loss: 1.0933 - acc: 0.5714
Epoch 82/100
0s - loss: 1.0809 - acc: 0.5758
Epoch 83/100
1s - loss: 1.0781 - acc: 0.5802
Epoch 84/100
1s - loss: 1.0739 - acc: 0.5876
Epoch 85/100
0s - loss: 1.0757 - acc: 0.5775
Epoch 86/100
0s - loss: 1.0776 - acc: 0.5680
Epoch 87/100
0s - loss: 1.0706 - acc: 0.5802
Epoch 88/100
0s - loss: 1.0656 - acc: 0.5884
Epoch 89/100
0s - loss: 1.0757 - acc: 0.5805
Epoch 90/100
0s - loss: 1.0691 - acc: 0.5756
Epoch 91/100
1s - loss: 1.0713 - acc: 0.5802
Epoch 92/100
1s - loss: 1.0688 - acc: 0.5790
Epoch 93/100
1s - loss: 1.0702 - acc: 0.5788
Epoch 94/100
1s - loss: 1.0716 - acc: 0.5852
Epoch 95/100
0s - loss: 1.0693 - acc: 0.5797
Epoch 96/100
0s - loss: 1.0544 - acc: 0.5994
Epoch 97/100
1s - loss: 1.0648 - acc: 0.5849
Epoch 98/100
0s - loss: 1.0627 - acc: 0.5871
Epoch 99/100
0s - loss: 1.0671 - acc: 0.5847
Epoch 100/100
0s - loss: 1.0745 - acc: 0.5837
Train time = 197 seconds
Test time = 15 seconds

Model Report
-Accuracy: 0.648562
-Adjacent Accuracy: 0.918407

Confusion Matrix
     Pred    SS  CSiS  FSiS  SiSh    MS    WS     D    PS    BS Total
     True
       SS   168    93     7                                       268
     CSiS    62   734   142                 2                     940
     FSiS     5   265   500     2           2           6         780
     SiSh           3     2   211     9    35     4     7         271
       MS           6     7    52    54   101    11    65         296
       WS                 2    73    27   339    13   125     3   582
        D                 2    16     6     7    83    27         141
       PS           2    11    26    10   124     8   478    27   686
       BS                       4     2    11     1    15    72   105

Precision  0.71  0.67  0.74  0.55  0.50  0.55  0.69  0.66  0.71  0.65
   Recall  0.63  0.78  0.64  0.78  0.18  0.58  0.59  0.70  0.69  0.65
       F1  0.67  0.72  0.69  0.64  0.27  0.56  0.64  0.68  0.70  0.64

We display the learned 1D convolution kernels


In [8]:
plot_weights()


(7, 1, 9, 63)
(63,)

In order to avoid overfitting, we evaluate our model by running a 5-fold stratified cross-validation routine.


In [ ]:
# Cross Validation
def cross_validate():
    t0 = time.time()
    estimator = KerasClassifier(build_fn=cld_model, nb_epoch=epochs, batch_size=n_per_batch, verbose=0)
    skf = StratifiedKFold(n_splits=5, shuffle=True)
    results_cld = cross_val_score(estimator, X_train, y_train, cv= skf.get_n_splits(X_train, y_train))
    t1 = time.time()
    print("Cross Validation time = %d" % (t1-t0) )
    print(' Cross Validation Results')
    print( results_cld )
    print(np.mean(results_cld))

cross_validate()

Prediction


To predict the STUART and CRAWFORD blind wells we do the following:

Set up a plotting function to display the logs & facies.


In [9]:
# 1=sandstone  2=c_siltstone   3=f_siltstone 
# 4=marine_silt_shale 5=mudstone 6=wackestone 7=dolomite
# 8=packstone 9=bafflestone
facies_colors = ['#F4D03F', '#F5B041','#DC7633','#6E2C00', '#1B4F72','#2E86C1', '#AED6F1', '#A569BD', '#196F3D']

#facies_color_map is a dictionary that maps facies labels
#to their respective colors
facies_color_map = {}
for ind, label in enumerate(facies_labels):
    facies_color_map[label] = facies_colors[ind]

def label_facies(row, labels):
    return labels[ row['Facies'] -1]

def make_facies_log_plot(logs, facies_colors, y_test=None, wellId=None):
    #make sure logs are sorted by depth
    logs = logs.sort_values(by='Depth')
    cmap_facies = colors.ListedColormap(
            facies_colors[0:len(facies_colors)], 'indexed')
    
    ztop=logs.Depth.min(); zbot=logs.Depth.max()

    facies = np.zeros(2*(int(zbot-ztop)+1))
    
    shift = 0
    depth = ztop
    for i in range(logs.Depth.count()-1):
        while (depth < logs.Depth.values[i] + 0.25 and depth < zbot+0.25):
            if (i<logs.Depth.count()-1):
                new = logs['Facies'].values[i]
                facies[shift] = new
                depth += 0.5
                shift += 1
    facies = facies[0:facies.shape[0]-1]
    cluster=np.repeat(np.expand_dims(facies,1), 100, 1)
    
    f, ax = plt.subplots(nrows=1, ncols=8, gridspec_kw={'width_ratios':[1,1,1,1,1,1,2,2]}, figsize=(10, 12))
    ax[0].plot(logs.GR, logs.Depth, '-g')
    ax[1].plot(logs.ILD_log10, logs.Depth, '-')
    ax[2].plot(logs.DeltaPHI, logs.Depth, '-', color='0.5')
    ax[3].plot(logs.PHIND, logs.Depth, '-', color='r')
    ax[4].plot(logs.PE, logs.Depth, '-', color='black')
    ax[5].plot(logs.NM_M, logs.Depth, '-', color='black')
    
    if (y_test is not None):
        for i in range(9):
            if (wellId == 'STUART'):
                ax[6].plot(y_test[0:474,i], logs.Depth, color=facies_colors[i], lw=1.5)
            else:
                ax[6].plot(y_test[474:,i], logs.Depth, color=facies_colors[i], lw=1.5)
                
    im=ax[7].imshow(cluster, interpolation='none', aspect='auto',
                    cmap=cmap_facies,vmin=1,vmax=9)
    
    divider = make_axes_locatable(ax[7])
    cax = divider.append_axes("right", size="20%", pad=0.05)
    cbar=plt.colorbar(im, cax=cax)
    cbar.set_label((17*' ').join([' SS ', 'CSiS', 'FSiS', 
                                'SiSh', ' MS ', ' WS ', ' D  ', 
                                ' PS ', ' BS ']))
    cbar.set_ticks(range(0,1)); cbar.set_ticklabels('')
    
    for i in range(len(ax)-1):
        ax[i].set_ylim(ztop,zbot)
        ax[i].invert_yaxis()
        ax[i].grid()
        ax[i].locator_params(axis='x', nbins=5)
    
    ax[0].set_xlabel("GR")
    ax[0].set_xlim(logs.GR.min(),logs.GR.max())
    ax[1].set_xlabel("ILD_log10")
    ax[1].set_xlim(logs.ILD_log10.min(),logs.ILD_log10.max())
    ax[2].set_xlabel("DeltaPHI")
    ax[2].set_xlim(logs.DeltaPHI.min(),logs.DeltaPHI.max())
    ax[3].set_xlabel("PHIND")
    ax[3].set_xlim(logs.PHIND.min(),logs.PHIND.max())
    ax[4].set_xlabel("PE")
    ax[4].set_xlim(logs.PE.min(),logs.PE.max())
    ax[5].set_xlabel("NM_M")
    ax[5].set_xlim(logs.NM_M.min()-1.,logs.NM_M.max()+1.)
    ax[6].set_xlabel("Facies Prob")
    ax[6].set_xlim(0.0,1.0)
    ax[7].set_xlabel('Facies')
    
    ax[0].set_yticklabels([]);
    ax[1].set_yticklabels([]); ax[2].set_yticklabels([]); ax[3].set_yticklabels([])
    ax[4].set_yticklabels([]); ax[5].set_yticklabels([])
    ax[6].set_xticklabels([]); ax[7].set_xticklabels([]);
    f.suptitle('Well: %s'%logs.iloc[0]['Well Name'], fontsize=14,y=0.94)

Run the model on the blind data

  • Output a CSV
  • Plot the wells in the notebook

In [10]:
# CLDNN model Prediction
y_test = model_cld.predict( X_test , batch_size=n_per_batch, verbose=0)
predictions_cld = np.zeros((len(y_test),1))
for i in range(len(y_test)):
    predictions_cld[i] = np.argmax(y_test[i]) + 1 
predictions_cld = predictions_cld.astype(int)
# Store results
train_data = pd.read_csv('train_test_data.csv')
test_data = pd.read_csv('../validation_data_nofacies.csv')
test_data['Facies'] = predictions_cld
test_data.to_csv('Prediction_StoDIG_5.csv')


for wellId in well_names_validate:
    make_facies_log_plot( test_data[test_data['Well Name'] == wellId], facies_colors=facies_colors, y_test=y_test, wellId=wellId)
    
#for wellId in well_names_test:
#    make_facies_log_plot( train_data[train_data['Well Name'] == wellId], facies_colors=facies_colors)