{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## General Information" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook is used to train a simple neural network model to predict the chemistry in the barite benchmark (50x50 grid). The training data is stored in the repository using **git large file storage** and can be downloaded after the installation of git lfs using the `git lfs pull` command.\n", "\n", "It is then recommended to create a Python environment using miniconda. The necessary dependencies are contained in `environment.yml` and can be installed using `conda env create -f environment.yml`.\n", "\n", "The data set is divided into a design and result part and consists of the iterations of a reference simulation. The design part of the data set contains the chemical concentrations at time $t$ and the result part at time $t+1$, which are to be learned by the model." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup Libraries" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2025-01-23 14:37:53.766781: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n", "2025-01-23 14:37:53.786741: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n", "To enable the following instructions: SSE4.1 SSE4.2 AVX AVX2 AVX_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Running Keras in version 3.6.0\n" ] } ], "source": [ "import keras\n", "import h5py\n", "import numpy as np\n", "import pandas as pd\n", "import time\n", "import sklearn.model_selection as sk\n", "import matplotlib.pyplot as plt\n", "from sklearn.cluster import KMeans\n", "from sklearn.pipeline import Pipeline, make_pipeline\n", "from sklearn.preprocessing import StandardScaler, MinMaxScaler\n", "from imblearn.over_sampling import SMOTE\n", "from imblearn.under_sampling import RandomUnderSampler\n", "from imblearn.over_sampling import RandomOverSampler\n", "from collections import Counter\n", "import os\n", "from preprocessing import *\n", "from sklearn import set_config\n", "set_config(transform_output = \"pandas\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define parameters" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [], "source": [ "dtype = \"float32\"\n", "activation = \"relu\"\n", "\n", "lr = 0.001\n", "batch_size = 512\n", "epochs = 50 # default 400 epochs\n", "\n", "lr_schedule = keras.optimizers.schedules.ExponentialDecay(\n", " initial_learning_rate=lr,\n", " decay_steps=2000,\n", " decay_rate=0.9,\n", " staircase=True\n", ")\n", "\n", "optimizer_simple = keras.optimizers.Adam(learning_rate=lr_schedule)\n", "optimizer_large = keras.optimizers.Adam(learning_rate=lr_schedule)\n", "\n", "loss = keras.losses.MeanSquaredError()\n", "\n", "sample_fraction = 0.8" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup the model" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Model: \"sequential_2\"\n",
"\n"
],
"text/plain": [
"\u001b[1mModel: \"sequential_2\"\u001b[0m\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
"┃ Layer (type) ┃ Output Shape ┃ Param # ┃\n",
"┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
"│ dense_7 (Dense) │ (None, 128) │ 1,664 │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_8 (Dense) │ (None, 128) │ 16,512 │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_9 (Dense) │ (None, 12) │ 1,548 │\n",
"└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
"\n"
],
"text/plain": [
"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
"┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
"┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
"│ dense_7 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m128\u001b[0m) │ \u001b[38;5;34m1,664\u001b[0m │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_8 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m128\u001b[0m) │ \u001b[38;5;34m16,512\u001b[0m │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_9 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m12\u001b[0m) │ \u001b[38;5;34m1,548\u001b[0m │\n",
"└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"Total params: 19,724 (77.05 KB)\n", "\n" ], "text/plain": [ "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m19,724\u001b[0m (77.05 KB)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Trainable params: 19,724 (77.05 KB)\n", "\n" ], "text/plain": [ "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m19,724\u001b[0m (77.05 KB)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Non-trainable params: 0 (0.00 B)\n", "\n" ], "text/plain": [ "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# small model\n", "model_simple = keras.Sequential(\n", " [\n", " keras.Input(shape = (12,), dtype = \"float32\"),\n", " keras.layers.Dense(units = 128, activation = \"relu\", dtype = \"float32\"),\n", " keras.layers.Dense(units = 128, activation = \"relu\", dtype = \"float32\"),\n", " keras.layers.Dense(units = 12, dtype = \"float32\")\n", " ]\n", ")\n", "\n", "model_simple.compile(optimizer=optimizer_simple, loss = loss)\n", "model_simple.summary()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Model: \"sequential_1\"\n",
"\n"
],
"text/plain": [
"\u001b[1mModel: \"sequential_1\"\u001b[0m\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
"┃ Layer (type) ┃ Output Shape ┃ Param # ┃\n",
"┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
"│ dense_3 (Dense) │ (None, 512) │ 6,656 │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_4 (Dense) │ (None, 1024) │ 525,312 │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_5 (Dense) │ (None, 512) │ 524,800 │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_6 (Dense) │ (None, 12) │ 6,156 │\n",
"└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
"\n"
],
"text/plain": [
"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
"┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
"┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
"│ dense_3 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m512\u001b[0m) │ \u001b[38;5;34m6,656\u001b[0m │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_4 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1024\u001b[0m) │ \u001b[38;5;34m525,312\u001b[0m │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_5 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m512\u001b[0m) │ \u001b[38;5;34m524,800\u001b[0m │\n",
"├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
"│ dense_6 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m12\u001b[0m) │ \u001b[38;5;34m6,156\u001b[0m │\n",
"└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"Total params: 1,062,924 (4.05 MB)\n", "\n" ], "text/plain": [ "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m1,062,924\u001b[0m (4.05 MB)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Trainable params: 1,062,924 (4.05 MB)\n", "\n" ], "text/plain": [ "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m1,062,924\u001b[0m (4.05 MB)\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Non-trainable params: 0 (0.00 B)\n", "\n" ], "text/plain": [ "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# large model\n", "model_large = keras.Sequential(\n", " [keras.layers.Input(shape=(12,), dtype=dtype),\n", " keras.layers.Dense(512, activation='relu', dtype=dtype),\n", " keras.layers.Dense(1024, activation='relu', dtype=dtype),\n", " keras.layers.Dense(512, activation='relu', dtype=dtype),\n", " keras.layers.Dense(12, dtype=dtype)\n", " ])\n", "\n", "model_large.compile(optimizer=optimizer_large, loss = loss)\n", "model_large.summary()\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# model from paper" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define transformer functions" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def Safelog(val):\n", " # get range of vector\n", " if val > 0:\n", " return np.log10(val)\n", " elif val < 0:\n", " return -np.log10(-val)\n", " else:\n", " return 0\n", "\n", "def Safeexp(val):\n", " if val > 0:\n", " return -10 ** -val\n", " elif val < 0:\n", " return 10 ** val\n", " else:\n", " return 0" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# ? Why does the charge is using another logarithm than the other species\n", "\n", "func_dict_in = {\n", " \"H\" : np.log1p,\n", " \"O\" : np.log1p,\n", " \"Charge\" : Safelog,\n", " \"H_0_\" : np.log1p,\n", " \"O_0_\" : np.log1p,\n", " \"Ba\" : np.log1p,\n", " \"Cl\" : np.log1p,\n", " \"S_2_\" : np.log1p,\n", " \"S_6_\" : np.log1p,\n", " \"Sr\" : np.log1p,\n", " \"Barite\" : np.log1p,\n", " \"Celestite\" : np.log1p,\n", "}\n", "\n", "func_dict_out = {\n", " \"H\" : np.expm1,\n", " \"O\" : np.expm1,\n", " \"Charge\" : Safeexp,\n", " \"H_0_\" : np.expm1,\n", " \"O_0_\" : np.expm1,\n", " \"Ba\" : np.expm1,\n", " \"Cl\" : np.expm1,\n", " \"S_2_\" : np.expm1,\n", " \"S_6_\" : np.expm1,\n", " \"Sr\" : np.expm1,\n", " \"Barite\" : np.expm1,\n", " \"Celestite\" : np.expm1,\n", "}\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read data from `.h5` file and convert it to a `pandas.DataFrame`" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "# os.chdir('/mnt/beegfs/home/signer/projects/model-training')\n", "data_file = h5py.File(\"Barite_50_Data_training.h5\")\n", "\n", "design = data_file[\"design\"]\n", "results = data_file[\"result\"]\n", "\n", "df_design = pd.DataFrame(np.array(design[\"data\"]).transpose(), columns = design[\"names\"].asstr())\n", "df_results = pd.DataFrame(np.array(results[\"data\"]).transpose(), columns = results[\"names\"].asstr())\n", "\n", "data_file.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Preprocess Data\n", "\n", "The data are preprocessed in the following way:\n", "\n", "1. Label data points in the `design` dataset with `reactive` and `non-reactive` labels using kmeans clustering\n", "2. Transform `design` and `results` data set into log-scaled data.\n", "3. Split data into training and test sets.\n", "4. Learn scaler on training data for `design` and `results` together (option `global`) or individual (option `individual`).\n", "5. Transform training and test data.\n", "6. Split training data into training and validation dataset." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/signer/bin/miniconda3/envs/training/lib/python3.11/site-packages/sklearn/base.py:1473: ConvergenceWarning: Number of distinct clusters (1) found smaller than n_clusters (2). Possibly due to duplicate points in X.\n", " return fit_method(estimator, *args, **kwargs)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Amount class 0 before: 0.9879169719169719\n", "Amount class 1 before: 0.012083028083028084\n", "Using Oversampling\n", "Amount class 0 after: 0.5\n", "Amount class 1 after: 0.5\n" ] } ], "source": [ "X_train, X_val, X_test, y_train, y_val, y_test, scaler_X, scaler_y = preprocessing_training(df_design, df_results, func_dict_in, func_dict_out, \"over\", 'individual', 0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Custom Loss function" ] }, { "cell_type": "code", "execution_count": 164, "metadata": {}, "outputs": [], "source": [ "def custom_loss_H20(df_design_log, df_result_log, data_min_log, data_max_log, func_dict_out, postprocess):\n", " df_result = postprocess(df_result_log, func_dict_out, data_min_log, data_max_log) \n", " return keras.losses.Huber + np.sum(((df_result['H'] / df_result['O']) - 2)**2)\n", "\n", "def loss_wrapper(data_min_log, data_max_log, func_dict_out, postprocess):\n", " def loss(df_design_log, df_result_log):\n", " return custom_loss_H20(df_design_log, df_result_log, data_min_log, data_max_log, func_dict_out, postprocess)\n", " return loss" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Train the model" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Epoch 1/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 0.0018 - val_loss: 3.6601e-05\n", "Epoch 2/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m13s\u001b[0m 2ms/step - loss: 3.6899e-05 - val_loss: 3.6822e-05\n", "Epoch 3/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m13s\u001b[0m 2ms/step - loss: 3.5005e-05 - val_loss: 3.5655e-05\n", "Epoch 4/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m13s\u001b[0m 2ms/step - loss: 3.4032e-05 - val_loss: 3.3455e-05\n", "Epoch 5/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.3279e-05 - val_loss: 3.3064e-05\n", "Epoch 6/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.3023e-05 - val_loss: 3.3338e-05\n", "Epoch 7/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2532e-05 - val_loss: 3.2765e-05\n", "Epoch 8/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m13s\u001b[0m 2ms/step - loss: 3.2749e-05 - val_loss: 3.2730e-05\n", "Epoch 9/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m13s\u001b[0m 2ms/step - loss: 3.2961e-05 - val_loss: 3.2593e-05\n", "Epoch 10/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2573e-05 - val_loss: 3.2576e-05\n", "Epoch 11/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m13s\u001b[0m 2ms/step - loss: 3.2442e-05 - val_loss: 3.2507e-05\n", "Epoch 12/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2135e-05 - val_loss: 3.2548e-05\n", "Epoch 13/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2451e-05 - val_loss: 3.2482e-05\n", "Epoch 14/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2296e-05 - val_loss: 3.2475e-05\n", "Epoch 15/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2081e-05 - val_loss: 3.2470e-05\n", "Epoch 16/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2440e-05 - val_loss: 3.2471e-05\n", "Epoch 17/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2050e-05 - val_loss: 3.2460e-05\n", "Epoch 18/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m14s\u001b[0m 2ms/step - loss: 3.2444e-05 - val_loss: 3.2452e-05\n", "Epoch 19/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m15s\u001b[0m 2ms/step - loss: 3.2259e-05 - val_loss: 3.2452e-05\n", "Epoch 20/20\n", "\u001b[1m7823/7823\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m15s\u001b[0m 2ms/step - loss: 3.2442e-05 - val_loss: 3.2448e-05\n", "Training took 276.5459449291229 seconds\n" ] } ], "source": [ "# measure time\n", "start = time.time()\n", "callback = keras.callbacks.EarlyStopping(monitor='loss', patience=3)\n", "history = model_simple.fit(X_train.iloc[:, X_train.columns != \"Class\"], \n", " y_train.iloc[:, y_train.columns != \"Class\"], \n", " batch_size = batch_size, \n", " epochs = 20, \n", " validation_data = (X_val.iloc[:, X_val.columns != \"Class\"], y_val.iloc[:, y_val.columns != \"Class\"]),\n", " callbacks = [callback])\n", "\n", "end = time.time()\n", "\n", "print(\"Training took {} seconds\".format(end - start))" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m32/32\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m0s\u001b[0m 1ms/step \n" ] }, { "data": { "image/png": "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bNkymzZtmvl8PrvmmmuCwogkO++886xdu3Y2Z84cW758uX3++edmZjZ06FB78cUXLSsry7Kysuyxxx6zqKiowPvmRHVUFmbee+89q127tnXs2NHmzZtnCxcutPT0dPN4PDZ37txAv/Iw06JFC7vnnnvsnXfesRdeeMHq1atnV199daBfbm6uRUZGWvfu3W3hwoX23nvv2WuvvWaDBw+uEHzx74UwA+fk5eWZJBswYMBx+916660myXbu3HnMPq1btw7MOpxIaWmpJSQkWNu2ba20tDTQXlRUZI0bN7YuXboE2soDwaRJk4K2MWLECIuMjAx8wLz//vsmycaPH3/M/W7dutXCw8PtnnvuCWovKioyv99vt9xyS6BtyJAhJsn++te/BvXt3bu3tWrVKvD4hx9+MEn28MMPV9hfee0PPfTQcUbjZyUlJbZv3z6LiYkJCl9/+9vfTJKtWLGiwnOODjPlwerosZo3b55Jsueeey7QVj4j9O233wbaDh48aPXr17dhw4Ydt9Znn3220rF56qmnTJItXbrUzMx27dplERER9sADDwT1u+WWWyw+Pt5++uknMzN7/fXXTZLNnz8/qF9OTo5JshkzZgTVHRYWZl9++eVxayx3dJgxM7vooossLS2tQt/MzEyrVauW5eTkBLX//e9/N0n21ltvBdokmc/nsx9//PG4+y8tLbWffvrJHn30UWvQoEFQIDpWHZWFmcsuu8waN25sRUVFgbaSkhJLSUmxpk2bBrZbHmZGjBgRtM1JkyaZJNuxY0fQMX366afHrR//fjgBGOcsM5MkeTyeKtnel19+qe+//16DBw9WrVr/96dTp04d3Xjjjfr444914MCBoOf86le/Cnrcrl07HTp0SPn5+ZKkt99+W5I0cuTIY+73nXfeUUlJiW6//XaVlJQElsjISKWlpVU4idfj8ahfv34V9vvtt9+e0vHeeOONFdr27dunsWPH6oILLlB4eLjCw8NVp04d7d+/X1988cUpbb/c8uXLJf18Bc+Rbr75ZsXExOjdd98Nam/fvr2aNWsWeBwZGalf/OIXJzy+5cuXKyYmRjfddFNQe/l+y/fToEED9evXT7Nnz1ZZWZkkac+ePVq0aJFuv/32wPlZixcvVt26ddWvX7+g16V9+/by+/0VXpd27drpF7/4xYkH5BQtXrxYKSkpat++fVAdPXr0kMfjqVDHNddco3r16lXYzvLly9WtWzf5fD6FhYWpdu3aeuihh7R79+7A+/VU7N+/X6tXr9ZNN90UdGVWWFiYBg8erO3bt+vLL78Mek5lfy+SAq9t+/btFRERobvuukuzZ8/WN998c8p14dxEmIFzGjZsqOjoaOXm5h6335YtWxQdHa369esfs0+zZs30ww8/aP/+/Sfc7+7duyX9fNXG0RISElRWVqY9e/YEtTdo0CDosdfrlfTzyZ+S9MMPPygsLEx+v/+Y+925c6ck6ZJLLlHt2rWDlnnz5mnXrl1B/aOjoxUZGVlhv4cOHTrhMR6psuMcOHCgpk+frt/+9rd65513tGbNGuXk5KhRo0aBYzpVu3fvVnh4uBo1ahTU7vF45Pf7A+Ne7ugxlX4+vhPtf/fu3fL7/RXCbePGjRUeHh60nzvvvFPfffedsrKyJEmvv/66iouLgwLXzp07tXfvXkVERFR4XfLy8iq8LpWNZ1XYuXOnPvvsswo1xMbGysxOqo41a9YoPT1dkvT888/ro48+Uk5OjsaPHy9Jp/Xa7tmzR2Z2zL8XSSd8bY/+ezn//PO1bNkyNW7cWCNHjtT555+v888/X9OmTTvl+nBu4WomOCcsLExXX321lixZou3btwddgVRu+/btWrdunXr16qWwsLBjbqtHjx5aunSp/vGPf2jAgAHH3W/5P7Q7duyosO77779XrVq1Kv0/3uNp1KiRSktLlZeXd8wPu4YNG0qS/v73vyspKemUtn8mjv7QLygo0OLFi/Xwww/r/vvvD7QXFxfrxx9/PO39NGjQQCUlJfrhhx+CAo2ZKS8vT5dccslpb/vo/axevVpmFnRs+fn5KikpCYyz9PP7IiEhQbNmzVKPHj00a9Yspaamqk2bNoE+DRs2VIMGDbRkyZJK9xcbGxv0uKpmCI/WsGFDRUVF6aWXXjrm+hPVMXfuXNWuXVuLFy8OCsILFy487brq1aunWrVqHfPvpbLaTsaVV16pK6+8UqWlpVq7dq3+/Oc/KyMjQ/Hx8Sf8G8a5i5kZOGncuHEyM40YMUKlpaVB60pLS3X33XfLzDRu3Ljjbuc3v/mN/H6/xowZo++++67SPgsWLJAktWrVSuedd57mzJkT+ApL+nk6ff78+ercufMpX77cq1cvSdLMmTOP2adHjx4KDw/X119/rU6dOlW6nKqj/4/3ZHg8HplZ4LnlXnjhhQqvwals/9prr5Ukvfrqq0Ht8+fP1/79+wPrz9S1116rffv2VfiA/stf/hJUh/R/X4UsXLhQH3zwgdauXas777wz6Hl9+/bV7t27VVpaWulr0qpVqyqpu9yxZp/69u2rr7/+Wg0aNKi0jpO5QaHH41F4eHhQ8D948KBeeeWVk67jaDExMUpNTdWCBQuC+peVlenVV19V06ZNz+hrt7CwMKWmpuqZZ56RJK1fv/60twX3MTMDJ11++eWaOnWqMjIydMUVV2jUqFFq1qyZtm7dqmeeeUarV6/W1KlT1aVLl+Nux+fzadGiRerbt686dOigUaNGqXPnzoqIiNDmzZv16quv6p///Kf69++vWrVqadKkSRo0aJD69u2rYcOGqbi4WH/84x+1d+9ePfnkk6d8HFdeeaUGDx6sxx9/XDt37lTfvn3l9Xr1ySefKDo6Wvfcc4+aN2+uRx99VOPHj9c333yjnj17ql69etq5c6fWrFmjmJgYPfLII6e039jYWCUlJWnRokW69tprVb9+fTVs2PC4H3xxcXG66qqr9Mc//jHQNzs7Wy+++KLq1q0b1DclJUWS9Nxzzyk2NlaRkZFKTk6u9Cui7t27q0ePHho7dqwKCwt1+eWX67PPPtPDDz+sDh06aPDgwad0bMdy++2365lnntGQIUO0ZcsWtW3bVh9++KEmTpyo3r17q1u3bkH977zzTj311FMaOHCgoqKidOuttwatHzBggF577TX17t1bo0eP1qWXXqratWtr+/btWrFiha677jrdcMMNVVK7JLVt21Zz587VvHnz1KJFC0VGRqpt27bKyMjQ/PnzddVVV+l3v/ud2rVrp7KyMm3dulVLly7V73//e6Wmph5323369NGUKVM0cOBA3XXXXdq9e7cmT55cIbger47KZGZmqnv37rr66qt13333KSIiQjNmzNDnn3+u119//ZRnq5599lktX75cffr0UbNmzXTo0KHAjNTRrx/+zYTs1GOgCqxatcpuuukmi4+Pt/DwcGvcuLH179+/wn1fTiQvL8/Gjh1rF110kUVHR5vX67ULLrjAhg0bZhs2bAjqu3DhQktNTbXIyEiLiYmxa6+91j766KOgPuVXBJVfrlyu/KqN3NzcQFtpaan96U9/spSUFIuIiDCfz2edO3e2f/zjHxX2e/XVV1tcXJx5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3XXW6gBwdoWHugAAOJHDhw8rIiLitJ/fqFGjKqwGQE3DzAyAajF06FBlZ2dr2rRp8ng88ng82rJliyRp06ZN6t27t+rUqaP4+HgNHjxYu3btCjy3a9euGjVqlO699141bNhQ3bt3lyRNmTJFbdu2VUxMjBITEzVixAjt27dPkvTee+/pjjvuUEFBQWB/EyZMkFTxa6atW7fquuuuU506dRQXF6dbbrlFO3fuDKyfMGGC2rdvr1deeUXNmzeXz+fTgAEDVFRUVL2DBuC0EGYAVItp06apc+fO+o//+A/t2LFDO3bsUGJionbs2KG0tDS1b99ea9eu1ZIlS7Rz507dcsstQc+fPXu2wsPD9dFHH+m///u/JUm1atXS008/rc8//1yzZ8/W8uXLNWbMGElSly5dNHXqVMXFxQX2d99991Woy8x0/fXX68cff1R2draysrL09ddf69Zbbw3q9/XXX2vhwoVavHixFi9erOzsbD355JPVNFoAzgRfMwGoFj6fTxEREYqOjpbf7w+0z5w5UxdffLEmTpwYaHvppZeUmJior776Sr/4xS8kSRdccIEmTZoUtM0jz79JTk7WY489prvvvlszZsxQRESEfD6fPB5P0P6OtmzZMn322WfKzc1VYmKiJOmVV17RRRddpJycHF1yySWSpLKyMr388suKjY2VJA0ePFjvvvuunnjiiTMbGABVjpkZAGfVunXrtGLFCtWpUyewtG7dWtLPsyHlOnXqVOG5K1asUPfu3XXeeecpNjZWt99+u3bv3q39+/ef9P6/+OILJSYmBoKMJLVp00Z169bVF198EWhr3rx5IMhIUpMmTZSfn39Kxwrg7GBmBsBZVVZWpn79+umpp56qsK5JkyaB/46JiQla9+2336p3794aPny4HnvsMdWvX18ffvihfvOb3+inn3466f2bmTwezwnba9euHbTe4/GorKzspPcD4OwhzACoNhERESotLQ1qu/jiizV//nw1b95c4eEn/0/Q2rVrVVJSov/6r/9SrVo/Tyr/9a9/PeH+jtamTRtt3bpV27ZtC8zObNq0SQUFBbrwwgtPuh4ANQdfMwGoNs2bN9fq1au1ZcsW7dq1S2VlZRo5cqR+/PFH3XbbbVqzZo2++eYbLV26VHfeeedxg8j555+vkpIS/fnPf9Y333yjV155Rc8++2yF/e3bt0/vvvuudu3apQMHDlTYTrdu3dSuXTsNGjRI69ev15o1a3T77bcrLS2t0q+2ANR8hBkA1ea+++5TWFiY2rRpo0aNGmnr1q1KSEjQRx99pNLSUvXo0UMpKSkaPXq0fD5fYMalMu3bt9eUKVP01FNPKSUlRa+99poyMzOD+nTp0kXDhw/XrbfeqkaNGlU4gVj6+euihQsXql69errqqqvUrVs3tWjRQvPmzavy4wdwdnjMzEJdBAAAwOliZgYAADiNMAMAAJxGmAEAAE4jzAAAAKcRZgAAgNMIMwAAwGmEGQAA4DTCDAAAcBphBgAAOI0wAwAAnEaYAQAATiPMAAAAp/1/7MgGXoPfaWQAAAAASUVORK5CYII=", "text/plain": [ "