{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Overview\n\nThis approach will use spectogram and create images of each sensor. Each file will be imported and mapped to create a 130x237 image per sensor. These images will be stacked in the third axis so each `time_to_eruption` will be represented by a 130x237x10 tensor. This data won't fit into memory so I created a custom generator to read batches of files at a time. \n\nThe objective of sharing this notebook is to highligh Tensorflows ability to create a custom generator that reads data from hard disk versus into memory."},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport pandas as pd\nfrom scipy import signal\nimport tensorflow as tf\n\nfrom tensorflow import keras\nfrom tensorflow.keras.models import Sequential, Model, load_model\nfrom tensorflow.keras.layers import Input, Conv2D, AveragePooling2D, MaxPooling2D, Dense, Dropout, Activation, Flatten\nfrom tensorflow.keras.layers import BatchNormalization\n\nimport glob\nimport os","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import tensorflow as tf\nprint(\"Num GPUs Available: \", len(tf.config.experimental.list_physical_devices('GPU')))\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We'll use the dataset API to read the hardisk files as necessary. Previously I ran out of memory to simply loop through. Once  the files are imported, we can perform a map function on the tensor to alter the sensor columns and 60001 readings to manageable spectograms."},{"metadata":{"trusted":true},"cell_type":"code","source":"df_example = pd.read_csv(\"../input/predict-volcanic-eruptions-ingv-oe/train/2068207140.csv\")\ndf_example.head()\n\ndata_columns = list(df_example.columns)\n\nprint('Index Dataframe Shape: {}'.format(df_example.shape))\nprint('Column Headers:\\n')\nprint(data_columns)\ndf_example","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(\"../input/predict-volcanic-eruptions-ingv-oe/train.csv\")\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create list of training file paths\n\nWe'll deconstruct the train_df to do this so the sequence and label match"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_list = []\ntrain_labels = []\ntrain_dir = '../input/predict-volcanic-eruptions-ingv-oe/train'\n\nfor index, row in train_df.iterrows():\n    segment_id = str(row['segment_id'])\n    fname = os.path.join(train_dir,segment_id+'.csv')\n\n    segment_label = row['time_to_eruption']\n    \n    train_list.append(fname)\n    train_labels.append(segment_label)\n    \nprint(\"Length of training list: {}\".format(len(train_list)))\nprint(\"Length of training labels: {}\".format(len(train_labels)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# File Read to Spectogram\n\nThis are the basic steps developed and incorporated into the custom generator."},{"metadata":{"trusted":true},"cell_type":"code","source":"def process_path(segment_path):\n    input_df = pd.read_csv(segment_path)\n    input_df = input_df.fillna(0.0)\n    \n    fname = os.path.split(segment_path)[1]\n    segment_id = os.path.splitext(fname)[0]\n    \n    spec_array = {}\n        \n    for col in input_df.columns:\n        f,t,Sxx = signal.spectrogram(input_df[col],100,window=('tukey',.25),nperseg=256,nfft=256,mode='psd',noverlap=3)\n        spec_array[col] = Sxx\n    \n    segment_data = np.stack((list(spec_array.values())),axis=2)\n    \n    return segment_data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X = np.array([process_path(file_name) for file_name in train_list[0:5]])\nX.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"y = df_example['sensor_2'].to_numpy()\nf,t,Sxx = signal.spectrogram(y,100,window=('tukey',.25),nperseg=256,nfft=256,mode='psd',noverlap=3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Length of f: {}\".format(len(f)))\nprint(\"Length of t: {}\".format(len(t)))\nprint(\"Shape of Sxx: {}\".format(Sxx.shape))\nSxx.dtype","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.pcolormesh(t, f, Sxx,shading='gouraud',vmax=10)\n\nplt.ylabel('Frequency [Hz]')\n\nplt.xlabel('Time [sec]')\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axs = plt.subplots(nrows=5, ncols=2)\nfig.set_size_inches(20,10)\nfig.subplots_adjust(hspace=0.5)\n\nfor col,ax in zip(data_columns, axs.flatten()):\n    y = df_example[col]\n    f,t,Sxx = signal.spectrogram(y,100,window=('tukey',.25),nperseg=256,nfft=256,mode='psd',noverlap=3)\n    ax.pcolormesh(t,f,Sxx,shading='auto',vmax=100)\n    ax.set_title(col)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Creation of Custom Generator\n\nThe generator is defined as a class and incorporates the above methods. Files are read and processes in batches thus making it memory friendly."},{"metadata":{"trusted":true},"cell_type":"code","source":"class SpectoGenerator(tf.keras.utils.Sequence):\n    \n    def __init__(self, filenames, labels, batch_size,to_predict=False):\n        self.filenames = filenames\n        self.labels = labels\n        self.batch_size = batch_size\n        self.to_predict = to_predict\n        \n    def __len__(self):\n        return (np.ceil(len(self.filenames) / float(self.batch_size))).astype(np.int)\n    \n    def __getitem__(self, index):  \n        train_ID_tmp = self.filenames[index * self.batch_size : (index+1) * self.batch_size]\n        \n        X = np.array([self._process_path(file_name) for file_name in train_ID_tmp])\n        \n        if self.to_predict:\n            return X\n        else:      \n            y = np.array(self.labels[index * self.batch_size : (index+1) * self.batch_size])\n            return X,y\n        \n    def _process_path(self, segment_path):\n        input_df = pd.read_csv(segment_path)\n        input_df = input_df.fillna(0.0)\n\n        fname = os.path.split(segment_path)[1]\n        segment_id = os.path.splitext(fname)[0]\n\n        spec_array = {}\n\n        for col in input_df.columns:\n            f,t,Sxx = signal.spectrogram(input_df[col],100,window=('tukey',.25),nperseg=256,nfft=256,mode='psd',noverlap=3)\n            spec_array[col] = Sxx\n\n        segment_data = np.stack((list(spec_array.values())),axis=2)\n\n        return segment_data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"training_generator = SpectoGenerator(train_list,train_labels,32)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"model = Sequential()\n\nmodel.add(Conv2D(filters=16, kernel_size = (2,8), activation='relu',input_shape=(129, 237, 10)))\nmodel.add(BatchNormalization(axis=3))\nmodel.add(MaxPooling2D(pool_size=(1,4)))\n\nmodel.add(Conv2D(filters=32, kernel_size = (8,2), dilation_rate=2,activation='relu'))\nmodel.add(BatchNormalization(axis=3))\nmodel.add(MaxPooling2D(pool_size=(2,1)))\n\nmodel.add(Conv2D(filters=32, kernel_size = (2,8), dilation_rate=3, activation='relu'))\nmodel.add(BatchNormalization(axis=3))\nmodel.add(MaxPooling2D(pool_size=(1,2)))\n\nmodel.add(Conv2D(filters=32, kernel_size = (8,2), dilation_rate=4,activation='relu'))\nmodel.add(BatchNormalization(axis=3))\nmodel.add(MaxPooling2D(pool_size=(2,1)))\n\nmodel.add(Flatten())\nmodel.add(Dense(1,activation=\"relu\"))\n\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def scheduler(epoch, lr):\n  if epoch < 15:\n    return lr\n  else:\n    return lr * tf.math.exp(-0.1*epoch)\n\nscheduler = tf.keras.callbacks.LearningRateScheduler(scheduler)\nearlystop = tf.keras.callbacks.EarlyStopping(monitor='mae',min_delta=50000,patience=3)\n\noptimizer = tf.keras.optimizers.Adam(learning_rate=0.01)\nmodel.compile(loss=tf.keras.losses.MeanSquaredError(), optimizer=optimizer, metrics=['mae'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit(training_generator,epochs=40,steps_per_epoch=int(4431//32),verbose=1,\n                   callbacks=[scheduler,earlystop])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.save('cnn_model_40epochs_XXX.h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"mae = history.history['mae']\nloss = history.history['loss']\n\nepochs = range(len(mae))\n\n#plt.plot(epochs, loss, 'r', label='loss')\nplt.plot(epochs, mae, 'b', label='MAE')\nplt.title('Training and validation accuracy')\nplt.legend(loc=0)\nplt.figure()\n\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create List of Test Path Files"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df = pd.read_csv('../sample_submission.csv')\ntest_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_list = []\ntest_labels = [...]\ntest_dir = '../test/'\n\nfor index, row in test_df.iterrows():\n    segment_id = str(row['segment_id'])\n    fname = os.path.join(test_dir,segment_id+'.csv')\n\n    segment_label = row['time_to_eruption']\n    \n    test_list.append(fname)\n    \nprint(\"Length of training list: {}\".format(len(train_list)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Create Test Data Generator\n\nSame generator can be used for creating a test class. I created a boolean `to_predict` which will return test set parameters. This can then be passed into the model for prediction."},{"metadata":{"trusted":true},"cell_type":"code","source":"test_generator = SpectoGenerator(test_list,test_labels,64,to_predict=True)\npred = model.predict(test_generator)\npred.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Predict Test Values"},{"metadata":{"trusted":true},"cell_type":"code","source":"df_submit = test_df.copy()\ndf_submit['time_to_eruption'] = abs(pred)\ndf_submit.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Minimum event time is: {}\".format(df_submit['time_to_eruption'].min()))\nprint(\"Maximum event time is: {}\".format(df_submit['time_to_eruption'].max()))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_submit.to_csv('./submission.csv',index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}